@outputai/cli 0.1.7-dev.b392109.0 → 0.1.7-next.ee88974.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.
@@ -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.6}
84
+ image: outputai/api:${OUTPUT_API_VERSION:-0.1.7-next.ee88974.0}
85
85
  init: true
86
86
  networks:
87
87
  - main
@@ -1,3 +1,3 @@
1
1
  {
2
- "framework": "0.1.6"
2
+ "framework": "0.1.7-next.ee88974.0"
3
3
  }
@@ -10,9 +10,3 @@ export declare const initCredentials: (environment: CredentialsEnvironment, work
10
10
  keyPath: string;
11
11
  credPath: string;
12
12
  };
13
- export declare const initCredentialsAtPath: (projectPath: string) => {
14
- keyPath: string;
15
- credPath: string;
16
- };
17
- export declare const readKeyAtPath: (projectPath: string) => string;
18
- export declare const writeEncryptedAtPath: (projectPath: string, plaintext: string) => void;
@@ -62,26 +62,3 @@ export const initCredentials = (environment, workflow) => {
62
62
  fs.writeFileSync(credPath, encrypt(template, key), 'utf8');
63
63
  return { keyPath, credPath };
64
64
  };
65
- export const initCredentialsAtPath = (projectPath) => {
66
- const credPath = resolveCredPath(projectPath);
67
- const keyPath = resolveKPath(projectPath);
68
- fs.mkdirSync(path.dirname(keyPath), { recursive: true });
69
- fs.mkdirSync(path.dirname(credPath), { recursive: true });
70
- const key = generateKey();
71
- fs.writeFileSync(keyPath, key, { mode: 0o600 });
72
- const template = stringifyYaml({
73
- anthropic: { api_key: '<FILL_ME_OUT>' },
74
- openai: { api_key: '<FILL_ME_OUT>' }
75
- });
76
- fs.writeFileSync(credPath, encrypt(template, key), 'utf8');
77
- return { keyPath, credPath };
78
- };
79
- export const readKeyAtPath = (projectPath) => {
80
- const keyPath = resolveKPath(projectPath);
81
- return fs.readFileSync(keyPath, 'utf8').trim();
82
- };
83
- export const writeEncryptedAtPath = (projectPath, plaintext) => {
84
- const key = readKeyAtPath(projectPath);
85
- const credPath = resolveCredPath(projectPath);
86
- fs.writeFileSync(credPath, encrypt(plaintext, key), 'utf8');
87
- };
@@ -8,7 +8,7 @@ const COMMENT_LINE = /^\s*#/;
8
8
  const COMMENTED_VAR = /^\s*#\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/;
9
9
  const ACTIVE_VAR = /^\s*([A-Z_][A-Z0-9_]*)\s*=\s*(.*)$/;
10
10
  const VAR_IN_COMMENT = /^\s*#\s*[A-Z_]+=/;
11
- const SECRET_MARKER = '<FILL_ME_OUT>';
11
+ const SECRET_MARKER = '<SECRET>';
12
12
  function extractDescription(commentLine) {
13
13
  return commentLine.replace(/^\s*#\s*/, '').trim();
14
14
  }
@@ -29,7 +29,7 @@ describe('configureEnvironmentVariables', () => {
29
29
  }
30
30
  });
