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 +44 -0
- package/bin.mjs +13 -0
- package/package.json +35 -0
- package/src/cli.mjs +79 -0
- package/src/commands/install.mjs +136 -0
- package/src/commands/status.mjs +66 -0
- package/src/lib/copy-tree.mjs +61 -0
- package/src/lib/resolve-source.mjs +43 -0
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
|
+
}
|