@code4bug/jarvis-agent 1.3.5 → 1.3.6

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/README.md CHANGED
@@ -98,7 +98,21 @@ Jarvis 会按下面顺序加载配置,后者覆盖前者:
98
98
 
99
99
  - `system.model` 对应 `models` 里的 key
100
100
  - `extra_body` 会直接合并进请求体,方便兼容不同服务商
101
- - 如果没有可用模型配置,程序会回退到 Mock Service,适合本地界面联调
101
+
102
+ ### 首次启动配置
103
+
104
+ 如果未检测到可用的全局配置,Jarvis 启动时会先进入首次配置向导,而不是直接进入主界面。
105
+
106
+ 当前配置向导支持两种接入方式:
107
+
108
+ - OpenAI 兼容接口
109
+ - Ollama / 本地模型
110
+
111
+ 配置写入位置:
112
+
113
+ - `~/.jarvis/config.json`
114
+
115
+ 项目目录下的 `./.jarvis/config.json` 仍可作为本地覆盖配置使用。
102
116
 
103
117
  ## 常用命令
104
118
 
@@ -116,21 +130,44 @@ jarvis --version
116
130
  | `/init` | 扫描当前项目并生成 `JARVIS.md` |
117
131
  | `/new` | 开启新会话 |
118
132
  | `/resume` | 恢复历史会话 |
119
- | `/agent` | 切换智能体 |
133
+ | `/resume <ID>` | 直接恢复指定历史会话 |
134
+ | `/agent` | 打开智能体切换列表 |
135
+ | `/agent <名称>` | 切换智能体,重启后生效 |
120
136
  | `/permissions` | 查看持久化授权 |
121
137
  | `/skills` | 查看当前工具与外部 Skill |
122
138
  | `/session_clear` | 清理非当前会话历史 |
123
139
  | `/version` | 显示版本 |
124
140
  | `/help` | 显示帮助 |
141
+ | `/create_skill <描述>` | 根据描述创建新的 Skill |
142
+ | `/exit` | 退出应用 |
143
+ | `/quit` | 退出应用 |
144
+ | `/bye` | 退出应用 |
145
+ | `/read <路径>` | 以工具方式读取文件 |
146
+ | `/write <路径>` | 以工具方式写入文件 |
147
+ | `/bash <命令>` | 以工具方式执行命令 |
148
+ | `/ls <路径>` | 以工具方式列出目录 |
149
+ | `/search <关键词>` | 以工具方式搜索文件内容 |
125
150
 
126
151
  ### 快捷键
127
152
 
128
153
  | 快捷键 | 说明 |
129
154
  | --- | --- |
130
155
  | `Ctrl + L` | 清屏并开始新会话 |
131
- | `Ctrl + C` | 退出 |
132
- | `Esc` | 中断当前任务或清空输入 |
156
+ | `Ctrl + C` | 3 秒内连续按两次退出 |
157
+ | `Ctrl + O` | 切换详情视图 |
158
+ | `Esc` | 中断当前任务;空闲时双击清空输入 |
133
159
  | `Alt/Option + Enter` | 输入换行 |
160
+ | `Tab` | 输入为空时填入提示词;在斜杠菜单中补全当前项 |
161
+ | `? + Enter` | 输出快捷键帮助信息 |
162
+
163
+ ### 输入与交互
164
+
165
+ - 输入 `/` 可打开斜杠菜单
166
+ - 斜杠菜单中可使用 `↑ / ↓` 切换命令
167
+ - 在斜杠菜单中按 `Tab` 可补全当前命令
168
+ - 在斜杠菜单中按 `Enter` 可提交当前命令
169
+ - 对于多行输入,`↑ / ↓` 用于在输入框内移动光标
170
+ - 对于单行输入,`↑ / ↓` 用于切换历史输入
134
171
 
135
172
  ## 内置工具
136
173
 
@@ -209,6 +246,12 @@ Agent 通过 Markdown 文件定义元信息与系统提示词,切换结果会
209
246
 
210
247
  每次会话会保存消息、摘要、更新时间和累计 Token。
211
248
 
249
+ 支持能力:
250
+
251
+ - `/resume` 从列表恢复历史会话
252
+ - `/resume <ID>` 直接恢复指定会话
253
+ - `/session_clear` 清理除当前会话外的历史记录
254
+
212
255
  ### 长期记忆
213
256
 
214
257
  - 记忆文件:`~/.jarvis/MEMORY.md`
@@ -3,6 +3,7 @@ import React from 'react';
3
3
  import { Box, Text } from 'ink';
4
4
  import Spinner from 'ink-spinner';
5
5
  import MarkdownText from './MarkdownText.js';