31
31
  it('should copy .env.example to .env when skipPrompt is true', async () => {
32
- const envExampleContent = 'API_KEY=<FILL_ME_OUT>\nDATABASE_URL=localhost';
32
+ const envExampleContent = 'API_KEY=<SECRET>\nDATABASE_URL=localhost';
33
33
  await fs.writeFile(testState.envExamplePath, envExampleContent);
34
34
  const result = await configureEnvironmentVariables(testState.tempDir, true);
35
35
  expect(result).toBe(false);
@@ -182,7 +182,7 @@ EMPTY_KEY=`);
182
182
  vi.mocked(confirm).mockResolvedValue(true);
183
183
  vi.mocked(password).mockResolvedValueOnce('my-secret-api-key');
184
184
  await fs.writeFile(testState.envExamplePath, `# API Key
185
- ANTHROPIC_API_KEY=<FILL_ME_OUT>`);
185
+ ANTHROPIC_API_KEY=<SECRET>`);
186
186
  const result = await configureEnvironmentVariables(testState.tempDir, false);
187
187
  expect(result).toBe(true);
188
188
  expect(vi.mocked(password)).toHaveBeenCalledTimes(1);
@@ -2,7 +2,7 @@
2
2
  * Success and informational messages for project initialization
3
3
  */
4
4
  export declare const getEjectSuccessMessage: (destPath: string, outputFile: string, binName: string) => string;
5
- export declare const getProjectSuccessMessage: (folderName: string, installSuccess: boolean, credentialsConfigured?: boolean) => string;
5
+ export declare const getProjectSuccessMessage: (folderName: string, installSuccess: boolean, envConfigured?: boolean) => string;
6
6
  export declare const getWorkflowGenerateSuccessMessage: (workflowName: string, targetDir: string, filesCreated: string[]) => string;
7
7
  export declare const getDevSuccessMessage: (services: Array<{
8
8
  name: string;
@@ -147,7 +147,7 @@ ${ux.colorize('dim', '💡 Tip: Test your changes with ')}${formatCommand('docke
147
147
  ${ux.colorize('green', ux.colorize('bold', 'Happy customizing! 🛠️'))}
148
148
  `;
149
149
  };
150
- export const getProjectSuccessMessage = (folderName, installSuccess, credentialsConfigured = false) => {
150
+ export const getProjectSuccessMessage = (folderName, installSuccess, envConfigured = false) => {
151
151
  const divider = ux.colorize('dim', '─'.repeat(80));
152
152
  const bulletPoint = ux.colorize('green', '▸');
153
153
  // Build the next steps array with proper formatting
@@ -164,11 +164,11 @@ export const getProjectSuccessMessage = (folderName, installSuccess, credentials
164
164
  note: 'Required before running workflows'
165
165
  });
166
166
  }
167
- if (!credentialsConfigured) {
167
+ if (!envConfigured) {
168
168
  steps.push({
169
- step: 'Add your API credentials',
170
- command: 'npx output credentials edit',
171
- note: 'Learn more: https://docs.output.ai/packages/credentials'
169
+ step: 'Configure environment variables',
170
+ command: 'cp .env.example .env',
171
+ note: 'Copy .env.example to .env and add your API keys'
172
172
  });
173
173
  }
174
174
  steps.push({
@@ -229,9 +229,6 @@ ${divider}
229
229
  ${ux.colorize('dim', '💡 Tip: Use ')}${formatCommand('npx output workflow plan')}${ux.colorize('dim', ' to design your first custom workflow')}
230
230
  ${ux.colorize('dim', ' with AI assistance.')}
231
231
 
232
- ${ux.colorize('dim', '🔑 Secrets: Use ')}${formatCommand('npx output credentials show|get|edit')}
233
- ${ux.colorize('dim', ' to manage your project secrets.')}
234
-
235
232
  ${ux.colorize('green', ux.colorize('bold', 'Happy building with Output! 🚀'))}
236
233
  `;
237
234
  };
@@ -1,7 +1,6 @@
1
1
  import { input, confirm } from '@inquirer/prompts';
2
2
  import { ux } from '@oclif/core';
3
3
  import { kebabCase, pascalCase } from 'change-case';
4
- import fs from 'node:fs/promises';
5
4
  import path from 'node:path';
6
5
  import { fileURLToPath } from 'node:url';
7
6
  import { FolderAlreadyExistsError, UserCancelledError, DirectoryCreationError } from '#types/errors.js';
@@ -11,8 +10,7 @@ import { getFrameworkVersion } from '#utils/framework_version.js';
11
10
  import { getErrorMessage, getErrorCode } from '#utils/error_utils.js';
12
11
  import { isDockerInstalled } from '#services/docker.js';
13
12
  import { isClaudeCliAvailable } from '#utils/claude.js';
14
- import { initCredentialsAtPath } from './credentials_service.js';
15
- import { configureCredentials } from './credentials_configurator.js';
13
+ import { configureEnvironmentVariables } from './env_configurator.js';
16
14
  import { getTemplateFiles, processTemplateFile } from './template_processor.js';
17
15
  import { initializeAgentConfig } from './coding_agents.js';
18
16
  import { getProjectSuccessMessage } from './messages.js';
@@ -107,12 +105,6 @@ async function scaffoldProjectFiles(projectPath, projectName, description) {
107
105
  await Promise.all(templateFiles.map(templateFile => processTemplateFile(templateFile, projectPath, templateVars)));
108
106
  return templateFiles.map(f => f.outputName);
109
107
  }
110
- const CREDENTIALS_TEMPLATE_CONTENT = 'anthropic:\n api_key: "<FILL_ME_OUT>"\nopenai:\n api_key: "<FILL_ME_OUT>"\n';
111
- async function createCredentialsTemplate(projectPath) {
112
- const filePath = path.join(projectPath, 'config', 'credentials.yml.template');
113
- await fs.mkdir(path.dirname(filePath), { recursive: true });
114
- await fs.writeFile(filePath, CREDENTIALS_TEMPLATE_CONTENT, 'utf-8');
115
- }
116
108
  async function executeNpmInstall(projectPath) {
117
109
  await executeCommand('npm', ['install'], projectPath);
118
110
  }
@@ -200,18 +192,13 @@ export async function runInit(skipEnv = false, folderName) {
200
192
  ux.stdout(`Created project folder: ${config.folderName}`);
201
193
  const filesCreated = await scaffoldProjectFiles(config.projectPath, config.projectName, config.description);
202
194
  ux.stdout(`Created ${filesCreated.length} project files`);
203
- await createCredentialsTemplate(config.projectPath);
204
- initCredentialsAtPath(config.projectPath);
205
- ux.stdout('Credentials initialized');
206
- const credentialsConfigured = await configureCredentials(config.projectPath, skipEnv);
207
- if (credentialsConfigured) {
208
- ux.stdout('API credentials configured');
195
+ const envConfigured = await configureEnvironmentVariables(config.projectPath, skipEnv);
196
+ if (envConfigured) {
197
+ ux.stdout('Environment variables configured in .env');
209
198
  }
210
- // Copy .env.example to .env (no secrets - they live in credentials.yml.enc)
211
- await fs.copyFile(path.join(config.projectPath, '.env.example'), path.join(config.projectPath, '.env'));
212
199
  await executeCommandWithMessages(() => initializeAgents(config.projectPath), 'Initializing agent system...', 'Agent system initialized');
213
200
  const installSuccess = await executeCommandWithMessages(() => executeNpmInstall(config.projectPath), 'Installing dependencies...', 'Dependencies installed');
214
- const nextSteps = getProjectSuccessMessage(config.folderName, installSuccess, credentialsConfigured);
201
+ const nextSteps = getProjectSuccessMessage(config.folderName, installSuccess, envConfigured);
215
202
  ux.stdout('Project created successfully!');
216
203
  ux.stdout(nextSteps);
217
204
  }
@@ -14,18 +14,8 @@ vi.mock('@inquirer/prompts', () => ({
14
14
  }));
15
15
  vi.mock('#utils/file_system.js');
16
16
  vi.mock('#utils/process.js');
17
- vi.mock('./credentials_service.js', () => ({
18
- initCredentialsAtPath: vi.fn()
19
- }));
20
- vi.mock('./credentials_configurator.js', () => ({
21
- configureCredentials: vi.fn().mockResolvedValue(false)
22
- }));
23
- vi.mock('node:fs/promises', () => ({
24
- default: {
25
- mkdir: vi.fn().mockResolvedValue(undefined),
26
- writeFile: vi.fn().mockResolvedValue(undefined),
27
- copyFile: vi.fn().mockResolvedValue(undefined)
28
- }
17
+ vi.mock('./env_configurator.js', () => ({
18
+ configureEnvironmentVariables: vi.fn().mockResolvedValue(false)
29
19
  }));
30
20
  vi.mock('./template_processor.js');
31
21
  vi.mock('./coding_agents.js');
@@ -2,8 +2,8 @@
2
2
  DOCKER_SERVICE_NAME={{projectName}}
3
3
 
4
4
  # Configure if you plan to use Anthropic Models in your LLM prompts
5
- ANTHROPIC_API_KEY=credential:anthropic.api_key
5
+ ANTHROPIC_API_KEY=<SECRET>
6
6
 
7
7
  # Configure if you plan to use OpenAI in your LLM prompts
8
- OPENAI_API_KEY=credential:openai.api_key
8
+ OPENAI_API_KEY=<SECRET>
9
9
 
@@ -21,8 +21,5 @@
21
21
  },
22
22
  "engines": {
23
23
  "node": ">=24.3.0"
24
- },
25
- "output": {
26
- "hookFiles": ["@outputai/credentials"]
27
24
  }
28
25
  }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@outputai/cli",
3
- "version": "0.1.7-dev.b392109.0",
3
+ "version": "0.1.7-next.ee88974.0",
4
4
  "description": "CLI for Output.ai workflow generation",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -33,9 +33,9 @@
33
33
  "log-update": "7.2.0",
34
34
  "semver": "7.7.4",
35
35
  "yaml": "^2.7.1",
36
- "@outputai/credentials": "0.1.7-dev.b392109.0",
37
- "@outputai/evals": "0.1.7-dev.b392109.0",
38
- "@outputai/llm": "0.1.7-dev.b392109.0"
36
+ "@outputai/credentials": "0.1.7-next.ee88974.0",
37
+ "@outputai/evals": "0.1.7-next.ee88974.0",
38
+ "@outputai/llm": "0.1.7-next.ee88974.0"
39
39
  },
40
40
  "devDependencies": {
41
41
  "@types/cli-progress": "3.11.6",
@@ -1 +0,0 @@
1
- export declare function configureCredentials(projectPath: string, skipPrompt?: boolean): Promise<boolean>;
@@ -1,68 +0,0 @@
1
- import { password, confirm } from '@inquirer/prompts';
2
- import fs from 'node:fs';
3
- import path from 'node:path';
4
- import { ux } from '@oclif/core';
5
- import { load as parseYaml, dump as stringifyYaml } from 'js-yaml';
6
- import { UserCancelledError } from '#types/errors.js';
7
- import { getErrorMessage } from '#utils/error_utils.js';
8
- import { writeEncryptedAtPath } from './credentials_service.js';
9
- const FILL_MARKER = '<FILL_ME_OUT>';
10
- const CREDENTIALS_TEMPLATE = path.join('config', 'credentials.yml.template');
11
- const findSecretFields = (obj, prefix = []) => Object.entries(obj).flatMap(([key, value]) => {
12
- const fieldPath = [...prefix, key];
13
- if (value === FILL_MARKER) {
14
- return [{ path: fieldPath, label: fieldPath.join('.') }];
15
- }
16
- if (typeof value === 'object' && value !== null) {
17
- return findSecretFields(value, fieldPath);
18
- }
19
- return [];
20
- });
21
- const setAtPath = (obj, [head, ...tail], value) => {
22
- if (tail.length === 0) {
23
- obj[head] = value;
24
- }
25
- else {
26
- setAtPath(obj[head], tail, value);
27
- }
28
- };
29
- export async function configureCredentials(projectPath, skipPrompt = false) {
30
- try {
31
- const templatePath = path.join(projectPath, CREDENTIALS_TEMPLATE);
32
- if (!fs.existsSync(templatePath)) {
33
- return false;
34
- }
35
- if (skipPrompt) {
36
- return false;
37
- }
38
- const shouldConfigure = await confirm({
39
- message: 'Would you like to configure API credentials now?',
40
- default: true
41
- });
42
- if (!shouldConfigure) {
43
- return false;
44
- }
45
- const templateContent = fs.readFileSync(templatePath, 'utf-8');
46
- const parsed = parseYaml(templateContent);
47
- const secretFields = findSecretFields(parsed);
48
- if (secretFields.length === 0) {
49
- return false;
50
- }
51
- for (const field of secretFields) {
52
- const value = await password({
53
- message: `${field.label} (secret):`,
54
- mask: true
55
- });
56
- setAtPath(parsed, field.path, value || '');
57
- }
58
- writeEncryptedAtPath(projectPath, stringifyYaml(parsed));
59
- return true;
60
- }
61
- catch (error) {
62
- if (error instanceof Error && error.name === 'ExitPromptError') {
63
- throw new UserCancelledError();
64
- }
65
- ux.warn(`Failed to configure credentials: ${getErrorMessage(error)}`);
66
- return false;
67
- }
68
- }