serpentstack 0.2.4 → 0.2.6
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/serpentstack.js +73 -23
- package/lib/commands/persistent.js +559 -0
- package/lib/commands/skills-init.js +51 -14
- package/lib/commands/stack-new.js +44 -9
- package/lib/utils/config.js +106 -0
- package/lib/utils/fs-helpers.js +1 -1
- package/lib/utils/models.js +156 -0
- package/lib/utils/ui.js +5 -5
- package/package.json +1 -1
- package/lib/commands/skills-persistent.js +0 -358
package/bin/serpentstack.js
CHANGED
|
@@ -2,13 +2,23 @@
|
|
|
2
2
|
|
|
3
3
|
import { error, bold, dim, green, cyan, getVersion, printHeader } from '../lib/utils/ui.js';
|
|
4
4
|
|
|
5
|
-
|
|
5
|
+
// Short flag aliases
|
|
6
|
+
const FLAG_ALIASES = { f: 'force', h: 'help', v: 'version', a: 'all' };
|
|
7
|
+
|
|
8
|
+
function parseArgs(args) {
|
|
6
9
|
const flags = {};
|
|
7
10
|
const positional = [];
|
|
8
11
|
for (const arg of args) {
|
|
9
12
|
if (arg.startsWith('--')) {
|
|
10
13
|
const [key, val] = arg.slice(2).split('=');
|
|
11
14
|
flags[key] = val ?? true;
|
|
15
|
+
} else if (arg.startsWith('-') && arg.length > 1 && !arg.startsWith('--')) {
|
|
16
|
+
// Short flags: -f, -h, -v, -a, or combined like -fa
|
|
17
|
+
for (const ch of arg.slice(1)) {
|
|
18
|
+
const long = FLAG_ALIASES[ch];
|
|
19
|
+
if (long) flags[long] = true;
|
|
20
|
+
else flags[ch] = true;
|
|
21
|
+
}
|
|
12
22
|
} else {
|
|
13
23
|
positional.push(arg);
|
|
14
24
|
}
|
|
@@ -16,39 +26,73 @@ function parseFlags(args) {
|
|
|
16
26
|
return { flags, positional };
|
|
17
27
|
}
|
|
18
28
|
|
|
29
|
+
// Known commands for fuzzy matching on typos
|
|
30
|
+
const KNOWN_COMMANDS = ['stack', 'skills', 'persistent'];
|
|
31
|
+
|
|
32
|
+
function suggestCommand(input) {
|
|
33
|
+
const lower = input.toLowerCase();
|
|
34
|
+
let best = null, bestDist = 3; // threshold: edit distance ≤ 2
|
|
35
|
+
for (const cmd of KNOWN_COMMANDS) {
|
|
36
|
+
if (cmd.startsWith(lower) || lower.startsWith(cmd)) return cmd;
|
|
37
|
+
const d = editDistance(lower, cmd);
|
|
38
|
+
if (d < bestDist) { bestDist = d; best = cmd; }
|
|
39
|
+
}
|
|
40
|
+
return best;
|
|
41
|
+
}
|
|
42
|
+
|
|
43
|
+
function editDistance(a, b) {
|
|
44
|
+
const m = a.length, n = b.length;
|
|
45
|
+
const dp = Array.from({ length: m + 1 }, () => new Array(n + 1));
|
|
46
|
+
for (let i = 0; i <= m; i++) dp[i][0] = i;
|
|
47
|
+
for (let j = 0; j <= n; j++) dp[0][j] = j;
|
|
48
|
+
for (let i = 1; i <= m; i++)
|
|
49
|
+
for (let j = 1; j <= n; j++)
|
|
50
|
+
dp[i][j] = Math.min(
|
|
51
|
+
dp[i - 1][j] + 1,
|
|
52
|
+
dp[i][j - 1] + 1,
|
|
53
|
+
dp[i - 1][j - 1] + (a[i - 1] !== b[j - 1] ? 1 : 0),
|
|
54
|
+
);
|
|
55
|
+
return dp[m][n];
|
|
56
|
+
}
|
|
57
|
+
|
|
19
58
|
function showHelp() {
|
|
20
59
|
printHeader();
|
|
21
60
|
console.log(` ${bold('Usage:')} serpentstack <command> [options]
|
|
22
61
|
|
|
23
|
-
${bold(green('
|
|
24
|
-
${cyan('stack new')} <name> Scaffold a
|
|
62
|
+
${bold(green('New projects'))}
|
|
63
|
+
${cyan('stack new')} <name> Scaffold a full project from the template
|
|
25
64
|
${cyan('stack update')} Update template-level files to latest
|
|
26
65
|
|
|
27
|
-
${bold(green('
|
|
28
|
-
${cyan('skills
|
|
66
|
+
${bold(green('Any project'))}
|
|
67
|
+
${cyan('skills')} Download base skills + persistent agent configs
|
|
29
68
|
${cyan('skills update')} Update base skills to latest versions
|
|
30
|
-
${cyan('
|
|
31
|
-
${cyan('
|
|
69
|
+
${cyan('persistent')} Manage and launch persistent agents
|
|
70
|
+
${cyan('persistent')} --stop Stop all running agents
|
|
71
|
+
${cyan('persistent')} --reconfigure Re-run setup (change models, enable/disable)
|
|
32
72
|
|
|
33
73
|
${bold('Options:')}
|
|
34
|
-
--force
|
|
35
|
-
--all
|
|
36
|
-
--version
|
|
37
|
-
--help
|
|
74
|
+
-f, --force Overwrite existing files
|
|
75
|
+
-a, --all Include new files in updates (skills update)
|
|
76
|
+
-v, --version Show version
|
|
77
|
+
-h, --help Show this help
|
|
38
78
|
|
|
39
79
|
${dim('Examples:')}
|
|
40
80
|
${dim('$')} serpentstack stack new my-saas-app
|
|
41
|
-
${dim('$')} serpentstack skills
|
|
42
|
-
${dim('$')} serpentstack
|
|
43
|
-
${dim('$')} serpentstack
|
|
81
|
+
${dim('$')} serpentstack skills
|
|
82
|
+
${dim('$')} serpentstack persistent
|
|
83
|
+
${dim('$')} serpentstack persistent --stop
|
|
44
84
|
|
|
45
85
|
${dim('Docs: https://github.com/Benja-Pauls/SerpentStack')}
|
|
46
86
|
`);
|
|
47
87
|
}
|
|
48
88
|
|
|
49
89
|
async function main() {
|
|
50
|
-
const
|
|
51
|
-
const { flags, positional } =
|
|
90
|
+
const rawArgs = process.argv.slice(2);
|
|
91
|
+
const { flags, positional } = parseArgs(rawArgs);
|
|
92
|
+
|
|
93
|
+
const noun = positional[0];
|
|
94
|
+
const verb = positional[1];
|
|
95
|
+
const rest = positional.slice(2);
|
|
52
96
|
|
|
53
97
|
// Top-level flags
|
|
54
98
|
if (noun === '--version' || flags.version) {
|
|
@@ -63,7 +107,7 @@ async function main() {
|
|
|
63
107
|
if (noun === 'stack') {
|
|
64
108
|
if (verb === 'new') {
|
|
65
109
|
const { stackNew } = await import('../lib/commands/stack-new.js');
|
|
66
|
-
await stackNew(
|
|
110
|
+
await stackNew(rest[0]);
|
|
67
111
|
} else if (verb === 'update') {
|
|
68
112
|
const { stackUpdate } = await import('../lib/commands/stack-update.js');
|
|
69
113
|
await stackUpdate({ force: !!flags.force });
|
|
@@ -73,23 +117,29 @@ async function main() {
|
|
|
73
117
|
process.exit(1);
|
|
74
118
|
}
|
|
75
119
|
} else if (noun === 'skills') {
|
|
76
|
-
if (verb === 'init') {
|
|
120
|
+
if (!verb || verb === 'init') {
|
|
121
|
+
// `serpentstack skills` or `serpentstack skills init` both work
|
|
77
122
|
const { skillsInit } = await import('../lib/commands/skills-init.js');
|
|
78
123
|
await skillsInit({ force: !!flags.force });
|
|
79
124
|
} else if (verb === 'update') {
|
|
80
125
|
const { skillsUpdate } = await import('../lib/commands/skills-update.js');
|
|
81
126
|
await skillsUpdate({ force: !!flags.force, all: !!flags.all });
|
|
82
|
-
} else if (verb === 'persistent') {
|
|
83
|
-
const { skillsPersistent } = await import('../lib/commands/skills-persistent.js');
|
|
84
|
-
await skillsPersistent({ stop: !!flags.stop });
|
|
85
127
|
} else {
|
|
86
128
|
error(`Unknown skills command: ${verb}`);
|
|
87
|
-
console.log(`\n Available: ${bold('skills
|
|
129
|
+
console.log(`\n Available: ${bold('skills')}, ${bold('skills update')}\n`);
|
|
88
130
|
process.exit(1);
|
|
89
131
|
}
|
|
132
|
+
} else if (noun === 'persistent') {
|
|
133
|
+
const { persistent } = await import('../lib/commands/persistent.js');
|
|
134
|
+
await persistent({ stop: !!flags.stop, reconfigure: !!flags.reconfigure });
|
|
90
135
|
} else {
|
|
91
136
|
error(`Unknown command: ${bold(noun)}`);
|
|
92
|
-
|
|
137
|
+
const suggestion = suggestCommand(noun);
|
|
138
|
+
if (suggestion) {
|
|
139
|
+
console.log(`\n Did you mean ${bold(suggestion)}? Run ${bold(`serpentstack ${suggestion}`)} or ${bold('serpentstack --help')}.\n`);
|
|
140
|
+
} else {
|
|
141
|
+
console.log(`\n Run ${bold('serpentstack --help')} to see available commands.\n`);
|
|
142
|
+
}
|
|
93
143
|
process.exit(1);
|
|
94
144
|
}
|
|
95
145
|
}
|