@outputai/cli 0.1.0 → 0.1.1

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.
@@ -277,6 +277,18 @@ export type PostWorkflowIdTerminate200 = {
277
277
  terminated?: boolean;
278
278
  workflowId?: string;
279
279
  };
280
+ export type PostWorkflowIdResetBody = {
281
+ /** The name of the step to reset after */
282
+ stepName: string;
283
+ /** Optional reason for the reset */
284
+ reason?: string;
285
+ };
286
+ export type PostWorkflowIdReset200 = {
287
+ /** The original workflow ID */
288
+ workflowId?: string;
289
+ /** The run ID of the new execution created by the reset */
290
+ runId?: string;
291
+ };
280
292
  /**
281
293
  * The workflow execution status
282
294
  */
@@ -518,6 +530,39 @@ export type postWorkflowIdTerminateResponseError = (postWorkflowIdTerminateRespo
518
530
  export type postWorkflowIdTerminateResponse = (postWorkflowIdTerminateResponseSuccess | postWorkflowIdTerminateResponseError);
519
531
  export declare const getPostWorkflowIdTerminateUrl: (id: string) => string;
520
532
  export declare const postWorkflowIdTerminate: (id: string, postWorkflowIdTerminateBody: PostWorkflowIdTerminateBody, options?: ApiRequestOptions) => Promise<postWorkflowIdTerminateResponse>;
533
+ /**
534
+ * Resets a workflow execution to the point after a completed step, creating a new run that replays from that point. The current execution is terminated.
535
+ * @summary Reset a workflow to re-run from after a specific step
536
+ */
537
+ export type postWorkflowIdResetResponse200 = {
538
+ data: PostWorkflowIdReset200;
539
+ status: 200;
540
+ };
541
+ export type postWorkflowIdResetResponse400 = {
542
+ data: BadRequestResponse;
543
+ status: 400;
544
+ };
545
+ export type postWorkflowIdResetResponse404 = {
546
+ data: NotFoundResponse;
547
+ status: 404;
548
+ };
549
+ export type postWorkflowIdResetResponse409 = {
550
+ data: ErrorResponse;
551
+ status: 409;
552
+ };
553
+ export type postWorkflowIdResetResponse500 = {
554
+ data: InternalServerErrorResponse;
555
+ status: 500;
556
+ };
557
+ export type postWorkflowIdResetResponseSuccess = (postWorkflowIdResetResponse200) & {
558
+ headers: Headers;
559
+ };
560
+ export type postWorkflowIdResetResponseError = (postWorkflowIdResetResponse400 | postWorkflowIdResetResponse404 | postWorkflowIdResetResponse409 | postWorkflowIdResetResponse500) & {
561
+ headers: Headers;
562
+ };
563
+ export type postWorkflowIdResetResponse = (postWorkflowIdResetResponseSuccess | postWorkflowIdResetResponseError);
564
+ export declare const getPostWorkflowIdResetUrl: (id: string) => string;
565
+ export declare const postWorkflowIdReset: (id: string, postWorkflowIdResetBody: PostWorkflowIdResetBody, options?: ApiRequestOptions) => Promise<postWorkflowIdResetResponse>;
521
566
  /**
522
567
  * @summary Return the result of a workflow
523
568
  */
@@ -107,6 +107,17 @@ export const postWorkflowIdTerminate = async (id, postWorkflowIdTerminateBody, o
107
107
  body: JSON.stringify(postWorkflowIdTerminateBody)
108
108
  });
109
109
  };
110
+ export const getPostWorkflowIdResetUrl = (id) => {
111
+ return `/workflow/${id}/reset`;
112
+ };
113
+ export const postWorkflowIdReset = async (id, postWorkflowIdResetBody, options) => {
114
+ return customFetchInstance(getPostWorkflowIdResetUrl(id), {
115
+ ...options,
116
+ method: 'POST',
117
+ headers: { 'Content-Type': 'application/json', ...options?.headers },
118
+ body: JSON.stringify(postWorkflowIdResetBody)
119
+ });
120
+ };
110
121
  export const getGetWorkflowIdResultUrl = (id) => {
111
122
  return `/workflow/${id}/result`;
112
123
  };
@@ -81,7 +81,7 @@ services:
81
81
  condition: service_healthy
82
82
  worker:
