@code4bug/jarvis-agent 1.1.8 → 1.3.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.
Files changed (65) hide show
  1. package/README.md +1 -1
  2. package/dist/agents/jarvis.md +1 -1
  3. package/dist/commands/init.js +4 -4
  4. package/dist/components/AnimatedStatusText.d.ts +10 -0
  5. package/dist/components/AnimatedStatusText.js +17 -0
  6. package/dist/components/ComposerPane.d.ts +25 -0
  7. package/dist/components/ComposerPane.js +10 -0
  8. package/dist/components/FooterPane.d.ts +9 -0
  9. package/dist/components/FooterPane.js +22 -0
  10. package/dist/components/InputTextView.d.ts +11 -0
  11. package/dist/components/InputTextView.js +44 -0
  12. package/dist/components/MarkdownText.d.ts +4 -0
  13. package/dist/components/MarkdownText.js +10 -3
  14. package/dist/components/MessageItem.js +4 -1
  15. package/dist/components/MessageList.d.ts +9 -0
  16. package/dist/components/MessageList.js +8 -0
  17. package/dist/components/MessageViewport.d.ts +21 -0
  18. package/dist/components/MessageViewport.js +11 -0
  19. package/dist/components/MultilineInput.js +62 -344
  20. package/dist/components/StatusBar.js +9 -6
  21. package/dist/components/StreamingDraft.d.ts +11 -0
  22. package/dist/components/StreamingDraft.js +14 -0
  23. package/dist/components/WelcomeHeader.js +4 -2
  24. package/dist/components/inputEditing.d.ts +20 -0
  25. package/dist/components/inputEditing.js +48 -0
  26. package/dist/components/setup/SetupConfirmStep.d.ts +8 -0
  27. package/dist/components/setup/SetupConfirmStep.js +12 -0
  28. package/dist/components/setup/SetupDoneStep.d.ts +7 -0
  29. package/dist/components/setup/SetupDoneStep.js +5 -0
  30. package/dist/components/setup/SetupFormStep.d.ts +11 -0
  31. package/dist/components/setup/SetupFormStep.js +44 -0
  32. package/dist/components/setup/SetupHeader.d.ts +9 -0
  33. package/dist/components/setup/SetupHeader.js +25 -0
  34. package/dist/components/setup/SetupProviderStep.d.ts +6 -0
  35. package/dist/components/setup/SetupProviderStep.js +20 -0
  36. package/dist/components/setup/SetupWelcomeStep.d.ts +5 -0
  37. package/dist/components/setup/SetupWelcomeStep.js +5 -0
  38. package/dist/config/bootstrap.d.ts +38 -0
  39. package/dist/config/bootstrap.js +155 -0
  40. package/dist/config/constants.d.ts +7 -6
  41. package/dist/config/constants.js +29 -16
  42. package/dist/config/loader.d.ts +2 -0
  43. package/dist/config/loader.js +4 -0
  44. package/dist/core/hint.js +3 -3
  45. package/dist/core/query.js +3 -2
  46. package/dist/hooks/useMultilineInputStream.d.ts +17 -0
  47. package/dist/hooks/useMultilineInputStream.js +141 -0
  48. package/dist/hooks/useTerminalCursorSync.d.ts +8 -0
  49. package/dist/hooks/useTerminalCursorSync.js +44 -0
  50. package/dist/hooks/useTerminalSize.d.ts +7 -0
  51. package/dist/hooks/useTerminalSize.js +21 -0
  52. package/dist/index.js +2 -2
  53. package/dist/screens/AppBootstrap.d.ts +1 -0
  54. package/dist/screens/AppBootstrap.js +14 -0
  55. package/dist/screens/repl.js +39 -28
  56. package/dist/screens/setup/SetupWizard.d.ts +7 -0
  57. package/dist/screens/setup/SetupWizard.js +198 -0
  58. package/dist/services/api/llm.js +5 -3
  59. package/dist/skills/index.js +10 -3
  60. package/dist/terminal/cursor.d.ts +6 -0
  61. package/dist/terminal/cursor.js +21 -0
  62. package/dist/tools/createSkill.js +59 -1
  63. package/dist/tools/readFile.js +28 -3
  64. package/dist/tools/writeFile.js +63 -2
  65. package/package.json +1 -1
