stealthos-cli 0.1.0-alpha.1

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 ADDED
@@ -0,0 +1,44 @@
1
+ # stealthos-cli
2
+
3
+ Install and manage the [StealthOS](https://github.com/IgorKadu/stealthos) knowledge base in `~/.stealthos/`.
4
+
5
+ ```bash
6
+ npm i -g stealthos-cli
7
+ stealthos install
8
+ stealthos status
9
+ ```
10
+
11
+ ## Commands
12
+
13
+ | Command | Purpose |
14
+ |---|---|
15
+ | `stealthos install` | Copy the canonical `.ai/` tree into `~/.stealthos/` |
16
+ | `stealthos status` | Show install version, paths, tracked projects |
17
+ | `stealthos version` | Print CLI version |
18
+ | `stealthos help` | Usage |
19
+
20
+ ## Options
21
+
22
+ | Flag | Default | Description |
23
+ |---|---|---|
24
+ | `--home <path>` | `~/.stealthos` | Target install dir |
25
+ | `--source <path>` | auto-detect | Source `.ai/` tree (used for dev/CI) |
26
+ | `--force` | off | Overwrite existing files |
27
+
28
+ ## Environment
29
+
30
+ | Variable | Default | Purpose |
31
+ |---|---|---|
32
+ | `STEALTHOS_HOME` | `~/.stealthos` | Same as `--home` |
33
+ | `STEALTHOS_SOURCE` | auto-detect | Same as `--source` |
34
+ | `STEALTHOS_DEBUG` | off | Set `=1` to print stack traces |
35
+
36
+ ## What gets installed
37
+
38
+ `stealthos install` copies the canonical knowledge base (rules, workflows, server, hooks, blueprints, specs templates) into `~/.stealthos/`, **excluding** runtime outputs (`artifacts/`, `runtime/`, `snapshots/`, `context/`) and project-specific memory (those are initialized fresh per project).
39
+
40
+ After install, set up your project with [`create-stealthos`](https://www.npmjs.com/package/create-stealthos).
41
+
42
+ ## License
43
+
44
+ MIT — see root [LICENSE](../../LICENSE).
package/bin.mjs ADDED
@@ -0,0 +1,13 @@
1
+ #!/usr/bin/env node
2
+ // stealthos — CLI entry point
3
+ // Subcommands: install, status, version, help
4
+
5
+ import { runCli } from "./src/cli.mjs";
6
+
7
+ runCli(process.argv.slice(2)).catch((err) => {
8
+ process.stderr.write(`\n✖ stealthos failed: ${err.message}\n`);
9
+ if (process.env.STEALTHOS_DEBUG === "1") {
10
+ process.stderr.write(`${err.stack}\n`);
11
+ }
12
+ process.exit(1);
13
+ });
package/package.json ADDED
@@ -0,0 +1,35 @@
1
+ {
2
+ "name": "stealthos-cli",
3
+ "version": "0.1.0-alpha.1",
4
+ "description": "StealthOS CLI — install/manage the StealthOS knowledge base in ~/.stealthos/. Subcommands: install, status, version.",
5
+ "type": "module",
6
+ "bin": {
7
+ "stealthos": "./bin.mjs"
8
+ },
9
+ "files": [
10
+ "bin.mjs",
11
+ "src/",
12
+ "README.md"
13
+ ],
14
+ "engines": {
15
+ "node": ">=18.0.0"
16
+ },
17
+ "keywords": [
18
+ "stealthos",
19
+ "mcp",
20
+ "ai",
21
+ "cli",
22
+ "claude-code"
23
+ ],
24
+ "author": "Igor Kadu (https://github.com/IgorKadu)",
25
+ "license": "MIT",
26
+ "repository": {
27
+ "type": "git",
28
+ "url": "git+https://github.com/IgorKadu/stealthos.git",
29
+ "directory": "packages/stealthos-cli"
30
+ },
31
+ "bugs": {
32
+ "url": "https://github.com/IgorKadu/stealthos/issues"
33
+ },
34
+ "homepage": "https://github.com/IgorKadu/stealthos/tree/main/packages/stealthos-cli#readme"
35
+ }
package/src/cli.mjs ADDED
@@ -0,0 +1,79 @@
1
+ // stealthos CLI router. Subcommand dispatcher.
2
+
3
+ import { installCommand } from "./commands/install.mjs";
4
+ import { statusCommand } from "./commands/status.mjs";
5
+
6
+ const VERSION = "0.1.0-alpha.1";
7
+
8
+ function usage() {
9
+ process.stdout.write(`
10
+ stealthos v${VERSION}
11
+
12
+ USAGE
13
+ stealthos <command> [options]
14
+
15
+ COMMANDS
16
+ install Install StealthOS knowledge base into ~/.stealthos/
17
+ Options:
18
+ --source <path> Source .ai/ tree (default: auto-detect in monorepo)
19
+ --home <path> Target home (default: ~/.stealthos)
20
+ --force Overwrite existing files
21
+ status Show ~/.stealthos/ install status (version, paths, project count)
22
+ version Print stealthos CLI version
23
+ help Show this message
24
+
25
+ ENVIRONMENT
26
+ STEALTHOS_HOME Override target home (default: ~/.stealthos)
27
+ STEALTHOS_SOURCE Override source .ai/ path (default: auto-detect)
28
+ STEALTHOS_DEBUG=1 Print stack traces on errors
29
+
30
+ EXAMPLES
31
+ stealthos install
32
+ stealthos install --home /opt/stealthos
33
+ stealthos status
34
+ `);
35
+ }
36
+
37
+ export async function runCli(argv) {
38
+ const [cmd = "help", ...rest] = argv;
39
+ switch (cmd) {
40
+ case "install":
41
+ return installCommand(parseFlags(rest));
42
+ case "status":
43
+ return statusCommand(parseFlags(rest));
44
+ case "version":
45
+ case "-v":
46
+ case "--version":
47
+ process.stdout.write(`${VERSION}\n`);
48
+ return;
49
+ case "help":
50
+ case "-h":
51
+ case "--help":
52
+ usage();
53
+ return;
54
+ default:
55
+ process.stderr.write(`Unknown command: ${cmd}\n`);
56
+ usage();
57
+ process.exit(2);
58
+ }
59
+ }
60
+
61
+ function parseFlags(args) {
62
+ const out = { _positional: [] };
63
+ for (let i = 0; i < args.length; i++) {
64
+ const a = args[i];
65
+ if (a.startsWith("--")) {
66
+ const key = a.slice(2);
67
+ const next = args[i + 1];
68
+ if (next && !next.startsWith("--")) {
69
+ out[key] = next;
70
+ i++;
71
+ } else {
72
+ out[key] = true;
73
+ }
74
+ } else {
75
+ out._positional.push(a);
76
+ }
77
+ }
78
+ return out;
79
+ }
@@ -0,0 +1,136 @@
1
+ // stealthos install — copies the canonical .ai/ tree into ~/.stealthos/
2
+ // (or wherever --home / STEALTHOS_HOME points).
3
+ // See ADR-0012 for the hybrid architecture.
4
+
5
+ import { existsSync } from "node:fs";
6
+ import { mkdir, writeFile, readFile } from "node:fs/promises";
7
+ import { join } from "node:path";
8
+ import { homedir } from "node:os";
9
+ import { resolveSource } from "../lib/resolve-source.mjs";
10
+ import { copyTree } from "../lib/copy-tree.mjs";
11
+
12
+ // Paths NOT to copy: runtime-generated outputs + project-specific history.
13
+ // These get freshly initialized per project by the server.
14
+ const DEFAULT_EXCLUDES = [
15
+ // Outputs
16
+ "artifacts",
17
+ "context",
18
+ "runtime",
19
+ "snapshots",
20
+ ".runtime",
21
+ // Project-specific memory (templates written below)
22
+ "memory/_detected-stack.json",
23
+ "memory/_summary.md",
24
+ "memory/decisions.md",
25
+ "memory/completed-tasks.md",
26
+ // Server runtime
27
+ "server/node_modules",
28
+ "server/.runtime",
29
+ ];
30
+
31
+ // Memory templates (empty histories so the server can append to them)
32
+ const MEMORY_TEMPLATES = {
33
+ "memory/decisions.md": `---
34
+ version: 1.0.0
35
+ updated: AUTO
36
+ tier: conditional
37
+ mutable: append_only
38
+ triggers: arquitetura, decisão, adr
39
+ ---
40
+
41
+ # Architecture Decision Records (ADRs)
42
+
43
+ > Registro persistente de decisões arquiteturais. Cada ADR captura contexto, decisão, consequências e alternativas.
44
+
45
+ ## Entradas
46
+
47
+ <!-- ADRs aparecem abaixo, mais recente primeiro -->
48
+ `,
49
+ "memory/completed-tasks.md": `---
50
+ version: 1.0.0
51
+ updated: AUTO
52
+ tier: conditional
53
+ mutable: append_only
54
+ ---
55
+
56
+ # Completed Tasks Log
57
+
58
+ > Histórico append-only de tarefas concluídas com contexto suficiente para recuperar a memória do projeto.
59
+
60
+ ## Entradas
61
+
62
+ <!-- Tarefas concluídas aparecem abaixo, mais recente primeiro -->
63
+ `,
64
+ };
65
+
66
+ export async function installCommand(flags = {}) {
67
+ const home = flags.home ? String(flags.home) : (process.env.STEALTHOS_HOME || join(homedir(), ".stealthos"));
68
+ const source = resolveSource({ flagSource: flags.source });
69
+ const force = !!flags.force;
70
+
71
+ process.stdout.write(`\n╔══════════════════════════════════════════════╗\n`);
72
+ process.stdout.write(`║ stealthos install ║\n`);
73
+ process.stdout.write(`╚══════════════════════════════════════════════╝\n`);
74
+ process.stdout.write(`📂 Source: ${source}\n`);
75
+ process.stdout.write(`📦 Target: ${home}\n`);
76
+ process.stdout.write(`🔧 Force: ${force ? "yes (overwrite)" : "no (skip existing)"}\n\n`);
77
+
78
+ if (existsSync(home) && !force) {
79
+ process.stdout.write(`⚠ ${home} já existe. Arquivos existentes serão preservados (use --force para sobrescrever).\n\n`);
80
+ }
81
+
82
+ await mkdir(home, { recursive: true });
83
+ await mkdir(join(home, "projects"), { recursive: true });
84
+
85
+ // Copy the canonical .ai/ tree
86
+ process.stdout.write(`📥 Copiando knowledge base...\n`);
87
+ const stats = await copyTree(source, home, {
88
+ excludes: DEFAULT_EXCLUDES,
89
+ force,
90
+ });
91
+
92
+ // Write empty memory templates if absent (or force)
93
+ let templatesWritten = 0;
94
+ for (const [rel, content] of Object.entries(MEMORY_TEMPLATES)) {
95
+ const dst = join(home, rel);
96
+ if (existsSync(dst) && !force) continue;
97
+ await mkdir(join(home, "memory"), { recursive: true });
98
+ const dated = content.replace(/AUTO/g, new Date().toISOString().slice(0, 10));
99
+ await writeFile(dst, dated, "utf8");
100
+ templatesWritten++;
101
+ }
102
+
103
+ // Write VERSION marker (records install timestamp + source manifest version)
104
+ let sourceVersion = "unknown";
105
+ try {
106
+ const m = JSON.parse(await readFile(join(source, "manifest.json"), "utf8"));
107
+ sourceVersion = m.version || "unknown";
108
+ } catch (_) {}
109
+ const versionMarker = {
110
+ installed_at: new Date().toISOString(),
111
+ installed_from: source,
112
+ source_version: sourceVersion,
113
+ cli_version: "0.1.0-alpha.1",
114
+ };
115
+ await writeFile(join(home, ".stealthos-version.json"), JSON.stringify(versionMarker, null, 2), "utf8");
116
+
117
+ process.stdout.write(`\n━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━━\n`);
118
+ process.stdout.write(`✓ Instalação concluída\n`);
119
+ process.stdout.write(` • Arquivos copiados: ${stats.copied.length}\n`);
120
+ process.stdout.write(` • Arquivos pulados: ${stats.skipped.length} (já existiam, sem --force)\n`);
121
+ process.stdout.write(` • Diretórios excluídos: ${stats.excluded.length} (outputs/state)\n`);
122
+ process.stdout.write(` • Memory templates: ${templatesWritten}\n`);
123
+ process.stdout.write(` • Versão source: ${sourceVersion}\n`);
124
+
125
+ // Check server/node_modules needs install
126
+ const serverPkgJson = join(home, "server", "package.json");
127
+ if (existsSync(serverPkgJson) && !existsSync(join(home, "server", "node_modules"))) {
128
+ process.stdout.write(`\n⚠ Próximo passo: instalar deps do server\n`);
129
+ process.stdout.write(` cd "${join(home, "server")}" && npm install\n`);
130
+ }
131
+
132
+ process.stdout.write(`\nPara usar em modo hybrid:\n`);
133
+ process.stdout.write(` export STEALTHOS_MODE=hybrid\n`);
134
+ process.stdout.write(` export STEALTHOS_HOME="${home}"\n`);
135
+ process.stdout.write(` node "${join(home, "server", "aios-server.mjs")}" --http\n`);
136
+ }
@@ -0,0 +1,66 @@
1
+ // stealthos status — quick view of ~/.stealthos/ install state.
2
+
3
+ import { existsSync } from "node:fs";
4
+ import { readFile, readdir, stat } from "node:fs/promises";
5
+ import { join } from "node:path";
6
+ import { homedir } from "node:os";
7
+
8
+ export async function statusCommand(flags = {}) {
9
+ const home = flags.home ? String(flags.home) : (process.env.STEALTHOS_HOME || join(homedir(), ".stealthos"));
10
+
11
+ process.stdout.write(`\n📦 StealthOS @ ${home}\n`);
12
+
13
+ if (!existsSync(home)) {
14
+ process.stdout.write(` ✖ não instalado (rode 'stealthos install')\n`);
15
+ return;
16
+ }
17
+
18
+ // Version marker
19
+ const versionFile = join(home, ".stealthos-version.json");
20
+ if (existsSync(versionFile)) {
21
+ const v = JSON.parse(await readFile(versionFile, "utf8"));
22
+ process.stdout.write(` • Instalado em: ${v.installed_at}\n`);
23
+ process.stdout.write(` • Source version: ${v.source_version}\n`);
24
+ process.stdout.write(` • CLI version: ${v.cli_version}\n`);
25
+ } else {
26
+ process.stdout.write(` ⚠ sem .stealthos-version.json (instalação manual ou parcial)\n`);
27
+ }
28
+
29
+ // Server presence
30
+ const serverFile = join(home, "server", "aios-server.mjs");
31
+ process.stdout.write(` • Server present: ${existsSync(serverFile) ? "yes" : "✖ NO"}\n`);
32
+ const nodeModules = join(home, "server", "node_modules");
33
+ process.stdout.write(` • Server deps: ${existsSync(nodeModules) ? "installed" : `⚠ missing (run: cd "${join(home, "server")}" && npm install)`}\n`);
34
+
35
+ // Knowledge base files
36
+ for (const f of ["INDEX.md", "CONTRACT.md", "ROUTER.md", "manifest.json"]) {
37
+ const p = join(home, f);
38
+ process.stdout.write(` • ${f.padEnd(18)} ${existsSync(p) ? "✓" : "✖"}\n`);
39
+ }
40
+
41
+ // Workflows count
42
+ const wfDir = join(home, "workflows");
43
+ if (existsSync(wfDir)) {
44
+ const wfs = (await readdir(wfDir)).filter((n) => n.endsWith(".json") && !n.startsWith("_"));
45
+ process.stdout.write(` • Workflows: ${wfs.join(", ") || "(none)"}\n`);
46
+ }
47
+
48
+ // Projects count
49
+ const projDir = join(home, "projects");
50
+ if (existsSync(projDir)) {
51
+ const projects = (await readdir(projDir, { withFileTypes: true })).filter((d) => d.isDirectory());
52
+ process.stdout.write(` • Projects tracked: ${projects.length}\n`);
53
+ for (const p of projects.slice(0, 5)) {
54
+ const sFile = join(projDir, p.name, "runtime", "state.json");
55
+ let name = "(no state)";
56
+ if (existsSync(sFile)) {
57
+ try {
58
+ const s = JSON.parse(await readFile(sFile, "utf8"));
59
+ name = s.identity?.project_name || "(no name)";
60
+ } catch (_) {}
61
+ }
62
+ process.stdout.write(` - ${p.name} → ${name}\n`);
63
+ }
64
+ if (projects.length > 5) process.stdout.write(` ... +${projects.length - 5} more\n`);
65
+ }
66
+ }
@@ -0,0 +1,61 @@
1
+ // Recursive tree copy with exclude patterns.
2
+ // Pure node — no glob lib. Patterns are simple relative-path prefixes.
3
+
4
+ import { readdir, mkdir, readFile, writeFile, stat } from "node:fs/promises";
5
+ import { existsSync } from "node:fs";
6
+ import { join, relative, sep } from "node:path";
7
+
8
+ // Normalize a path segment for matching (always forward slashes, lowercase first segment only)
9
+ function normalizeRel(p) {
10
+ return p.split(sep).join("/");
11
+ }
12
+
13
+ function isExcluded(relPath, excludes) {
14
+ const norm = normalizeRel(relPath);
15
+ for (const ex of excludes) {
16
+ // Exact match OR prefix match (directory)
17
+ if (norm === ex) return true;
18
+ if (norm.startsWith(ex + "/")) return true;
19
+ }
20
+ return false;
21
+ }
22
+
23
+ export async function copyTree(srcRoot, dstRoot, {
24
+ excludes = [],
25
+ force = false,
26
+ log,
27
+ } = {}) {
28
+ const stats = { copied: [], skipped: [], excluded: [] };
29
+ if (!existsSync(srcRoot)) {
30
+ throw new Error(`Source does not exist: ${srcRoot}`);
31
+ }
32
+
33
+ async function walk(srcDir, dstDir) {
34
+ await mkdir(dstDir, { recursive: true });
35
+ const entries = await readdir(srcDir, { withFileTypes: true });
36
+ for (const e of entries) {
37
+ const srcPath = join(srcDir, e.name);
38
+ const dstPath = join(dstDir, e.name);
39
+ const rel = relative(srcRoot, srcPath);
40
+ if (isExcluded(rel, excludes)) {
41
+ stats.excluded.push(rel);
42
+ if (log) log(` - excluded: ${normalizeRel(rel)}`);
43
+ continue;
44
+ }
45
+ if (e.isDirectory()) {
46
+ await walk(srcPath, dstPath);
47
+ } else if (e.isFile()) {
48
+ if (existsSync(dstPath) && !force) {
49
+ stats.skipped.push(rel);
50
+ continue;
51
+ }
52
+ const buf = await readFile(srcPath);
53
+ await writeFile(dstPath, buf);
54
+ stats.copied.push(rel);
55
+ }
56
+ }
57
+ }
58
+
59
+ await walk(srcRoot, dstRoot);
60
+ return stats;
61
+ }
@@ -0,0 +1,43 @@
1
+ // Locate the source .ai/ tree to install from.
2
+ // Strategy:
3
+ // 1. Explicit --source flag wins
4
+ // 2. STEALTHOS_SOURCE env var
5
+ // 3. Monorepo discovery: walk up from __dirname looking for sibling .ai/
6
+ // 4. Fail loudly
7
+
8
+ import { existsSync } from "node:fs";
9
+ import { resolve, dirname, join } from "node:path";
10
+ import { fileURLToPath } from "node:url";
11
+
12
+ const __dirname = dirname(fileURLToPath(import.meta.url));
13
+
14
+ export function resolveSource({ flagSource } = {}) {
15
+ if (flagSource) {
16
+ const abs = resolve(flagSource);
17
+ if (!existsSync(join(abs, "manifest.json"))) {
18
+ throw new Error(`--source path does not contain manifest.json: ${abs}`);
19
+ }
20
+ return abs;
21
+ }
22
+ if (process.env.STEALTHOS_SOURCE) {
23
+ const abs = resolve(process.env.STEALTHOS_SOURCE);
24
+ if (!existsSync(join(abs, "manifest.json"))) {
25
+ throw new Error(`STEALTHOS_SOURCE does not contain manifest.json: ${abs}`);
26
+ }
27
+ return abs;
28
+ }
29
+ // Monorepo discovery: walk up from packages/stealthos-cli/src/lib/ to find sibling .ai/
30
+ let dir = __dirname;
31
+ for (let i = 0; i < 6; i++) {
32
+ const candidate = join(dir, ".ai");
33
+ if (existsSync(join(candidate, "manifest.json"))) {
34
+ return candidate;
35
+ }
36
+ const parent = dirname(dir);
37
+ if (parent === dir) break;
38
+ dir = parent;
39
+ }
40
+ throw new Error(
41
+ "Could not locate source .ai/ tree. Pass --source <path> or set STEALTHOS_SOURCE env var.",
42
+ );
43
+ }