stealthos-cli 0.1.0-alpha.4 → 0.1.0-alpha.5
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/ai/scripts/init-ai-os.ps1 +215 -170
- package/package.json +42 -42
- package/src/cli.mjs +83 -79
- package/src/commands/run.mjs +117 -0
|
@@ -1,170 +1,215 @@
|
|
|
1
|
-
# init-ai-os.ps1
|
|
2
|
-
# Inicializa ou verifica o AI Operating System em um projeto.
|
|
3
|
-
# Uso:
|
|
4
|
-
# .\scripts\init-ai-os.ps1 # interativo
|
|
5
|
-
# .\scripts\init-ai-os.ps1 -Mode new # forca novo
|
|
6
|
-
# .\scripts\init-ai-os.ps1 -Mode existing # forca existente
|
|
7
|
-
# .\scripts\init-ai-os.ps1 -Verify # so verifica integridade
|
|
8
|
-
# .\scripts\init-ai-os.ps1 -TargetPath C:\path\to\project
|
|
9
|
-
|
|
10
|
-
param(
|
|
11
|
-
[ValidateSet("auto", "new", "existing", "verify")]
|
|
12
|
-
[string]$Mode = "auto",
|
|
13
|
-
|
|
14
|
-
[string]$TargetPath = (Get-Location).Path,
|
|
15
|
-
|
|
16
|
-
[string]$TemplateRoot =
|
|
17
|
-
|
|
18
|
-
[switch]$Verify
|
|
19
|
-
)
|
|
20
|
-
|
|
21
|
-
if ($Verify) { $Mode = "verify" }
|
|
22
|
-
|
|
23
|
-
$ErrorActionPreference = "Stop"
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
#
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
$
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
}
|
|
81
|
-
|
|
82
|
-
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
Write-Ok "
|
|
113
|
-
}
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
$templateAi =
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
Write-Host ""
|
|
166
|
-
Write-Host "Proximo passo: abra Claude Code / Gemini / GPT neste diretorio."
|
|
167
|
-
Write-Host "O agente seguira o fluxo de bootstrap/
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
1
|
+
# init-ai-os.ps1
|
|
2
|
+
# Inicializa ou verifica o AI Operating System em um projeto.
|
|
3
|
+
# Uso:
|
|
4
|
+
# .\scripts\init-ai-os.ps1 # interativo
|
|
5
|
+
# .\scripts\init-ai-os.ps1 -Mode new # forca novo
|
|
6
|
+
# .\scripts\init-ai-os.ps1 -Mode existing # forca existente
|
|
7
|
+
# .\scripts\init-ai-os.ps1 -Verify # so verifica integridade
|
|
8
|
+
# .\scripts\init-ai-os.ps1 -TargetPath C:\path\to\project
|
|
9
|
+
|
|
10
|
+
param(
|
|
11
|
+
[ValidateSet("auto", "new", "existing", "verify")]
|
|
12
|
+
[string]$Mode = "auto",
|
|
13
|
+
|
|
14
|
+
[string]$TargetPath = (Get-Location).Path,
|
|
15
|
+
|
|
16
|
+
[string]$TemplateRoot = $null,
|
|
17
|
+
|
|
18
|
+
[switch]$Verify
|
|
19
|
+
)
|
|
20
|
+
|
|
21
|
+
if ($Verify) { $Mode = "verify" }
|
|
22
|
+
|
|
23
|
+
$ErrorActionPreference = "Stop"
|
|
24
|
+
|
|
25
|
+
# ----- Auto-detect template layout -----
|
|
26
|
+
# Layout A (monorepo): <repo>/.ai/scripts/init-ai-os.ps1 -> template at <repo>/.ai
|
|
27
|
+
# Layout B (installed): <home>/scripts/init-ai-os.ps1 -> template at <home> itself
|
|
28
|
+
if (-not $TemplateRoot) {
|
|
29
|
+
$grandparent = Split-Path (Split-Path $PSScriptRoot -Parent) -Parent
|
|
30
|
+
$parent = Split-Path $PSScriptRoot -Parent
|
|
31
|
+
if (Test-Path (Join-Path $grandparent ".ai\manifest.json")) {
|
|
32
|
+
$TemplateRoot = $grandparent
|
|
33
|
+
} elseif (Test-Path (Join-Path $parent "manifest.json")) {
|
|
34
|
+
# Installed layout: parent IS the content root; use it as a virtual .ai/
|
|
35
|
+
$TemplateRoot = $parent
|
|
36
|
+
$script:InstalledLayout = $true
|
|
37
|
+
} else {
|
|
38
|
+
$TemplateRoot = $grandparent
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
# Files/folders never copied to user projects (runtime/snapshots/outputs)
|
|
43
|
+
$script:ExcludeNames = @(
|
|
44
|
+
"node_modules", ".runtime", "runtime", "artifacts",
|
|
45
|
+
"context", "snapshots", "projects", "archive", "outputs"
|
|
46
|
+
)
|
|
47
|
+
|
|
48
|
+
function Copy-Tree-Filtered {
|
|
49
|
+
param([string]$Src, [string]$Dst)
|
|
50
|
+
New-Item -ItemType Directory -Force -Path $Dst | Out-Null
|
|
51
|
+
Get-ChildItem -LiteralPath $Src -Force | ForEach-Object {
|
|
52
|
+
if ($script:ExcludeNames -contains $_.Name) { return }
|
|
53
|
+
$target = Join-Path $Dst $_.Name
|
|
54
|
+
if ($_.PSIsContainer) {
|
|
55
|
+
Copy-Tree-Filtered -Src $_.FullName -Dst $target
|
|
56
|
+
} else {
|
|
57
|
+
Copy-Item -LiteralPath $_.FullName -Destination $target -Force
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
function Write-Section($text) {
|
|
63
|
+
Write-Host ""
|
|
64
|
+
Write-Host "=== $text ===" -ForegroundColor Cyan
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
function Write-Ok($text) { Write-Host "[OK] $text" -ForegroundColor Green }
|
|
68
|
+
function Write-Warn($text) { Write-Host "[WARN] $text" -ForegroundColor Yellow }
|
|
69
|
+
function Write-Err($text) { Write-Host "[ERR] $text" -ForegroundColor Red }
|
|
70
|
+
|
|
71
|
+
# ----- Detect mode if auto -----
|
|
72
|
+
function Get-ProjectMode {
|
|
73
|
+
param([string]$Path)
|
|
74
|
+
|
|
75
|
+
$aiExists = Test-Path (Join-Path $Path ".ai")
|
|
76
|
+
$hasCode = @(
|
|
77
|
+
"package.json", "pyproject.toml", "requirements.txt", "Cargo.toml",
|
|
78
|
+
"go.mod", "pom.xml", "build.gradle", "composer.json", "Gemfile",
|
|
79
|
+
"mix.exs", "pubspec.yaml"
|
|
80
|
+
) | Where-Object { Test-Path (Join-Path $Path $_) }
|
|
81
|
+
|
|
82
|
+
if ($aiExists) { return "verify" }
|
|
83
|
+
if ($hasCode.Count -gt 0) { return "existing" }
|
|
84
|
+
return "new"
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
if ($Mode -eq "auto") {
|
|
88
|
+
$Mode = Get-ProjectMode -Path $TargetPath
|
|
89
|
+
Write-Host "Modo detectado: $Mode"
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
# ----- VERIFY MODE -----
|
|
93
|
+
if ($Mode -eq "verify") {
|
|
94
|
+
Write-Section "Verificando integridade do AI OS em $TargetPath"
|
|
95
|
+
|
|
96
|
+
$manifestPath = Join-Path $TargetPath ".ai\manifest.json"
|
|
97
|
+
if (-not (Test-Path $manifestPath)) {
|
|
98
|
+
Write-Err "manifest.json nao encontrado em .ai/. OS pode estar quebrado."
|
|
99
|
+
exit 1
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
$manifest = Get-Content $manifestPath -Raw | ConvertFrom-Json
|
|
103
|
+
$missing = @()
|
|
104
|
+
foreach ($f in $manifest.required_files) {
|
|
105
|
+
$full = Join-Path $TargetPath ($f -replace '/', '\')
|
|
106
|
+
if (-not (Test-Path $full)) {
|
|
107
|
+
$missing += $f
|
|
108
|
+
}
|
|
109
|
+
}
|
|
110
|
+
|
|
111
|
+
if ($missing.Count -eq 0) {
|
|
112
|
+
Write-Ok "Todos os $($manifest.required_files.Count) arquivos obrigatorios presentes."
|
|
113
|
+
} else {
|
|
114
|
+
Write-Err "$($missing.Count) arquivos faltando:"
|
|
115
|
+
$missing | ForEach-Object { Write-Host " - $_" }
|
|
116
|
+
exit 1
|
|
117
|
+
}
|
|
118
|
+
|
|
119
|
+
# Link lint
|
|
120
|
+
Write-Section "Validando links cruzados"
|
|
121
|
+
& (Join-Path $PSScriptRoot "lint-os.ps1") -TargetPath $TargetPath
|
|
122
|
+
exit $LASTEXITCODE
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
# ----- NEW MODE -----
|
|
126
|
+
if ($Mode -eq "new") {
|
|
127
|
+
Write-Section "Bootstrap: projeto NOVO em $TargetPath"
|
|
128
|
+
|
|
129
|
+
if (Test-Path (Join-Path $TargetPath ".ai")) {
|
|
130
|
+
Write-Warn ".ai/ ja existe. Use -Mode verify para checar integridade ou -Mode existing para retrofit."
|
|
131
|
+
exit 1
|
|
132
|
+
}
|
|
133
|
+
|
|
134
|
+
# Copia template (suporta layout monorepo e installed)
|
|
135
|
+
if ($script:InstalledLayout) {
|
|
136
|
+
$templateAi = $TemplateRoot
|
|
137
|
+
} else {
|
|
138
|
+
$templateAi = Join-Path $TemplateRoot ".ai"
|
|
139
|
+
}
|
|
140
|
+
$templateClaude = Join-Path $TemplateRoot ".claude"
|
|
141
|
+
$templateEntries = @("CLAUDE.md", "GEMINI.md", "GPT.md")
|
|
142
|
+
|
|
143
|
+
if (-not (Test-Path $templateAi)) {
|
|
144
|
+
Write-Err "Template .ai/ nao encontrado em $templateAi. Rode este script a partir do diretorio raiz do template."
|
|
145
|
+
exit 1
|
|
146
|
+
}
|
|
147
|
+
|
|
148
|
+
Copy-Tree-Filtered -Src $templateAi -Dst (Join-Path $TargetPath ".ai")
|
|
149
|
+
Write-Ok "Estrutura .ai/ copiada."
|
|
150
|
+
|
|
151
|
+
if (Test-Path $templateClaude) {
|
|
152
|
+
Copy-Item $templateClaude (Join-Path $TargetPath ".claude") -Recurse
|
|
153
|
+
Write-Ok "Hooks .claude/ copiados."
|
|
154
|
+
}
|
|
155
|
+
|
|
156
|
+
foreach ($entry in $templateEntries) {
|
|
157
|
+
$src = Join-Path $TemplateRoot $entry
|
|
158
|
+
$dst = Join-Path $TargetPath $entry
|
|
159
|
+
if ((Test-Path $src) -and -not (Test-Path $dst)) {
|
|
160
|
+
Copy-Item $src $dst
|
|
161
|
+
Write-Ok "Entry point $entry copiado."
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
Write-Host ""
|
|
166
|
+
Write-Host "Proximo passo: abra Claude Code / Gemini / GPT neste diretorio."
|
|
167
|
+
Write-Host "O agente seguira o fluxo de bootstrap/new-project.md automaticamente."
|
|
168
|
+
exit 0
|
|
169
|
+
}
|
|
170
|
+
|
|
171
|
+
# ----- EXISTING MODE -----
|
|
172
|
+
if ($Mode -eq "existing") {
|
|
173
|
+
Write-Section "Bootstrap: projeto EXISTENTE em $TargetPath"
|
|
174
|
+
|
|
175
|
+
if (-not (Test-Path (Join-Path $TargetPath ".ai"))) {
|
|
176
|
+
# Copia o template (suporta layout monorepo e installed)
|
|
177
|
+
if ($script:InstalledLayout) {
|
|
178
|
+
$templateAi = $TemplateRoot
|
|
179
|
+
} else {
|
|
180
|
+
$templateAi = Join-Path $TemplateRoot ".ai"
|
|
181
|
+
}
|
|
182
|
+
if (-not (Test-Path $templateAi)) {
|
|
183
|
+
Write-Err "Template .ai/ nao encontrado em $templateAi."
|
|
184
|
+
exit 1
|
|
185
|
+
}
|
|
186
|
+
Copy-Tree-Filtered -Src $templateAi -Dst (Join-Path $TargetPath ".ai")
|
|
187
|
+
Write-Ok "Estrutura .ai/ enxertada no projeto."
|
|
188
|
+
|
|
189
|
+
$templateClaude = Join-Path $TemplateRoot ".claude"
|
|
190
|
+
if (Test-Path $templateClaude) {
|
|
191
|
+
Copy-Item $templateClaude (Join-Path $TargetPath ".claude") -Recurse
|
|
192
|
+
Write-Ok "Hooks .claude/ enxertados."
|
|
193
|
+
}
|
|
194
|
+
|
|
195
|
+
@("CLAUDE.md", "GEMINI.md", "GPT.md") | ForEach-Object {
|
|
196
|
+
$src = Join-Path $TemplateRoot $_
|
|
197
|
+
$dst = Join-Path $TargetPath $_
|
|
198
|
+
if ((Test-Path $src) -and -not (Test-Path $dst)) {
|
|
199
|
+
Copy-Item $src $dst
|
|
200
|
+
Write-Ok "Entry point $_ enxertado."
|
|
201
|
+
}
|
|
202
|
+
}
|
|
203
|
+
} else {
|
|
204
|
+
Write-Ok ".ai/ ja existe - pulando copia."
|
|
205
|
+
}
|
|
206
|
+
|
|
207
|
+
# Detectar stack e gerar pre-fill de project-context.md
|
|
208
|
+
Write-Section "Detectando stack do projeto"
|
|
209
|
+
& (Join-Path $PSScriptRoot "detect-stack.ps1") -TargetPath $TargetPath
|
|
210
|
+
Write-Host ""
|
|
211
|
+
Write-Host "Proximo passo: abra Claude Code / Gemini / GPT neste diretorio."
|
|
212
|
+
Write-Host "O agente seguira o fluxo de bootstrap/existing-project.md."
|
|
213
|
+
Write-Host "Ele vai validar a deteccao com voce e completar o que falta via discovery-questions."
|
|
214
|
+
exit 0
|
|
215
|
+
}
|
package/package.json
CHANGED
|
@@ -1,42 +1,42 @@
|
|
|
1
|
-
{
|
|
2
|
-
"name": "stealthos-cli",
|
|
3
|
-
"version": "0.1.0-alpha.
|
|
4
|
-
"scripts": {
|
|
5
|
-
"bundle-ai": "node scripts/bundle-ai.mjs",
|
|
6
|
-
"prepublishOnly": "node scripts/bundle-ai.mjs"
|
|
7
|
-
},
|
|
8
|
-
"description": "StealthOS CLI — install/manage the StealthOS knowledge base in ~/.stealthos/. Subcommands: install, status, version.",
|
|
9
|
-
"type": "module",
|
|
10
|
-
"bin": {
|
|
11
|
-
"stealthos": "./bin.cjs"
|
|
12
|
-
},
|
|
13
|
-
"files": [
|
|
14
|
-
"bin.cjs",
|
|
15
|
-
"bin.mjs",
|
|
16
|
-
"src/",
|
|
17
|
-
"scripts/",
|
|
18
|
-
"ai/",
|
|
19
|
-
"README.md"
|
|
20
|
-
],
|
|
21
|
-
"engines": {
|
|
22
|
-
"node": ">=18.0.0"
|
|
23
|
-
},
|
|
24
|
-
"keywords": [
|
|
25
|
-
"stealthos",
|
|
26
|
-
"mcp",
|
|
27
|
-
"ai",
|
|
28
|
-
"cli",
|
|
29
|
-
"claude-code"
|
|
30
|
-
],
|
|
31
|
-
"author": "Igor Kadu (https://github.com/IgorKadu)",
|
|
32
|
-
"license": "MIT",
|
|
33
|
-
"repository": {
|
|
34
|
-
"type": "git",
|
|
35
|
-
"url": "git+https://github.com/IgorKadu/stealthos.git",
|
|
36
|
-
"directory": "packages/stealthos-cli"
|
|
37
|
-
},
|
|
38
|
-
"bugs": {
|
|
39
|
-
"url": "https://github.com/IgorKadu/stealthos/issues"
|
|
40
|
-
},
|
|
41
|
-
"homepage": "https://github.com/IgorKadu/stealthos/tree/main/packages/stealthos-cli#readme"
|
|
42
|
-
}
|
|
1
|
+
{
|
|
2
|
+
"name": "stealthos-cli",
|
|
3
|
+
"version": "0.1.0-alpha.5",
|
|
4
|
+
"scripts": {
|
|
5
|
+
"bundle-ai": "node scripts/bundle-ai.mjs",
|
|
6
|
+
"prepublishOnly": "node scripts/bundle-ai.mjs"
|
|
7
|
+
},
|
|
8
|
+
"description": "StealthOS CLI — install/manage the StealthOS knowledge base in ~/.stealthos/. Subcommands: install, status, version.",
|
|
9
|
+
"type": "module",
|
|
10
|
+
"bin": {
|
|
11
|
+
"stealthos": "./bin.cjs"
|
|
12
|
+
},
|
|
13
|
+
"files": [
|
|
14
|
+
"bin.cjs",
|
|
15
|
+
"bin.mjs",
|
|
16
|
+
"src/",
|
|
17
|
+
"scripts/",
|
|
18
|
+
"ai/",
|
|
19
|
+
"README.md"
|
|
20
|
+
],
|
|
21
|
+
"engines": {
|
|
22
|
+
"node": ">=18.0.0"
|
|
23
|
+
},
|
|
24
|
+
"keywords": [
|
|
25
|
+
"stealthos",
|
|
26
|
+
"mcp",
|
|
27
|
+
"ai",
|
|
28
|
+
"cli",
|
|
29
|
+
"claude-code"
|
|
30
|
+
],
|
|
31
|
+
"author": "Igor Kadu (https://github.com/IgorKadu)",
|
|
32
|
+
"license": "MIT",
|
|
33
|
+
"repository": {
|
|
34
|
+
"type": "git",
|
|
35
|
+
"url": "git+https://github.com/IgorKadu/stealthos.git",
|
|
36
|
+
"directory": "packages/stealthos-cli"
|
|
37
|
+
},
|
|
38
|
+
"bugs": {
|
|
39
|
+
"url": "https://github.com/IgorKadu/stealthos/issues"
|
|
40
|
+
},
|
|
41
|
+
"homepage": "https://github.com/IgorKadu/stealthos/tree/main/packages/stealthos-cli#readme"
|
|
42
|
+
}
|
package/src/cli.mjs
CHANGED
|
@@ -1,79 +1,83 @@
|
|
|
1
|
-
// stealthos CLI router. Subcommand dispatcher.
|
|
2
|
-
|
|
3
|
-
import { installCommand } from "./commands/install.mjs";
|
|
4
|
-
import { statusCommand } from "./commands/status.mjs";
|
|
5
|
-
|
|
6
|
-
|
|
7
|
-
|
|
8
|
-
|
|
9
|
-
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
--
|
|
20
|
-
--
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
stealthos
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
case "
|
|
43
|
-
return
|
|
44
|
-
case "
|
|
45
|
-
|
|
46
|
-
case "
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
case "
|
|
50
|
-
case "
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
usage();
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
}
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
1
|
+
// stealthos CLI router. Subcommand dispatcher.
|
|
2
|
+
|
|
3
|
+
import { installCommand } from "./commands/install.mjs";
|
|
4
|
+
import { statusCommand } from "./commands/status.mjs";
|
|
5
|
+
import { runCommand } from "./commands/run.mjs";
|
|
6
|
+
|
|
7
|
+
const VERSION = "0.1.0-alpha.5";
|
|
8
|
+
|
|
9
|
+
function usage() {
|
|
10
|
+
process.stdout.write(`
|
|
11
|
+
stealthos v${VERSION}
|
|
12
|
+
|
|
13
|
+
USAGE
|
|
14
|
+
stealthos <command> [options]
|
|
15
|
+
|
|
16
|
+
COMMANDS
|
|
17
|
+
install Install StealthOS knowledge base into ~/.stealthos/
|
|
18
|
+
Options:
|
|
19
|
+
--source <path> Source .ai/ tree (default: auto-detect in monorepo)
|
|
20
|
+
--home <path> Target home (default: ~/.stealthos)
|
|
21
|
+
--force Overwrite existing files
|
|
22
|
+
status Show ~/.stealthos/ install status (version, paths, project count)
|
|
23
|
+
run <wf> Run a workflow via the local MCP server (init|sync|work [intent...])
|
|
24
|
+
version Print stealthos CLI version
|
|
25
|
+
help Show this message
|
|
26
|
+
|
|
27
|
+
ENVIRONMENT
|
|
28
|
+
STEALTHOS_HOME Override target home (default: ~/.stealthos)
|
|
29
|
+
STEALTHOS_SOURCE Override source .ai/ path (default: auto-detect)
|
|
30
|
+
STEALTHOS_DEBUG=1 Print stack traces on errors
|
|
31
|
+
|
|
32
|
+
EXAMPLES
|
|
33
|
+
stealthos install
|
|
34
|
+
stealthos install --home /opt/stealthos
|
|
35
|
+
stealthos status
|
|
36
|
+
`);
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
export async function runCli(argv) {
|
|
40
|
+
const [cmd = "help", ...rest] = argv;
|
|
41
|
+
switch (cmd) {
|
|
42
|
+
case "install":
|
|
43
|
+
return installCommand(parseFlags(rest));
|
|
44
|
+
case "status":
|
|
45
|
+
return statusCommand(parseFlags(rest));
|
|
46
|
+
case "run":
|
|
47
|
+
return runCommand(parseFlags(rest));
|
|
48
|
+
case "version":
|
|
49
|
+
case "-v":
|
|
50
|
+
case "--version":
|
|
51
|
+
process.stdout.write(`${VERSION}\n`);
|
|
52
|
+
return;
|
|
53
|
+
case "help":
|
|
54
|
+
case "-h":
|
|
55
|
+
case "--help":
|
|
56
|
+
usage();
|
|
57
|
+
return;
|
|
58
|
+
default:
|
|
59
|
+
process.stderr.write(`Unknown command: ${cmd}\n`);
|
|
60
|
+
usage();
|
|
61
|
+
process.exit(2);
|
|
62
|
+
}
|
|
63
|
+
}
|
|
64
|
+
|
|
65
|
+
function parseFlags(args) {
|
|
66
|
+
const out = { _positional: [] };
|
|
67
|
+
for (let i = 0; i < args.length; i++) {
|
|
68
|
+
const a = args[i];
|
|
69
|
+
if (a.startsWith("--")) {
|
|
70
|
+
const key = a.slice(2);
|
|
71
|
+
const next = args[i + 1];
|
|
72
|
+
if (next && !next.startsWith("--")) {
|
|
73
|
+
out[key] = next;
|
|
74
|
+
i++;
|
|
75
|
+
} else {
|
|
76
|
+
out[key] = true;
|
|
77
|
+
}
|
|
78
|
+
} else {
|
|
79
|
+
out._positional.push(a);
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
return out;
|
|
83
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
// `stealthos run <workflow> [intent...]`
|
|
2
|
+
// Spawns aios-server.mjs as an MCP stdio process and sends a single
|
|
3
|
+
// tools/call for aios_run_workflow. Streams stdout/stderr from the server
|
|
4
|
+
// and prints the parsed tool result.
|
|
5
|
+
//
|
|
6
|
+
// Used by VSCode tasks (and as a generic CLI escape hatch) so users on IDEs
|
|
7
|
+
// without MCP can still trigger workflows directly.
|
|
8
|
+
|
|
9
|
+
import { spawn } from "node:child_process";
|
|
10
|
+
import { existsSync } from "node:fs";
|
|
11
|
+
import { join } from "node:path";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
|
|
14
|
+
function resolveServerPath() {
|
|
15
|
+
const home = process.env.STEALTHOS_HOME || join(homedir(), ".stealthos");
|
|
16
|
+
const candidate = join(home, "server", "aios-server.mjs");
|
|
17
|
+
if (!existsSync(candidate)) {
|
|
18
|
+
throw new Error(
|
|
19
|
+
`aios-server.mjs nao encontrado em ${candidate}. ` +
|
|
20
|
+
`Rode 'stealthos install' primeiro.`,
|
|
21
|
+
);
|
|
22
|
+
}
|
|
23
|
+
return { home, server: candidate };
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function sendRpc(child, payload) {
|
|
27
|
+
return new Promise((resolve, reject) => {
|
|
28
|
+
let buf = "";
|
|
29
|
+
const onData = (chunk) => {
|
|
30
|
+
buf += chunk.toString("utf8");
|
|
31
|
+
// MCP framing: each message terminated by newline
|
|
32
|
+
const lines = buf.split("\n");
|
|
33
|
+
buf = lines.pop();
|
|
34
|
+
for (const line of lines) {
|
|
35
|
+
if (!line.trim()) continue;
|
|
36
|
+
try {
|
|
37
|
+
const msg = JSON.parse(line);
|
|
38
|
+
if (msg.id === payload.id) {
|
|
39
|
+
child.stdout.off("data", onData);
|
|
40
|
+
resolve(msg);
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
} catch {
|
|
44
|
+
// ignore non-JSON lines (server logs)
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
};
|
|
48
|
+
child.stdout.on("data", onData);
|
|
49
|
+
child.stdin.write(JSON.stringify(payload) + "\n");
|
|
50
|
+
setTimeout(() => {
|
|
51
|
+
child.stdout.off("data", onData);
|
|
52
|
+
reject(new Error("timeout aguardando resposta do MCP server"));
|
|
53
|
+
}, 60000);
|
|
54
|
+
});
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
export async function runCommand(opts) {
|
|
58
|
+
const positional = opts._positional || [];
|
|
59
|
+
const workflow = positional[0];
|
|
60
|
+
if (!workflow) {
|
|
61
|
+
throw new Error("Uso: stealthos run <workflow> [intent...]");
|
|
62
|
+
}
|
|
63
|
+
const intent = positional.slice(1).join(" ");
|
|
64
|
+
|
|
65
|
+
const { home, server } = resolveServerPath();
|
|
66
|
+
process.stdout.write(`▶ stealthos run ${workflow}${intent ? ` (intent: ${intent})` : ""}\n`);
|
|
67
|
+
process.stdout.write(` home: ${home}\n server: ${server}\n\n`);
|
|
68
|
+
|
|
69
|
+
const child = spawn(process.execPath, [server], {
|
|
70
|
+
stdio: ["pipe", "pipe", "inherit"],
|
|
71
|
+
env: {
|
|
72
|
+
...process.env,
|
|
73
|
+
STEALTHOS_MODE: "hybrid",
|
|
74
|
+
STEALTHOS_HOME: home,
|
|
75
|
+
STEALTHOS_PROJECT_DIR: process.cwd(),
|
|
76
|
+
},
|
|
77
|
+
});
|
|
78
|
+
|
|
79
|
+
let exitCode = 0;
|
|
80
|
+
try {
|
|
81
|
+
// 1. initialize
|
|
82
|
+
await sendRpc(child, {
|
|
83
|
+
jsonrpc: "2.0",
|
|
84
|
+
id: 1,
|
|
85
|
+
method: "initialize",
|
|
86
|
+
params: { protocolVersion: "2024-11-05", capabilities: {}, clientInfo: { name: "stealthos-cli", version: "0.1" } },
|
|
87
|
+
});
|
|
88
|
+
|
|
89
|
+
// 2. tools/call aios_run_workflow
|
|
90
|
+
const resp = await sendRpc(child, {
|
|
91
|
+
jsonrpc: "2.0",
|
|
92
|
+
id: 2,
|
|
93
|
+
method: "tools/call",
|
|
94
|
+
params: {
|
|
95
|
+
name: "aios_run_workflow",
|
|
96
|
+
arguments: { name: workflow, input: { intent } },
|
|
97
|
+
},
|
|
98
|
+
});
|
|
99
|
+
|
|
100
|
+
if (resp.error) {
|
|
101
|
+
process.stderr.write(`Erro do MCP: ${JSON.stringify(resp.error, null, 2)}\n`);
|
|
102
|
+
exitCode = 1;
|
|
103
|
+
} else {
|
|
104
|
+
const content = resp.result?.content || [];
|
|
105
|
+
for (const part of content) {
|
|
106
|
+
if (part.type === "text") process.stdout.write(part.text + "\n");
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
} catch (err) {
|
|
110
|
+
process.stderr.write(`Erro: ${err.message}\n`);
|
|
111
|
+
exitCode = 1;
|
|
112
|
+
} finally {
|
|
113
|
+
child.stdin.end();
|
|
114
|
+
child.kill();
|
|
115
|
+
}
|
|
116
|
+
process.exit(exitCode);
|
|
117
|
+
}
|