@solidactions/cli 1.3.0 → 1.5.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 +9 -1
- package/dist/commands/ai-init.js +40 -3
- package/dist/commands/deploy.js +65 -13
- package/dist/index.js +2 -1
- package/dist/utils/skills.js +71 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -127,9 +127,17 @@ 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`, `--no-skills` | Install AI helper docs |
|
|
131
131
|
| `ai examples [names...]` | `--all`, `--overwrite` | Install example workflows |
|
|
132
132
|
|
|
133
|
+
By default, `ai init` also installs three lazy-loaded SolidActions skills
|
|
134
|
+
into `.claude/skills/`. These activate automatically when you scaffold a
|
|
135
|
+
project, write workflow code, or deploy. The CLAUDE.md injection becomes
|
|
136
|
+
a slim pointer to keep always-loaded context light.
|
|
137
|
+
|
|
138
|
+
Pass `--no-skills` to skip the skill install and get the legacy full
|
|
139
|
+
CLAUDE.md content instead.
|
|
140
|
+
|
|
133
141
|
## Development
|
|
134
142
|
|
|
135
143
|
```bash
|
package/dist/commands/ai-init.js
CHANGED
|
@@ -8,8 +8,10 @@ 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
17
|
// Determine target file
|
|
@@ -40,15 +42,50 @@ async function aiInit(options = {}) {
|
|
|
40
42
|
}
|
|
41
43
|
targetFile = response.file;
|
|
42
44
|
}
|
|
45
|
+
// Decide whether to install skills (default true; --no-skills opts out)
|
|
46
|
+
const installSkillsEnabled = options.skills !== false;
|
|
47
|
+
// Determine skill targets if installing.
|
|
48
|
+
// Skills are Claude Code-specific (.claude/skills/) and not meaningful for other AI tools.
|
|
49
|
+
let skillTargets = [];
|
|
50
|
+
if (installSkillsEnabled && targetFile === 'CLAUDE.md') {
|
|
51
|
+
skillTargets = (0, skills_1.detectSkillTargets)();
|
|
52
|
+
if (skillTargets.length === 0) {
|
|
53
|
+
const resp = await (0, prompts_1.default)({
|
|
54
|
+
type: 'confirm',
|
|
55
|
+
name: 'create',
|
|
56
|
+
message: 'No `.claude/` directory found. Create `.claude/skills/` for SolidActions skills?',
|
|
57
|
+
initial: true,
|
|
58
|
+
});
|
|
59
|
+
if (resp.create === undefined) {
|
|
60
|
+
// User cancelled (Ctrl+C) — treat as abort, not decline
|
|
61
|
+
console.log(chalk_1.default.yellow('Cancelled.'));
|
|
62
|
+
process.exit(0);
|
|
63
|
+
}
|
|
64
|
+
else if (resp.create) {
|
|
65
|
+
skillTargets = [path_1.default.join(process.cwd(), '.claude', 'skills')];
|
|
66
|
+
}
|
|
67
|
+
else {
|
|
68
|
+
console.log(chalk_1.default.yellow('Skipping skill install. Run with --no-skills to silence this prompt.'));
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
}
|
|
43
72
|
console.log(chalk_1.default.blue('Fetching AI helper content...'));
|
|
44
|
-
// Fetch
|
|
45
|
-
const aiContent = await (0,
|
|
46
|
-
// Fetch SDK reference
|
|
73
|
+
// Fetch CLAUDE.md content (slim pointer if skills installed, else full)
|
|
74
|
+
const aiContent = await (0, skills_1.fetchClaudeMdContent)(skillTargets.length > 0);
|
|
75
|
+
// Fetch SDK reference (always)
|
|
47
76
|
const sdkContent = await (0, github_1.fetchRawFile)('SolidActions', 'solidactions-ts-sdk', 'docs/sdk-reference.md');
|
|
48
77
|
// Save SDK reference to .solidactions/sdk-reference.md
|
|
49
78
|
fs_extra_1.default.ensureDirSync('.solidactions');
|
|
50
79
|
fs_1.default.writeFileSync('.solidactions/sdk-reference.md', sdkContent, 'utf8');
|
|
51
80
|
console.log(chalk_1.default.green('✓ SDK reference saved to .solidactions/sdk-reference.md'));
|
|
81
|
+
// Install skills if enabled and targets exist
|
|
82
|
+
if (skillTargets.length > 0) {
|
|
83
|
+
console.log(chalk_1.default.blue('Installing SolidActions skills...'));
|
|
84
|
+
const { written } = await (0, skills_1.installSkills)(skillTargets);
|
|
85
|
+
for (const f of written) {
|
|
86
|
+
console.log(chalk_1.default.green(`✓ ${path_1.default.relative(process.cwd(), f)}`));
|
|
87
|
+
}
|
|
88
|
+
}
|
|
52
89
|
// Upsert main AI helper section
|
|
53
90
|
(0, markers_1.upsertMarkerSection)(targetFile, 'SolidActions', aiContent);
|
|
54
91
|
// Upsert SDK reference pointer section
|
package/dist/commands/deploy.js
CHANGED
|
@@ -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
|
-
|
|
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
|
|
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
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
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,
|
|
214
|
+
// For non-production environments, require --create or give a clear hint
|
|
167
215
|
if (environment !== 'production' && !options.create) {
|
|
168
|
-
console.
|
|
169
|
-
console.
|
|
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...`));
|
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)
|
|
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) => {
|
|
@@ -314,6 +314,7 @@ ai
|
|
|
314
314
|
.description('Install AI helper documentation (CLAUDE.md or AGENTS.md) for AI-assisted workflow development')
|
|
315
315
|
.option('--claude', 'Use CLAUDE.md (for Claude Code)')
|
|
316
316
|
.option('--agents', 'Use AGENTS.md (for Cursor, Windsurf, etc.)')
|
|
317
|
+
.option('--no-skills', 'Skip installing SolidActions skill files (uses legacy full CLAUDE.md injection)')
|
|
317
318
|
.action((options) => { (0, ai_init_1.aiInit)(options); });
|
|
318
319
|
ai
|
|
319
320
|
.command('examples')
|
|
@@ -0,0 +1,71 @@
|
|
|
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.detectSkillTargets = detectSkillTargets;
|
|
7
|
+
exports.installSkills = installSkills;
|
|
8
|
+
exports.fetchClaudeMdContent = fetchClaudeMdContent;
|
|
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
|
+
* Detect which AI-tool skill directories should receive skills.
|
|
23
|
+
* Returns absolute paths to the target skills/ subdirectories.
|
|
24
|
+
*
|
|
25
|
+
* v1 supports Claude Code only. Codex was resolved as user-global
|
|
26
|
+
* (~/.codex/skills) with no project-local equivalent, so it is
|
|
27
|
+
* intentionally NOT a target here.
|
|
28
|
+
*
|
|
29
|
+
* Detection rules:
|
|
30
|
+
* - If `.claude/` exists in cwd → include `.claude/skills/`
|
|
31
|
+
* - If neither exists → return empty (caller should prompt)
|
|
32
|
+
*/
|
|
33
|
+
function detectSkillTargets(cwd = process.cwd()) {
|
|
34
|
+
const targets = [];
|
|
35
|
+
if (fs_1.default.existsSync(path_1.default.join(cwd, '.claude'))) {
|
|
36
|
+
targets.push(path_1.default.join(cwd, '.claude', 'skills'));
|
|
37
|
+
}
|
|
38
|
+
return targets;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* Fetch all SolidActions skill files from the examples repo and write
|
|
42
|
+
* them into each target directory. Overwrites existing files (skills
|
|
43
|
+
* are versioned upstream).
|
|
44
|
+
*/
|
|
45
|
+
async function installSkills(targetDirs) {
|
|
46
|
+
const written = [];
|
|
47
|
+
for (const dir of targetDirs) {
|
|
48
|
+
fs_extra_1.default.ensureDirSync(dir);
|
|
49
|
+
}
|
|
50
|
+
for (const skillName of SKILL_NAMES) {
|
|
51
|
+
const remotePath = `${SKILLS_PATH_PREFIX}/${skillName}.md`;
|
|
52
|
+
const content = await (0, github_1.fetchRawFile)(EXAMPLES_OWNER, EXAMPLES_REPO, remotePath);
|
|
53
|
+
for (const dir of targetDirs) {
|
|
54
|
+
const filePath = path_1.default.join(dir, `${skillName}.md`);
|
|
55
|
+
fs_1.default.writeFileSync(filePath, content, 'utf8');
|
|
56
|
+
written.push(filePath);
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
return { written };
|
|
60
|
+
}
|
|
61
|
+
/**
|
|
62
|
+
* Pick the right CLAUDE.md content variant from the examples repo
|
|
63
|
+
* based on whether skills are being installed.
|
|
64
|
+
*
|
|
65
|
+
* - skillsInstalled = true → fetches CLAUDE-skills-pointer.md (slim)
|
|
66
|
+
* - skillsInstalled = false → fetches CLAUDE.md (full, legacy)
|
|
67
|
+
*/
|
|
68
|
+
async function fetchClaudeMdContent(skillsInstalled) {
|
|
69
|
+
const file = skillsInstalled ? 'CLAUDE-skills-pointer.md' : 'CLAUDE.md';
|
|
70
|
+
return (0, github_1.fetchRawFile)(EXAMPLES_OWNER, EXAMPLES_REPO, file);
|
|
71
|
+
}
|