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,22 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon chat / dragon ask — the Dragon agent: a Claude-Code-style terminal
|
|
3
|
+
* coding agent that runs tools locally, reasons with a pluggable brain (Claude
|
|
4
|
+
* by default), and is wired into Wyrm long-term memory by default.
|
|
5
|
+
*
|
|
6
|
+
* dragon chat interactive agent — edits files, runs commands,
|
|
7
|
+
* remembers across sessions via Wyrm
|
|
8
|
+
* dragon ask "…" one-shot (read-only unless --auto); pipeable
|
|
9
|
+
*
|
|
10
|
+
* --brain claude|openai|local reasoning model (default: config/claude)
|
|
11
|
+
* --model <id> override the model
|
|
12
|
+
* --no-wyrm disable Wyrm memory for this run
|
|
13
|
+
* --no-portal don't expose the hosted account assistant tool
|
|
14
|
+
* --auto auto-approve file writes + shell (use with care)
|
|
15
|
+
* --cwd <dir> run the agent against another directory
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
|
|
18
|
+
*/
|
|
19
|
+
import type { Command } from 'commander';
|
|
20
|
+
import { type DragonConfig } from '../config.js';
|
|
21
|
+
export declare function registerChatCommands(program: Command, _config: DragonConfig): void;
|
|
22
|
+
//# sourceMappingURL=chat.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"chat.d.ts","sourceRoot":"","sources":["../../src/commands/chat.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;;;;GAiBG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAsKxE,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,QAmI3E"}
|
|
@@ -0,0 +1,422 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* dragon chat / dragon ask — the Dragon agent: a Claude-Code-style terminal
|
|
3
|
+
* coding agent that runs tools locally, reasons with a pluggable brain (Claude
|
|
4
|
+
* by default), and is wired into Wyrm long-term memory by default.
|
|
5
|
+
*
|
|
6
|
+
* dragon chat interactive agent — edits files, runs commands,
|
|
7
|
+
* remembers across sessions via Wyrm
|
|
8
|
+
* dragon ask "…" one-shot (read-only unless --auto); pipeable
|
|
9
|
+
*
|
|
10
|
+
* --brain claude|openai|local reasoning model (default: config/claude)
|
|
11
|
+
* --model <id> override the model
|
|
12
|
+
* --no-wyrm disable Wyrm memory for this run
|
|
13
|
+
* --no-portal don't expose the hosted account assistant tool
|
|
14
|
+
* --auto auto-approve file writes + shell (use with care)
|
|
15
|
+
* --cwd <dir> run the agent against another directory
|
|
16
|
+
*
|
|
17
|
+
* Copyright 2026 Ghost Protocol (Pvt) Ltd. All Rights Reserved.
|
|
18
|
+
*/
|
|
19
|
+
import { loadConfig, saveConfig } from '../config.js';
|
|
20
|
+
import { C, error, info, success } from '../utils.js';
|
|
21
|
+
import { saveSession, loadLastSession, exportMarkdown } from '../agent/session.js';
|
|
22
|
+
import { panel, chrome, kv, statusDot, tidyPath } from '../ui.js';
|
|
23
|
+
import { getBrain, BrainConfigError } from '../brain/index.js';
|
|
24
|
+
import { Wyrm } from '../wyrm/mcp.js';
|
|
25
|
+
import { BWRAP } from '../agent/tools.js';
|
|
26
|
+
import { runAgent, buildSystemPrompt, buildToolSpecs } from '../agent/loop.js';
|
|
27
|
+
import { recordTurn, traceEnabled } from '../agent/trace.js';
|
|
28
|
+
import { loadSkillLibrary } from '../agent/skills.js';
|
|
29
|
+
import { makeStackTools } from '../agent/stack.js';
|
|
30
|
+
import { loadMcpHub } from '../agent/mcp.js';
|
|
31
|
+
import { makeTaskTool } from '../agent/task.js';
|
|
32
|
+
import { resolveAuth } from '../auth.js';
|
|
33
|
+
import { streamAgent, AgentError, SURFACES } from '../agent.js';
|
|
34
|
+
import { readFileSync, existsSync } from 'node:fs';
|
|
35
|
+
import { createInterface } from 'node:readline/promises';
|
|
36
|
+
import { stdin, stdout } from 'node:process';
|
|
37
|
+
function expandFileRefs(text) {
|
|
38
|
+
return text.replace(/@(\S+)/g, (match, path) => {
|
|
39
|
+
if (path === '-' || !existsSync(path))
|
|
40
|
+
return match;
|
|
41
|
+
try {
|
|
42
|
+
return `\n\n--- ${path} ---\n${readFileSync(path, 'utf-8')}\n--- end ${path} ---\n\n`;
|
|
43
|
+
}
|
|
44
|
+
catch {
|
|
45
|
+
return match;
|
|
46
|
+
}
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
async function readStdinAll() {
|
|
50
|
+
const chunks = [];
|
|
51
|
+
for await (const c of stdin)
|
|
52
|
+
chunks.push(c);
|
|
53
|
+
return Buffer.concat(chunks).toString('utf-8');
|
|
54
|
+
}
|
|
55
|
+
/** The hosted account.ghosts.lk assistant, exposed to the agent as one tool. */
|
|
56
|
+
function makePortalTool() {
|
|
57
|
+
if (resolveAuth().mode === 'none')
|
|
58
|
+
return null;
|
|
59
|
+
const spec = {
|
|
60
|
+
name: 'portal_ask',
|
|
61
|
+
description: "Ask the Ghost Protocol account portal assistant about the operator's licenses, product catalog, security services/pricing, or to book an engagement. Account/business questions only — not coding.",
|
|
62
|
+
parameters: { type: 'object', properties: { question: { type: 'string' }, surface: { type: 'string', enum: [...SURFACES] } }, required: ['question'] },
|
|
63
|
+
};
|
|
64
|
+
return {
|
|
65
|
+
spec,
|
|
66
|
+
async call(args) {
|
|
67
|
+
const surface = SURFACES.includes(String(args.surface)) ? String(args.surface) : 'dashboard';
|
|
68
|
+
try {
|
|
69
|
+
return await streamAgent({ messages: [{ role: 'user', content: String(args.question ?? '') }], surface, onDelta: () => { } });
|
|
70
|
+
}
|
|
71
|
+
catch (e) {
|
|
72
|
+
return e instanceof AgentError ? `portal error: ${e.message}` : `portal error: ${String(e instanceof Error ? e.message : e)}`;
|
|
73
|
+
}
|
|
74
|
+
},
|
|
75
|
+
};
|
|
76
|
+
}
|
|
77
|
+
function hasKey(p) {
|
|
78
|
+
const cfg = loadConfig();
|
|
79
|
+
return p === 'anthropic' ? !!(process.env.ANTHROPIC_API_KEY || cfg.brain?.keys?.anthropic) : !!(process.env.OPENAI_API_KEY || cfg.brain?.keys?.openai);
|
|
80
|
+
}
|
|
81
|
+
/** First-launch model picker (interactive). Persists the choice to config. */
|
|
82
|
+
async function runPicker() {
|
|
83
|
+
const signedIn = resolveAuth().mode !== 'none';
|
|
84
|
+
const ready = (ok, need) => (ok ? C.accent('● ready') : C.faint('○ ' + need));
|
|
85
|
+
console.log();
|
|
86
|
+
console.log(` ${chrome("CHOOSE DRAGON'S BRAIN")}`);
|
|
87
|
+
console.log(` ${C.faint('Tools always run locally — this only picks the reasoning model.')}`);
|
|
88
|
+
console.log();
|
|
89
|
+
console.log(` ${C.accent('1')} Cloudflare worker ${C.faint('· free · our infra')} ${ready(signedIn, 'needs `dragon login`')}`);
|
|
90
|
+
console.log(` ${C.accent('2')} Claude ${C.faint('· best for code')} ${ready(hasKey('anthropic'), 'needs ANTHROPIC_API_KEY')}`);
|
|
91
|
+
console.log(` ${C.accent('3')} Local Ollama ${C.faint('· private · free')}`);
|
|
92
|
+
console.log(` ${C.accent('4')} OpenAI ${C.faint('· hosted')} ${ready(hasKey('openai'), 'needs OPENAI_API_KEY')}`);
|
|
93
|
+
console.log();
|
|
94
|
+
const rl = createInterface({ input: stdin, output: stdout });
|
|
95
|
+
let ans = '';
|
|
96
|
+
try {
|
|
97
|
+
ans = (await rl.question(` ${C.accent('▸')} pick ${C.faint('[1-4, default 1]')} `)).trim();
|
|
98
|
+
}
|
|
99
|
+
finally {
|
|
100
|
+
rl.close();
|
|
101
|
+
}
|
|
102
|
+
const provider = { '1': 'worker', '2': 'claude', '3': 'local', '4': 'openai' }[ans] ?? 'worker';
|
|
103
|
+
const cfg = loadConfig();
|
|
104
|
+
cfg.brain = { ...(cfg.brain ?? {}), provider };
|
|
105
|
+
saveConfig(cfg);
|
|
106
|
+
info(`brain → ${C.accent(provider)}. Change anytime with ${C.info('dragon config brain <name>')}.`);
|
|
107
|
+
}
|
|
108
|
+
/** Resolve the brain: explicit --brain, else configured (with a first-launch picker),
|
|
109
|
+
* else gracefully fall back to the free Cloudflare worker / local Ollama. */
|
|
110
|
+
const HOSTED = ['claude', 'worker', 'openai'];
|
|
111
|
+
async function chooseBrain(opts) {
|
|
112
|
+
const tryGet = (provider) => {
|
|
113
|
+
try {
|
|
114
|
+
return getBrain({ provider, model: opts.model });
|
|
115
|
+
}
|
|
116
|
+
catch (e) {
|
|
117
|
+
error(e instanceof BrainConfigError ? e.message : String(e instanceof Error ? e.message : e));
|
|
118
|
+
return null;
|
|
119
|
+
}
|
|
120
|
+
};
|
|
121
|
+
if (opts.sovereign) {
|
|
122
|
+
// local brain only — nothing leaves the host
|
|
123
|
+
if (opts.brain && HOSTED.includes(opts.brain))
|
|
124
|
+
info(`sovereign mode — ignoring hosted brain '${opts.brain}', using local.`);
|
|
125
|
+
return tryGet(opts.brain && !HOSTED.includes(opts.brain) ? opts.brain : 'local');
|
|
126
|
+
}
|
|
127
|
+
if (opts.brain)
|
|
128
|
+
return tryGet(opts.brain);
|
|
129
|
+
if (!loadConfig().brain?.provider && stdin.isTTY)
|
|
130
|
+
await runPicker();
|
|
131
|
+
try {
|
|
132
|
+
return getBrain({ model: opts.model });
|
|
133
|
+
}
|
|
134
|
+
catch (e) {
|
|
135
|
+
if (!(e instanceof BrainConfigError)) {
|
|
136
|
+
error(String(e instanceof Error ? e.message : e));
|
|
137
|
+
return null;
|
|
138
|
+
}
|
|
139
|
+
if (resolveAuth().mode !== 'none') {
|
|
140
|
+
info(`${e.provider} brain unavailable — using the free Cloudflare brain.`);
|
|
141
|
+
return getBrain({ provider: 'worker' });
|
|
142
|
+
}
|
|
143
|
+
info(`${e.provider} brain unavailable — using local Ollama. (Run \`dragon login\` for the free Cloudflare brain.)`);
|
|
144
|
+
return tryGet('local');
|
|
145
|
+
}
|
|
146
|
+
}
|
|
147
|
+
/** Build brain + wyrm + portal + system prompt. Returns null after printing on a brain config error. */
|
|
148
|
+
async function setup(opts, announce) {
|
|
149
|
+
const brain = await chooseBrain(opts);
|
|
150
|
+
if (!brain)
|
|
151
|
+
return null;
|
|
152
|
+
const cwd = opts.cwd ? (opts.cwd.startsWith('/') ? opts.cwd : `${process.cwd()}/${opts.cwd}`) : process.cwd();
|
|
153
|
+
let wyrm = null;
|
|
154
|
+
let primed = null;
|
|
155
|
+
if (opts.wyrm !== false) {
|
|
156
|
+
wyrm = new Wyrm();
|
|
157
|
+
const ok = await wyrm.connect();
|
|
158
|
+
if (ok)
|
|
159
|
+
primed = await wyrm.prime(cwd);
|
|
160
|
+
else {
|
|
161
|
+
if (announce)
|
|
162
|
+
info('Wyrm memory unavailable — continuing without it.');
|
|
163
|
+
wyrm = null;
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
const portal = (opts.portal === false || opts.sovereign) ? null : makePortalTool();
|
|
167
|
+
const skills = loadSkillLibrary();
|
|
168
|
+
const stack = makeStackTools();
|
|
169
|
+
const mcp = await loadMcpHub(loadConfig().mcpServers);
|
|
170
|
+
if (mcp && announce)
|
|
171
|
+
info(`MCP: ${mcp.serverCount} server(s) connected (${mcp.toolSpecs().length} tools).`);
|
|
172
|
+
const task = makeTaskTool({ brain, skills, cwd });
|
|
173
|
+
const system = buildSystemPrompt({ cwd, wyrm: !!wyrm, portal: !!portal, brainId: `${brain.id}:${brain.model}`, skills: skills.count || undefined, primed });
|
|
174
|
+
return { brain, wyrm, portal, skills, stack, mcp, task, cwd, system, primed };
|
|
175
|
+
}
|
|
176
|
+
function banner(s, tools) {
|
|
177
|
+
const lines = [
|
|
178
|
+
kv('brain', C.info(`${s.brain.id}:${s.brain.model}`)),
|
|
179
|
+
kv('cwd', C.info(tidyPath(s.cwd))),
|
|
180
|
+
kv('state', `memory ${statusDot(!!s.wyrm)} ${s.wyrm ? C.info('wyrm') : C.faint('off')} portal ${statusDot(!!s.portal)} skills ${C.info(String(s.skills?.count ?? 0))} tools ${C.info(String(tools.length))}`),
|
|
181
|
+
];
|
|
182
|
+
console.log();
|
|
183
|
+
console.log(panel(lines, { title: chrome('DRAGON AGENT') }));
|
|
184
|
+
console.log(` ${C.faint('/exit · /reset · /brain · /tools · /memory <q> · /auto · /plan · /save · @<file>')}`);
|
|
185
|
+
}
|
|
186
|
+
function makeRender() {
|
|
187
|
+
return {
|
|
188
|
+
onAssistantStart() { process.stdout.write(`\n ${C.hot('◆')} `); },
|
|
189
|
+
onDelta(s) { process.stdout.write(s); },
|
|
190
|
+
onToolStart(summary) { process.stdout.write(`\n ${C.faint('⚙ ' + summary)}`); },
|
|
191
|
+
onToolEnd(_summary, preview, ok) { process.stdout.write(` ${ok ? C.accent('✓') : C.critical('✗')} ${C.faint(preview)}\n`); },
|
|
192
|
+
};
|
|
193
|
+
}
|
|
194
|
+
function makeApprove(rl, state) {
|
|
195
|
+
return async (summary, opts) => {
|
|
196
|
+
if (state.plan) {
|
|
197
|
+
console.log(` ${C.faint('⊘ plan mode — declined: ' + summary)}`);
|
|
198
|
+
return false;
|
|
199
|
+
} // read-only
|
|
200
|
+
const dangerous = !!opts?.dangerous;
|
|
201
|
+
if (state.auto && !dangerous)
|
|
202
|
+
return true; // --auto covers safe in-cwd edits only — NEVER bash / outside-cwd
|
|
203
|
+
if (opts?.detail)
|
|
204
|
+
console.log('\n' + C.faint(opts.detail.split('\n').map((l) => ' ' + l).join('\n')));
|
|
205
|
+
const tag = dangerous ? C.critical('?') : C.accent('?');
|
|
206
|
+
const hint = dangerous ? (state.auto ? '[y/N — auto-approve does NOT cover this]' : '[y/N]') : '[y/N/a=always-safe]';
|
|
207
|
+
const ans = (await rl.question(`\n ${tag} ${summary} ${C.faint(hint)} `)).trim().toLowerCase();
|
|
208
|
+
if (ans === 'a' && !dangerous) {
|
|
209
|
+
state.auto = true;
|
|
210
|
+
return true;
|
|
211
|
+
}
|
|
212
|
+
return ans === 'y' || ans === 'yes';
|
|
213
|
+
};
|
|
214
|
+
}
|
|
215
|
+
export function registerChatCommands(program, _config) {
|
|
216
|
+
program
|
|
217
|
+
.command('chat')
|
|
218
|
+
.description('Dragon agent — codes, runs tools, remembers via Wyrm (interactive)')
|
|
219
|
+
.option('--brain <provider>', 'reasoning brain: claude | worker | local | openai | ghost')
|
|
220
|
+
.option('--model <id>', 'override the model id')
|
|
221
|
+
.option('--no-wyrm', 'disable Wyrm long-term memory for this session')
|
|
222
|
+
.option('--no-portal', "don't expose the hosted account assistant as a tool")
|
|
223
|
+
.option('--auto', 'auto-approve file writes + shell commands', false)
|
|
224
|
+
.option('--plan', 'read-only: the agent explores but makes no writes/shell', false)
|
|
225
|
+
.option('--sovereign', 'local brain + Wyrm only — nothing leaves the host', false)
|
|
226
|
+
.option('--sandbox', 'run shell in a bwrap jail (cwd writable, rest read-only)', false)
|
|
227
|
+
.option('--no-trace', 'do not record tool-use traces for DragonSpark training')
|
|
228
|
+
.option('--resume', 'resume the previous session', false)
|
|
229
|
+
.option('--cwd <dir>', 'run against a different working directory')
|
|
230
|
+
.action(async (opts) => {
|
|
231
|
+
const s = await setup(opts, true);
|
|
232
|
+
if (!s) {
|
|
233
|
+
process.exitCode = 1;
|
|
234
|
+
return;
|
|
235
|
+
}
|
|
236
|
+
const rl = createInterface({ input: stdin, output: stdout, terminal: stdin.isTTY === true });
|
|
237
|
+
const state = { auto: !!opts.auto, plan: !!opts.plan };
|
|
238
|
+
const toolCtx = { cwd: s.cwd, approve: makeApprove(rl, state), sandbox: !!opts.sandbox };
|
|
239
|
+
const messages = [];
|
|
240
|
+
if (opts.resume) {
|
|
241
|
+
const prior = loadLastSession();
|
|
242
|
+
if (prior) {
|
|
243
|
+
messages.push(...prior);
|
|
244
|
+
info(`resumed ${prior.length} messages from your last session`);
|
|
245
|
+
}
|
|
246
|
+
else
|
|
247
|
+
info('no previous session to resume');
|
|
248
|
+
}
|
|
249
|
+
const deps = { ...s, toolCtx, messages };
|
|
250
|
+
const render = makeRender();
|
|
251
|
+
banner(s, buildToolSpecs(s));
|
|
252
|
+
if (state.plan)
|
|
253
|
+
info('plan mode ON — read-only: the agent explores but makes no writes/shell (/plan to toggle).');
|
|
254
|
+
else if (state.auto)
|
|
255
|
+
info('auto-approve ON — safe in-cwd writes run without asking (bash still prompts).');
|
|
256
|
+
if (opts.sovereign)
|
|
257
|
+
info('sovereign mode — local brain + Wyrm only; nothing leaves the host.');
|
|
258
|
+
if (opts.sandbox)
|
|
259
|
+
info(BWRAP ? 'sandbox ON — shell runs in bwrap (cwd writable, secrets masked, network shared).' : 'sandbox requested but bwrap not found — shell runs unsandboxed (approval still applies).');
|
|
260
|
+
if (traceEnabled(opts.trace === false))
|
|
261
|
+
info('flywheel ON — turns logged to ~/.dragon/traces for DragonSpark (--no-trace to disable).');
|
|
262
|
+
let aborter = null;
|
|
263
|
+
const onSigint = () => { if (aborter) {
|
|
264
|
+
aborter.abort();
|
|
265
|
+
aborter = null;
|
|
266
|
+
}
|
|
267
|
+
else {
|
|
268
|
+
void cleanup().then(() => process.exit(0));
|
|
269
|
+
} };
|
|
270
|
+
const cleanup = async () => { process.off('SIGINT', onSigint); rl.close(); saveSession(messages, { cwd: s.cwd, brain: `${s.brain.id}:${s.brain.model}` }); await s.wyrm?.close(); await s.mcp?.close(); };
|
|
271
|
+
process.on('SIGINT', onSigint);
|
|
272
|
+
for (;;) {
|
|
273
|
+
let userIn;
|
|
274
|
+
try {
|
|
275
|
+
userIn = (await rl.question(`${C.accent('▸')} `)).trim();
|
|
276
|
+
}
|
|
277
|
+
catch {
|
|
278
|
+
break;
|
|
279
|
+
}
|
|
280
|
+
if (!userIn)
|
|
281
|
+
continue;
|
|
282
|
+
if (userIn === '/exit' || userIn === '/quit')
|
|
283
|
+
break;
|
|
284
|
+
if (userIn === '/reset') {
|
|
285
|
+
messages.length = 0;
|
|
286
|
+
state.auto = false;
|
|
287
|
+
info('conversation reset · auto-approve re-armed');
|
|
288
|
+
continue;
|
|
289
|
+
}
|
|
290
|
+
if (userIn === '/auto') {
|
|
291
|
+
state.auto = !state.auto;
|
|
292
|
+
if (state.auto)
|
|
293
|
+
state.plan = false;
|
|
294
|
+
info(`auto-approve ${state.auto ? 'ON' : 'OFF'}`);
|
|
295
|
+
continue;
|
|
296
|
+
}
|
|
297
|
+
if (userIn === '/plan') {
|
|
298
|
+
state.plan = !state.plan;
|
|
299
|
+
if (state.plan)
|
|
300
|
+
state.auto = false;
|
|
301
|
+
info(`plan mode ${state.plan ? 'ON — read-only' : 'OFF'}`);
|
|
302
|
+
continue;
|
|
303
|
+
}
|
|
304
|
+
if (userIn === '/brain') {
|
|
305
|
+
console.log(` ${C.faint('brain:')} ${C.info(s.brain.id + ':' + s.brain.model)}`);
|
|
306
|
+
continue;
|
|
307
|
+
}
|
|
308
|
+
if (userIn === '/tools') {
|
|
309
|
+
console.log(' ' + buildToolSpecs(s).map((t) => t.name).join(' '));
|
|
310
|
+
continue;
|
|
311
|
+
}
|
|
312
|
+
if (userIn === '/clear') {
|
|
313
|
+
console.clear();
|
|
314
|
+
banner(s, buildToolSpecs(s));
|
|
315
|
+
continue;
|
|
316
|
+
}
|
|
317
|
+
if (userIn.startsWith('/save')) {
|
|
318
|
+
success(`transcript → ${exportMarkdown(messages, userIn.slice(5).trim() || undefined)}`);
|
|
319
|
+
continue;
|
|
320
|
+
}
|
|
321
|
+
if (userIn.startsWith('/memory')) {
|
|
322
|
+
const q = userIn.slice(7).trim();
|
|
323
|
+
if (!s.wyrm) {
|
|
324
|
+
error('Wyrm is off this session');
|
|
325
|
+
continue;
|
|
326
|
+
}
|
|
327
|
+
if (!q) {
|
|
328
|
+
info('usage: /memory <query>');
|
|
329
|
+
continue;
|
|
330
|
+
}
|
|
331
|
+
console.log(C.faint(await s.wyrm.call('wyrm_recall', { query: q })));
|
|
332
|
+
continue;
|
|
333
|
+
}
|
|
334
|
+
if (userIn.startsWith('/')) {
|
|
335
|
+
error(`unknown command ${userIn}`);
|
|
336
|
+
continue;
|
|
337
|
+
}
|
|
338
|
+
aborter = new AbortController();
|
|
339
|
+
const before = messages.length;
|
|
340
|
+
try {
|
|
341
|
+
await runAgent(deps, expandFileRefs(userIn), render, aborter.signal);
|
|
342
|
+
process.stdout.write('\n\n');
|
|
343
|
+
if (traceEnabled(opts.trace === false))
|
|
344
|
+
recordTurn(messages.slice(before), { cwd: s.cwd, brain: `${s.brain.id}:${s.brain.model}`, context: s.primed });
|
|
345
|
+
}
|
|
346
|
+
catch (e) {
|
|
347
|
+
aborter = null;
|
|
348
|
+
if (e?.name === 'AbortError') {
|
|
349
|
+
process.stdout.write(`\n ${C.faint('⊘ interrupted')}\n\n`);
|
|
350
|
+
continue;
|
|
351
|
+
}
|
|
352
|
+
process.stdout.write('\n');
|
|
353
|
+
if (e instanceof BrainConfigError)
|
|
354
|
+
error(e.message);
|
|
355
|
+
else
|
|
356
|
+
error(String(e instanceof Error ? e.message : e));
|
|
357
|
+
}
|
|
358
|
+
aborter = null;
|
|
359
|
+
}
|
|
360
|
+
await cleanup();
|
|
361
|
+
});
|
|
362
|
+
program
|
|
363
|
+
.command('ask [prompt...]')
|
|
364
|
+
.description('One-shot Dragon agent question (read-only unless --auto; pipeable)')
|
|
365
|
+
.option('--brain <provider>', 'reasoning brain: claude | worker | local | openai | ghost')
|
|
366
|
+
.option('--model <id>', 'override the model id')
|
|
367
|
+
.option('--no-wyrm', 'disable Wyrm memory')
|
|
368
|
+
.option('--no-portal', "don't expose the hosted account assistant")
|
|
369
|
+
.option('--auto', 'allow file writes + shell (off = read-only)', false)
|
|
370
|
+
.option('--plan', 'read-only (deny all writes/shell)', false)
|
|
371
|
+
.option('--sovereign', 'local brain + Wyrm only — nothing leaves the host', false)
|
|
372
|
+
.option('--sandbox', 'run shell in a bwrap jail', false)
|
|
373
|
+
.option('--no-trace', 'do not record a trace for DragonSpark training')
|
|
374
|
+
.option('--cwd <dir>', 'working directory')
|
|
375
|
+
.action(async (parts = [], opts) => {
|
|
376
|
+
let prompt = parts.join(' ').trim();
|
|
377
|
+
if (prompt === '-' || (!prompt && !stdin.isTTY))
|
|
378
|
+
prompt = (await readStdinAll()).trim();
|
|
379
|
+
if (!prompt) {
|
|
380
|
+
error('no prompt. Pass args, pipe stdin, or use "-".');
|
|
381
|
+
process.exit(1);
|
|
382
|
+
}
|
|
383
|
+
const s = await setup(opts, false);
|
|
384
|
+
if (!s) {
|
|
385
|
+
process.exit(1);
|
|
386
|
+
}
|
|
387
|
+
const denied = [];
|
|
388
|
+
const toolCtx = {
|
|
389
|
+
cwd: s.cwd,
|
|
390
|
+
sandbox: !!opts.sandbox,
|
|
391
|
+
approve: async (summary, o) => { if (opts.auto && !opts.plan && !o?.dangerous)
|
|
392
|
+
return true; denied.push(summary); return false; },
|
|
393
|
+
};
|
|
394
|
+
const messages = [];
|
|
395
|
+
const deps = { ...s, toolCtx, messages };
|
|
396
|
+
const render = {
|
|
397
|
+
onAssistantStart() { },
|
|
398
|
+
onDelta(t) { process.stdout.write(t); },
|
|
399
|
+
onToolStart(summary) { process.stderr.write(C.faint(`⚙ ${summary}\n`)); },
|
|
400
|
+
onToolEnd(_s, preview, ok) { process.stderr.write(C.faint(` ${ok ? '✓' : '✗'} ${preview}\n`)); },
|
|
401
|
+
};
|
|
402
|
+
try {
|
|
403
|
+
await runAgent(deps, expandFileRefs(prompt), render, new AbortController().signal);
|
|
404
|
+
process.stdout.write('\n');
|
|
405
|
+
if (traceEnabled(opts.trace === false))
|
|
406
|
+
recordTurn(messages, { cwd: s.cwd, brain: `${s.brain.id}:${s.brain.model}`, context: s.primed });
|
|
407
|
+
if (denied.length)
|
|
408
|
+
process.stderr.write(C.faint(`\n(${denied.length} mutating action(s) skipped — re-run with --auto to allow)\n`));
|
|
409
|
+
}
|
|
410
|
+
catch (e) {
|
|
411
|
+
if (e instanceof BrainConfigError)
|
|
412
|
+
error(e.message);
|
|
413
|
+
else
|
|
414
|
+
error(String(e instanceof Error ? e.message : e));
|
|
415
|
+
await s.wyrm?.close();
|
|
416
|
+
await s.mcp?.close();
|
|
417
|
+
process.exit(1);
|
|
418
|
+
}
|
|
419
|
+
await s.wyrm?.close();
|
|
420
|
+
});
|
|
421
|
+
}
|
|
422
|
+
//# sourceMappingURL=chat.js.map
|
|
@@ -0,0 +1,18 @@
|
|
|
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
|
+
import type { Command } from 'commander';
|
|
16
|
+
import { type DragonConfig } from '../config.js';
|
|
17
|
+
export declare function registerConfigCommands(program: Command, _config: DragonConfig): void;
|
|
18
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/commands/config.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;GAaG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAA0B,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAkBxE,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,QAsG7E"}
|
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
import { loadConfig, saveConfig } from '../config.js';
|
|
16
|
+
import { resolveProvider, PROVIDERS } from '../brain/index.js';
|
|
17
|
+
import { resolveAuth } from '../auth.js';
|
|
18
|
+
import { C, success, error } from '../utils.js';
|
|
19
|
+
import { panel, chrome, statusDot } from '../ui.js';
|
|
20
|
+
import { chmodSync, existsSync } from 'node:fs';
|
|
21
|
+
import { homedir } from 'node:os';
|
|
22
|
+
import { join } from 'node:path';
|
|
23
|
+
function lock() {
|
|
24
|
+
try {
|
|
25
|
+
chmodSync(join(homedir(), '.dragon', 'config.json'), 0o600);
|
|
26
|
+
}
|
|
27
|
+
catch { /* best-effort */ }
|
|
28
|
+
}
|
|
29
|
+
function redact(v) {
|
|
30
|
+
if (!v)
|
|
31
|
+
return C.faint('—');
|
|
32
|
+
return C.accent(`set (…${v.slice(-4)})`);
|
|
33
|
+
}
|
|
34
|
+
export function registerConfigCommands(program, _config) {
|
|
35
|
+
const cfg = program.command('config').description('Inspect + set the Dragon agent brain + credentials');
|
|
36
|
+
cfg
|
|
37
|
+
.command('show')
|
|
38
|
+
.description('Show current config (secrets redacted)')
|
|
39
|
+
.action(() => {
|
|
40
|
+
const c = loadConfig();
|
|
41
|
+
const a = resolveAuth();
|
|
42
|
+
console.log();
|
|
43
|
+
console.log(` ${C.accent.bold('brain')}`);
|
|
44
|
+
console.log(` provider: ${C.info(resolveProvider())} ${C.faint('(default')} ${C.faint(c.brain?.provider ?? 'claude')}${C.faint(')')}`);
|
|
45
|
+
console.log(` model: ${C.info(c.brain?.model ?? C.faint('default'))}`);
|
|
46
|
+
console.log(` local model: ${C.info(c.brain?.localModel ?? 'qwen2.5-coder:7b')} ${C.faint(c.brain?.localBaseURL ?? 'http://localhost:11434/v1')}`);
|
|
47
|
+
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)}`);
|
|
48
|
+
console.log(` ${C.accent.bold('portal')} (account.ghosts.lk)`);
|
|
49
|
+
console.log(` endpoint: ${C.info(a.apiBase)}`);
|
|
50
|
+
console.log(` auth: ${a.mode === 'none' ? C.faint('none') : C.accent(a.mode)}${a.email ? C.faint(' · ' + a.email) : ''}`);
|
|
51
|
+
console.log();
|
|
52
|
+
});
|
|
53
|
+
cfg
|
|
54
|
+
.command('brain <provider>')
|
|
55
|
+
.description(`Set the default reasoning brain: ${PROVIDERS.join(' | ')}`)
|
|
56
|
+
.action((provider) => {
|
|
57
|
+
const p = provider.toLowerCase();
|
|
58
|
+
if (!PROVIDERS.includes(p)) {
|
|
59
|
+
error(`unknown provider "${provider}". One of: ${PROVIDERS.join(', ')}`);
|
|
60
|
+
process.exitCode = 1;
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
const c = loadConfig();
|
|
64
|
+
c.brain = { ...(c.brain ?? {}), provider: p };
|
|
65
|
+
saveConfig(c);
|
|
66
|
+
success(`default brain → ${p}`);
|
|
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) => {
|
|
72
|
+
const c = loadConfig();
|
|
73
|
+
c.brain = { ...(c.brain ?? {}), model: id };
|
|
74
|
+
saveConfig(c);
|
|
75
|
+
success(`model → ${id}`);
|
|
76
|
+
});
|
|
77
|
+
cfg
|
|
78
|
+
.command('local-model <tag>')
|
|
79
|
+
.description('Set the local (Ollama) model tag, e.g. qwen2.5-coder:7b')
|
|
80
|
+
.action((tag) => {
|
|
81
|
+
const c = loadConfig();
|
|
82
|
+
c.brain = { ...(c.brain ?? {}), localModel: tag };
|
|
83
|
+
saveConfig(c);
|
|
84
|
+
success(`local model → ${tag}`);
|
|
85
|
+
});
|
|
86
|
+
cfg
|
|
87
|
+
.command('key <provider> <key>')
|
|
88
|
+
.description('Store an API key (anthropic | openai) in ~/.dragon/config.json (0600)')
|
|
89
|
+
.action((provider, key) => {
|
|
90
|
+
const p = provider.toLowerCase();
|
|
91
|
+
if (p !== 'anthropic' && p !== 'openai') {
|
|
92
|
+
error('provider must be "anthropic" or "openai"');
|
|
93
|
+
process.exitCode = 1;
|
|
94
|
+
return;
|
|
95
|
+
}
|
|
96
|
+
const c = loadConfig();
|
|
97
|
+
c.brain = { ...(c.brain ?? {}), keys: { ...(c.brain?.keys ?? {}), [p]: key } };
|
|
98
|
+
saveConfig(c);
|
|
99
|
+
lock();
|
|
100
|
+
success(`${p} key stored (config locked to 0600). Env vars still override.`);
|
|
101
|
+
});
|
|
102
|
+
cfg
|
|
103
|
+
.command('custom-url <url>')
|
|
104
|
+
.description('Set a custom OpenAI-compatible endpoint (OpenRouter / vLLM / LM Studio)')
|
|
105
|
+
.action((url) => { const c = loadConfig(); c.brain = { ...(c.brain ?? {}), customBaseURL: url }; saveConfig(c); success(`custom endpoint → ${url} (use: dragon config brain custom)`); });
|
|
106
|
+
cfg
|
|
107
|
+
.command('custom-model <id>')
|
|
108
|
+
.description('Set the model id for the custom endpoint')
|
|
109
|
+
.action((id) => { const c = loadConfig(); c.brain = { ...(c.brain ?? {}), customModel: id }; saveConfig(c); success(`custom model → ${id}`); });
|
|
110
|
+
// `dragon brains` — the model picker, as a command.
|
|
111
|
+
program
|
|
112
|
+
.command('brains')
|
|
113
|
+
.description('List reasoning brains + which are ready (the model picker)')
|
|
114
|
+
.action(() => {
|
|
115
|
+
const c = loadConfig();
|
|
116
|
+
const a = resolveAuth();
|
|
117
|
+
const active = resolveProvider();
|
|
118
|
+
const hasA = !!(process.env.ANTHROPIC_API_KEY || c.brain?.keys?.anthropic);
|
|
119
|
+
const hasO = !!(process.env.OPENAI_API_KEY || c.brain?.keys?.openai);
|
|
120
|
+
const hasC = !!(process.env.DRAGON_OPENAI_BASE || c.brain?.customBaseURL);
|
|
121
|
+
const ollama = ['/usr/local/bin/ollama', '/usr/bin/ollama', join(homedir(), '.local/bin/ollama')].some((p) => existsSync(p));
|
|
122
|
+
const rows = [
|
|
123
|
+
['claude', hasA, 'best for code', hasA ? '' : 'set ANTHROPIC_API_KEY'],
|
|
124
|
+
['worker', a.mode !== 'none', 'free · our Cloudflare', a.mode !== 'none' ? '' : 'run `dragon login`'],
|
|
125
|
+
['local', ollama, 'Ollama · private · free', ollama ? 'pull a model' : 'install Ollama'],
|
|
126
|
+
['ghost', false, 'DragonSpark · our nano LLM', 'train + serve it'],
|
|
127
|
+
['openai', hasO, 'GPT', hasO ? '' : 'set OPENAI_API_KEY'],
|
|
128
|
+
['custom', hasC, 'any OpenAI-compatible endpoint', hasC ? '' : 'dragon config custom-url <url>'],
|
|
129
|
+
];
|
|
130
|
+
const lines = rows.map(([id, ready, desc, need]) => `${id === active ? C.accent('▸') : ' '} ${statusDot(ready)} ${C.info(id.padEnd(7))} ${C.faint(desc)}${need ? C.faint(' · ' + need) : ''}`);
|
|
131
|
+
console.log();
|
|
132
|
+
console.log(panel(lines, { title: chrome('BRAINS') }));
|
|
133
|
+
console.log(` ${C.faint('active:')} ${C.accent(active)} ${C.faint('switch:')} dragon config brain <name> ${C.faint('per-run:')} dragon chat --brain <name>`);
|
|
134
|
+
});
|
|
135
|
+
}
|
|
136
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1,11 @@
|
|
|
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
|
+
import type { Command } from 'commander';
|
|
9
|
+
import { type DragonConfig } from '../config.js';
|
|
10
|
+
export declare function registerDoctorCommands(program: Command, _config: DragonConfig): void;
|
|
11
|
+
//# sourceMappingURL=doctor.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"doctor.d.ts","sourceRoot":"","sources":["../../src/commands/doctor.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAA;AACxC,OAAO,EAAc,KAAK,YAAY,EAAE,MAAM,cAAc,CAAA;AAe5D,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,OAAO,EAAE,OAAO,EAAE,YAAY,QA8C7E"}
|