6
+ import ShortcutHelpMessage from './ShortcutHelpMessage.js';
6
7
  // 状态圆点 icon,根据消息类型 + 状态决定颜色
7
8
  // reasoning 完成 → 白色 / tool_exec 成功 → 绿色 / error → 红色 / aborted → 黄色 / pending → 黄色
8
9
  function statusDot(status, type) {
@@ -80,7 +81,7 @@ function MessageItem({ msg, showDetails = false }) {
80
81
  _jsxs(_Fragment, { children: [_jsxs(Box, { children: [_jsxs(Text, { color: dotColor, children: [dot, " "] }), _jsxs(Text, { color: "blue", dimColor: true, children: [msg.subAgentId, " \u203A "] })] }), _jsx(Box, { marginLeft: 2, flexDirection: "column", children: _jsx(MarkdownText, { text: msg.content }) })] })) : (_jsxs(Box, { flexDirection: "row", alignItems: "flex-start", children: [_jsxs(Text, { color: dotColor, children: [dot, " "] }), _jsx(Box, { flexDirection: "column", flexShrink: 1, children: _jsx(MarkdownText, { text: msg.content }) })] })), _jsx(MessageStats, { msg: msg, show: showDetails })] }));
81
82
  }
82
83
  if (msg.type === 'system' && msg.content) {
83
- return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { flexDirection: "row", alignItems: "flex-start", children: [_jsxs(Text, { color: dotColor, children: [dot, " "] }), _jsx(Box, { flexDirection: "column", flexShrink: 1, children: _jsx(MarkdownText, { text: msg.content, color: "gray" }) })] }), _jsx(MessageStats, { msg: msg, show: showDetails })] }));
84
+ return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { flexDirection: "row", alignItems: "flex-start", children: [_jsxs(Text, { color: dotColor, children: [dot, " "] }), _jsx(Box, { flexDirection: "column", flexShrink: 1, children: msg.systemKind === 'shortcut_help' ? (_jsx(ShortcutHelpMessage, {})) : (_jsx(MarkdownText, { text: msg.content, color: "gray" })) })] }), _jsx(MessageStats, { msg: msg, show: showDetails })] }));
84
85
  }
