@solidactions/cli 1.3.0 → 1.6.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/README.md CHANGED
@@ -12,7 +12,7 @@ npm install -g @solidactions/cli
12
12
 
13
13
  ```bash
14
14
  solidactions init <api-key> # Initialize (prompts for workspace)
15
- solidactions init <api-key> --workspace <name> # Initialize with workspace
15
+ solidactions ai init # Install AI skills and SDK reference (required for AI-assisted dev)
16
16
  solidactions project deploy <project> <path> # Deploy a project
17
17
  solidactions run start <project> <workflow> # Trigger a workflow
18
18
  solidactions run view <run-id> # Inspect a run
@@ -127,9 +127,21 @@ Use `solidactions <command> --help` for full flag details on any command.
127
127
 
128
128
  | Command | Key Flags | Description |
129
129
  |---------|-----------|-------------|
130
- | `ai init` | `--claude`, `--agents` | Install AI helper docs |
130
+ | `ai init` | `--claude`, `--agents` | Install AI skills and SDK reference |
131
131
  | `ai examples [names...]` | `--all`, `--overwrite` | Install example workflows |
132
132
 
133
+ `ai init` installs three auto-activating SolidActions skills and a
134
+ full SDK reference into your project:
135
+
136
+ - Skills go to `.claude/skills/` (for `--claude` / Claude Code) or
137
+ `.agents/skills/` (for `--agents` / Codex, Cursor, Gemini, Windsurf).
138
+ Codex auto-discovers the `.agents/skills/` path — see
139
+ https://developers.openai.com/codex/skills.
140
+ - The SDK reference is saved to `.solidactions/sdk-reference.md`.
141
+ - A slim pointer section is injected into `CLAUDE.md` or `AGENTS.md`
142
+ listing the skills and inlining the highest-cost hard rules
143
+ (determinism, step discipline, messaging) as a safety net.
144
+
133
145
  ## Development
134
146
 
135
147
  ```bash
@@ -8,11 +8,13 @@ const fs_1 = __importDefault(require("fs"));
8
8
  const chalk_1 = __importDefault(require("chalk"));
9
9
  const prompts_1 = __importDefault(require("prompts"));
10
10
  const fs_extra_1 = __importDefault(require("fs-extra"));
11
+ const path_1 = __importDefault(require("path"));
11
12
  const github_1 = require("../utils/github");
12
13
  const markers_1 = require("../utils/markers");