83
83
  condition: service_healthy
84
- image: outputai/api:${OUTPUT_API_VERSION:-0.1.0}
84
+ image: outputai/api:${OUTPUT_API_VERSION:-0.1.1}
85
85
  init: true
86
86
  networks:
87
87
  - main
@@ -32,12 +32,16 @@ export default class CredentialsEdit extends Command {
32
32
  if (!credentialsExist(environment, workflow)) {
33
33
  this.error(`No credentials file found at ${resolveCredentialsPath(environment, workflow)}. Run "output credentials init" first.`);
34
34
  }
35
- const editor = process.env.EDITOR || process.env.VISUAL || 'vi';
35
+ const editorEnv = process.env.EDITOR || process.env.VISUAL || 'vi';
36
+ const [editorCmd, ...editorArgs] = editorEnv.split(/\s+/);
36
37
  const plaintext = decryptCredentials(environment, workflow);
37
38
  const tmpFile = path.join(os.tmpdir(), `output-credentials-${Date.now()}.yml`);
38
39
  try {
39
40
  fs.writeFileSync(tmpFile, plaintext, { mode: 0o600 });
40
- const result = spawnSync(editor, [tmpFile], { stdio: 'inherit' });
41
+ const result = spawnSync(editorCmd, [...editorArgs, tmpFile], { stdio: 'inherit' });
42
+ if (result.error) {
43
+ this.error(`Failed to launch editor: ${result.error.message}`);
44
+ }
41
45
  if (result.status !== 0) {
42
46
  this.error(`Editor exited with non-zero status: ${result.status}`);
43
47
  }
@@ -6,6 +6,7 @@ export default class Dev extends Command {
6
6
  static flags: {
7
7
  'compose-file': import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
8
8
  'image-pull-policy': import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
9
+ detached: import("@oclif/core/interfaces").BooleanFlag<boolean>;
9
10
  };
10
11
  private dockerProcess;
11
12
  run(): Promise<void>;
@@ -2,7 +2,7 @@ import { Command, Flags } from '@oclif/core';
2
2
  import fs from 'node:fs/promises';
3
3
  import path from 'node:path';
4
4
  import logUpdate from 'log-update';
5
- import { validateDockerEnvironment, startDockerCompose, stopDockerCompose, getServiceStatus, DockerComposeConfigNotFoundError, getDefaultDockerComposePath, SERVICE_HEALTH, SERVICE_STATE } from '#services/docker.js';
5
+ import { validateDockerEnvironment, startDockerCompose, startDockerComposeDetached, stopDockerCompose, getServiceStatus, DockerComposeConfigNotFoundError, getDefaultDockerComposePath, SERVICE_HEALTH, SERVICE_STATE } from '#services/docker.js';
6
6
  import { getErrorMessage } from '#utils/error_utils.js';
7
7
  import { getDevSuccessMessage } from '#services/messages.js';
8
8
  import { ensureClaudePlugin } from '#services/coding_agents.js';
@@ -87,6 +87,11 @@ export default class Dev extends Command {
87
87
  description: 'Image pull policy for docker compose (always, missing, never)',
88
88
  options: ['always', 'missing', 'never'],
89
89
  default: 'always'
90
+ }),
91
+ detached: Flags.boolean({
92
+ description: 'Start services in detached (background) mode and exit immediately',
93
+ default: false,
94
+ char: 'd'
90
95
  })
91
96
  };
92
97
  dockerProcess = null;
@@ -108,6 +113,13 @@ export default class Dev extends Command {
108
113
  if (flags['compose-file']) {
109
114
  this.log(`Using custom docker-compose file: ${flags['compose-file']}\n`);
110
115
  }
116
+ const pullPolicy = flags['image-pull-policy'];
117
+ if (flags.detached) {
118
+ this.log('🐳 Starting services in detached mode...\n');
119
+ startDockerComposeDetached(dockerComposePath, pullPolicy);
120
+ this.log('✅ Services started. Run `output dev` without --detached to monitor status.\n');
121
+ return;
122
+ }
111
123
  this.log('File watching enabled - worker will restart automatically on changes\n');
112
124
  const cleanup = async () => {
113
125
  this.log('\n');
@@ -119,7 +131,6 @@ export default class Dev extends Command {
119
131
  };
120
132
  process.on('SIGINT', cleanup);
121
133
  process.on('SIGTERM', cleanup);
122
- const pullPolicy = flags['image-pull-policy'];
123
134
  try {
124
135
  const { process: dockerProc, waitForHealthy } = await startDockerCompose(dockerComposePath, pullPolicy);
125
136
  this.dockerProcess = dockerProc;
@@ -0,0 +1,14 @@
1
+ import { Command } from '@oclif/core';
2
+ export default class WorkflowReset extends Command {
3
+ static description: string;
4
+ static examples: string[];
5
+ static args: {
6
+ workflowId: import("@oclif/core/interfaces").Arg<string, Record<string, unknown>>;
7
+ };
8
+ static flags: {
9
+ step: import("@oclif/core/interfaces").OptionFlag<string, import("@oclif/core/interfaces").CustomOptions>;
10
+ reason: import("@oclif/core/interfaces").OptionFlag<string | undefined, import("@oclif/core/interfaces").CustomOptions>;
11
+ };
12
+ run(): Promise<void>;
13
+ catch(error: Error): Promise<void>;
14
+ }
@@ -0,0 +1,51 @@
1
+ import { Args, Command, Flags } from '@oclif/core';
2
+ import { postWorkflowIdReset } from '#api/generated/api.js';
3
+ import { handleApiError } from '#utils/error_handler.js';
4
+ export default class WorkflowReset extends Command {
5
+ static description = 'Reset a workflow to re-run from after a specific step';
6
+ static examples = [
7
+ '<%= config.bin %> <%= command.id %> wf-12345 --step generateBlogPost',
8
+ '<%= config.bin %> <%= command.id %> wf-12345 --step consolidateCompetitors --reason "Retry with updated prompt"'
9
+ ];
10
+ static args = {
11
+ workflowId: Args.string({
12
+ description: 'The workflow ID to reset',
13
+ required: true
14
+ })
15
+ };
16
+ static flags = {
17
+ step: Flags.string({
18
+ char: 's',
19
+ description: 'The step name to reset after',
20
+ required: true
21
+ }),
22
+ reason: Flags.string({
23
+ char: 'r',
24
+ description: 'Reason for the reset'
25
+ })
26
+ };
27
+ async run() {
28
+ const { args, flags } = await this.parse(WorkflowReset);
29
+ this.log(`Resetting workflow: ${args.workflowId} to after step: ${flags.step}...`);
30
+ const response = await postWorkflowIdReset(args.workflowId, { stepName: flags.step, reason: flags.reason });
31
+ if (!response || !response.data) {
32
+ this.error('API returned invalid response', { exit: 1 });
33
+ }
34
+ const data = response.data;
35
+ const output = [
36
+ 'Workflow reset successfully',
37
+ '',
38
+ `Workflow ID: ${args.workflowId}`,
39
+ `New Run ID: ${data.runId}`,
40
+ `Reset after step: ${flags.step}`,
41
+ flags.reason ? `Reason: ${flags.reason}` : ''
42
+ ].filter(Boolean).join('\n');
43
+ this.log(`\n${output}`);
44
+ }
45
+ async catch(error) {
46
+ return handleApiError(error, (...args) => this.error(...args), {
47
+ 404: 'Workflow or step not found. Check the workflow ID and step name.',
48
+ 409: 'Step has not completed yet. Cannot reset to an incomplete step.'
49
+ });
50
+ }
51
+ }
@@ -1,3 +1,3 @@
1
1
  {
2
- "framework": "0.1.0"
2
+ "framework": "0.1.1"
3
3
  }
@@ -14,7 +14,7 @@ import { executeClaudeCommand } from '#utils/claude.js';
14
14
  import { processTemplate } from '#utils/template.js';
15
15
  import { ClaudePluginError, UserCancelledError } from '#types/errors.js';
16
16
  const debug = debugFactory('output-cli:agent');
17
- const EXPECTED_MARKETPLACE_REPO = 'growthxai/output-claude-plugins';
17
+ const EXPECTED_MARKETPLACE_REPO = 'growthxai/output';
18
18
  function createLogger(silent) {
19
19
  return silent ? debug : (msg) => ux.stdout(ux.colorize('gray', msg));
20
20
  }
@@ -169,7 +169,7 @@ async function registerPluginMarketplace(projectRoot, silent = false) {
169
169
  const log = createLogger(silent);
170
170
  log('Registering plugin marketplace...');
171
171
  try {
172
- await executeClaudeCommand(['plugin', 'marketplace', 'add', 'growthxai/output-claude-plugins'], projectRoot, { ignoreFailure: true });
172
+ await executeClaudeCommand(['plugin', 'marketplace', 'add', 'growthxai/output'], projectRoot, { ignoreFailure: true });
173
173
  }
174
174
  catch (error) {
175
175
  await handlePluginError(error, 'plugin marketplace add', silent);
@@ -43,7 +43,7 @@ describe('coding_agents service', () => {
43
43
  'team-tools': {
44
44
  source: {
45
45
  source: 'github',
46
- repo: 'growthxai/output-claude-plugins'
46
+ repo: 'growthxai/output'
47
47
  }
48
48
  }
49
49
  },
@@ -151,7 +151,7 @@ describe('coding_agents service', () => {
151
151
  it('should call registerPluginMarketplace and installOutputAIPlugin', async () => {
152
152
  const { executeClaudeCommand } = await import('../utils/claude.js');
153
153
  await ensureClaudePlugin('/test/project');
154
- expect(executeClaudeCommand).toHaveBeenCalledWith(['plugin', 'marketplace', 'add', 'growthxai/output-claude-plugins'], '/test/project', { ignoreFailure: true });
154
+ expect(executeClaudeCommand).toHaveBeenCalledWith(['plugin', 'marketplace', 'add', 'growthxai/output'], '/test/project', { ignoreFailure: true });
155
155
  expect(executeClaudeCommand).toHaveBeenCalledWith(['plugin', 'marketplace', 'update', 'outputai'], '/test/project');
156
156
  expect(executeClaudeCommand).toHaveBeenCalledWith(['plugin', 'install', 'outputai@outputai', '--scope', 'project'], '/test/project');
157
157
  });
@@ -188,7 +188,7 @@ describe('coding_agents service', () => {
188
188
  vi.mocked(fs.readFile).mockResolvedValue(JSON.stringify({
189
189
  extraKnownMarketplaces: {
190
190
  'team-tools': {
191
- source: { source: 'github', repo: 'growthxai/output-claude-plugins' }
191
+ source: { source: 'github', repo: 'growthxai/output' }
192
192
  }
193
193
  },
194
194
  enabledPlugins: { 'outputai@outputai': true }
@@ -15,4 +15,8 @@ describe('copy-assets build output', () => {
15
15
  expect(fs.existsSync(filePath), `Missing dotfile template in dist: ${dotfile}`).toBe(true);
16
16
  }
17
17
  });
18
+ it('should include config templates in dist/templates/project/', () => {
19
+ const configFile = path.join(distTemplatesDir, 'config', 'costs.yml.template');
20
+ expect(fs.existsSync(configFile), 'Missing config/costs.yml.template in dist').toBe(true);
21
+ });
18
22
  });
@@ -34,5 +34,6 @@ export interface DockerComposeProcess {
34
34
  }
35
35
  export type PullPolicy = 'always' | 'missing' | 'never';
36
36
  export declare function startDockerCompose(dockerComposePath: string, pullPolicy?: PullPolicy): Promise<DockerComposeProcess>;
37
+ export declare function startDockerComposeDetached(dockerComposePath: string, pullPolicy?: PullPolicy): void;
37
38
  export declare function stopDockerCompose(dockerComposePath: string): Promise<void>;
38
39
  export { isDockerInstalled, isDockerComposeAvailable, isDockerDaemonRunning, DockerValidationError };
@@ -141,6 +141,18 @@ export async function startDockerCompose(dockerComposePath, pullPolicy) {
141
141
  waitForHealthy: () => waitForServicesHealthy(dockerComposePath)
142
142
  };
143
143
  }
144
+ export function startDockerComposeDetached(dockerComposePath, pullPolicy) {
145
+ const args = [
146
+ 'compose',
147
+ '-f', dockerComposePath,
148
+ '--project-directory', process.cwd(),
149
+ 'up', '-d'
150
+ ];
151
+ if (pullPolicy) {
152
+ args.push('--pull', pullPolicy);
153
+ }
154
+ execFileSync('docker', args, { stdio: 'inherit', cwd: process.cwd() });
155
+ }
144
156
  export async function stopDockerCompose(dockerComposePath) {
145
157
  ux.stdout('⏹️ Stopping services...\n');
146
158
  execFileSync('docker', ['compose', '-f', dockerComposePath, 'down'], { stdio: 'inherit', cwd: process.cwd() });
@@ -1,6 +1,6 @@
1
1
  ---
2
2
  provider: anthropic
3
- model: claude-sonnet-4-20250514
3
+ model: claude-haiku-4-5
4
4
  temperature: 0.3
5
5
  ---
6
6
 
@@ -7,7 +7,7 @@ This is an **Output.ai** project - a framework for building reliable, production
7
7
  For full framework documentation, commands, and AI-assisted workflow development, install our Claude Code plugins:
8
8
 
9
9
  ```bash
10
- claude plugin marketplace add growthxai/output-claude-plugins
10
+ claude plugin marketplace add growthxai/output
11
11
  claude plugin install outputai@outputai --scope project
12
12
  ```
13
13
 
@@ -16,7 +16,7 @@
16
16
  "team-tools": {
17
17
  "source": {
18
18
  "source": "github",
19
- "repo": "growthxai/output-claude-plugins"
19
+ "repo": "growthxai/output"
20
20
  }
21
21
  }
22
22
  },
@@ -0,0 +1,29 @@
1
+ # Token & API Pricing Configuration
2
+ # Prices are per million tokens, in USD
3
+ # Entries here are merged with built-in defaults (project values take precedence)
4
+ # Model names support prefix matching: 'claude-sonnet-4' matches 'claude-sonnet-4-20250514'
5
+ # models:
6
+ # # =============================================================================
7
+ # # Anthropic
8
+ # # =============================================================================
9
+ # claude-haiku-4-5:
10
+ # provider: anthropic
11
+ # input: 1.00
12
+ # output: 5.00
13
+ # cached_input: 0.10
14
+
15
+ # claude-sonnet-4:
16
+ # provider: anthropic
17
+ # input: 3.00
18
+ # output: 15.00
19
+ # cached_input: 0.30
20
+
21
+ # services:
22
+ # # =============================================================================
23
+ # # Jina Reader API - Token-based
24
+ # # =============================================================================
25
+ # jina:
26
+ # type: token
27
+ # url_pattern: "r.jina.ai"
28
+ # usage_path: "body.data.usage.tokens"
29
+ # per_million: 0.045
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/cli",
3
- "version": "0.1.0",
3
+ "version": "0.1.1",
4
4
  "description": "CLI for Output.ai workflow generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -15,11 +15,11 @@
15
15
  },
16
16
  "dependencies": {
17
17
  "@anthropic-ai/claude-agent-sdk": "0.1.71",
18
- "@aws-sdk/client-s3": "3.1004.0",
18
+ "@aws-sdk/client-s3": "3.1011.0",
19
19
  "@hackylabs/deep-redact": "3.0.5",
20
- "@inquirer/prompts": "8.3.0",
21
- "@oclif/core": "4.8.3",
22
- "@oclif/plugin-help": "6.2.37",
20
+ "@inquirer/prompts": "8.3.2",
21
+ "@oclif/core": "4.9.0",
22
+ "@oclif/plugin-help": "6.2.38",
23
23
  "change-case": "5.4.4",
24
24
  "cli-progress": "3.12.0",
25
25
  "cli-table3": "0.6.5",
@@ -28,14 +28,14 @@
28
28
  "dotenv": "17.3.1",
29
29
  "handlebars": "4.7.8",
30
30
  "js-yaml": "4.1.1",
31
- "json-schema-library": "11.0.4",
31
+ "json-schema-library": "11.0.5",
32
32
  "ky": "1.14.3",
33
33
  "log-update": "7.2.0",
34
34
  "semver": "7.7.4",
35
35
  "yaml": "^2.7.1",
36
- "@outputai/credentials": "0.1.0",
37
- "@outputai/evals": "0.1.0",
38
- "@outputai/llm": "0.1.0"
36
+ "@outputai/credentials": "0.1.1",
37
+ "@outputai/llm": "0.1.1",
38
+ "@outputai/evals": "0.1.1"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/cli-progress": "3.11.6",