85
86
  if (msg.status !== 'pending' && msg.content) {
86
87
  return (_jsxs(Box, { flexDirection: "column", marginBottom: 1, children: [_jsxs(Box, { flexDirection: "row", alignItems: "flex-start", children: [_jsxs(Text, { color: dotColor, children: [dot, " "] }), _jsx(Box, { flexDirection: "column", flexShrink: 1, children: _jsx(MarkdownText, { text: msg.content, color: "gray" }) })] }), _jsx(MessageStats, { msg: msg, show: showDetails })] }));
@@ -0,0 +1,4 @@
1
+ import React from 'react';
2
+ declare function ShortcutHelpMessage(): import("react/jsx-runtime").JSX.Element;
3
+ declare const _default: React.MemoExoticComponent<typeof ShortcutHelpMessage>;
4
+ export default _default;
@@ -0,0 +1,10 @@
1
+ import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
2
+ import React from 'react';
3
+ import { Box, Text } from 'ink';
4
+ import { getShortcutPlatformNote, getShortcutSections } from '../config/shortcuts.js';
5
+ function ShortcutHelpMessage() {
6
+ const sections = getShortcutSections();
7
+ const note = getShortcutPlatformNote();
8
+ return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Text, { color: "cyan", bold: true, children: "\u5FEB\u6377\u952E\u5E2E\u52A9" }), _jsx(Text, { color: "gray", children: note }), sections.map((section) => (_jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsx(Text, { color: "yellow", bold: true, children: section.title }), section.items.map((item, index) => (_jsxs(Box, { children: [_jsx(Box, { width: 24, flexShrink: 0, children: _jsx(Text, { color: "green", bold: true, children: item.key }) }), _jsx(Text, { color: "white", children: item.description })] }, `${section.title}-${item.key}-${index}`)))] }, section.title)))] }));
9
+ }
10
+ export default React.memo(ShortcutHelpMessage);
@@ -22,6 +22,6 @@ function WelcomeHeader({ width }) {
22
22
  const showLogo = width >= 52;
23
23
  const appName = getAppName();
24
24
  const modelName = getModelName();
25
- return (_jsxs(Box, { flexDirection: "column", paddingX: 1, width: width, children: [showLogo && (_jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => (_jsx(Text, { color: LOGO_COLORS[i], children: line }, i))) })), _jsxs(Box, { marginTop: 0, children: [_jsx(Text, { color: "gray" }), _jsx(Text, { color: "white", bold: true, children: "Your AI-Powered Dev Companion" }), _jsx(Text, { color: "gray" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(width - 4, 48)) }) }), _jsxs(Box, { marginTop: 0, children: [_jsx(Text, { color: "gray", children: "model " }), _jsx(Text, { color: "cyan", children: modelName }), _jsxs(Text, { color: "gray", children: [" ", appName, " "] }), _jsx(Text, { color: "magenta", children: APP_VERSION })] }), _jsx(Box, { children: _jsx(Text, { color: "gray", children: truncatePath(process.cwd(), maxPath) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(width - 4, 48)) }) }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "/" }), _jsx(Text, { color: "cyan", children: "init" }), _jsx(Text, { color: "gray", children: " \u521D\u59CB\u5316 " }), _jsx(Text, { color: "gray", children: "/" }), _jsx(Text, { color: "cyan", children: "help" }), _jsx(Text, { color: "gray", children: " \u5E2E\u52A9 " }), _jsx(Text, { color: "gray", children: "/" }), _jsx(Text, { color: "cyan", children: "new" }), _jsx(Text, { color: "gray", children: " \u65B0\u4F1A\u8BDD " }), _jsx(Text, { color: "gray", children: "/" }), _jsx(Text, { color: "cyan", children: "agent" }), _jsx(Text, { color: "gray", children: " \u5207\u6362" })] }), _jsx(Text, { children: ' ' })] }));
25
+ return (_jsxs(Box, { flexDirection: "column", paddingX: 1, width: width, children: [showLogo && (_jsx(Box, { flexDirection: "column", children: LOGO_LINES.map((line, i) => (_jsx(Text, { color: LOGO_COLORS[i], children: line }, i))) })), _jsxs(Box, { marginTop: 0, children: [_jsx(Text, { color: "gray" }), _jsx(Text, { color: "white", bold: true, children: "Your AI-Powered Dev Companion" }), _jsx(Text, { color: "gray" })] }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(width - 4, 48)) }) }), _jsxs(Box, { marginTop: 0, children: [_jsx(Text, { color: "gray", children: "model " }), _jsx(Text, { color: "cyan", children: modelName }), _jsxs(Text, { color: "gray", children: [" ", appName, " "] }), _jsx(Text, { color: "magenta", children: APP_VERSION })] }), _jsx(Box, { children: _jsx(Text, { color: "gray", children: truncatePath(process.cwd(), maxPath) }) }), _jsx(Box, { marginTop: 1, children: _jsx(Text, { color: "gray", children: '─'.repeat(Math.min(width - 4, 48)) }) }), _jsxs(Box, { children: [_jsx(Text, { color: "gray", children: "/" }), _jsx(Text, { color: "cyan", children: "init" }), _jsx(Text, { color: "gray", children: " \u521D\u59CB\u5316 " }), _jsx(Text, { color: "gray", children: "/" }), _jsx(Text, { color: "cyan", children: "help" }), _jsx(Text, { color: "gray", children: " \u5E2E\u52A9 " }), _jsx(Text, { color: "gray", children: "/" }), _jsx(Text, { color: "cyan", children: "new" }), _jsx(Text, { color: "gray", children: " \u65B0\u4F1A\u8BDD " }), _jsx(Text, { color: "gray", children: "/" }), _jsx(Text, { color: "cyan", children: "agent" }), _jsx(Text, { color: "gray", children: " \u5207\u6362 " }), _jsx(Text, { color: "yellow", bold: true, children: "?" }), _jsx(Text, { color: "gray", children: " \u5FEB\u6377\u952E\u63D0\u793A" })] }), _jsx(Text, { children: ' ' })] }));
26
26
  }
27
27
  export default React.memo(WelcomeHeader);