14
+ const skills_1 = require("../utils/skills");
13
15
  async function aiInit(options = {}) {
14
16
  try {
15
- // Determine target file
17
+ // Determine target file.
16
18
  let targetFile;
17
19
  if (options.claude && options.agents) {
18
20
  console.error(chalk_1.default.red('Please specify only one of --claude or --agents'));
@@ -30,8 +32,8 @@ async function aiInit(options = {}) {
30
32
  name: 'file',
31
33
  message: 'Which AI helper file?',
32
34
  choices: [
33
- { title: 'CLAUDE.md', value: 'CLAUDE.md' },
34
- { title: 'AGENTS.md', value: 'AGENTS.md' },
35
+ { title: 'CLAUDE.md (Claude Code)', value: 'CLAUDE.md' },
36
+ { title: 'AGENTS.md (Codex, Cursor, Gemini, etc.)', value: 'AGENTS.md' },
35
37
  ],
36
38
  });
37
39
  if (!response.file) {
@@ -41,22 +43,26 @@ async function aiInit(options = {}) {
41
43
  targetFile = response.file;
42
44
  }
43
45
  console.log(chalk_1.default.blue('Fetching AI helper content...'));
44
- // Fetch AI helper content from examples repo
45
- const aiContent = await (0, github_1.fetchRawFile)('SolidActions', 'solidactions-examples', 'CLAUDE.md');
46
- // Fetch SDK reference
46
+ // Fetch slim helper content for the chosen target.
47
+ const aiContent = await (0, skills_1.fetchAiHelperContent)(targetFile);
48
+ // Fetch SDK reference (always).
47
49
  const sdkContent = await (0, github_1.fetchRawFile)('SolidActions', 'solidactions-ts-sdk', 'docs/sdk-reference.md');
48
- // Save SDK reference to .solidactions/sdk-reference.md
50
+ // Save SDK reference to .solidactions/sdk-reference.md.
49
51
  fs_extra_1.default.ensureDirSync('.solidactions');
50
52
  fs_1.default.writeFileSync('.solidactions/sdk-reference.md', sdkContent, 'utf8');
51
53
  console.log(chalk_1.default.green('✓ SDK reference saved to .solidactions/sdk-reference.md'));
52
- // Upsert main AI helper section
54
+ // Always install skills no prompt. The user chose `ai init <target>`,
55
+ // so they want AI tooling set up.
56
+ const targetDir = (0, skills_1.skillTargetDir)(targetFile);
57
+ console.log(chalk_1.default.blue(`Installing SolidActions skills to ${path_1.default.relative(process.cwd(), targetDir)}/ ...`));
58
+ const { written } = await (0, skills_1.installSkills)(targetDir);
59
+ for (const f of written) {
60
+ console.log(chalk_1.default.green(`✓ ${path_1.default.relative(process.cwd(), f)}`));
61
+ }
62
+ // Upsert the AI helper marker section.
53
63
  (0, markers_1.upsertMarkerSection)(targetFile, 'SolidActions', aiContent);
54
- // Upsert SDK reference pointer section
55
- const sdkReferenceNote = `## SolidActions SDK Reference
56
-
57
- The full SDK API reference is available at \`.solidactions/sdk-reference.md\`. Refer to it for detailed function signatures, error classes, retry configuration, and advanced patterns like forking, streaming, and signal URLs.`;
58
- (0, markers_1.upsertMarkerSection)(targetFile, 'SolidActions SDK Reference', sdkReferenceNote);
59
64
  console.log(chalk_1.default.green(`✓ AI helper installed to ${targetFile}`));
65
+ console.log(chalk_1.default.gray(' Existing files may still contain a separate "SolidActions SDK Reference" marker block from older CLI versions. That content is now redundant and can be deleted manually.'));
60
66
  }
61
67
  catch (error) {
62
68
  if (error.message?.includes('rate limit')) {
@@ -114,11 +114,51 @@ async function pushYamlDeclarations(config, projectSlug, yamlConfig) {
114
114
  async function deploy(projectName, sourcePath, options = {}) {
115
115
  const config = await (0, api_1.requireConfigWithWorkspace)();
116
116
  const sourceDir = sourcePath ? path_1.default.resolve(sourcePath) : process.cwd();
117
- const environment = options.env || 'dev';
117
+ // Capture whether -e was explicitly passed by the caller. Commander leaves
118
+ // options.env as undefined when no default is set and the flag is omitted.
119
+ const explicitEnv = options.env;
118
120
  if (!fs_1.default.existsSync(sourceDir)) {
119
121
  console.error(chalk_1.default.red(`Source directory not found: ${sourceDir}`));
120
122
  process.exit(1);
121
123
  }
124
+ // -------------------------------------------------------------------------
125
+ // Production-existence check — must happen BEFORE we apply any env default
126
+ // so that first-time deploys without -e get a helpful error instead of
127
+ // silently creating a dev-only project with no production root.
128
+ // -------------------------------------------------------------------------
129
+ let productionExists = null; // null = unknown (error path)
130
+ let productionSlug = null;
131
+ try {
132
+ const prodResponse = await axios_1.default.get(`${config.host}/api/v1/projects/${projectName}`, {
133
+ headers: (0, api_1.getApiHeaders)(config),
134
+ });
135
+ productionExists = true;
136
+ productionSlug = prodResponse.data.slug || prodResponse.data.name;
137
+ }
138
+ catch (error) {
139
+ if (error.response?.status === 404) {
140
+ productionExists = false;
141
+ }
142
+ else {
143
+ // 5xx, network error, auth failure, etc. — fail conservatively rather
144
+ // than treating the project as non-existent and potentially creating it.
145
+ console.error(chalk_1.default.red('Failed to check project existence:'), error.response?.data?.message || error.message);
146
+ process.exit(1);
147
+ }
148
+ }
149
+ // Decision: if production does not exist and no -e flag was given, the user
150
+ // must pick an environment explicitly to avoid creating a broken dev-only project.
151
+ if (productionExists === false && explicitEnv === undefined) {
152
+ console.error(chalk_1.default.red(`\nThis is the first deploy of "${projectName}" — please pick an environment explicitly:\n`));
153
+ console.error(` solidactions project deploy ${projectName} <path> -e production # most projects start here`);
154
+ console.error(` solidactions project deploy ${projectName} <path> -e dev # dev-only (uncommon; no production root will exist)`);
155
+ console.error(` solidactions project deploy ${projectName} <path> -e staging # staging-only (uncommon)`);
156
+ console.error(chalk_1.default.gray('\nTip: most projects should start with `-e production`. A project needs a production root before dev/staging children can attach to it.'));
157
+ process.exit(1);
158
+ }
159
+ // Apply default: if production already exists and the user didn't pass -e,
160
+ // use 'dev' — this preserves existing behavior for mature projects.
161
+ const environment = explicitEnv ?? 'dev';
122
162
  const envLabel = environment !== 'production' ? ` (${environment})` : '';
123
163
  console.log(chalk_1.default.blue(`Deploying to project "${projectName}"${envLabel}...`));
124
164
  console.log(chalk_1.default.gray(`Source: ${sourceDir}`));
@@ -149,24 +189,36 @@ async function deploy(projectName, sourcePath, options = {}) {
149
189
  process.exit(1);
150
190
  }
151
191
  console.log(chalk_1.default.green('✓ Project structure validated'));
152
- // Check if project exists, create if not
192
+ // Check if the target environment's project record exists; create if needed.
193
+ // For production deploys where we already confirmed existence above, reuse
194
+ // the cached slug to avoid a duplicate GET.
153
195
  let projectSlug = projectName;
154
196
  try {
155
- // For non-production environments, append the environment to the slug for lookup
156
- const lookupSlug = environment === 'production'
157
- ? projectName
158
- : `${projectName}-${environment}`;
159
- const checkResponse = await axios_1.default.get(`${config.host}/api/v1/projects/${lookupSlug}`, {
160
- headers: (0, api_1.getApiHeaders)(config),
161
- });
162
- projectSlug = checkResponse.data.slug || checkResponse.data.name;
197
+ if (environment === 'production' && productionExists === true && productionSlug !== null) {
198
+ // Already confirmed skip the re-lookup.
199
+ projectSlug = productionSlug;
200
+ }
201
+ else {
202
+ // For non-production environments, append the environment to the slug for lookup
203
+ const lookupSlug = environment === 'production'
204
+ ? projectName
205
+ : `${projectName}-${environment}`;
206
+ const checkResponse = await axios_1.default.get(`${config.host}/api/v1/projects/${lookupSlug}`, {
207
+ headers: (0, api_1.getApiHeaders)(config),
208
+ });
209
+ projectSlug = checkResponse.data.slug || checkResponse.data.name;
210
+ }
163
211
  }
164
212
  catch (error) {
165
213
  if (error.response?.status === 404) {
166
- // For non-production environments, check if we should create
214
+ // For non-production environments, require --create or give a clear hint
167
215
  if (environment !== 'production' && !options.create) {
168
- console.log(chalk_1.default.yellow(`Project "${projectName}" doesn't have a ${environment} environment.`));
169
- console.log(chalk_1.default.gray('Use --create to create it, or deploy to production first.'));
216
+ console.error(chalk_1.default.red(`\nProject "${projectName}" doesn't have a ${environment} environment.\n`));
217
+ console.error(` If production is the intended target:`);
218
+ console.error(` solidactions project deploy ${projectName} <path> -e production`);
219
+ console.error('');
220
+ console.error(` If you really want a new ${environment} environment:`);
221
+ console.error(` solidactions project deploy ${projectName} <path> -e ${environment} --create`);
170
222
  process.exit(1);
171
223
  }
172
224
  console.log(chalk_1.default.yellow(`Project "${projectName}"${envLabel} not found. Creating...`));
@@ -152,6 +152,9 @@ async function init(apiKey, options) {
152
152
  console.log(chalk_1.default.yellow('Could not set workspace. Run `solidactions workspace set` later.'));
153
153
  }
154
154
  console.log('');
155
+ console.log(chalk_1.default.blue('Next step — install AI helper docs and skills:'));
156
+ console.log(chalk_1.default.gray(' solidactions ai init Picks CLAUDE.md or AGENTS.md interactively'));
157
+ console.log('');
155
158
  console.log(chalk_1.default.blue('Quick start:'));
156
159
  console.log(chalk_1.default.gray(' solidactions project deploy <name> Deploy current directory'));
157
160
  console.log(chalk_1.default.gray(' solidactions run start <proj> <wf> Run a workflow'));
package/dist/index.js CHANGED
@@ -104,7 +104,7 @@ project
104
104
  .description('Deploy a project to SolidActions')
105
105
  .argument('<project-name>', 'Project name (will be created if it doesn\'t exist)')
106
106
  .argument('[path]', 'Source directory to deploy (defaults to current directory)')
107
- .option('-e, --env <environment>', 'Target environment (production/staging/dev)', 'dev')
107
+ .option('-e, --env <environment>', 'Target environment (production/staging/dev). Required on first deploy of a new project.')
108
108
  .option('--create', 'Create environment project if it doesn\'t exist')
109
109
  .option('--config-only', 'Sync YAML env declarations without building/deploying')
110
110
  .action((projectName, path, options) => {
@@ -311,9 +311,9 @@ workspace
311
311
  const ai = program.command('ai').description('AI helper tools');
312
312
  ai
313
313
  .command('init')
314
- .description('Install AI helper documentation (CLAUDE.md or AGENTS.md) for AI-assisted workflow development')
314
+ .description('Install SolidActions AI skills and SDK reference for AI-assisted development')
315
315
  .option('--claude', 'Use CLAUDE.md (for Claude Code)')
316
- .option('--agents', 'Use AGENTS.md (for Cursor, Windsurf, etc.)')
316
+ .option('--agents', 'Use AGENTS.md (for Codex, Cursor, Gemini, Windsurf, etc.)')
317
317
  .action((options) => { (0, ai_init_1.aiInit)(options); });
318
318
  ai
319
319
  .command('examples')
@@ -0,0 +1,59 @@
1
+ "use strict";
2
+ var __importDefault = (this && this.__importDefault) || function (mod) {
3
+ return (mod && mod.__esModule) ? mod : { "default": mod };
4
+ };
5
+ Object.defineProperty(exports, "__esModule", { value: true });
6
+ exports.skillTargetDir = skillTargetDir;
7
+ exports.installSkills = installSkills;
8
+ exports.fetchAiHelperContent = fetchAiHelperContent;
9
+ const fs_1 = __importDefault(require("fs"));
10
+ const path_1 = __importDefault(require("path"));
11
+ const fs_extra_1 = __importDefault(require("fs-extra"));
12
+ const github_1 = require("./github");
13
+ const SKILL_NAMES = [
14
+ 'solidactions-getting-started',
15
+ 'solidactions-workflow-coding',
16
+ 'solidactions-deploy-and-config',
17
+ ];
18
+ const EXAMPLES_OWNER = 'SolidActions';
19
+ const EXAMPLES_REPO = 'solidactions-examples';
20
+ const SKILLS_PATH_PREFIX = 'skills';
21
+ /**
22
+ * Resolve the skills directory for an AI helper target.
23
+ *
24
+ * - `CLAUDE.md` → `<cwd>/.claude/skills/` (Claude Code convention)
25
+ * - `AGENTS.md` → `<cwd>/.agents/skills/` (Codex auto-discovers this path;
26
+ * Cursor/Gemini read via AGENTS.md pointers)
27
+ *
28
+ * The directory does not need to exist — the caller creates it.
29
+ */
30
+ function skillTargetDir(targetFile, cwd = process.cwd()) {
31
+ if (targetFile === 'CLAUDE.md') {
32
+ return path_1.default.join(cwd, '.claude', 'skills');
33
+ }
34
+ return path_1.default.join(cwd, '.agents', 'skills');
35
+ }
36
+ /**
37
+ * Fetch all SolidActions skill files from the examples repo and write
38
+ * them into the target directory. Overwrites existing files (skills
39
+ * are versioned upstream).
40
+ */
41
+ async function installSkills(targetDir) {
42
+ const written = [];
43
+ fs_extra_1.default.ensureDirSync(targetDir);
44
+ for (const skillName of SKILL_NAMES) {
45
+ const remotePath = `${SKILLS_PATH_PREFIX}/${skillName}.md`;
46
+ const content = await (0, github_1.fetchRawFile)(EXAMPLES_OWNER, EXAMPLES_REPO, remotePath);
47
+ const filePath = path_1.default.join(targetDir, `${skillName}.md`);
48
+ fs_1.default.writeFileSync(filePath, content, 'utf8');
49
+ written.push(filePath);
50
+ }
51
+ return { written };
52
+ }
53
+ /**
54
+ * Fetch the slim AI-helper content for the target file from the examples
55
+ * repo. Single source per target — no legacy fallback.
56
+ */
57
+ async function fetchAiHelperContent(targetFile) {
58
+ return (0, github_1.fetchRawFile)(EXAMPLES_OWNER, EXAMPLES_REPO, targetFile);
59
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@solidactions/cli",
3
- "version": "1.3.0",
3
+ "version": "1.6.0",
4
4
  "description": "SolidActions CLI - Deploy and manage workflow automation",
5
5
  "main": "dist/index.js",
6
6
  "bin": {