@@ -0,0 +1,12 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ function maskApiKey(apiKey) {
4
+ if (!apiKey)
5
+ return '未填写';
6
+ if (apiKey.length <= 8)
7
+ return '*'.repeat(apiKey.length);
8
+ return `${apiKey.slice(0, 4)}${'*'.repeat(Math.max(apiKey.length - 8, 4))}${apiKey.slice(-4)}`;
9
+ }
10
+ export default function SetupConfirmStep({ form, preview, isSubmitting }) {
11
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "white", bold: true, children: "\u786E\u8BA4\u5199\u5165\u914D\u7F6E" }), _jsx(Text, { color: "gray", children: "\u786E\u8BA4\u65E0\u8BEF\u540E\u6309 Enter \u5199\u5165\u3002Esc \u53EF\u8FD4\u56DE\u4FEE\u6539\u3002" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsxs(Text, { children: ["\u914D\u7F6E\u540D\u79F0\uFF1A", form.profileName] }), _jsxs(Text, { children: ["API \u5730\u5740\uFF1A", form.apiUrl] }), _jsxs(Text, { children: ["\u6A21\u578B ID\uFF1A", form.model] }), _jsxs(Text, { children: ["API Key\uFF1A", maskApiKey(form.apiKey)] })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "JSON \u9884\u89C8" }), preview.split('\n').map((line, index) => (_jsx(Text, { color: "gray", children: line }, `${index}-${line}`)))] }), isSubmitting ? (_jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", children: "\u6B63\u5728\u5199\u5165\u914D\u7F6E..." }) })) : null] }));
12
+ }
@@ -0,0 +1,7 @@
1
+ interface SetupDoneStepProps {
2
+ success: boolean;
3
+ message: string;
4
+ configPath?: string;
5
+ }
6
+ export default function SetupDoneStep({ success, message, configPath }: SetupDoneStepProps): import("react/jsx-runtime").JSX.Element;
7
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export default function SetupDoneStep({ success, message, configPath }) {
4
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: success ? 'green' : 'red', bold: true, children: success ? '配置写入成功' : '配置写入失败' }), _jsx(Text, { children: message }), configPath ? _jsxs(Text, { color: "gray", children: ["\u914D\u7F6E\u6587\u4EF6\uFF1A", configPath] }) : null, _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", children: success ? '按 Enter 进入 Jarvis' : '按 Enter 返回修改' }) })] }));
5
+ }
@@ -0,0 +1,11 @@
1
+ import { ProviderType, SetupFormData, SetupValidationErrors } from '../../config/bootstrap.js';
2
+ interface SetupFormStepProps {
3
+ provider: ProviderType;
4
+ form: SetupFormData;
5
+ errors: SetupValidationErrors;
6
+ selectedField: keyof SetupFormData;
7
+ editingField: keyof SetupFormData | null;
8
+ editBuffer: string;
9
+ }
10
+ export default function SetupFormStep({ provider, form, errors, selectedField, editingField, editBuffer, }: SetupFormStepProps): import("react/jsx-runtime").JSX.Element;
11
+ export {};
@@ -0,0 +1,44 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ const FIELD_ORDER = [
4
+ 'profileName',
5
+ 'apiUrl',
6
+ 'apiKey',
7
+ 'model',
8
+ 'temperature',
9
+ 'maxTokens',
10
+ ];
11
+ const FIELD_LABELS = {
12
+ profileName: '配置名称',
13
+ apiUrl: 'API 地址',
14
+ apiKey: 'API Key',
15
+ model: '模型 ID',
16
+ temperature: 'temperature',
17
+ maxTokens: 'max_tokens',
18
+ };
19
+ const FIELD_DESCRIPTIONS = {
20
+ profileName: '写入到 models 下的配置名,同时会被设置为默认激活模型。',
21
+ apiUrl: '完整的 Chat Completions 接口地址。',
22
+ apiKey: '鉴权密钥。显示时会自动脱敏。',
23
+ model: '服务端实际使用的模型标识。',
24
+ temperature: '可选,默认 0.1。',
25
+ maxTokens: '响应最大 token 数,建议保持默认即可。',
26
+ };
27
+ function maskValue(field, value) {
28
+ if (field !== 'apiKey')
29
+ return value || '未填写';
30
+ if (!value)
31
+ return '未填写';
32
+ if (value.length <= 8)
33
+ return '*'.repeat(value.length);
34
+ return `${value.slice(0, 4)}${'*'.repeat(Math.max(value.length - 8, 4))}${value.slice(-4)}`;
35
+ }
36
+ export default function SetupFormStep({ provider, form, errors, selectedField, editingField, editBuffer, }) {
37
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "white", bold: true, children: "\u586B\u5199\u914D\u7F6E\u53C2\u6570" }), _jsxs(Text, { color: "gray", children: ["\u5F53\u524D\u63A5\u5165\u65B9\u5F0F\uFF1A", provider === 'ollama' ? 'Ollama / 本地模型' : 'OpenAI 兼容接口'] }), _jsx(Text, { color: "gray", children: "\u4E0A\u4E0B\u952E\u5207\u5B57\u6BB5\uFF0C\u6309 e \u7F16\u8F91\u5F53\u524D\u5B57\u6BB5\uFF0CEnter \u6821\u9A8C\u5E76\u8FDB\u5165\u786E\u8BA4\u3002" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: FIELD_ORDER.map((field) => {
38
+ const selected = field === selectedField;
39
+ const editing = field === editingField;
40
+ const displayValue = editing ? editBuffer : maskValue(field, form[field]);
41
+ const error = errors[field];
42
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: selected ? 'cyan' : 'white', children: [selected ? '›' : ' ', " ", FIELD_LABELS[field], "\uFF1A", displayValue] }), _jsx(Text, { color: "gray", children: FIELD_DESCRIPTIONS[field] }), error ? _jsx(Text, { color: "red", children: error }) : null] }, field));
43
+ }) })] }));
44
+ }
@@ -0,0 +1,9 @@
1
+ import { BootstrapStatus } from '../../config/bootstrap.js';
2
+ type SetupStep = 'welcome' | 'provider' | 'form' | 'confirm' | 'done';
3
+ interface SetupHeaderProps {
4
+ width: number;
5
+ currentStep: SetupStep;
6
+ status: BootstrapStatus;
7
+ }
8
+ export default function SetupHeader({ width, currentStep, status }: SetupHeaderProps): import("react/jsx-runtime").JSX.Element;
9
+ export {};
@@ -0,0 +1,25 @@
1
+ import { jsxs as _jsxs, jsx as _jsx } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ import { getBootstrapConfigPath } from '../../config/bootstrap.js';
4
+ const STEPS = [
5
+ { key: 'welcome', label: '欢迎' },
6
+ { key: 'provider', label: '接入方式' },
7
+ { key: 'form', label: '参数填写' },
8
+ { key: 'confirm', label: '确认写入' },
9
+ { key: 'done', label: '完成' },
10
+ ];
11
+ function stepColor(currentStep, step) {
12
+ const currentIndex = STEPS.findIndex((item) => item.key === currentStep);
13
+ const stepIndex = STEPS.findIndex((item) => item.key === step);
14
+ if (stepIndex < currentIndex)
15
+ return 'green';
16
+ if (stepIndex === currentIndex)
17
+ return 'cyan';
18
+ return 'gray';
19
+ }
20
+ export default function SetupHeader({ width, currentStep, status }) {
21
+ const progress = STEPS
22
+ .map((step, index) => ({ ...step, index: index + 1 }))
23
+ .map((step) => (_jsxs(Text, { color: stepColor(currentStep, step.key), children: [step.index, ". ", step.label, ' '] }, step.key)));
24
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, width: width, children: [_jsx(Text, { color: "magenta", bold: true, children: "Jarvis \u9996\u6B21\u542F\u52A8\u914D\u7F6E" }), _jsx(Text, { color: "gray", children: status.ok ? '配置校验通过' : status.message }), _jsxs(Text, { color: "gray", children: ["\u914D\u7F6E\u6587\u4EF6\u8DEF\u5F84\uFF1A", getBootstrapConfigPath()] }), _jsx(Box, { marginTop: 1, children: progress }), _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(Math.max(width - 2, 20), 72)) })] }));
25
+ }
@@ -0,0 +1,6 @@
1
+ import { ProviderType } from '../../config/bootstrap.js';
2
+ interface SetupProviderStepProps {
3
+ provider: ProviderType;
4
+ }
5
+ export default function SetupProviderStep({ provider }: SetupProviderStepProps): import("react/jsx-runtime").JSX.Element;
6
+ export {};
@@ -0,0 +1,20 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ const OPTIONS = [
4
+ {
5
+ value: 'openai-compatible',
6
+ title: 'OpenAI 兼容接口',
7
+ desc: '适合 OpenAI、OneAPI、云厂商兼容接口等场景。',
8
+ },
9
+ {
10
+ value: 'ollama',
11
+ title: 'Ollama / 本地模型',
12
+ desc: '自动带入本地默认地址,适合本机 Ollama 服务。',
13
+ },
14
+ ];
15
+ export default function SetupProviderStep({ provider }) {
16
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "white", bold: true, children: "\u9009\u62E9\u63A5\u5165\u65B9\u5F0F" }), _jsx(Text, { color: "gray", children: "\u4E0A\u4E0B\u952E\u5207\u6362\uFF0CEnter \u786E\u8BA4\u540E\u8FDB\u5165\u53C2\u6570\u586B\u5199\u3002" }), _jsx(Box, { marginTop: 1, flexDirection: "column", children: OPTIONS.map((option) => {
17
+ const selected = option.value === provider;
18
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Text, { color: selected ? 'cyan' : 'white', children: [selected ? '›' : ' ', " ", option.title] }), _jsx(Text, { color: "gray", children: option.desc })] }, option.value));
19
+ }) })] }));
20
+ }
@@ -0,0 +1,5 @@
1
+ interface SetupWelcomeStepProps {
2
+ reasonText: string;
3
+ }
4
+ export default function SetupWelcomeStep({ reasonText }: SetupWelcomeStepProps): import("react/jsx-runtime").JSX.Element;
5
+ export {};
@@ -0,0 +1,5 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import { Box, Text } from 'ink';
3
+ export default function SetupWelcomeStep({ reasonText }) {
4
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, children: [_jsx(Text, { color: "white", bold: true, children: "\u6B22\u8FCE\u4F7F\u7528 Jarvis" }), _jsx(Text, { color: "gray", children: "\u68C0\u6D4B\u5230\u5F53\u524D\u5C1A\u672A\u5B8C\u6210\u53EF\u7528\u6A21\u578B\u914D\u7F6E\uFF0C\u9996\u6B21\u542F\u52A8\u9700\u8981\u5148\u5B8C\u6210\u57FA\u7840\u5F15\u5BFC\u3002" }), _jsx(Text, { color: "gray", children: "\u5B8C\u6210\u540E\u4F1A\u76F4\u63A5\u8FDB\u5165\u4E3B\u754C\u9762\uFF0C\u65E0\u9700\u518D\u6B21\u6267\u884C\u989D\u5916\u547D\u4EE4\u3002" }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "\u5F53\u524D\u72B6\u6001" }), _jsx(Text, { children: reasonText })] }), _jsxs(Box, { marginTop: 1, flexDirection: "column", children: [_jsx(Text, { color: "yellow", children: "\u672C\u6B21\u4F1A\u5B8C\u6210\u7684\u914D\u7F6E" }), _jsx(Text, { children: "1. \u8BBE\u7F6E\u9ED8\u8BA4\u6A21\u578B\u914D\u7F6E\u540D" }), _jsx(Text, { children: "2. \u5199\u5165 API \u5730\u5740\u4E0E API Key" }), _jsx(Text, { children: "3. \u5199\u5165\u6A21\u578B ID \u4E0E\u57FA\u7840\u53C2\u6570" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "cyan", children: "\u6309 Enter \u5F00\u59CB\u914D\u7F6E" }) })] }));
5
+ }
@@ -0,0 +1,38 @@
1
+ import { JarvisConfig } from './loader.js';
2
+ export type ProviderType = 'openai-compatible' | 'ollama';
3
+ export interface SetupFormData {
4
+ profileName: string;
5
+ apiUrl: string;
6
+ apiKey: string;
7
+ model: string;
8
+ temperature: string;
9
+ maxTokens: string;
10
+ }
11
+ export interface SetupValidationErrors {
12
+ profileName?: string;
13
+ apiUrl?: string;
14
+ apiKey?: string;
15
+ model?: string;
16
+ temperature?: string;
17
+ maxTokens?: string;
18
+ form?: string;
19
+ }
20
+ export type BootstrapStatus = {
21
+ ok: true;
22
+ } | {
23
+ ok: false;
24
+ reason: 'missing' | 'invalid_json' | 'incomplete';
25
+ message: string;
26
+ };
27
+ export declare function getBootstrapConfigPath(): string;
28
+ export declare function getDefaultSetupForm(provider: ProviderType): SetupFormData;
29
+ export declare function checkBootstrapStatus(): BootstrapStatus;
30
+ export declare function validateSetupForm(form: SetupFormData): SetupValidationErrors;
31
+ export declare function buildJarvisConfig(form: SetupFormData): JarvisConfig;
32
+ export declare function writeBootstrapConfig(config: JarvisConfig): {
33
+ success: true;
34
+ path: string;
35
+ } | {
36
+ success: false;
37
+ message: string;
38
+ };
@@ -0,0 +1,155 @@
1
+ import fs from 'fs';
2
+ import os from 'os';
3
+ import path from 'path';
4
+ import { loadConfig, resetConfigCache } from './loader.js';
5
+ const JARVIS_HOME_DIR = path.join(os.homedir(), '.jarvis');
6
+ const CONFIG_PATH = path.join(JARVIS_HOME_DIR, 'config.json');
7
+ export function getBootstrapConfigPath() {
8
+ return CONFIG_PATH;
9
+ }
10
+ export function getDefaultSetupForm(provider) {
11
+ if (provider === 'ollama') {
12
+ return {
13
+ profileName: 'ollama',
14
+ apiUrl: 'http://127.0.0.1:11434/v1/chat/completions',
15
+ apiKey: 'ollama',
16
+ model: '',
17
+ temperature: '0.1',
18
+ maxTokens: '10000',
19
+ };
20
+ }
21
+ return {
22
+ profileName: 'default',
23
+ apiUrl: 'https://api.openai.com/v1/chat/completions',
24
+ apiKey: '',
25
+ model: '',
26
+ temperature: '0.1',
27
+ maxTokens: '10000',
28
+ };
29
+ }
30
+ export function checkBootstrapStatus() {
31
+ if (!fs.existsSync(CONFIG_PATH)) {
32
+ return { ok: false, reason: 'missing', message: '未检测到全局配置文件,需要先完成首次配置。' };
33
+ }
34
+ try {
35
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
36
+ const parsed = JSON.parse(raw);
37
+ const modelName = parsed.system?.model;
38
+ if (!modelName) {
39
+ return { ok: false, reason: 'incomplete', message: '配置文件缺少 system.model,无法确定默认模型。' };
40
+ }
41
+ const activeModel = parsed.models?.[modelName];
42
+ if (!activeModel) {
43
+ return { ok: false, reason: 'incomplete', message: `配置文件缺少 models.${modelName},无法完成启动。` };
44
+ }
45
+ if (!activeModel.api_url || !activeModel.api_key || !activeModel.model) {
46
+ return { ok: false, reason: 'incomplete', message: '当前默认模型缺少 api_url、api_key 或 model 字段。' };
47
+ }
48
+ return { ok: true };
49
+ }
50
+ catch (error) {
51
+ return {
52
+ ok: false,
53
+ reason: 'invalid_json',
54
+ message: `配置文件解析失败:${error instanceof Error ? error.message : String(error)}`,
55
+ };
56
+ }
57
+ }
58
+ export function validateSetupForm(form) {
59
+ const errors = {};
60
+ if (!form.profileName.trim()) {
61
+ errors.profileName = '配置名称不能为空';
62
+ }
63
+ else if (/\s/.test(form.profileName)) {
64
+ errors.profileName = '配置名称不能包含空格';
65
+ }
66
+ if (!form.apiUrl.trim()) {
67
+ errors.apiUrl = 'API 地址不能为空';
68
+ }
69
+ else {
70
+ try {
71
+ new URL(form.apiUrl);
72
+ }
73
+ catch {
74
+ errors.apiUrl = 'API 地址格式不合法';
75
+ }
76
+ }
77
+ if (!form.apiKey.trim()) {
78
+ errors.apiKey = 'API Key 不能为空';
79
+ }
80
+ if (!form.model.trim()) {
81
+ errors.model = '模型 ID 不能为空';
82
+ }
83
+ if (form.temperature.trim()) {
84
+ const temperature = Number(form.temperature);
85
+ if (Number.isNaN(temperature)) {
86
+ errors.temperature = 'temperature 必须是数字';
87
+ }
88
+ }
89
+ if (!form.maxTokens.trim()) {
90
+ errors.maxTokens = 'max_tokens 不能为空';
91
+ }
92
+ else {
93
+ const maxTokens = Number(form.maxTokens);
94
+ if (!Number.isInteger(maxTokens) || maxTokens <= 0) {
95
+ errors.maxTokens = 'max_tokens 必须是正整数';
96
+ }
97
+ }
98
+ return errors;
99
+ }
100
+ export function buildJarvisConfig(form) {
101
+ const baseConfig = loadExistingConfig();
102
+ const temperature = form.temperature.trim() ? Number(form.temperature) : undefined;
103
+ const maxTokens = Number(form.maxTokens);
104
+ const config = {
105
+ system: {
106
+ ...baseConfig.system,
107
+ model: form.profileName.trim(),
108
+ },
109
+ models: {
110
+ ...baseConfig.models,
111
+ [form.profileName.trim()]: {
112
+ ...(baseConfig.models?.[form.profileName.trim()] ?? {}),
113
+ api_url: form.apiUrl.trim(),
114
+ api_key: form.apiKey.trim(),
115
+ model: form.model.trim(),
116
+ ...(temperature !== undefined ? { temperature } : {}),
117
+ max_tokens: maxTokens,
118
+ },
119
+ },
120
+ };
121
+ return config;
122
+ }
123
+ function loadExistingConfig() {
124
+ try {
125
+ if (!fs.existsSync(CONFIG_PATH)) {
126
+ return { system: {}, models: {} };
127
+ }
128
+ const raw = fs.readFileSync(CONFIG_PATH, 'utf-8');
129
+ const parsed = JSON.parse(raw);
130
+ return {
131
+ system: parsed.system ?? {},
132
+ models: parsed.models ?? {},
133
+ };
134
+ }
135
+ catch {
136
+ return { system: {}, models: {} };
137
+ }
138
+ }
139
+ export function writeBootstrapConfig(config) {
140
+ try {
141
+ if (!fs.existsSync(JARVIS_HOME_DIR)) {
142
+ fs.mkdirSync(JARVIS_HOME_DIR, { recursive: true });
143
+ }
144
+ fs.writeFileSync(CONFIG_PATH, JSON.stringify(config, null, 2), 'utf-8');
145
+ resetConfigCache();
146
+ loadConfig();
147
+ return { success: true, path: CONFIG_PATH };
148
+ }
149
+ catch (error) {
150
+ return {
151
+ success: false,
152
+ message: error instanceof Error ? error.message : String(error),
153
+ };
154
+ }
155
+ }
@@ -3,16 +3,18 @@ export declare const APP_VERSION: string;
3
3
  export declare const PROJECT_NAME: string;
