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.
@@ -2,13 +2,23 @@
2
2
 
3
3
  import { error, bold, dim, green, cyan, getVersion, printHeader } from '../lib/utils/ui.js';
4
4
 
5
- function parseFlags(args) {
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('Stack commands'))} ${dim('(new projects)')}
24
- ${cyan('stack new')} <name> Scaffold a new project from the template
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('Skills commands'))} ${dim('(any project)')}
28
- ${cyan('skills init')} Download base skills + persistent agent configs
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('skills persistent')} Guided setup: configure + install + start all agents
31
- ${cyan('skills persistent')} --stop Stop all running agents
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 Overwrite existing files
35
- --all Include new files in updates (skills update)
36
- --version Show version
37
- --help Show this 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 init
42
- ${dim('$')} serpentstack skills persistent
43
- ${dim('$')} serpentstack skills persistent --stop
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 [,, noun, verb, ...rest] = process.argv;
51
- const { flags, positional } = parseFlags(rest);
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(positional[0]);
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 init')}, ${bold('skills update')}, ${bold('skills persistent')}\n`);
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
- console.log(`\n Run ${bold('serpentstack --help')} to see available commands.\n`);
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
  }