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,191 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon scale — DragonScale Commerce Platform management
|
|
3
|
+
*
|
|
4
|
+
* Manages Upalis/DragonScale deployments:
|
|
5
|
+
* - Status & health checks via API
|
|
6
|
+
* - Order management
|
|
7
|
+
* - Menu management
|
|
8
|
+
* - Database operations (backup, seed)
|
|
9
|
+
* - Deployment (pull, update)
|
|
10
|
+
*/
|
|
11
|
+
import { getProductPath } from '../config.js';
|
|
12
|
+
import { exec, run, fetchJSON, label, success, error, info, warn, table } from '../utils.js';
|
|
13
|
+
import chalk from 'chalk';
|
|
14
|
+
import { existsSync } from 'fs';
|
|
15
|
+
import { join } from 'path';
|
|
16
|
+
export function registerScaleCommands(program, config) {
|
|
17
|
+
const scale = program
|
|
18
|
+
.command('scale')
|
|
19
|
+
.description('DragonScale — Commerce platform management');
|
|
20
|
+
// --- status ---
|
|
21
|
+
scale
|
|
22
|
+
.command('status')
|
|
23
|
+
.description('Check platform health & today\'s stats')
|
|
24
|
+
.option('-u, --url <url>', 'Base URL of the DragonScale instance')
|
|
25
|
+
.action(async (opts) => {
|
|
26
|
+
const url = opts.url || config.products.scale.url;
|
|
27
|
+
if (!url) {
|
|
28
|
+
error('No URL configured. Use --url or run: dragon init');
|
|
29
|
+
process.exit(1);
|
|
30
|
+
}
|
|
31
|
+
console.log(label('DragonScale'), 'Checking status...\n');
|
|
32
|
+
try {
|
|
33
|
+
const health = await fetchJSON(`${url}/api/health.php`);
|
|
34
|
+
console.log(` Platform: ${health.status === 'healthy' ? chalk.green('● Online') : chalk.red('● Down')}`);
|
|
35
|
+
console.log(` PHP: ${chalk.dim(health.checks?.php?.version || 'unknown')}`);
|
|
36
|
+
console.log(` Database: ${health.checks?.database?.status === 'connected' ? chalk.green('Connected') : chalk.red('Disconnected')}`);
|
|
37
|
+
console.log(` Disk: ${chalk.dim(health.checks?.filesystem?.status || 'unknown')}`);
|
|
38
|
+
success('Health check passed');
|
|
39
|
+
}
|
|
40
|
+
catch (e) {
|
|
41
|
+
error(`Cannot reach ${url}/api/health.php — ${e.message}`);
|
|
42
|
+
}
|
|
43
|
+
});
|
|
44
|
+
// --- orders ---
|
|
45
|
+
scale
|
|
46
|
+
.command('orders')
|
|
47
|
+
.description('List recent orders')
|
|
48
|
+
.option('-u, --url <url>', 'Base URL')
|
|
49
|
+
.option('-n, --limit <n>', 'Number of orders', '10')
|
|
50
|
+
.option('-s, --status <status>', 'Filter by status (pending, confirmed, preparing, ready, delivered)')
|
|
51
|
+
.action(async (opts) => {
|
|
52
|
+
const url = opts.url || config.products.scale.url;
|
|
53
|
+
if (!url) {
|
|
54
|
+
error('No URL configured.');
|
|
55
|
+
process.exit(1);
|
|
56
|
+
}
|
|
57
|
+
console.log(label('DragonScale'), 'Fetching orders...\n');
|
|
58
|
+
try {
|
|
59
|
+
const params = new URLSearchParams({ action: 'list', limit: opts.limit });
|
|
60
|
+
if (opts.status)
|
|
61
|
+
params.set('status', opts.status);
|
|
62
|
+
const data = await fetchJSON(`${url}/api/orders.php?${params}`);
|
|
63
|
+
if (data.orders?.length) {
|
|
64
|
+
table(data.orders.map((o) => ({
|
|
65
|
+
ID: `#${o.id}`,
|
|
66
|
+
Status: o.status,
|
|
67
|
+
Type: o.order_type || '-',
|
|
68
|
+
Total: `LKR ${o.total}`,
|
|
69
|
+
Time: o.created_at || '-',
|
|
70
|
+
})));
|
|
71
|
+
}
|
|
72
|
+
else {
|
|
73
|
+
info('No orders found');
|
|
74
|
+
}
|
|
75
|
+
}
|
|
76
|
+
catch (e) {
|
|
77
|
+
error(`Failed to fetch orders: ${e.message}`);
|
|
78
|
+
}
|
|
79
|
+
});
|
|
80
|
+
// --- menu ---
|
|
81
|
+
scale
|
|
82
|
+
.command('menu')
|
|
83
|
+
.description('List menu items')
|
|
84
|
+
.option('-u, --url <url>', 'Base URL')
|
|
85
|
+
.option('-c, --category <id>', 'Filter by category ID')
|
|
86
|
+
.action(async (opts) => {
|
|
87
|
+
const url = opts.url || config.products.scale.url;
|
|
88
|
+
if (!url) {
|
|
89
|
+
error('No URL configured.');
|
|
90
|
+
process.exit(1);
|
|
91
|
+
}
|
|
92
|
+
console.log(label('DragonScale'), 'Fetching menu...\n');
|
|
93
|
+
try {
|
|
94
|
+
const params = new URLSearchParams({ action: 'all' });
|
|
95
|
+
if (opts.category)
|
|
96
|
+
params.set('category_id', opts.category);
|
|
97
|
+
const data = await fetchJSON(`${url}/api/menu.php?${params}`);
|
|
98
|
+
if (data.categories?.length) {
|
|
99
|
+
for (const cat of data.categories) {
|
|
100
|
+
console.log(chalk.green.bold(`\n ${cat.name}`));
|
|
101
|
+
if (cat.items?.length) {
|
|
102
|
+
for (const item of cat.items) {
|
|
103
|
+
const status = item.available ? chalk.green('●') : chalk.red('○');
|
|
104
|
+
console.log(` ${status} ${item.name} ${chalk.dim(`LKR ${item.price}`)}`);
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
}
|
|
108
|
+
console.log();
|
|
109
|
+
}
|
|
110
|
+
else {
|
|
111
|
+
info('No menu data');
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
catch (e) {
|
|
115
|
+
error(`Failed to fetch menu: ${e.message}`);
|
|
116
|
+
}
|
|
117
|
+
});
|
|
118
|
+
// --- deploy ---
|
|
119
|
+
scale
|
|
120
|
+
.command('deploy')
|
|
121
|
+
.description('Pull latest code & update production')
|
|
122
|
+
.action(async () => {
|
|
123
|
+
const path = getProductPath(config, 'scale');
|
|
124
|
+
const script = join(path, 'scripts/update-production.sh');
|
|
125
|
+
if (!existsSync(script)) {
|
|
126
|
+
error(`Deploy script not found: ${script}`);
|
|
127
|
+
process.exit(1);
|
|
128
|
+
}
|
|
129
|
+
console.log(label('DragonScale'), 'Deploying...\n');
|
|
130
|
+
const code = await run('bash', [script], path);
|
|
131
|
+
code === 0 ? success('Deployment complete') : error(`Deploy failed (exit ${code})`);
|
|
132
|
+
});
|
|
133
|
+
// --- backup ---
|
|
134
|
+
scale
|
|
135
|
+
.command('backup')
|
|
136
|
+
.description('Backup the database')
|
|
137
|
+
.option('-o, --output <dir>', 'Output directory', './backups')
|
|
138
|
+
.action(async (opts) => {
|
|
139
|
+
const path = getProductPath(config, 'scale');
|
|
140
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, '-').slice(0, 19);
|
|
141
|
+
const outDir = join(path, opts.output);
|
|
142
|
+
console.log(label('DragonScale'), 'Backing up database...\n');
|
|
143
|
+
try {
|
|
144
|
+
exec(`mkdir -p "${outDir}"`);
|
|
145
|
+
// Try mysqldump first, fall back to sqlite
|
|
146
|
+
try {
|
|
147
|
+
exec(`mysqldump --defaults-file="${join(path, '.my.cnf')}" upalis > "${join(outDir, `backup-${timestamp}.sql`)}"`, path);
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
const dbFile = join(path, 'data', 'database.sqlite');
|
|
151
|
+
if (existsSync(dbFile)) {
|
|
152
|
+
exec(`cp "${dbFile}" "${join(outDir, `backup-${timestamp}.sqlite`)}"`, path);
|
|
153
|
+
}
|
|
154
|
+
else {
|
|
155
|
+
throw new Error('No database found to backup');
|
|
156
|
+
}
|
|
157
|
+
}
|
|
158
|
+
success(`Backup saved to ${outDir}/backup-${timestamp}.*`);
|
|
159
|
+
}
|
|
160
|
+
catch (e) {
|
|
161
|
+
error(`Backup failed: ${e.message}`);
|
|
162
|
+
}
|
|
163
|
+
});
|
|
164
|
+
// --- seed ---
|
|
165
|
+
scale
|
|
166
|
+
.command('seed')
|
|
167
|
+
.description('Seed database with sample data')
|
|
168
|
+
.action(async () => {
|
|
169
|
+
const path = getProductPath(config, 'scale');
|
|
170
|
+
console.log(label('DragonScale'), 'Seeding database...\n');
|
|
171
|
+
const code = await run('php', ['scripts/seed-production.php'], path);
|
|
172
|
+
code === 0 ? success('Seeding complete') : error(`Seed failed (exit ${code})`);
|
|
173
|
+
});
|
|
174
|
+
// --- logs ---
|
|
175
|
+
scale
|
|
176
|
+
.command('logs')
|
|
177
|
+
.description('Tail application logs')
|
|
178
|
+
.option('-n, --lines <n>', 'Number of lines', '50')
|
|
179
|
+
.option('-f, --follow', 'Follow log output')
|
|
180
|
+
.action(async (opts) => {
|
|
181
|
+
const path = getProductPath(config, 'scale');
|
|
182
|
+
const logFile = join(path, 'logs/app.log');
|
|
183
|
+
if (!existsSync(logFile)) {
|
|
184
|
+
warn(`No log file at ${logFile}`);
|
|
185
|
+
return;
|
|
186
|
+
}
|
|
187
|
+
const args = [opts.follow ? '-f' : `-n${opts.lines}`, logFile];
|
|
188
|
+
await run('tail', args, path);
|
|
189
|
+
});
|
|
190
|
+
}
|
|
191
|
+
//# sourceMappingURL=scale.js.map
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon serve — Start the entire stack
|
|
3
|
+
*
|
|
4
|
+
* Spawns:
|
|
5
|
+
* - PhantomDragon Control API (Python uvicorn :4091)
|
|
6
|
+
* - PhantomDragon Control dashboard (Next.js :4090)
|
|
7
|
+
* - DragonNet API (Fastify :4080)
|
|
8
|
+
* - DragonNet dashboard (Next.js :4081)
|
|
9
|
+
*
|
|
10
|
+
* PID files at ~/.dragon/pids/ so `dragon down` can stop them cleanly.
|
|
11
|
+
* Foreground mode tails stdout; --detach forks to background.
|
|
12
|
+
*/
|
|
13
|
+
import type { Command } from 'commander';
|
|
14
|
+
import type { DragonConfig } from '../config.js';
|
|
15
|
+
export declare function registerServeCommands(program: Command, config: DragonConfig): void;
|
|
16
|
+
//# sourceMappingURL=serve.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"serve.d.ts","sourceRoot":"","sources":["../../src/commands/serve.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;GAWG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AA+FhD,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,QA6D3E"}
|
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon serve — Start the entire stack
|
|
3
|
+
*
|
|
4
|
+
* Spawns:
|
|
5
|
+
* - PhantomDragon Control API (Python uvicorn :4091)
|
|
6
|
+
* - PhantomDragon Control dashboard (Next.js :4090)
|
|
7
|
+
* - DragonNet API (Fastify :4080)
|
|
8
|
+
* - DragonNet dashboard (Next.js :4081)
|
|
9
|
+
*
|
|
10
|
+
* PID files at ~/.dragon/pids/ so `dragon down` can stop them cleanly.
|
|
11
|
+
* Foreground mode tails stdout; --detach forks to background.
|
|
12
|
+
*/
|
|
13
|
+
import { label, success, error, info, warn } from '../utils.js';
|
|
14
|
+
import chalk from 'chalk';
|
|
15
|
+
import { spawn } from 'child_process';
|
|
16
|
+
import { existsSync, mkdirSync, writeFileSync, readFileSync, readdirSync, unlinkSync } from 'fs';
|
|
17
|
+
import { homedir } from 'os';
|
|
18
|
+
import { join } from 'path';
|
|
19
|
+
const PIDS_DIR = join(homedir(), '.dragon', 'pids');
|
|
20
|
+
const LOGS_DIR = join(homedir(), '.dragon', 'logs');
|
|
21
|
+
function ensureDirs() {
|
|
22
|
+
mkdirSync(PIDS_DIR, { recursive: true });
|
|
23
|
+
mkdirSync(LOGS_DIR, { recursive: true });
|
|
24
|
+
}
|
|
25
|
+
function pidAlive(pid) {
|
|
26
|
+
try {
|
|
27
|
+
process.kill(pid, 0);
|
|
28
|
+
return true;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
return false;
|
|
32
|
+
}
|
|
33
|
+
}
|
|
34
|
+
function buildServices(config) {
|
|
35
|
+
const services = [];
|
|
36
|
+
const pentest = config.products.pentest.path;
|
|
37
|
+
if (pentest && existsSync(join(pentest, 'phantomdragon.py'))) {
|
|
38
|
+
services.push({
|
|
39
|
+
key: 'pd-control-api',
|
|
40
|
+
name: 'PhantomDragon Control API',
|
|
41
|
+
cwd: pentest,
|
|
42
|
+
cmd: 'python3',
|
|
43
|
+
args: ['-m', 'phantom_dragon_ai.control_api'],
|
|
44
|
+
port: config.products.pentest.controlPort,
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
// The Next.js dashboard lives in a sibling dir
|
|
48
|
+
const controlDash = pentest ? join(pentest, '..', 'phantomdragon-control') : '';
|
|
49
|
+
if (controlDash && existsSync(controlDash) && existsSync(join(controlDash, 'package.json'))) {
|
|
50
|
+
services.push({
|
|
51
|
+
key: 'pd-control-ui',
|
|
52
|
+
name: 'PhantomDragon Control UI',
|
|
53
|
+
cwd: controlDash,
|
|
54
|
+
cmd: 'npm',
|
|
55
|
+
args: ['run', 'dev'],
|
|
56
|
+
port: config.products.pentest.controlUiPort,
|
|
57
|
+
});
|
|
58
|
+
}
|
|
59
|
+
const net = config.products.net.path;
|
|
60
|
+
if (net && existsSync(join(net, 'pnpm-workspace.yaml'))) {
|
|
61
|
+
services.push({
|
|
62
|
+
key: 'dn-api',
|
|
63
|
+
name: 'DragonNet API',
|
|
64
|
+
cwd: net,
|
|
65
|
+
cmd: 'pnpm',
|
|
66
|
+
args: ['--filter', '@dragonnet/api', 'dev'],
|
|
67
|
+
port: config.products.net.apiPort,
|
|
68
|
+
});
|
|
69
|
+
services.push({
|
|
70
|
+
key: 'dn-ui',
|
|
71
|
+
name: 'DragonNet Dashboard',
|
|
72
|
+
cwd: net,
|
|
73
|
+
cmd: 'pnpm',
|
|
74
|
+
args: ['--filter', '@dragonnet/dashboard', 'dev'],
|
|
75
|
+
port: config.products.net.uiPort,
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
return services;
|
|
79
|
+
}
|
|
80
|
+
function startService(s) {
|
|
81
|
+
const logPath = join(LOGS_DIR, `${s.key}.log`);
|
|
82
|
+
const child = spawn(s.cmd, s.args, {
|
|
83
|
+
cwd: s.cwd,
|
|
84
|
+
detached: true,
|
|
85
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
86
|
+
env: { ...process.env, ...(s.env ?? {}) },
|
|
87
|
+
});
|
|
88
|
+
const { openSync, closeSync } = require('fs');
|
|
89
|
+
const fd = openSync(logPath, 'a');
|
|
90
|
+
child.stdout?.on('data', (chunk) => writeFileSync(logPath, chunk, { flag: 'a' }));
|
|
91
|
+
child.stderr?.on('data', (chunk) => writeFileSync(logPath, chunk, { flag: 'a' }));
|
|
92
|
+
child.unref();
|
|
93
|
+
closeSync(fd);
|
|
94
|
+
writeFileSync(join(PIDS_DIR, `${s.key}.pid`), String(child.pid));
|
|
95
|
+
return child.pid ?? 0;
|
|
96
|
+
}
|
|
97
|
+
export function registerServeCommands(program, config) {
|
|
98
|
+
program
|
|
99
|
+
.command('serve')
|
|
100
|
+
.description('Start the entire dragon stack (PhantomDragon Control + DragonNet)')
|
|
101
|
+
.option('--detach', 'Background mode (default: foreground)', false)
|
|
102
|
+
.action(async () => {
|
|
103
|
+
ensureDirs();
|
|
104
|
+
const services = buildServices(config);
|
|
105
|
+
if (services.length === 0) {
|
|
106
|
+
error('No products configured. Run: dragon init');
|
|
107
|
+
return;
|
|
108
|
+
}
|
|
109
|
+
console.log(label('Dragon'), 'Starting stack...\n');
|
|
110
|
+
for (const s of services) {
|
|
111
|
+
const pidFile = join(PIDS_DIR, `${s.key}.pid`);
|
|
112
|
+
if (existsSync(pidFile)) {
|
|
113
|
+
const oldPid = Number(readFileSync(pidFile, 'utf-8'));
|
|
114
|
+
if (pidAlive(oldPid)) {
|
|
115
|
+
info(`${s.name} already running (pid ${oldPid})`);
|
|
116
|
+
continue;
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
try {
|
|
120
|
+
const pid = startService(s);
|
|
121
|
+
success(`${s.name.padEnd(28)} ${chalk.dim(`pid ${pid}` + (s.port ? ` :${s.port}` : ''))}`);
|
|
122
|
+
}
|
|
123
|
+
catch (e) {
|
|
124
|
+
error(`${s.name} → ${e}`);
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
console.log();
|
|
128
|
+
info('Logs at ~/.dragon/logs/<service>.log');
|
|
129
|
+
info('Stop with: dragon down');
|
|
130
|
+
});
|
|
131
|
+
program
|
|
132
|
+
.command('down')
|
|
133
|
+
.description('Stop all dragon services')
|
|
134
|
+
.action(() => {
|
|
135
|
+
ensureDirs();
|
|
136
|
+
const files = readdirSync(PIDS_DIR).filter((f) => f.endsWith('.pid'));
|
|
137
|
+
if (files.length === 0) {
|
|
138
|
+
info('Nothing to stop.');
|
|
139
|
+
return;
|
|
140
|
+
}
|
|
141
|
+
for (const f of files) {
|
|
142
|
+
const pidPath = join(PIDS_DIR, f);
|
|
143
|
+
const pid = Number(readFileSync(pidPath, 'utf-8'));
|
|
144
|
+
const name = f.replace(/\.pid$/, '');
|
|
145
|
+
if (pidAlive(pid)) {
|
|
146
|
+
try {
|
|
147
|
+
process.kill(-pid, 'SIGTERM');
|
|
148
|
+
}
|
|
149
|
+
catch {
|
|
150
|
+
try {
|
|
151
|
+
process.kill(pid, 'SIGTERM');
|
|
152
|
+
}
|
|
153
|
+
catch { /* swallow */ }
|
|
154
|
+
}
|
|
155
|
+
success(`Stopped ${name} (pid ${pid})`);
|
|
156
|
+
}
|
|
157
|
+
else {
|
|
158
|
+
warn(`${name} not running`);
|
|
159
|
+
}
|
|
160
|
+
try {
|
|
161
|
+
unlinkSync(pidPath);
|
|
162
|
+
}
|
|
163
|
+
catch { /* swallow */ }
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
//# sourceMappingURL=serve.js.map
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon tui — the operator command-center. Full-screen, in the ghosts.lk visual
|
|
3
|
+
* language: near-black depth, a brushed-chrome phantom sigil + wordmark, two-column
|
|
4
|
+
* live panels (system · signal) with a traces-per-hour sparkline + disk gauge, and a
|
|
5
|
+
* pulsing emerald OPSEC bar.
|
|
6
|
+
*
|
|
7
|
+
* Rendered like a flagship TUI (see the advanced-tui-design + terminal-subcell-graphics
|
|
8
|
+
* skills): runs on the ALTERNATE screen buffer (htop-style, restores your scrollback),
|
|
9
|
+
* hides the cursor, and paints each frame inside SYNCHRONIZED OUTPUT (DEC ?2026) with
|
|
10
|
+
* per-line clear-to-EOL — so frames are atomic and flicker-free. `q` / Ctrl-C to quit.
|
|
11
|
+
*
|
|
12
|
+
* Copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
|
|
13
|
+
*/
|
|
14
|
+
import type { Command } from 'commander';
|
|
15
|
+
import { type DragonConfig } from '../config.js';
|
|
16
|
+
export declare function registerTuiCommands(program: Command, _config: DragonConfig): void;
|
|
17
|
+
//# sourceMappingURL=tui.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"tui.d.ts","sourceRoot":"","sources":["../../src/commands/tui.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AA2B5D,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,QAmF1E"}
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon tui — the operator command-center. Full-screen, in the ghosts.lk visual
|
|
3
|
+
* language: near-black depth, a brushed-chrome phantom sigil + wordmark, two-column
|
|
4
|
+
* live panels (system · signal) with a traces-per-hour sparkline + disk gauge, and a
|
|
5
|
+
* pulsing emerald OPSEC bar.
|
|
6
|
+
*
|
|
7
|
+
* Rendered like a flagship TUI (see the advanced-tui-design + terminal-subcell-graphics
|
|
8
|
+
* skills): runs on the ALTERNATE screen buffer (htop-style, restores your scrollback),
|
|
9
|
+
* hides the cursor, and paints each frame inside SYNCHRONIZED OUTPUT (DEC ?2026) with
|
|
10
|
+
* per-line clear-to-EOL — so frames are atomic and flicker-free. `q` / Ctrl-C to quit.
|
|
11
|
+
*
|
|
12
|
+
* Copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
|
|
13
|
+
*/
|
|
14
|
+
import { loadConfig } from '../config.js';
|
|
15
|
+
import { C } from '../utils.js';
|
|
16
|
+
import { panel, chrome, chromeRule, joinColumns, emerald, brailleChart, gauge } from '../ui.js';
|
|
17
|
+
import { resolveAuth } from '../auth.js';
|
|
18
|
+
import { resolveProvider } from '../brain/index.js';
|
|
19
|
+
import { loadSkillLibrary } from '../agent/skills.js';
|
|
20
|
+
import { existsSync, statfsSync, readFileSync } from 'node:fs';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
import { stdin, stdout } from 'node:process';
|
|
24
|
+
const SIGIL = [' ▄▄▄▄▄ ', ' ▟█████▙ ', ' █ ▘ ▘ █ ', ' ▜█████▛ ', ' ▘▘ ▘▘ '];
|
|
25
|
+
const SIGIL_W = 11;
|
|
26
|
+
const VER = '4.2.1';
|
|
27
|
+
const PULSE = ['·', '•', '●', '•']; // heartbeat
|
|
28
|
+
const dot = (on) => (on ? emerald('●') : C.faint('○'));
|
|
29
|
+
const indent = (s) => s.split('\n').map((l) => ' ' + l).join('\n');
|
|
30
|
+
// Terminal control. ?1049 = alt screen, ?25 = cursor, ?2026 = synchronized output.
|
|
31
|
+
const ENTER = '\x1b[?1049h\x1b[?25l';
|
|
32
|
+
const LEAVE = '\x1b[?25h\x1b[?1049l';
|
|
33
|
+
/** Atomic, flicker-free frame: sync-begin → home → per-line clear-to-EOL → clear-below → sync-end. */
|
|
34
|
+
function paint(content) {
|
|
35
|
+
const lines = content.split('\n');
|
|
36
|
+
stdout.write('\x1b[?2026h\x1b[H' + lines.map((l) => l + '\x1b[K').join('\n') + '\x1b[J\x1b[?2026l');
|
|
37
|
+
}
|
|
38
|
+
export function registerTuiCommands(program, _config) {
|
|
39
|
+
program
|
|
40
|
+
.command('tui')
|
|
41
|
+
.description('Live operator command-center (press q to quit)')
|
|
42
|
+
.action(async () => {
|
|
43
|
+
const skills = loadSkillLibrary().count;
|
|
44
|
+
const ollama = ['/usr/local/bin/ollama', '/usr/bin/ollama', join(homedir(), '.local/bin/ollama')].some(existsSync);
|
|
45
|
+
const wyrmReady = [process.env.WYRM_MCP_BIN, join(homedir(), '.npm-global/bin/wyrm-mcp')].filter(Boolean).some((p) => existsSync(p));
|
|
46
|
+
let frame = 0;
|
|
47
|
+
const draw = () => {
|
|
48
|
+
const cols = Math.max(64, Math.min(stdout.columns || 100, 124));
|
|
49
|
+
const inner = cols - 4;
|
|
50
|
+
const wide = cols >= 92;
|
|
51
|
+
const leftW = wide ? Math.min(44, Math.floor(inner / 2) - 2) : inner;
|
|
52
|
+
const rightW = wide ? inner - leftW - 3 : inner;
|
|
53
|
+
const actMax = (wide ? rightW : inner) - 8;
|
|
54
|
+
const pulse = emerald(PULSE[frame % PULSE.length]);
|
|
55
|
+
const cfg = loadConfig();
|
|
56
|
+
const a = resolveAuth();
|
|
57
|
+
const active = resolveProvider();
|
|
58
|
+
const ready = {
|
|
59
|
+
claude: !!(process.env.ANTHROPIC_API_KEY || cfg.brain?.keys?.anthropic),
|
|
60
|
+
worker: a.mode !== 'none', local: ollama,
|
|
61
|
+
openai: !!(process.env.OPENAI_API_KEY || cfg.brain?.keys?.openai),
|
|
62
|
+
custom: !!(process.env.DRAGON_OPENAI_BASE || cfg.brain?.customBaseURL), ghost: false,
|
|
63
|
+
};
|
|
64
|
+
let diskFrac = 0;
|
|
65
|
+
let diskFree = '—';
|
|
66
|
+
try {
|
|
67
|
+
const st = statfsSync(process.cwd());
|
|
68
|
+
const free = st.bavail * st.bsize;
|
|
69
|
+
const total = st.blocks * st.bsize;
|
|
70
|
+
diskFrac = total ? 1 - free / total : 0;
|
|
71
|
+
diskFree = `${(free / 1e9).toFixed(0)}G free`;
|
|
72
|
+
}
|
|
73
|
+
catch { /* skip */ }
|
|
74
|
+
let traces = 0;
|
|
75
|
+
let recent = [];
|
|
76
|
+
const hours = new Array(24).fill(0);
|
|
77
|
+
try {
|
|
78
|
+
const lines = readFileSync(join(homedir(), '.dragon', 'traces', `${new Date().toISOString().slice(0, 10)}.jsonl`), 'utf-8').trim().split('\n').filter(Boolean);
|
|
79
|
+
traces = lines.length;
|
|
80
|
+
for (const l of lines) {
|
|
81
|
+
try {
|
|
82
|
+
const t = JSON.parse(l);
|
|
83
|
+
hours[new Date(t.ts).getHours()]++;
|
|
84
|
+
}
|
|
85
|
+
catch { /* skip */ }
|
|
86
|
+
}
|
|
87
|
+
recent = lines.slice(-3).map((l) => { try {
|
|
88
|
+
return (JSON.parse(l).prompt || '').replace(/\s+/g, ' ').trim();
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
return '';
|
|
92
|
+
} }).filter(Boolean);
|
|
93
|
+
}
|
|
94
|
+
catch { /* none today */ }
|
|
95
|
+
// ── hero ──
|
|
96
|
+
const hero = joinColumns(SIGIL.map((l) => chrome(l)).join('\n'), ['', chrome('G H O S T P R O T O C O L'), `${C.faint('▌')} ${emerald('OPERATOR CONSOLE')} ${C.faint('//')} ${C.info('DRAGON v' + VER)}`, C.faint('on-host coding + ops agent · sovereign-capable · ghosts.lk'), ''].join('\n'), SIGIL_W);
|
|
97
|
+
// ── panels ──
|
|
98
|
+
const sysLines = [
|
|
99
|
+
`${C.faint('brain ')} ${dot(ready[active] ?? false)} ${C.info(active)}`,
|
|
100
|
+
`${C.faint('auth ')} ${dot(a.mode !== 'none')} ${a.mode === 'none' ? C.faint('signed out') : C.info(a.email || a.mode)}`,
|
|
101
|
+
`${C.faint('memory')} ${dot(wyrmReady)} ${wyrmReady ? C.info('Wyrm') : C.faint('—')} ${C.faint('ollama')} ${dot(ollama)}`,
|
|
102
|
+
`${C.faint('skills')} ${C.info(String(skills))}`,
|
|
103
|
+
`${C.faint('disk ')} ${gauge(diskFrac, 12)} ${C.faint(diskFree)}`,
|
|
104
|
+
];
|
|
105
|
+
const chartW = Math.max(16, Math.min(actMax, 40));
|
|
106
|
+
const peak = Math.max(...hours, 1);
|
|
107
|
+
const actLines = [
|
|
108
|
+
...(recent.length ? recent.map((r) => `${C.faint('›')} ${C.info(r.slice(0, actMax))}`) : [C.faint('no agent activity today')]),
|
|
109
|
+
'',
|
|
110
|
+
`${C.faint('24h traces')} ${C.faint('· peak')} ${emerald(String(peak))}`,
|
|
111
|
+
...brailleChart(hours, { width: chartW, height: 3, area: true }),
|
|
112
|
+
`${pulse} ${C.faint(`listening · ${traces} traces today`)}`,
|
|
113
|
+
];
|
|
114
|
+
const body = wide
|
|
115
|
+
? joinColumns(panel(sysLines, { title: chrome('SYSTEM'), width: leftW }), panel(actLines, { title: chrome('SIGNAL'), width: rightW }), leftW)
|
|
116
|
+
: panel(sysLines, { title: chrome('SYSTEM'), width: inner }) + '\n' + panel(actLines, { title: chrome('SIGNAL'), width: inner });
|
|
117
|
+
const bar = ` ${pulse} ${C.faint('SYS')} ${emerald('OK')} ${C.faint('· brain')} ${C.info(active)} ${C.faint('· skills')} ${C.info(String(skills))} ${C.faint('·')} ${C.info(new Date().toLocaleTimeString())} ${C.faint('· q quit')}`;
|
|
118
|
+
paint('\n' + indent(hero) + '\n ' + chromeRule(inner) + '\n\n' + indent(body) + '\n\n' + bar);
|
|
119
|
+
};
|
|
120
|
+
stdout.write(ENTER);
|
|
121
|
+
draw();
|
|
122
|
+
const iv = setInterval(() => { frame++; draw(); }, 1000);
|
|
123
|
+
const quit = () => { clearInterval(iv); if (stdin.isTTY) {
|
|
124
|
+
try {
|
|
125
|
+
stdin.setRawMode(false);
|
|
126
|
+
}
|
|
127
|
+
catch { /* ignore */ }
|
|
128
|
+
} stdout.write(LEAVE); process.exit(0); };
|
|
129
|
+
process.on('SIGINT', quit);
|
|
130
|
+
if (stdin.isTTY) {
|
|
131
|
+
stdin.setRawMode(true);
|
|
132
|
+
stdin.resume();
|
|
133
|
+
stdin.on('data', (d) => { const s = d.toString(); if (s === 'q' || s === 'Q' || d[0] === 3)
|
|
134
|
+
quit(); });
|
|
135
|
+
}
|
|
136
|
+
});
|
|
137
|
+
}
|
|
138
|
+
//# sourceMappingURL=tui.js.map
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon wyrm — Wyrm AI Memory System bridge.
|
|
3
|
+
*
|
|
4
|
+
* dragon wyrm status — health + stats from the running HTTP API
|
|
5
|
+
* dragon wyrm serve — start the Wyrm HTTP API on :3333
|
|
6
|
+
* dragon wyrm mcp — start the Wyrm MCP server over stdio
|
|
7
|
+
* dragon wyrm projects — list registered projects
|
|
8
|
+
* dragon wyrm scan <path> — discover git projects under <path>
|
|
9
|
+
* dragon wyrm search <q> — search across all memory
|
|
10
|
+
* dragon wyrm quests — list active quests
|
|
11
|
+
* dragon wyrm maintenance — vacuum + archive the database
|
|
12
|
+
*
|
|
13
|
+
* Endpoints + binary paths match Wyrm v5.x (packages/mcp-server). The
|
|
14
|
+
* earlier shape (`/api/health`, `npx wyrm`, `wyrm-deploy`) was written
|
|
15
|
+
* against a v3-era layout that no longer exists.
|
|
16
|
+
*/
|
|
17
|
+
import type { Command } from 'commander';
|
|
18
|
+
import type { DragonConfig } from '../config.js';
|
|
19
|
+
export declare function registerWyrmCommands(program: Command, config: DragonConfig): void;
|
|
20
|
+
//# sourceMappingURL=wyrm.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"wyrm.d.ts","sourceRoot":"","sources":["../../src/commands/wyrm.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;GAeG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,KAAK,EAAE,YAAY,EAAE,MAAM,cAAc,CAAA;AA2DhD,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,MAAM,EAAE,YAAY,QA+L1E"}
|