4
4
  /** Agentic Loop 最大迭代次数 */
5
5
  export declare const MAX_ITERATIONS = 50;
6
+ /** 会话存储目录(~/.jarvis/sessions/) */
6
7
  export declare const JARVIS_HOME_DIR: string;
7
8
  export declare const SESSIONS_DIR: string;
8
9
  export declare const LOGS_DIR: string;
9
10
  /** 输入后是否隐藏 WelcomeHeader,默认 false(不隐藏) */
10
11
  export declare const HIDE_WELCOME_AFTER_INPUT = false;
11
- export declare const MODEL_NAME: string;
12
+ /** 从配置文件获取当前模型名称 */
13
+ export declare function getModelName(): string;
12
14
  /** 是否支持思考/非思考模式切换,默认 false(隐藏该功能) */
13
- export declare const ENABLE_THINKING_MODE_TOGGLE: boolean;
15
+ export declare function isThinkingModeToggleEnabled(): boolean;
14
16
  /** 上下文 token 上限 */
15
- export declare const CONTEXT_TOKEN_LIMIT: number;
17
+ export declare function getContextTokenLimit(): number;
16
18
  /** 默认智能体名称(硬编码兜底值) */
17
19
  export declare const DEFAULT_AGENT_FALLBACK = "Jarvis";
