ghost-dragon 4.2.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/.github/workflows/ci.yml +23 -0
- package/CHANGELOG.md +96 -0
- package/README.md +193 -0
- package/bootstrap.ps1 +83 -0
- package/bootstrap.sh +71 -0
- package/dist/agent/loop.d.ts +68 -0
- package/dist/agent/loop.d.ts.map +1 -0
- package/dist/agent/loop.js +135 -0
- package/dist/agent/mcp.d.ts +33 -0
- package/dist/agent/mcp.d.ts.map +1 -0
- package/dist/agent/mcp.js +107 -0
- package/dist/agent/session.d.ts +16 -0
- package/dist/agent/session.d.ts.map +1 -0
- package/dist/agent/session.js +55 -0
- package/dist/agent/skills.d.ts +36 -0
- package/dist/agent/skills.d.ts.map +1 -0
- package/dist/agent/skills.js +153 -0
- package/dist/agent/stack.d.ts +21 -0
- package/dist/agent/stack.d.ts.map +1 -0
- package/dist/agent/stack.js +158 -0
- package/dist/agent/task.d.ts +21 -0
- package/dist/agent/task.d.ts.map +1 -0
- package/dist/agent/task.js +45 -0
- package/dist/agent/tools.d.ts +44 -0
- package/dist/agent/tools.d.ts.map +1 -0
- package/dist/agent/tools.js +262 -0
- package/dist/agent/trace.d.ts +34 -0
- package/dist/agent/trace.d.ts.map +1 -0
- package/dist/agent/trace.js +72 -0
- package/dist/agent.d.ts +46 -0
- package/dist/agent.d.ts.map +1 -0
- package/dist/agent.js +103 -0
- package/dist/auth.d.ts +74 -0
- package/dist/auth.d.ts.map +1 -0
- package/dist/auth.js +116 -0
- package/dist/brain/anthropic.d.ts +19 -0
- package/dist/brain/anthropic.d.ts.map +1 -0
- package/dist/brain/anthropic.js +74 -0
- package/dist/brain/claude-cli.d.ts +20 -0
- package/dist/brain/claude-cli.d.ts.map +1 -0
- package/dist/brain/claude-cli.js +79 -0
- package/dist/brain/ghost-ember.d.ts +28 -0
- package/dist/brain/ghost-ember.d.ts.map +1 -0
- package/dist/brain/ghost-ember.js +97 -0
- package/dist/brain/index.d.ts +22 -0
- package/dist/brain/index.d.ts.map +1 -0
- package/dist/brain/index.js +95 -0
- package/dist/brain/openai-compat.d.ts +21 -0
- package/dist/brain/openai-compat.d.ts.map +1 -0
- package/dist/brain/openai-compat.js +119 -0
- package/dist/brain/router/classify.d.ts +23 -0
- package/dist/brain/router/classify.d.ts.map +1 -0
- package/dist/brain/router/classify.js +160 -0
- package/dist/brain/router/execute.d.ts +23 -0
- package/dist/brain/router/execute.d.ts.map +1 -0
- package/dist/brain/router/execute.js +84 -0
- package/dist/brain/router/index.d.ts +26 -0
- package/dist/brain/router/index.d.ts.map +1 -0
- package/dist/brain/router/index.js +118 -0
- package/dist/brain/router/routing-memory.d.ts +27 -0
- package/dist/brain/router/routing-memory.d.ts.map +1 -0
- package/dist/brain/router/routing-memory.js +77 -0
- package/dist/brain/router/select.d.ts +32 -0
- package/dist/brain/router/select.d.ts.map +1 -0
- package/dist/brain/router/select.js +146 -0
- package/dist/brain/router/two-hop.d.ts +23 -0
- package/dist/brain/router/two-hop.d.ts.map +1 -0
- package/dist/brain/router/two-hop.js +39 -0
- package/dist/brain/router/verify.d.ts +37 -0
- package/dist/brain/router/verify.d.ts.map +1 -0
- package/dist/brain/router/verify.js +111 -0
- package/dist/brain/types.d.ts +55 -0
- package/dist/brain/types.d.ts.map +1 -0
- package/dist/brain/types.js +16 -0
- package/dist/brain/worker.d.ts +27 -0
- package/dist/brain/worker.d.ts.map +1 -0
- package/dist/brain/worker.js +71 -0
- package/dist/commands/ai.d.ts +24 -0
- package/dist/commands/ai.d.ts.map +1 -0
- package/dist/commands/ai.js +137 -0
- package/dist/commands/alerts.d.ts +19 -0
- package/dist/commands/alerts.d.ts.map +1 -0
- package/dist/commands/alerts.js +114 -0
- package/dist/commands/billing.d.ts +13 -0
- package/dist/commands/billing.d.ts.map +1 -0
- package/dist/commands/billing.js +55 -0
- package/dist/commands/chat.d.ts +22 -0
- package/dist/commands/chat.d.ts.map +1 -0
- package/dist/commands/chat.js +422 -0
- package/dist/commands/config.d.ts +18 -0
- package/dist/commands/config.d.ts.map +1 -0
- package/dist/commands/config.js +136 -0
- package/dist/commands/doctor.d.ts +11 -0
- package/dist/commands/doctor.d.ts.map +1 -0
- package/dist/commands/doctor.js +73 -0
- package/dist/commands/global.d.ts +11 -0
- package/dist/commands/global.d.ts.map +1 -0
- package/dist/commands/global.js +253 -0
- package/dist/commands/keep.d.ts +12 -0
- package/dist/commands/keep.d.ts.map +1 -0
- package/dist/commands/keep.js +58 -0
- package/dist/commands/lifecycle.d.ts +17 -0
- package/dist/commands/lifecycle.d.ts.map +1 -0
- package/dist/commands/lifecycle.js +267 -0
- package/dist/commands/login.d.ts +16 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +234 -0
- package/dist/commands/maintenance.d.ts +12 -0
- package/dist/commands/maintenance.d.ts.map +1 -0
- package/dist/commands/maintenance.js +76 -0
- package/dist/commands/mcp.d.ts +16 -0
- package/dist/commands/mcp.d.ts.map +1 -0
- package/dist/commands/mcp.js +56 -0
- package/dist/commands/memory.d.ts +13 -0
- package/dist/commands/memory.d.ts.map +1 -0
- package/dist/commands/memory.js +218 -0
- package/dist/commands/osint.d.ts +14 -0
- package/dist/commands/osint.d.ts.map +1 -0
- package/dist/commands/osint.js +161 -0
- package/dist/commands/pentest.d.ts +13 -0
- package/dist/commands/pentest.d.ts.map +1 -0
- package/dist/commands/pentest.js +131 -0
- package/dist/commands/scale.d.ts +14 -0
- package/dist/commands/scale.d.ts.map +1 -0
- package/dist/commands/scale.js +191 -0
- package/dist/commands/serve.d.ts +16 -0
- package/dist/commands/serve.d.ts.map +1 -0
- package/dist/commands/serve.js +167 -0
- package/dist/commands/tui.d.ts +17 -0
- package/dist/commands/tui.d.ts.map +1 -0
- package/dist/commands/tui.js +138 -0
- package/dist/commands/wyrm.d.ts +20 -0
- package/dist/commands/wyrm.d.ts.map +1 -0
- package/dist/commands/wyrm.js +274 -0
- package/dist/config.d.ts +67 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +54 -0
- package/dist/index.d.ts +16 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +85 -0
- package/dist/manifest.d.ts +31 -0
- package/dist/manifest.d.ts.map +1 -0
- package/dist/manifest.js +83 -0
- package/dist/ui.d.ts +57 -0
- package/dist/ui.d.ts.map +1 -0
- package/dist/ui.js +174 -0
- package/dist/utils.d.ts +33 -0
- package/dist/utils.d.ts.map +1 -0
- package/dist/utils.js +155 -0
- package/dist/wyrm/mcp.d.ts +37 -0
- package/dist/wyrm/mcp.d.ts.map +1 -0
- package/dist/wyrm/mcp.js +137 -0
- package/docs/SYSTEM-PREMORTEM.md +397 -0
- package/dragon-manifest.toml +241 -0
- package/dragon.py +177 -0
- package/install/launchd/lk.ghosts.dragonkeep.plist +57 -0
- package/install/systemd/dragonkeep.service +40 -0
- package/media/dragon-silver-lockup.svg +931 -0
- package/media/dragon-silver-mark.svg +931 -0
- package/media/dragon-silver.png +0 -0
- package/package.json +45 -0
- package/specs/001-godmode/constitution.md +54 -0
- package/specs/001-godmode/plan.md +30 -0
- package/specs/001-godmode/spec.md +64 -0
- package/specs/001-godmode/tasks.md +35 -0
- package/specs/002-premortem-positioning/premortem.md +211 -0
- package/src/agent/loop.ts +165 -0
- package/src/agent/mcp.ts +92 -0
- package/src/agent/session.ts +48 -0
- package/src/agent/skills.ts +138 -0
- package/src/agent/stack.ts +154 -0
- package/src/agent/task.ts +55 -0
- package/src/agent/tools.ts +255 -0
- package/src/agent/trace.ts +76 -0
- package/src/agent.ts +114 -0
- package/src/auth.ts +133 -0
- package/src/brain/anthropic.ts +83 -0
- package/src/brain/claude-cli.ts +78 -0
- package/src/brain/ghost-ember.ts +94 -0
- package/src/brain/index.ts +99 -0
- package/src/brain/openai-compat.ts +115 -0
- package/src/brain/router/classify.ts +167 -0
- package/src/brain/router/execute.ts +80 -0
- package/src/brain/router/index.ts +125 -0
- package/src/brain/router/routing-memory.ts +71 -0
- package/src/brain/router/select.ts +156 -0
- package/src/brain/router/two-hop.ts +62 -0
- package/src/brain/router/verify.ts +123 -0
- package/src/brain/types.ts +61 -0
- package/src/brain/worker.ts +72 -0
- package/src/commands/ai.ts +144 -0
- package/src/commands/alerts.ts +131 -0
- package/src/commands/billing.ts +59 -0
- package/src/commands/chat.ts +318 -0
- package/src/commands/config.ts +137 -0
- package/src/commands/doctor.ts +71 -0
- package/src/commands/global.ts +256 -0
- package/src/commands/keep.ts +67 -0
- package/src/commands/lifecycle.ts +273 -0
- package/src/commands/login.ts +184 -0
- package/src/commands/maintenance.ts +54 -0
- package/src/commands/mcp.ts +57 -0
- package/src/commands/memory.ts +229 -0
- package/src/commands/osint.ts +171 -0
- package/src/commands/pentest.ts +140 -0
- package/src/commands/scale.ts +185 -0
- package/src/commands/serve.ts +171 -0
- package/src/commands/tui.ts +126 -0
- package/src/commands/wyrm.ts +269 -0
- package/src/config.ts +93 -0
- package/src/index.ts +92 -0
- package/src/manifest.ts +104 -0
- package/src/ui.ts +188 -0
- package/src/utils.ts +153 -0
- package/src/wyrm/mcp.ts +130 -0
- package/test/auth.test.ts +70 -0
- package/test/brain.test.ts +39 -0
- package/test/security.test.ts +104 -0
- package/test/skills.test.ts +38 -0
- package/test/ui.test.ts +46 -0
- package/tsconfig.json +19 -0
- package/worker/package-lock.json +1527 -0
- package/worker/package.json +17 -0
- package/worker/src/index.ts +76 -0
- package/worker/tsconfig.json +15 -0
- package/worker/wrangler.toml +26 -0
|
@@ -0,0 +1,137 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon config — inspect + set the agent's brain and credentials.
|
|
3
|
+
*
|
|
4
|
+
* dragon config show
|
|
5
|
+
* dragon config brain claude|openai|local
|
|
6
|
+
* dragon config model claude-sonnet-4-6
|
|
7
|
+
* dragon config local-model qwen2.5-coder:7b
|
|
8
|
+
* dragon config key anthropic sk-ant-… (stored in ~/.dragon/config.json, 0600)
|
|
9
|
+
*
|
|
10
|
+
* Env vars (ANTHROPIC_API_KEY / OPENAI_API_KEY / DRAGON_BRAIN / DRAGON_MODEL)
|
|
11
|
+
* always take precedence over stored config.
|
|
12
|
+
*
|
|
13
|
+
* Copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
|
|
14
|
+
*/
|
|
15
|
+
|
|
16
|
+
import type { Command } from 'commander'
|
|
17
|
+
import { loadConfig, saveConfig, type DragonConfig } from '../config.js'
|
|
18
|
+
import { resolveProvider, PROVIDERS } from '../brain/index.js'
|
|
19
|
+
import { resolveAuth } from '../auth.js'
|
|
20
|
+
import { C, success, error } from '../utils.js'
|
|
21
|
+
import { panel, chrome, statusDot } from '../ui.js'
|
|
22
|
+
import { chmodSync, existsSync } from 'node:fs'
|
|
23
|
+
import { homedir } from 'node:os'
|
|
24
|
+
import { join } from 'node:path'
|
|
25
|
+
|
|
26
|
+
function lock(): void {
|
|
27
|
+
try { chmodSync(join(homedir(), '.dragon', 'config.json'), 0o600) } catch { /* best-effort */ }
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
function redact(v?: string): string {
|
|
31
|
+
if (!v) return C.faint('—')
|
|
32
|
+
return C.accent(`set (…${v.slice(-4)})`)
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export function registerConfigCommands(program: Command, _config: DragonConfig) {
|
|
36
|
+
const cfg = program.command('config').description('Inspect + set the Dragon agent brain + credentials')
|
|
37
|
+
|
|
38
|
+
cfg
|
|
39
|
+
.command('show')
|
|
40
|
+
.description('Show current config (secrets redacted)')
|
|
41
|
+
.action(() => {
|
|
42
|
+
const c = loadConfig()
|
|
43
|
+
const a = resolveAuth()
|
|
44
|
+
console.log()
|
|
45
|
+
console.log(` ${C.accent.bold('brain')}`)
|
|
46
|
+
console.log(` provider: ${C.info(resolveProvider())} ${C.faint('(default')} ${C.faint(c.brain?.provider ?? 'claude')}${C.faint(')')}`)
|
|
47
|
+
console.log(` model: ${C.info(c.brain?.model ?? C.faint('default'))}`)
|
|
48
|
+
console.log(` local model: ${C.info(c.brain?.localModel ?? 'qwen2.5-coder:7b')} ${C.faint(c.brain?.localBaseURL ?? 'http://localhost:11434/v1')}`)
|
|
49
|
+
console.log(` keys: anthropic ${redact(process.env.ANTHROPIC_API_KEY ?? c.brain?.keys?.anthropic)} openai ${redact(process.env.OPENAI_API_KEY ?? c.brain?.keys?.openai)}`)
|
|
50
|
+
console.log(` ${C.accent.bold('portal')} (account.ghosts.lk)`)
|
|
51
|
+
console.log(` endpoint: ${C.info(a.apiBase)}`)
|
|
52
|
+
console.log(` auth: ${a.mode === 'none' ? C.faint('none') : C.accent(a.mode)}${a.email ? C.faint(' · ' + a.email) : ''}`)
|
|
53
|
+
console.log()
|
|
54
|
+
})
|
|
55
|
+
|
|
56
|
+
cfg
|
|
57
|
+
.command('brain <provider>')
|
|
58
|
+
.description(`Set the default reasoning brain: ${PROVIDERS.join(' | ')}`)
|
|
59
|
+
.action((provider: string) => {
|
|
60
|
+
const p = provider.toLowerCase()
|
|
61
|
+
if (!(PROVIDERS as string[]).includes(p)) { error(`unknown provider "${provider}". One of: ${PROVIDERS.join(', ')}`); process.exitCode = 1; return }
|
|
62
|
+
const c = loadConfig()
|
|
63
|
+
c.brain = { ...(c.brain ?? {}), provider: p as 'claude' | 'openai' | 'local' | 'ghost' | 'worker' | 'custom' }
|
|
64
|
+
saveConfig(c)
|
|
65
|
+
success(`default brain → ${p}`)
|
|
66
|
+
})
|
|
67
|
+
|
|
68
|
+
cfg
|
|
69
|
+
.command('model <id>')
|
|
70
|
+
.description('Set the hosted model id (claude/openai), e.g. claude-sonnet-4-6')
|
|
71
|
+
.action((id: string) => {
|
|
72
|
+
const c = loadConfig()
|
|
73
|
+
c.brain = { ...(c.brain ?? {}), model: id }
|
|
74
|
+
saveConfig(c)
|
|
75
|
+
success(`model → ${id}`)
|
|
76
|
+
})
|
|
77
|
+
|
|
78
|
+
cfg
|
|
79
|
+
.command('local-model <tag>')
|
|
80
|
+
.description('Set the local (Ollama) model tag, e.g. qwen2.5-coder:7b')
|
|
81
|
+
.action((tag: string) => {
|
|
82
|
+
const c = loadConfig()
|
|
83
|
+
c.brain = { ...(c.brain ?? {}), localModel: tag }
|
|
84
|
+
saveConfig(c)
|
|
85
|
+
success(`local model → ${tag}`)
|
|
86
|
+
})
|
|
87
|
+
|
|
88
|
+
cfg
|
|
89
|
+
.command('key <provider> <key>')
|
|
90
|
+
.description('Store an API key (anthropic | openai) in ~/.dragon/config.json (0600)')
|
|
91
|
+
.action((provider: string, key: string) => {
|
|
92
|
+
const p = provider.toLowerCase()
|
|
93
|
+
if (p !== 'anthropic' && p !== 'openai') { error('provider must be "anthropic" or "openai"'); process.exitCode = 1; return }
|
|
94
|
+
const c = loadConfig()
|
|
95
|
+
c.brain = { ...(c.brain ?? {}), keys: { ...(c.brain?.keys ?? {}), [p]: key } }
|
|
96
|
+
saveConfig(c)
|
|
97
|
+
lock()
|
|
98
|
+
success(`${p} key stored (config locked to 0600). Env vars still override.`)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
cfg
|
|
102
|
+
.command('custom-url <url>')
|
|
103
|
+
.description('Set a custom OpenAI-compatible endpoint (OpenRouter / vLLM / LM Studio)')
|
|
104
|
+
.action((url: string) => { const c = loadConfig(); c.brain = { ...(c.brain ?? {}), customBaseURL: url }; saveConfig(c); success(`custom endpoint → ${url} (use: dragon config brain custom)`) })
|
|
105
|
+
|
|
106
|
+
cfg
|
|
107
|
+
.command('custom-model <id>')
|
|
108
|
+
.description('Set the model id for the custom endpoint')
|
|
109
|
+
.action((id: string) => { const c = loadConfig(); c.brain = { ...(c.brain ?? {}), customModel: id }; saveConfig(c); success(`custom model → ${id}`) })
|
|
110
|
+
|
|
111
|
+
// `dragon brains` — the model picker, as a command.
|
|
112
|
+
program
|
|
113
|
+
.command('brains')
|
|
114
|
+
.description('List reasoning brains + which are ready (the model picker)')
|
|
115
|
+
.action(() => {
|
|
116
|
+
const c = loadConfig()
|
|
117
|
+
const a = resolveAuth()
|
|
118
|
+
const active = resolveProvider()
|
|
119
|
+
const hasA = !!(process.env.ANTHROPIC_API_KEY || c.brain?.keys?.anthropic)
|
|
120
|
+
const hasO = !!(process.env.OPENAI_API_KEY || c.brain?.keys?.openai)
|
|
121
|
+
const hasC = !!(process.env.DRAGON_OPENAI_BASE || c.brain?.customBaseURL)
|
|
122
|
+
const ollama = ['/usr/local/bin/ollama', '/usr/bin/ollama', join(homedir(), '.local/bin/ollama')].some((p) => existsSync(p))
|
|
123
|
+
const rows: [string, boolean, string, string][] = [
|
|
124
|
+
['claude', hasA, 'best for code', hasA ? '' : 'set ANTHROPIC_API_KEY'],
|
|
125
|
+
['worker', a.mode !== 'none', 'free · our Cloudflare', a.mode !== 'none' ? '' : 'run `dragon login`'],
|
|
126
|
+
['local', ollama, 'Ollama · private · free', ollama ? 'pull a model' : 'install Ollama'],
|
|
127
|
+
['ghost', false, 'DragonSpark · our nano LLM', 'train + serve it'],
|
|
128
|
+
['openai', hasO, 'GPT', hasO ? '' : 'set OPENAI_API_KEY'],
|
|
129
|
+
['custom', hasC, 'any OpenAI-compatible endpoint', hasC ? '' : 'dragon config custom-url <url>'],
|
|
130
|
+
]
|
|
131
|
+
const lines = rows.map(([id, ready, desc, need]) =>
|
|
132
|
+
`${id === active ? C.accent('▸') : ' '} ${statusDot(ready)} ${C.info(id.padEnd(7))} ${C.faint(desc)}${need ? C.faint(' · ' + need) : ''}`)
|
|
133
|
+
console.log()
|
|
134
|
+
console.log(panel(lines, { title: chrome('BRAINS') }))
|
|
135
|
+
console.log(` ${C.faint('active:')} ${C.accent(active)} ${C.faint('switch:')} dragon config brain <name> ${C.faint('per-run:')} dragon chat --brain <name>`)
|
|
136
|
+
})
|
|
137
|
+
}
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon doctor — one-glance health check of everything the agent depends on:
|
|
3
|
+
* node, the active brain + readiness, auth, Wyrm memory, Ollama, the skill library,
|
|
4
|
+
* disk, and config-file permissions. Read-only; green/amber/red per check.
|
|
5
|
+
*
|
|
6
|
+
* Copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Command } from 'commander'
|
|
10
|
+
import { loadConfig, type DragonConfig } from '../config.js'
|
|
11
|
+
import { C } from '../utils.js'
|
|
12
|
+
import { panel, chrome } from '../ui.js'
|
|
13
|
+
import { resolveAuth, whoami } from '../auth.js'
|
|
14
|
+
import { resolveProvider } from '../brain/index.js'
|
|
15
|
+
import { Wyrm } from '../wyrm/mcp.js'
|
|
16
|
+
import { loadSkillLibrary } from '../agent/skills.js'
|
|
17
|
+
import { existsSync, statSync, statfsSync } from 'node:fs'
|
|
18
|
+
import { homedir } from 'node:os'
|
|
19
|
+
import { join } from 'node:path'
|
|
20
|
+
|
|
21
|
+
type Status = 'ok' | 'warn' | 'fail'
|
|
22
|
+
const mark = (s: Status) => (s === 'ok' ? C.accent('●') : s === 'warn' ? C.high('◐') : C.critical('○'))
|
|
23
|
+
const paint = (s: Status, v: string) => (s === 'fail' ? C.critical(v) : s === 'warn' ? C.high(v) : C.info(v))
|
|
24
|
+
|
|
25
|
+
export function registerDoctorCommands(program: Command, _config: DragonConfig) {
|
|
26
|
+
program
|
|
27
|
+
.command('doctor')
|
|
28
|
+
.description('Health check — brain, auth, memory, models, disk, perms')
|
|
29
|
+
.action(async () => {
|
|
30
|
+
const rows: [Status, string, string][] = []
|
|
31
|
+
const cfg = loadConfig()
|
|
32
|
+
const a = resolveAuth()
|
|
33
|
+
const ollama = ['/usr/local/bin/ollama', '/usr/bin/ollama', join(homedir(), '.local/bin/ollama')].some(existsSync)
|
|
34
|
+
|
|
35
|
+
const major = Number(process.version.slice(1).split('.')[0])
|
|
36
|
+
rows.push([major >= 20 ? 'ok' : 'warn', 'node', `${process.version}${major >= 20 ? '' : ' (20+ recommended)'}`])
|
|
37
|
+
|
|
38
|
+
const active = resolveProvider()
|
|
39
|
+
const ready: Record<string, boolean> = {
|
|
40
|
+
claude: !!(process.env.ANTHROPIC_API_KEY || cfg.brain?.keys?.anthropic),
|
|
41
|
+
worker: a.mode !== 'none',
|
|
42
|
+
local: ollama,
|
|
43
|
+
openai: !!(process.env.OPENAI_API_KEY || cfg.brain?.keys?.openai),
|
|
44
|
+
custom: !!(process.env.DRAGON_OPENAI_BASE || cfg.brain?.customBaseURL),
|
|
45
|
+
ghost: false,
|
|
46
|
+
}
|
|
47
|
+
const brainOk = ready[active] ?? false
|
|
48
|
+
rows.push([brainOk ? 'ok' : 'warn', 'brain', `${active}${brainOk ? ' · ready' : ' · not set up (will fall back)'}`])
|
|
49
|
+
|
|
50
|
+
if (a.mode === 'none') rows.push(['warn', 'auth', 'not signed in — run `dragon login`'])
|
|
51
|
+
else { const me = await whoami(); rows.push([me.ok ? 'ok' : 'fail', 'auth', me.ok ? `${a.mode} · ${me.email ?? 'verified'}` : `${a.mode} · invalid (re-login)`]) }
|
|
52
|
+
|
|
53
|
+
const w = new Wyrm()
|
|
54
|
+
const wok = await w.connect()
|
|
55
|
+
rows.push([wok ? 'ok' : 'warn', 'memory', wok ? `Wyrm · ${w.toolSpecs().length} tools` : 'Wyrm unavailable (agent works without it)'])
|
|
56
|
+
await w.close()
|
|
57
|
+
|
|
58
|
+
rows.push([ollama ? 'ok' : 'warn', 'ollama', ollama ? 'installed' : 'not found (local brain needs it)'])
|
|
59
|
+
const sk = loadSkillLibrary()
|
|
60
|
+
rows.push([sk.count > 0 ? 'ok' : 'warn', 'skills', `${sk.count} loaded`])
|
|
61
|
+
|
|
62
|
+
try { const st = statfsSync(process.cwd()); const g = (st.bavail * st.bsize) / 1e9; rows.push([g > 2 ? 'ok' : g > 0.5 ? 'warn' : 'fail', 'disk', `${g.toFixed(1)} GB free`]) } catch { /* skip */ }
|
|
63
|
+
try { const m = statSync(join(homedir(), '.dragon', 'config.json')).mode & 0o777; rows.push([m === 0o600 ? 'ok' : 'warn', 'config', `~/.dragon/config.json ${m.toString(8)}${m === 0o600 ? '' : ' (expected 600)'}`]) } catch { /* no config yet */ }
|
|
64
|
+
|
|
65
|
+
console.log()
|
|
66
|
+
console.log(panel(rows.map(([s, k, v]) => `${mark(s)} ${C.faint(k.padEnd(7))} ${paint(s, v)}`), { title: chrome('DOCTOR') }))
|
|
67
|
+
const fails = rows.filter((r) => r[0] === 'fail').length
|
|
68
|
+
const warns = rows.filter((r) => r[0] === 'warn').length
|
|
69
|
+
console.log(` ${fails ? C.critical(`${fails} failing`) : warns ? C.high(`${warns} warning${warns > 1 ? 's' : ''}`) : C.accent('all systems green')}`)
|
|
70
|
+
})
|
|
71
|
+
}
|
|
@@ -0,0 +1,256 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon init / dragon status / dragon doctor — Global commands
|
|
3
|
+
*
|
|
4
|
+
* - init: Auto-detect product paths and write ~/.dragon/config.json
|
|
5
|
+
* - status: Health check across all products + APIs
|
|
6
|
+
* - doctor: Contract-test the cross-tool flows (lightweight)
|
|
7
|
+
*/
|
|
8
|
+
|
|
9
|
+
import type { Command } from 'commander'
|
|
10
|
+
import type { DragonConfig } from '../config.js'
|
|
11
|
+
import { saveConfig } from '../config.js'
|
|
12
|
+
import { label, success, warn } from '../utils.js'
|
|
13
|
+
import chalk from 'chalk'
|
|
14
|
+
import { existsSync, statSync } from 'fs'
|
|
15
|
+
import { join, resolve } from 'path'
|
|
16
|
+
import { homedir } from 'os'
|
|
17
|
+
|
|
18
|
+
const KNOWN_MARKERS: Record<string, { files: string[]; name: string }> = {
|
|
19
|
+
scale: { files: ['includes/payment.php', 'admin/orders.php', 'api/health.php'], name: 'DragonScale' },
|
|
20
|
+
wyrm: { files: ['packages/mcp-server/src/index.ts', 'install.sh'], name: 'Wyrm' },
|
|
21
|
+
pentest: { files: ['phantomdragon.py', 'phantom_dragon_ai/control_api.py'], name: 'PhantomDragon' },
|
|
22
|
+
keep: { files: ['src/engine/sentinel.rs', 'src/engine/shield.rs'], name: 'DragonKeep' },
|
|
23
|
+
net: { files: ['packages/api/src/server.ts', 'pnpm-workspace.yaml'], name: 'DragonNet' },
|
|
24
|
+
}
|
|
25
|
+
|
|
26
|
+
function detectProduct(dir: string, product: string): boolean {
|
|
27
|
+
const markers = KNOWN_MARKERS[product]
|
|
28
|
+
if (!markers) return false
|
|
29
|
+
return markers.files.some(f => existsSync(join(dir, f)))
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
async function ping(url: string, timeoutMs = 1500): Promise<{ ok: boolean; status?: number; data?: any; error?: string }> {
|
|
33
|
+
try {
|
|
34
|
+
const controller = new AbortController()
|
|
35
|
+
const timer = setTimeout(() => controller.abort(), timeoutMs)
|
|
36
|
+
try {
|
|
37
|
+
const res = await fetch(url, { signal: controller.signal })
|
|
38
|
+
const text = await res.text()
|
|
39
|
+
let parsed: any = null
|
|
40
|
+
try { parsed = JSON.parse(text) } catch { parsed = text }
|
|
41
|
+
return { ok: res.ok, status: res.status, data: parsed }
|
|
42
|
+
} finally { clearTimeout(timer) }
|
|
43
|
+
} catch (e) {
|
|
44
|
+
return { ok: false, error: String(e) }
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
export function registerGlobalCommands(program: Command, config: DragonConfig) {
|
|
49
|
+
|
|
50
|
+
// --- init ---
|
|
51
|
+
program
|
|
52
|
+
.command('init')
|
|
53
|
+
.description('Auto-detect products and configure dragon CLI')
|
|
54
|
+
.option('-d, --dir <dir>', 'Directory to scan', resolve('.'))
|
|
55
|
+
.action(async (opts) => {
|
|
56
|
+
console.log(label('Dragon'), 'Initializing...\n')
|
|
57
|
+
|
|
58
|
+
const scanDirs = [
|
|
59
|
+
opts.dir,
|
|
60
|
+
join(homedir(), 'Git Projects'),
|
|
61
|
+
join(homedir(), 'Projects'),
|
|
62
|
+
join(homedir(), 'src'),
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
const found: Record<string, string> = {}
|
|
66
|
+
|
|
67
|
+
for (const baseDir of scanDirs) {
|
|
68
|
+
if (!existsSync(baseDir)) continue
|
|
69
|
+
const { readdirSync } = await import('fs')
|
|
70
|
+
const entries = readdirSync(baseDir, { withFileTypes: true })
|
|
71
|
+
|
|
72
|
+
for (const entry of entries) {
|
|
73
|
+
if (!entry.isDirectory()) continue
|
|
74
|
+
const fullPath = join(baseDir, entry.name)
|
|
75
|
+
|
|
76
|
+
for (const [product, _] of Object.entries(KNOWN_MARKERS)) {
|
|
77
|
+
if (!found[product] && detectProduct(fullPath, product)) {
|
|
78
|
+
found[product] = fullPath
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
// Update config
|
|
85
|
+
for (const [product, path] of Object.entries(found)) {
|
|
86
|
+
(config.products as any)[product].path = path
|
|
87
|
+
success(`${KNOWN_MARKERS[product].name} → ${chalk.dim(path)}`)
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
for (const [product, markers] of Object.entries(KNOWN_MARKERS)) {
|
|
91
|
+
if (!found[product]) {
|
|
92
|
+
warn(`${markers.name} not found`)
|
|
93
|
+
}
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
saveConfig(config)
|
|
97
|
+
console.log()
|
|
98
|
+
success(`Config saved to ${chalk.dim('~/.dragon/config.json')}`)
|
|
99
|
+
})
|
|
100
|
+
|
|
101
|
+
// --- status ---
|
|
102
|
+
program
|
|
103
|
+
.command('status')
|
|
104
|
+
.description('Health check across all products + APIs')
|
|
105
|
+
.action(async () => {
|
|
106
|
+
console.log(label('Dragon'), 'System status:\n')
|
|
107
|
+
|
|
108
|
+
// DragonScale
|
|
109
|
+
const scaleUrl = config.products.scale.url
|
|
110
|
+
const scalePath = config.products.scale.path
|
|
111
|
+
if (scalePath && existsSync(join(scalePath, 'index.php'))) {
|
|
112
|
+
console.log(` ${chalk.green('●')} DragonScale ${chalk.dim(scaleUrl || scalePath)} ${chalk.green('Installed')}`)
|
|
113
|
+
} else {
|
|
114
|
+
console.log(` ${chalk.dim('○')} DragonScale ${chalk.dim('Not configured')}`)
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Wyrm
|
|
118
|
+
const wyrmPath = config.products.wyrm.path
|
|
119
|
+
const wyrmPort = config.products.wyrm.port || 3333
|
|
120
|
+
if (wyrmPath) {
|
|
121
|
+
const r = await ping(`http://localhost:${wyrmPort}/health`)
|
|
122
|
+
if (r.ok) {
|
|
123
|
+
console.log(` ${chalk.green('●')} Wyrm ${chalk.dim(`localhost:${wyrmPort}`)} ${chalk.green('Online')}`)
|
|
124
|
+
} else {
|
|
125
|
+
console.log(` ${chalk.yellow('●')} Wyrm ${chalk.dim(`localhost:${wyrmPort}`)} ${chalk.yellow('Installed · Offline')}`)
|
|
126
|
+
}
|
|
127
|
+
} else {
|
|
128
|
+
console.log(` ${chalk.dim('○')} Wyrm ${chalk.dim('Not configured')}`)
|
|
129
|
+
}
|
|
130
|
+
|
|
131
|
+
// PhantomDragon engine + control_api
|
|
132
|
+
const pentestPath = config.products.pentest.path
|
|
133
|
+
if (pentestPath && (existsSync(join(pentestPath, 'phantom_dragon_ai/control_api.py'))
|
|
134
|
+
|| existsSync(join(pentestPath, 'phantomdragon.py')))) {
|
|
135
|
+
console.log(` ${chalk.green('●')} PhantomDragon engine ${chalk.dim(pentestPath)} ${chalk.green('Installed')}`)
|
|
136
|
+
const apiPort = config.products.pentest.controlPort ?? 4091
|
|
137
|
+
const uiPort = config.products.pentest.controlUiPort ?? 4090
|
|
138
|
+
const apiR = await ping(`http://localhost:${apiPort}/health`)
|
|
139
|
+
if (apiR.ok && apiR.data) {
|
|
140
|
+
const d = apiR.data as any
|
|
141
|
+
console.log(` ${chalk.green('●')} Control API ${chalk.dim(`localhost:${apiPort}`)} ${chalk.green('Online')} ${chalk.dim(`(${d.scan_count} scans, ${d.active_runs} runs)`)}`)
|
|
142
|
+
} else {
|
|
143
|
+
console.log(` ${chalk.yellow('●')} Control API ${chalk.dim(`localhost:${apiPort}`)} ${chalk.yellow('Offline')}`)
|
|
144
|
+
}
|
|
145
|
+
const uiR = await ping(`http://localhost:${uiPort}/`)
|
|
146
|
+
if (uiR.ok) {
|
|
147
|
+
console.log(` ${chalk.green('●')} Control Dashboard ${chalk.dim(`localhost:${uiPort}`)} ${chalk.green('Online')}`)
|
|
148
|
+
} else {
|
|
149
|
+
console.log(` ${chalk.yellow('●')} Control Dashboard ${chalk.dim(`localhost:${uiPort}`)} ${chalk.yellow('Offline')}`)
|
|
150
|
+
}
|
|
151
|
+
} else {
|
|
152
|
+
console.log(` ${chalk.dim('○')} PhantomDragon engine ${chalk.dim('Not configured')}`)
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
// DragonNet
|
|
156
|
+
const netPath = config.products.net.path
|
|
157
|
+
if (netPath && existsSync(join(netPath, 'pnpm-workspace.yaml'))) {
|
|
158
|
+
console.log(` ${chalk.green('●')} DragonNet ${chalk.dim(netPath)} ${chalk.green('Installed')}`)
|
|
159
|
+
const apiPort = config.products.net.apiPort ?? 4080
|
|
160
|
+
const uiPort = config.products.net.uiPort ?? 4081
|
|
161
|
+
const apiR = await ping(`http://localhost:${apiPort}/health`)
|
|
162
|
+
if (apiR.ok) {
|
|
163
|
+
console.log(` ${chalk.green('●')} OSINT API ${chalk.dim(`localhost:${apiPort}`)} ${chalk.green('Online')}`)
|
|
164
|
+
} else {
|
|
165
|
+
console.log(` ${chalk.yellow('●')} OSINT API ${chalk.dim(`localhost:${apiPort}`)} ${chalk.yellow('Offline')}`)
|
|
166
|
+
}
|
|
167
|
+
const uiR = await ping(`http://localhost:${uiPort}/`)
|
|
168
|
+
if (uiR.ok) {
|
|
169
|
+
console.log(` ${chalk.green('●')} OSINT Dashboard ${chalk.dim(`localhost:${uiPort}`)} ${chalk.green('Online')}`)
|
|
170
|
+
} else {
|
|
171
|
+
console.log(` ${chalk.yellow('●')} OSINT Dashboard ${chalk.dim(`localhost:${uiPort}`)} ${chalk.yellow('Offline')}`)
|
|
172
|
+
}
|
|
173
|
+
} else {
|
|
174
|
+
console.log(` ${chalk.dim('○')} DragonNet ${chalk.dim('Not configured')}`)
|
|
175
|
+
}
|
|
176
|
+
|
|
177
|
+
// Phantom Memory
|
|
178
|
+
const memPath = join(homedir(), '.copilot', 'phantom-memory.db')
|
|
179
|
+
if (existsSync(memPath)) {
|
|
180
|
+
const bytes = statSync(memPath).size
|
|
181
|
+
const apiPort = config.products.pentest.controlPort ?? 4091
|
|
182
|
+
const memR = await ping(`http://localhost:${apiPort}/v1/memory`)
|
|
183
|
+
let targetCount = '—'
|
|
184
|
+
if (memR.ok && memR.data?.targets) targetCount = String(memR.data.targets.length)
|
|
185
|
+
const sizeStr = bytes < 1024*1024 ? `${(bytes/1024).toFixed(1)} KB` : `${(bytes/1024/1024).toFixed(1)} MB`
|
|
186
|
+
console.log(` ${chalk.green('●')} Phantom Memory ${chalk.dim(memPath)} ${chalk.green(`${targetCount} targets · ${sizeStr}`)}`)
|
|
187
|
+
} else {
|
|
188
|
+
console.log(` ${chalk.dim('○')} Phantom Memory ${chalk.dim('No DB yet — runs auto-create on first scan')}`)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
// DragonKeep (Rust defensive backbone)
|
|
192
|
+
const keepPath = config.products.keep.path
|
|
193
|
+
if (keepPath && existsSync(join(keepPath, 'Cargo.toml'))) {
|
|
194
|
+
const dbgBin = join(keepPath, 'target/debug/dragonkeep')
|
|
195
|
+
const relBin = join(keepPath, 'target/release/dragonkeep')
|
|
196
|
+
const built = existsSync(relBin) || existsSync(dbgBin)
|
|
197
|
+
const label_ = built ? chalk.green('Installed · built') : chalk.yellow('Installed · not yet built')
|
|
198
|
+
console.log(` ${chalk.green('●')} DragonKeep ${chalk.dim(keepPath)} ${label_}`)
|
|
199
|
+
if (!built) {
|
|
200
|
+
console.log(` ${chalk.dim('→ cargo build --release in ' + keepPath)}`)
|
|
201
|
+
}
|
|
202
|
+
} else {
|
|
203
|
+
console.log(` ${chalk.dim('○')} DragonKeep ${chalk.dim('Not configured')}`)
|
|
204
|
+
}
|
|
205
|
+
console.log()
|
|
206
|
+
})
|
|
207
|
+
|
|
208
|
+
// --- contract (cross-tool flow checks; `dragon doctor` is the health panel) ---
|
|
209
|
+
program
|
|
210
|
+
.command('contract')
|
|
211
|
+
.description('Contract-test cross-tool flows (Phantom Memory / Control API / DragonNet / Wyrm)')
|
|
212
|
+
.action(async () => {
|
|
213
|
+
console.log(label('Dragon'), 'Contract checks:\n')
|
|
214
|
+
|
|
215
|
+
const checks: Array<{ name: string; ok: boolean; note?: string }> = []
|
|
216
|
+
|
|
217
|
+
// Phantom Memory exists?
|
|
218
|
+
const memPath = join(homedir(), '.copilot', 'phantom-memory.db')
|
|
219
|
+
checks.push({ name: 'Phantom Memory DB present', ok: existsSync(memPath), note: memPath })
|
|
220
|
+
|
|
221
|
+
// Control API speaks memory?
|
|
222
|
+
const apiPort = config.products.pentest.controlPort ?? 4091
|
|
223
|
+
const memR = await ping(`http://localhost:${apiPort}/v1/memory`)
|
|
224
|
+
checks.push({
|
|
225
|
+
name: 'Control API /v1/memory reachable',
|
|
226
|
+
ok: memR.ok,
|
|
227
|
+
note: memR.ok ? `${memR.data?.targets?.length ?? 0} targets` : memR.error,
|
|
228
|
+
})
|
|
229
|
+
|
|
230
|
+
// DragonNet API reachable?
|
|
231
|
+
const dnPort = config.products.net.apiPort ?? 4080
|
|
232
|
+
const dnR = await ping(`http://localhost:${dnPort}/health`)
|
|
233
|
+
checks.push({
|
|
234
|
+
name: 'DragonNet /health reachable',
|
|
235
|
+
ok: dnR.ok,
|
|
236
|
+
note: dnR.ok ? 'OK' : 'offline (run: dragon osint serve)',
|
|
237
|
+
})
|
|
238
|
+
|
|
239
|
+
// Wyrm reachable?
|
|
240
|
+
const wyrmR = await ping(`http://localhost:${config.products.wyrm.port ?? 3333}/health`)
|
|
241
|
+
checks.push({
|
|
242
|
+
name: 'Wyrm /health reachable',
|
|
243
|
+
ok: wyrmR.ok,
|
|
244
|
+
note: wyrmR.ok ? 'OK' : 'offline (Wyrm integration becomes best-effort)',
|
|
245
|
+
})
|
|
246
|
+
|
|
247
|
+
checks.forEach((c) => {
|
|
248
|
+
const sym = c.ok ? chalk.green('✓') : chalk.yellow('⚠')
|
|
249
|
+
console.log(` ${sym} ${c.name.padEnd(40)} ${chalk.dim(c.note ?? '')}`)
|
|
250
|
+
})
|
|
251
|
+
console.log()
|
|
252
|
+
const failing = checks.filter((c) => !c.ok).length
|
|
253
|
+
if (failing === 0) success('All contract checks passing')
|
|
254
|
+
else warn(`${failing} check(s) need attention — see ~/Git Projects/dragon-cli/docs/SYSTEM-PREMORTEM.md`)
|
|
255
|
+
})
|
|
256
|
+
}
|
|
@@ -0,0 +1,67 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon keep — DragonKeep defensive engine bridge.
|
|
3
|
+
*
|
|
4
|
+
* DragonKeep ships independently as a Rust binary (`dragonkeep`); this
|
|
5
|
+
* subcommand is a thin shim that surfaces its status + control loop
|
|
6
|
+
* from the unified `dragon` CLI so operators don't need to memorise
|
|
7
|
+
* a second toolchain.
|
|
8
|
+
*/
|
|
9
|
+
|
|
10
|
+
import type { Command } from 'commander'
|
|
11
|
+
import type { DragonConfig } from '../config.js'
|
|
12
|
+
import { spawnSync } from 'node:child_process'
|
|
13
|
+
import { error, info, label } from '../utils.js'
|
|
14
|
+
import chalk from 'chalk'
|
|
15
|
+
|
|
16
|
+
function which(bin: string): string | null {
|
|
17
|
+
const r = spawnSync('command', ['-v', bin], { shell: true, encoding: 'utf-8' })
|
|
18
|
+
const out = (r.stdout || '').trim()
|
|
19
|
+
return out || null
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
function passthrough(args: string[]): number {
|
|
23
|
+
const bin = which('dragonkeep')
|
|
24
|
+
if (!bin) {
|
|
25
|
+
error('dragonkeep binary not on PATH')
|
|
26
|
+
info('install it via `dragon install dragonkeep` or build from source in ~/Git Projects/DragonKeep')
|
|
27
|
+
return 127
|
|
28
|
+
}
|
|
29
|
+
const r = spawnSync(bin, args, { stdio: 'inherit' })
|
|
30
|
+
return r.status ?? 1
|
|
31
|
+
}
|
|
32
|
+
|
|
33
|
+
export function registerKeepCommands(program: Command, _config: DragonConfig) {
|
|
34
|
+
const keep = program
|
|
35
|
+
.command('keep')
|
|
36
|
+
.description('DragonKeep — defensive engine (NGAV · MDR · compliance · SOAR)')
|
|
37
|
+
|
|
38
|
+
keep
|
|
39
|
+
.command('status')
|
|
40
|
+
.description('Show DragonKeep daemon status + active engines')
|
|
41
|
+
.action(() => { process.exit(passthrough(['status'])) })
|
|
42
|
+
|
|
43
|
+
keep
|
|
44
|
+
.command('scan [path]')
|
|
45
|
+
.description('Run an on-demand AV scan')
|
|
46
|
+
.action((path?: string) => { process.exit(passthrough(['scan', ...(path ? [path] : [])])) })
|
|
47
|
+
|
|
48
|
+
keep
|
|
49
|
+
.command('engines')
|
|
50
|
+
.description('List the active defensive engines')
|
|
51
|
+
.action(() => { process.exit(passthrough(['engines'])) })
|
|
52
|
+
|
|
53
|
+
// Default action: print a short summary instead of dumping help.
|
|
54
|
+
keep.action(() => {
|
|
55
|
+
console.log()
|
|
56
|
+
console.log(` ${label('DragonKeep')} ${chalk.dim('defensive engine · 28 modules · NGAV + MDR')}`)
|
|
57
|
+
console.log()
|
|
58
|
+
console.log(` ${chalk.dim('dragon keep status')} ${chalk.dim('— daemon + engine health')}`)
|
|
59
|
+
console.log(` ${chalk.dim('dragon keep scan')} ${chalk.dim('— on-demand scan')}`)
|
|
60
|
+
console.log(` ${chalk.dim('dragon keep engines')} ${chalk.dim('— list active engines')}`)
|
|
61
|
+
console.log()
|
|
62
|
+
if (!which('dragonkeep')) {
|
|
63
|
+
info(`run ${chalk.green('dragon install dragonkeep')} to install the daemon`)
|
|
64
|
+
console.log()
|
|
65
|
+
}
|
|
66
|
+
})
|
|
67
|
+
}
|