@@ -0,0 +1,13 @@
1
+ export interface ShortcutItem {
2
+ key: string;
3
+ description: string;
4
+ }
5
+ export interface ShortcutSection {
6
+ title: string;
7
+ items: ShortcutItem[];
8
+ }
9
+ export declare function getShortcutPlatformLabel(): string;
10
+ export declare function getShortcutAltKeyLabel(): string;
11
+ export declare function getShortcutPlatformNote(): string;
12
+ export declare function getShortcutSections(): ShortcutSection[];
13
+ export declare function buildShortcutHelpText(): string;
@@ -0,0 +1,67 @@
1
+ const isMac = process.platform === 'darwin';
2
+ const isWindows = process.platform === 'win32';
3
+ export function getShortcutPlatformLabel() {
4
+ if (isMac)
5
+ return 'macOS';
6
+ if (isWindows)
7
+ return 'Windows';
8
+ return 'Linux';
9
+ }
10
+ export function getShortcutAltKeyLabel() {
11
+ return isMac ? 'Option' : 'Alt';
12
+ }
13
+ export function getShortcutPlatformNote() {
14
+ if (isMac)
15
+ return '当前平台:macOS(终端内快捷键使用 Ctrl / Option,通常不是 Cmd)';
16
+ if (isWindows)
17
+ return '当前平台:Windows(快捷键使用 Ctrl / Alt)';
18
+ return '当前平台:Linux(快捷键使用 Ctrl / Alt)';
19
+ }
20
+ export function getShortcutSections() {
21
+ const altKey = getShortcutAltKeyLabel();
22
+ const platform = getShortcutPlatformLabel();
23
+ return [
24
+ {
25
+ title: '通用快捷键',
26
+ items: [
27
+ { key: '? + Enter', description: '查看全部快捷键说明' },
28
+ { key: 'Ctrl + C', description: '退出 Jarvis(需连续按两次)' },
29
+ { key: 'Ctrl + L', description: '清屏并开始新会话' },
30
+ { key: 'Ctrl + O', description: '切换详情视图' },
31
+ { key: 'Esc', description: '中断当前任务;空闲时双击清空输入' },
32
+ { key: `${altKey} + Enter`, description: '插入换行' },
33
+ { key: 'Tab', description: '输入为空时填入提示词;斜杠菜单中补全选中项' },
34
+ { key: 'Enter', description: '发送消息;斜杠菜单中提交当前选中命令' },
35
+ ],
36
+ },
37
+ {
38
+ title: `输入编辑(${platform})`,
39
+ items: [
40
+ { key: '← / →', description: '左右移动光标' },
41
+ { key: '↑ / ↓', description: '多行输入时上下移动光标' },
42
+ { key: '↑ / ↓', description: '单行输入时切换历史记录' },
43
+ ],
44
+ },
45
+ {
46
+ title: '斜杠菜单',
47
+ items: [
48
+ { key: '/', description: '打开命令菜单' },
49
+ { key: '↑ / ↓', description: '切换命令' },
50
+ { key: 'Tab / Enter', description: '补全或提交当前命令' },
51
+ { key: 'Esc', description: '关闭命令菜单' },
52
+ ],
53
+ },
54
+ ];
55
+ }
56
+ export function buildShortcutHelpText() {
57
+ return [
58
+ '快捷键帮助',
59
+ getShortcutPlatformNote(),
60
+ '',
61
+ ...getShortcutSections().flatMap((section) => [
62
+ `${section.title}:`,
63
+ ...section.items.map((item) => ` ${item.key} ${item.description}`),
64
+ '',
65
+ ]),
66
+ ].join('\n').trim();
67
+ }
@@ -19,6 +19,7 @@ import { subscribeAgentCount, getActiveAgentCount } from '../core/spawnRegistry.
19
19
  import { logError, logInfo, logWarn } from '../core/logger.js';
20
20
  import { getAgentSubCommands } from '../commands/index.js';
21
21
  import { setActiveAgent } from '../config/agentState.js';
22
+ import { buildShortcutHelpText } from '../config/shortcuts.js';
22
23
  import { hideTerminalCursor, showTerminalCursor } from '../terminal/cursor.js';
23
24
  export default function REPL() {
24
25
  const { exit } = useApp();
@@ -200,6 +201,22 @@ export default function REPL() {
200
201
  const trimmed = value.trim();
201
202
  if (!trimmed || isProcessing || !engineRef.current)
202
203
  return;
204
+ if (trimmed === '?') {
205
+ setInput('');
206
+ slashMenu.setSlashMenuVisible(false);
207
+ if (HIDE_WELCOME_AFTER_INPUT)
208
+ setShowWelcome(false);
209
+ setMessages((prev) => [...prev, {
210
+ id: `shortcut-help-${Date.now()}`,
211
+ type: 'system',
212
+ status: 'success',
213
+ systemKind: 'shortcut_help',
214
+ content: buildShortcutHelpText(),
215
+ timestamp: Date.now(),
216
+ }]);
217
+ logInfo('ui.shortcut_help.open');
218
+ return;
219
+ }
203
220
  logInfo('ui.submit', {
204
221
  inputLength: trimmed.length,
205
222
  isSlashCommand: trimmed.startsWith('/'),
@@ -6,6 +6,7 @@ export interface Message {
6
6
  status: MessageStatus;
7
7
  content: string;
8
8
  timestamp: number;
9
+ systemKind?: 'shortcut_help';
9
10
  /** 耗时 ms */
10
11
  duration?: number;
11
12
  /** token 统计 */
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@code4bug/jarvis-agent",
3
- "version": "1.3.5",
3
+ "version": "1.3.6",
4
4
  "description": "基于 React + TypeScript + Ink 构建的命令行智能体交互界面",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",