18
20
  /** 智能体定义文件目录(相对项目根目录) */
@@ -22,6 +24,5 @@ export declare const DEFAULT_AGENT_COLOR = "green";
22
24
  /** 智能体默认标识符 */
23
25
  export declare const DEFAULT_AGENT_EMOJI = ">";
24
26
  /** 当前激活的智能体名称 — 启动时从 ~/.jarvis/agent.json 读取,运行时可切换 */
25
- export declare const DEFAULT_AGENT: string;
26
- /** 应用名称 取自当前激活智能体的 name */
27
- export declare const APP_NAME: string;
27
+ export declare function getDefaultAgent(): string;
28
+ export declare function getAppName(): string;
@@ -1,6 +1,10 @@
1
1
  import fs from 'fs';
2
2
  import path from 'path';
3
3
  import { fileURLToPath } from 'url';
4
+ import os from 'os';
5
+ import { loadConfig, getActiveModel } from './loader.js';
6
+ import { getAgent } from '../agents/index.js';
7
+ import { getActiveAgent } from './agentState.js';
4
8
  /** 从 package.json 动态读取版本号 */
5
9
  function resolveAppVersion() {
6
10
  try {
@@ -19,29 +23,40 @@ export const PROJECT_NAME = path.basename(process.cwd());
19
23
  /** Agentic Loop 最大迭代次数 */
20
24
  export const MAX_ITERATIONS = 50;
21
25
  /** 会话存储目录(~/.jarvis/sessions/) */
22
- import os from 'os';
23
26
  export const JARVIS_HOME_DIR = path.join(os.homedir(), '.jarvis');
24
27
  export const SESSIONS_DIR = path.join(JARVIS_HOME_DIR, 'sessions');
25
28
  export const LOGS_DIR = path.join(JARVIS_HOME_DIR, 'logs');
26
29
  /** 输入后是否隐藏 WelcomeHeader,默认 false(不隐藏) */
27
30
  export const HIDE_WELCOME_AFTER_INPUT = false;
28
31
  /** 从配置文件获取当前模型名称 */
29
- import { loadConfig, getActiveModel } from './loader.js';
30
- const _cfg = loadConfig();
31
- function resolveModelName() {
32
+ export function getModelName() {
32
33
  try {
33
- const active = getActiveModel(_cfg);
34
- return active?.model ?? _cfg.system.model ?? 'unknown';
34
+ const config = loadConfig();
35
+ const active = getActiveModel(config);
36
+ return active?.model ?? config.system.model ?? 'unknown';
35
37
  }
36
38
  catch {
37
39
  return 'unknown';
38
40
  }
39
41
  }
40
- export const MODEL_NAME = resolveModelName();
41
42
  /** 是否支持思考/非思考模式切换,默认 false(隐藏该功能) */
42
- export const ENABLE_THINKING_MODE_TOGGLE = _cfg.system.enable_thinking_mode_toggle ?? false;
43
+ export function isThinkingModeToggleEnabled() {
44
+ try {
45
+ return loadConfig().system.enable_thinking_mode_toggle ?? false;
46
+ }
47
+ catch {
48
+ return false;
49
+ }
50
+ }
43
51
  /** 上下文 token 上限 */
44
- export const CONTEXT_TOKEN_LIMIT = _cfg.system.context_token_limit ?? 18000;
52
+ export function getContextTokenLimit() {
53
+ try {
54
+ return loadConfig().system.context_token_limit ?? 18000;
55
+ }
56
+ catch {
57
+ return 18000;
58
+ }
59
+ }
45
60
  // ===== 智能体默认配置 =====
46
61
  /** 默认智能体名称(硬编码兜底值) */
47
62
  export const DEFAULT_AGENT_FALLBACK = 'Jarvis';
@@ -52,18 +67,16 @@ export const DEFAULT_AGENT_COLOR = 'green';
52
67
  /** 智能体默认标识符 */
53
68
  export const DEFAULT_AGENT_EMOJI = '>';
54
69
  // ===== 动态应用名称(跟随激活智能体) =====
55
- import { getAgent } from '../agents/index.js';
56
- import { getActiveAgent } from './agentState.js';
57
70
  /** 当前激活的智能体名称 — 启动时从 ~/.jarvis/agent.json 读取,运行时可切换 */
58
- export const DEFAULT_AGENT = getActiveAgent(DEFAULT_AGENT_FALLBACK);
59
- function resolveAppName() {
71
+ export function getDefaultAgent() {
72
+ return getActiveAgent(DEFAULT_AGENT_FALLBACK);
73
+ }
74
+ export function getAppName() {
60
75
  try {
61
- const agent = getAgent(DEFAULT_AGENT);
76
+ const agent = getAgent(getDefaultAgent());
62
77
  return agent?.meta.name ?? 'Jarvis';
63
78
  }
64
79
  catch {
65
80
  return 'Jarvis';
66
81
  }
67
82
  }
68
- /** 应用名称 — 取自当前激活智能体的 name */
69
- export const APP_NAME = resolveAppName();
@@ -28,5 +28,7 @@ export interface JarvisConfig {
28
28
  }
29
29
  /** 加载合并后的最终配置(结果会被缓存,多次调用只解析一次) */
30
30
  export declare function loadConfig(): JarvisConfig;
31
+ /** 清空配置缓存,供首次引导写入后重新加载 */
32
+ export declare function resetConfigCache(): void;
31
33
  /** 根据当前配置获取活跃模型配置 */
32
34
  export declare function getActiveModel(config: JarvisConfig): ModelConfig | null;
@@ -55,6 +55,10 @@ export function loadConfig() {
55
55
  _cachedConfig = mergeConfigs(globalCfg, localCfg);
56
56
  return _cachedConfig;
57
57
  }
58
+ /** 清空配置缓存,供首次引导写入后重新加载 */
59
+ export function resetConfigCache() {
60
+ _cachedConfig = null;
61
+ }
58
62
  /** 根据当前配置获取活跃模型配置 */
59
63
  export function getActiveModel(config) {
60
64
  const modelName = config.system.model;
package/dist/core/hint.js CHANGED
@@ -7,7 +7,7 @@
7
7
  import fs from 'fs';
8
8
  import path from 'path';
9
9
  import { getAgent } from '../agents/index.js';
10
- import { DEFAULT_AGENT } from '../config/constants.js';
10
+ import { getDefaultAgent } from '../config/constants.js';
11
11
  import { loadConfig, getActiveModel } from '../config/loader.js';
12
12
  import { getDefaultConfig } from '../services/api/llm.js';
13
13
  /** 安全读取 JSON 文件 */
@@ -155,7 +155,7 @@ export function getFallbackHint(agentName) {
155
155
  if (agentName) {
156
156
  return FALLBACK_HINTS[agentName.toLowerCase()] ?? DEFAULT_HINT;
157
157
  }
158
- const agent = getAgent(DEFAULT_AGENT);
158
+ const agent = getAgent(getDefaultAgent());
159
159
  const name = agent?.meta.name ?? 'Jarvis';
160
160
  return FALLBACK_HINTS[name.toLowerCase()] ?? DEFAULT_HINT;
161
161
  }
@@ -180,7 +180,7 @@ function normalizeHint(raw) {
180
180
  * @returns 生成的提示文本,失败时返回静态兜底
181
181
  */
182
182
  export async function generateAgentHint() {
183
- const agent = getAgent(DEFAULT_AGENT);
183
+ const agent = getAgent(getDefaultAgent());
184
184
  const agentName = agent?.meta.name ?? 'Jarvis';
185
185
  const fallback = getFallbackHint(agentName);
186
186
  // 检查 LLM 是否可用
@@ -3,7 +3,7 @@ import { Worker } from 'worker_threads';
3
3
  import { fileURLToPath } from 'url';
4
4
  import path from 'path';
5
5
  import { findToolMerged as findTool } from '../tools/index.js';
6
- import { MAX_ITERATIONS, CONTEXT_TOKEN_LIMIT } from '../config/constants.js';
6
+ import { MAX_ITERATIONS, getContextTokenLimit } from '../config/constants.js';
7
7
  import { sanitizeOutput, validateCommand, authorizeCommand, authorizeRule } from './safeguard.js';
8
8
  import { logError, logInfo, logWarn } from './logger.js';
9
9
  // 兼容 ESM __dirname
@@ -27,6 +27,7 @@ function estimateTokens(text) {
27
27
  * - 确保总估算 token 不超过 CONTEXT_TOKEN_LIMIT
28
28
  */
29
29
  function compressTranscript(transcript) {
30
+ const contextTokenLimit = getContextTokenLimit();
30
31
  // 单条工具结果最大字符数
31
32
  const MAX_TOOL_RESULT_CHARS = 3000;
32
33
  // 旧条目压缩到的字符数
@@ -51,7 +52,7 @@ function compressTranscript(transcript) {
51
52
  return m.content;
52
53
  return JSON.stringify(m.content);
53
54
  }).join(''));
54
- if (totalTokens <= CONTEXT_TOKEN_LIMIT)
55
+ if (totalTokens <= contextTokenLimit)
55
56
  return result;
56
57
  // 找出所有 tool_result 的索引,保留最近 KEEP_RECENT 条,其余压缩
57
58
  const toolResultIndices = result
@@ -0,0 +1,17 @@
1
+ interface UseMultilineInputStreamOptions {
2
+ stdin?: NodeJS.ReadStream;
3
+ isActive: boolean;
4
+ pasteDetectWindowMs: number;
5
+ imeComposeWindowMs: number;
6
+ isAsciiLowerAlpha: (text: string) => boolean;
7
+ hasNonAscii: (text: string) => boolean;
8
+ insertPaste: (text: string) => void;
9
+ handleNormalInput: (raw: string) => void;
10
+ commitImeBuffer: () => void;
11
+ flushImeBuffer: () => void;
12
+ appendImeBuffer: (text: string) => void;
13
+ getImeBuffer: () => string;
14
+ clearImeBufferWithInsertedLenRollback: () => void;
15
+ }
16
+ export declare function useMultilineInputStream({ stdin, isActive, pasteDetectWindowMs, imeComposeWindowMs, isAsciiLowerAlpha, hasNonAscii, insertPaste, handleNormalInput, commitImeBuffer, flushImeBuffer, appendImeBuffer, getImeBuffer, clearImeBufferWithInsertedLenRollback, }: UseMultilineInputStreamOptions): void;
17
+ export {};