zhitalk 0.1.1 → 0.1.2

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/CLAUDE.md CHANGED
@@ -1,38 +1,57 @@
1
1
  # CLAUDE.md
2
2
 
3
- This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
3
+ This file provides guidance to Claude Code when working with code in this repository.
4
4
 
5
5
  ## Project Overview
6
6
 
7
- Zhitalk is a personal AI Agent (like OpenClaw) running in the terminal. It uses LangGraph for agent orchestration, Moonshot Kimi as the LLM, and Commander.js for CLI commands.
7
+ Zhitalk is a terminal AI Agent using LangGraph for agent orchestration, OpenAI-compatible APIs (default Moonshot Kimi), and Commander.js. Node.js >=22 is required.
8
8
 
9
9
  ## Commands
10
10
 
11
11
  ```bash
12
- pnpm dev # Run with ts-node (development)
13
- pnpm test # Run tests
14
- pnpm test:watch # Run tests in watch mode
15
- pnpm test:coverage # Run tests with coverage
16
- pnpm build # Compile TypeScript, copy skills, fix permissions
17
- pnpm start # Run compiled output from dist/
18
- pnpm clean # Remove dist directory
12
+ pnpm dev # ts-node/esm development
13
+ pnpm test # Jest with experimental-vm-modules
14
+ pnpm test:watch # Watch mode
15
+ pnpm test:coverage
16
+ pnpm build # Compile to dist/
17
+ pnpm start # Run dist/
18
+ pnpm clean # Remove dist/
19
19
  ```
20
20
 
21
- Run a single test with `pnpm test -- <pattern>`.
21
+ Run a single test: `pnpm test -- <pattern>`.
22
22
 
23
23
  ## Architecture
24
24
 
25
- See [`src/agent/CLAUDE.md`](src/agent/CLAUDE.md) for details on the agent graph, tools, skills, memory, and session management.
25
+ See [`src/agent/CLAUDE.md`](src/agent/CLAUDE.md) for the agent graph, tools, skills, memory, hooks, sub-agents, MCP, and session management.
26
+
27
+ Entry point `src/index.ts` parses CLI args, runs `src/install.ts` for first-time setup, then launches `interactiveChat()` from `src/agent/cli.ts`.
28
+
29
+ ### CLI Modes
30
+
31
+ - **Interactive** (default): `zhitalk` launches a REPL with streaming output, ESC cancellation, and slash commands (`/new`, `/rewind`, `/sessions`, `/compact`).
32
+ - **Single-shot**: `zhitalk run <query> [options]` executes one turn and exits:
33
+ - `-a, --auto-approve` — auto-approve all tool calls (non-interactive)
34
+ - `-j, --json` — output JSON with `response`, `usage`, `thread_id`
35
+ - `-t, --thread-id <id>` — reuse a specific session
36
+
37
+ ### Agent APIs
38
+
39
+ - `runAgentStream(userMessage, onToken, onToolConfirmation, threadId)` — interactive streaming with per-tool confirmation callbacks.
40
+ - `runAgent(userMessage, threadId?, { autoApprove?, signal? })` — non-streaming, terminal-free API. Used by the `run` CLI command and by tests.
26
41
 
27
42
  ## TypeScript Config
28
43
 
29
- Module type is `commonjs`, output goes to `dist/`. The build script also copies `src/agent/skills/` to `dist/agent/skills/` and makes the CLI executable.
44
+ ES modules (`"type": "module"`), output goes to `dist/`. The build script fixes CLI executable permissions.
30
45
 
31
46
  ## Testing
32
47
 
33
- Jest with ts-jest preset. Test files match `**/*.test.ts`. Run a single test with `pnpm test -- <pattern>`.
48
+ Jest with ts-jest preset and `--experimental-vm-modules`. Test files match `**/*.test.ts`.
34
49
 
35
- ## behavior rules
50
+ - `FakeChatOpenAI` (`src/agent/testing/fake_model.ts`) replays scripted message sequences for testing the agent loop without calling real LLM APIs.
51
+ - `createAgentGraph(nativeTools, customModel?)` accepts an optional model for testing.
52
+ - Existing tests favor integration style: real temp files, real SQLite, mocked `fetch` for network tools.
53
+
54
+ ## Behavior Rules
36
55
 
37
56
  **Tradeoff:** These guidelines bias toward caution over speed. For trivial tasks, use judgment.
38
57
 
@@ -43,7 +62,7 @@ Jest with ts-jest preset. Test files match `**/*.test.ts`. Run a single test wit
43
62
  Before implementing:
44
63
 
45
64
  - State your assumptions explicitly. If uncertain, ask.
46
- - If multiple interpretations exist, present them - don't pick silently.
65
+ - If multiple interpretations exist, present them don't pick silently.
47
66
  - If a simpler approach exists, say so. Push back when warranted.
48
67
  - If something is unclear, stop. Name what's confusing. Ask.
49
68
 
@@ -57,8 +76,6 @@ Before implementing:
57
76
  - No error handling for impossible scenarios.
58
77
  - If you write 200 lines and it could be 50, rewrite it.
59
78
 
60
- Ask yourself: "Would a senior engineer say this is overcomplicated?" If yes, simplify.
61
-
62
79
  ### 3. Surgical Changes
63
80
 
64
81
  **Touch only what you must. Clean up only your own mess.**
@@ -68,12 +85,7 @@ When editing existing code:
68
85
  - Don't "improve" adjacent code, comments, or formatting.
69
86
  - Don't refactor things that aren't broken.
70
87
  - Match existing style, even if you'd do it differently.
71
- - If you notice unrelated dead code, mention it - don't delete it.
72
-
73
- When your changes create orphans:
74
-
75
- - Remove imports/variables/functions that YOUR changes made unused.
76
- - Don't remove pre-existing dead code unless asked.
88
+ - When your changes create orphans, remove imports/variables/functions that YOUR changes made unused.
77
89
 
78
90
  The test: Every changed line should trace directly to the user's request.
79
91
 
package/README.md CHANGED
@@ -42,7 +42,17 @@ zhitalk
42
42
  ### Command
43
43
 
44
44
  ```bash
45
+ # 开发
45
46
  pnpm dev
46
47
  pnpm test
47
48
  pnpm build
49
+
50
+ # 交互模式(默认)
51
+ zhitalk
52
+
53
+ # 单次命令模式(执行一次即退出)
54
+ zhitalk run "帮我查一下北京天气"
55
+ zhitalk run "帮我查一下北京天气" --auto-approve # 自动批准 tools
56
+ zhitalk run "帮我查一下北京天气" --json # JSON 输出
57
+ zhitalk run "继续" --thread-id xxx # 指定会话 ID
48
58
  ```
@@ -1,4 +1,43 @@
1
- import { type UsageMetadata } from '@langchain/core/messages';
1
+ import { type UsageMetadata, ToolMessage } from '@langchain/core/messages';
2
+ import { CompiledStateGraph } from '@langchain/langgraph';
3
+ import type { BaseMessage } from '@langchain/core/messages';
4
+ import { tools } from './tools.js';
5
+ export declare function createAgentGraph(toolList: typeof tools, customModel?: any): CompiledStateGraph<{
6
+ messages: BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[];
7
+ contextSummary: string | null;
8
+ compressionCount: number;
9
+ lastCompressedIndex: number;
10
+ todoList: import("./todo.js").TodoItem[] | null;
11
+ }, {
12
+ messages?: BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[] | import("@langchain/langgraph").OverwriteValue<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[]> | undefined;
13
+ contextSummary?: string | import("@langchain/langgraph").OverwriteValue<string | null> | null | undefined;
14
+ compressionCount?: number | import("@langchain/langgraph").OverwriteValue<number> | undefined;
15
+ lastCompressedIndex?: number | import("@langchain/langgraph").OverwriteValue<number> | undefined;
16
+ todoList?: import("./todo.js").TodoItem[] | import("@langchain/langgraph").OverwriteValue<import("./todo.js").TodoItem[] | null> | null | undefined;
17
+ }, "tools" | "__start__" | "model_request", {
18
+ messages: import("@langchain/langgraph").BaseChannel<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[], BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[] | import("@langchain/langgraph").OverwriteValue<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[]>, unknown>;
19
+ contextSummary: import("@langchain/langgraph").BaseChannel<string | null, string | import("@langchain/langgraph").OverwriteValue<string | null> | null, unknown>;
20
+ compressionCount: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
21
+ lastCompressedIndex: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
22
+ todoList: import("@langchain/langgraph").BaseChannel<import("./todo.js").TodoItem[] | null, import("./todo.js").TodoItem[] | import("@langchain/langgraph").OverwriteValue<import("./todo.js").TodoItem[] | null> | null, unknown>;
23
+ }, {
24
+ messages: import("@langchain/langgraph").BaseChannel<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[], BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[] | import("@langchain/langgraph").OverwriteValue<BaseMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>, import("@langchain/core/messages").MessageType>[]>, unknown>;
25
+ contextSummary: import("@langchain/langgraph").BaseChannel<string | null, string | import("@langchain/langgraph").OverwriteValue<string | null> | null, unknown>;
26
+ compressionCount: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
27
+ lastCompressedIndex: import("@langchain/langgraph").BaseChannel<number, number | import("@langchain/langgraph").OverwriteValue<number>, unknown>;
28
+ todoList: import("@langchain/langgraph").BaseChannel<import("./todo.js").TodoItem[] | null, import("./todo.js").TodoItem[] | import("@langchain/langgraph").OverwriteValue<import("./todo.js").TodoItem[] | null> | null, unknown>;
29
+ }, import("@langchain/langgraph").StateDefinition, {
30
+ model_request: {
31
+ messages: any[];
32
+ };
33
+ tools: {
34
+ messages: never[];
35
+ todoList?: undefined;
36
+ } | {
37
+ messages: ToolMessage<import("@langchain/core/messages").MessageStructure<import("@langchain/core/messages").MessageToolSet>>[];
38
+ todoList: import("./todo.js").TodoItem[] | null;
39
+ };
40
+ }, unknown, unknown, []>;
2
41
  export declare function initAgent(): Promise<void>;
3
42
  /**
4
43
  * 以流式方式运行 agent,将 token 逐个回调给调用方
@@ -12,6 +51,22 @@ export declare function runAgentStream(userMessage: string, onToken: (token: str
12
51
  response: string;
13
52
  usageMetadata?: UsageMetadata;
14
53
  }>;
54
+ /**
55
+ * 以非流式方式运行 agent,无需终端回调,适合脚本和测试
56
+ * @param {string} userMessage - 用户输入
57
+ * @param {string} threadId - 会话 ID
58
+ * @param {Object} options - 选项
59
+ * @param {boolean} options.autoApprove - 是否自动批准所有 tool 调用
60
+ * @param {AbortSignal} options.signal - 取消信号
61
+ * @returns {Promise<{ response: string; usageMetadata?: UsageMetadata }>}
62
+ */
63
+ export declare function runAgent(userMessage: string, threadId?: string, options?: {
64
+ autoApprove?: boolean;
65
+ signal?: AbortSignal;
66
+ }): Promise<{
67
+ response: string;
68
+ usageMetadata?: UsageMetadata;
69
+ }>;
15
70
  /**
16
71
  * 启动一个 subagent 执行独立任务,完成后返回结果
17
72
  * @param {string} prompt - 给 subagent 的任务提示
@@ -14,8 +14,6 @@ import { checkExecPermission } from './permission/exec.js';
14
14
  import { checkNetworkPermission } from './permission/network.js';
15
15
  import { runPreToolUseHooks, runPostToolUseHooks } from './hooks.js';
16
16
  import { createModel } from './model.js';
17
- // ── 模型 ──────────────────────────────────────────────────
18
- const model = createModel({ streaming: true });
19
17
  // ── State Schema ──────────────────────────────────────────
20
18
  const StateAnnotation = Annotation.Root({
21
19
  messages: Annotation({
@@ -100,8 +98,8 @@ function ensureCompleteToolCalls(messages) {
100
98
  // ── 记忆 ──────────────────────────────────────────────────
101
99
  const checkpointer = SqliteSaver.fromConnString(DB_PATH);
102
100
  // ── Agent Graph 工厂 ──────────────────────────────────────
103
- function createAgentGraph(toolList) {
104
- const modelWithTheseTools = model.bindTools(toolList);
101
+ export function createAgentGraph(toolList, customModel) {
102
+ const modelWithTheseTools = (customModel || createModel({ streaming: true })).bindTools(toolList);
105
103
  async function modelRequest(state, config) {
106
104
  let modelMessages = state.messages ?? [];
107
105
  // compact context
@@ -269,6 +267,10 @@ function createAgentGraph(toolList) {
269
267
  if (confirmCalls.length === 0) {
270
268
  return { messages: [...allowOutputs, ...blockMessages], todoList: todoResult.todoList };
271
269
  }
270
+ if (config?.configurable?.auto_approve) {
271
+ const confirmOutputs = await executeCalls(confirmCalls);
272
+ return { messages: [...allowOutputs, ...blockMessages, ...confirmOutputs], todoList: todoResult.todoList };
273
+ }
272
274
  const result = interrupt({ toolCalls: confirmCalls });
273
275
  if (result !== 'approved') {
274
276
  const deniedMessages = confirmCalls.map((call) => new ToolMessage({
@@ -316,8 +318,8 @@ function getSubAgent() {
316
318
  return subAgent;
317
319
  }
318
320
  // ── 核心运行逻辑 ──────────────────────────────────────────
319
- async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation, threadId, signal) {
320
- const config = { configurable: { thread_id: threadId } };
321
+ async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation, threadId, signal, options) {
322
+ const config = { configurable: { thread_id: threadId, auto_approve: options?.autoApprove } };
321
323
  let fullResponse = '';
322
324
  let usageMetadata;
323
325
  let input = { messages: [new HumanMessage(userMessage)] };
@@ -379,6 +381,18 @@ async function _runAgent(compiledAgent, userMessage, onToken, onToolConfirmation
379
381
  export async function runAgentStream(userMessage, onToken, onToolConfirmation, threadId = 'default-session', signal) {
380
382
  return _runAgent(getAgent(), userMessage, onToken, onToolConfirmation, threadId, signal);
381
383
  }
384
+ /**
385
+ * 以非流式方式运行 agent,无需终端回调,适合脚本和测试
386
+ * @param {string} userMessage - 用户输入
387
+ * @param {string} threadId - 会话 ID
388
+ * @param {Object} options - 选项
389
+ * @param {boolean} options.autoApprove - 是否自动批准所有 tool 调用
390
+ * @param {AbortSignal} options.signal - 取消信号
391
+ * @returns {Promise<{ response: string; usageMetadata?: UsageMetadata }>}
392
+ */
393
+ export async function runAgent(userMessage, threadId = 'default-session', options) {
394
+ return _runAgent(getAgent(), userMessage, () => { }, async () => true, threadId, options?.signal, options);
395
+ }
382
396
  /**
383
397
  * 启动一个 subagent 执行独立任务,完成后返回结果
384
398
  * @param {string} prompt - 给 subagent 的任务提示
@@ -18,8 +18,10 @@ const MODEL_CONTEXT_LIMITS = {
18
18
  'deepseek-r1': 65536,
19
19
  // MiniMax
20
20
  'minimax-text-01': 1048576,
21
- 'minimax-m1': 1048576,
22
- 'minimax-m3': 1048576,
21
+ 'minimax-m2': 204800,
22
+ 'minimax-m2.7': 204800,
23
+ 'minimax-m2.5': 204800,
24
+ 'minimax-m3': 1000000,
23
25
  // GLM (Zhipu)
24
26
  'glm-4': 131072,
25
27
  'glm-4-plus': 131072,
@@ -3,13 +3,10 @@ import path from 'path';
3
3
  import { discoverSkills, getSkillsListText } from './skills.js';
4
4
  import { WORKSPACE_DIR } from './config.js';
5
5
  import { listRecentMemories } from './db.js';
6
- // ── Skills ────────────────────────────────────────────────
7
- discoverSkills();
8
- const skillsText = getSkillsListText();
9
6
  const basePrompt = 'You are zhitalk, 中文名字叫智语, a personal AI Agent like OpenClaw, including tools, skills, memory, hook, sub-agent, MCP server, etc. Run in the terminal.';
10
- function readProfile() {
7
+ function readProfile(profilePath) {
11
8
  try {
12
- const filePath = path.join(WORKSPACE_DIR, '.data', 'profile.md');
9
+ const filePath = profilePath || path.join(WORKSPACE_DIR, '.data', 'profile.md');
13
10
  const content = fs.readFileSync(filePath, 'utf-8').trim();
14
11
  return `<profile_info>${content}</profile_info>`;
15
12
  }
@@ -17,7 +14,8 @@ function readProfile() {
17
14
  return '<profile_info></profile_info>';
18
15
  }
19
16
  }
20
- const profilePrompt = `## User Profile
17
+ function getProfilePrompt() {
18
+ return `## User Profile
21
19
 
22
20
  When you learn information about the user that fits the following categories, store it as profile information for future personalization.
23
21
 
@@ -32,22 +30,28 @@ When you learn information about the user that fits the following categories, st
32
30
 
33
31
  Here is the user's current profile information:
34
32
  ${readProfile()}`;
33
+ }
35
34
  const memoryPrompt = `## Memory Management Rules
36
35
 
37
36
  - When deleting a memory, first use memory_retrieve to find its id, then call memory_delete with that id. If no matching memory is found, politely inform the user.
38
37
  - When updating a memory, first delete the old memory, then create a new one.
39
38
  - When information fits the categories in <profile_template>, do not record it as a memory. It will be stored in the profile file instead.`;
40
- const dateTimePrompt = `## Current DateTime
39
+ function getDateTimePrompt() {
40
+ return `## Current DateTime
41
41
 
42
42
  ${new Date().toLocaleString('zh-CN', { timeZone: 'Asia/Shanghai' })}`;
43
- const skillList = skillsText
44
- ? `You have access to the following skills. When a user's request matches a skill's description, you MUST call the \`load_skill\` tool to load that skill's full instructions, then follow them.
43
+ }
44
+ function getSkillPrompt() {
45
+ const skillsText = getSkillsListText();
46
+ const skillList = skillsText
47
+ ? `You have access to the following skills. When a user's request matches a skill's description, you MUST call the \`load_skill\` tool to load that skill's full instructions, then follow them.
45
48
 
46
49
  ${skillsText}`
47
- : 'There are no skills available.';
48
- const skillPrompt = `## Skills
50
+ : 'There are no skills available.';
51
+ return `## Skills
49
52
 
50
53
  ${skillList}\n\nNote: If you want to add a new skill, it must be placed in the ${path.join(WORKSPACE_DIR, 'skills')} directory.`;
54
+ }
51
55
  const todoPrompt = `## Task Planning
52
56
 
53
57
  When a user's request is a complex, multi-step task (such as data analysis, writing a document, planning a task, or any task clearly requiring more than 3 steps), you MUST:
@@ -62,6 +66,7 @@ export function invalidateMemoryCache() {
62
66
  cachedMemories = null;
63
67
  }
64
68
  export function buildSystemPrompt() {
69
+ discoverSkills();
65
70
  if (!cachedMemories) {
66
71
  cachedMemories = listRecentMemories(10);
67
72
  }
@@ -70,11 +75,11 @@ export function buildSystemPrompt() {
70
75
  : '';
71
76
  return [
72
77
  basePrompt,
73
- profilePrompt,
78
+ getProfilePrompt(),
74
79
  memorySection,
75
80
  memoryPrompt,
76
- skillPrompt,
77
- dateTimePrompt,
81
+ getSkillPrompt(),
82
+ getDateTimePrompt(),
78
83
  todoPrompt,
79
84
  ]
80
85
  .filter(Boolean)
@@ -3,6 +3,7 @@ export interface SkillInfo {
3
3
  description: string;
4
4
  dirPath: string;
5
5
  }
6
- export declare function discoverSkills(): SkillInfo[];
6
+ export declare function discoverSkills(searchPaths?: string[]): SkillInfo[];
7
7
  export declare function loadSkill(name: string): string | null;
8
8
  export declare function getSkillsListText(): string;
9
+ export declare function clearSkillsCache(): void;
@@ -24,10 +24,10 @@ function parseFrontmatter(content) {
24
24
  }
25
25
  return { name: result.name || '', description: result.description || '' };
26
26
  }
27
- export function discoverSkills() {
27
+ export function discoverSkills(searchPaths) {
28
28
  skills.length = 0;
29
29
  skillContentMap.clear();
30
- const skillDirs = [
30
+ const skillDirs = searchPaths || [
31
31
  join(WORKSPACE_DIR, 'skills'),
32
32
  join(process.cwd(), '.agents', 'skills'),
33
33
  ];
@@ -81,3 +81,7 @@ export function getSkillsListText() {
81
81
  // console.log('skills...\n', lines.join('\n'))
82
82
  return lines.join('\n');
83
83
  }
84
+ export function clearSkillsCache() {
85
+ skills.length = 0;
86
+ skillContentMap.clear();
87
+ }
@@ -0,0 +1,26 @@
1
+ import { AIMessage, type BaseMessage } from '@langchain/core/messages';
2
+ /**
3
+ * Fake LLM for testing the agent loop without calling real APIs.
4
+ * Replays a scripted sequence of messages on each invoke() call.
5
+ */
6
+ export declare class FakeChatOpenAI {
7
+ private responses;
8
+ private callIndex;
9
+ constructor(responses: BaseMessage[]);
10
+ bindTools(_tools: any[]): this;
11
+ invoke(_messages: BaseMessage[], _config?: any): Promise<BaseMessage>;
12
+ stream(_messages: BaseMessage[], _config?: any): AsyncGenerator<BaseMessage>;
13
+ getCallCount(): number;
14
+ }
15
+ /**
16
+ * Helper to build an AIMessage with tool_calls.
17
+ */
18
+ export declare function aiMessageWithTools(toolCalls: Array<{
19
+ name: string;
20
+ args: Record<string, any>;
21
+ id?: string;
22
+ }>): AIMessage;
23
+ /**
24
+ * Helper to build a simple text AIMessage.
25
+ */
26
+ export declare function aiMessage(content: string): AIMessage;
@@ -0,0 +1,48 @@
1
+ import { AIMessage, } from '@langchain/core/messages';
2
+ /**
3
+ * Fake LLM for testing the agent loop without calling real APIs.
4
+ * Replays a scripted sequence of messages on each invoke() call.
5
+ */
6
+ export class FakeChatOpenAI {
7
+ constructor(responses) {
8
+ this.callIndex = 0;
9
+ this.responses = responses;
10
+ }
11
+ bindTools(_tools) {
12
+ return this;
13
+ }
14
+ async invoke(_messages, _config) {
15
+ const response = this.responses[this.callIndex];
16
+ if (!response) {
17
+ throw new Error(`FakeChatOpenAI: no more responses (call ${this.callIndex})`);
18
+ }
19
+ this.callIndex++;
20
+ return response;
21
+ }
22
+ async *stream(_messages, _config) {
23
+ const response = await this.invoke(_messages, _config);
24
+ yield response;
25
+ }
26
+ getCallCount() {
27
+ return this.callIndex;
28
+ }
29
+ }
30
+ /**
31
+ * Helper to build an AIMessage with tool_calls.
32
+ */
33
+ export function aiMessageWithTools(toolCalls) {
34
+ return new AIMessage({
35
+ content: '',
36
+ tool_calls: toolCalls.map((tc, i) => ({
37
+ name: tc.name,
38
+ args: tc.args,
39
+ id: tc.id || `fake-call-${i}`,
40
+ })),
41
+ });
42
+ }
43
+ /**
44
+ * Helper to build a simple text AIMessage.
45
+ */
46
+ export function aiMessage(content) {
47
+ return new AIMessage({ content });
48
+ }
package/dist/index.d.ts CHANGED
@@ -1,2 +1,10 @@
1
1
  #!/usr/bin/env node
2
- export {};
2
+ export interface RunOptions {
3
+ autoApprove?: boolean;
4
+ threadId?: string;
5
+ }
6
+ export declare function runOneShot(query: string, options?: RunOptions): Promise<{
7
+ response: string;
8
+ usageMetadata?: any;
9
+ threadId: string;
10
+ }>;
package/dist/index.js CHANGED
@@ -6,6 +6,36 @@ import { fileURLToPath } from 'url';
6
6
  import { CONFIG_PATH } from './agent/config.js';
7
7
  const __dirname = dirname(fileURLToPath(import.meta.url));
8
8
  const pkg = JSON.parse(readFileSync(join(__dirname, '../package.json'), 'utf-8'));
9
+ async function initApp() {
10
+ const { initColors } = await import('./agent/colors.js');
11
+ const { initDb } = await import('./agent/db.js');
12
+ const { initAgent } = await import('./agent/agent.js');
13
+ const { checkModel } = await import('./agent/model.js');
14
+ const { shutdownMcp } = await import('./agent/mcp/index.js');
15
+ await initColors();
16
+ await checkModel();
17
+ initDb();
18
+ await initAgent();
19
+ return {
20
+ shutdown: async () => {
21
+ await shutdownMcp();
22
+ },
23
+ };
24
+ }
25
+ export async function runOneShot(query, options = {}) {
26
+ const threadId = options.threadId || `oneshot-${Date.now()}`;
27
+ const { shutdown } = await initApp();
28
+ try {
29
+ const { runAgent } = await import('./agent/agent.js');
30
+ const result = await runAgent(query, threadId, {
31
+ autoApprove: options.autoApprove,
32
+ });
33
+ return { response: result.response, usageMetadata: result.usageMetadata, threadId };
34
+ }
35
+ finally {
36
+ await shutdown();
37
+ }
38
+ }
9
39
  async function main() {
10
40
  const program = new Command();
11
41
  program.name(pkg.name).description(pkg.description).version(pkg.version);
@@ -16,30 +46,62 @@ async function main() {
16
46
  console.log(`配置文件位置: ${CONFIG_PATH}`);
17
47
  console.log('请使用编辑器打开并修改配置,修改完成后重启 zhitalk');
18
48
  });
19
- if (process.argv.length > 2) {
20
- program.parse();
21
- return;
22
- }
23
- if (!existsSync(CONFIG_PATH)) {
24
- const { runInstall } = await import('./install.js');
25
- await runInstall();
26
- return;
27
- }
28
- const { initColors } = await import('./agent/colors.js');
29
- const { initDb } = await import('./agent/db.js');
30
- const { initAgent } = await import('./agent/agent.js');
31
- const { interactiveChat } = await import('./agent/cli.js');
32
- const { shutdownMcp } = await import('./agent/mcp/index.js');
33
- const { checkModel } = await import('./agent/model.js');
34
- await initColors();
35
- await checkModel();
36
- initDb();
37
- await initAgent();
38
- process.once('SIGINT', async () => {
39
- await shutdownMcp();
40
- process.exit(0);
49
+ program
50
+ .command('run <query>')
51
+ .description('Run a single query and exit')
52
+ .option('-a, --auto-approve', 'Auto approve all tool calls without confirmation', false)
53
+ .option('-j, --json', 'Output result as JSON', false)
54
+ .option('-t, --thread-id <id>', 'Thread ID for the session')
55
+ .action(async (query, options) => {
56
+ if (!existsSync(CONFIG_PATH)) {
57
+ console.error(`找不到配置文件 ${CONFIG_PATH}`);
58
+ console.error('请先运行 zhitalk 进行初始化');
59
+ process.exit(1);
60
+ }
61
+ try {
62
+ const result = await runOneShot(query, {
63
+ autoApprove: options.autoApprove,
64
+ threadId: options.threadId,
65
+ });
66
+ if (options.json) {
67
+ console.log(JSON.stringify({
68
+ response: result.response,
69
+ usage: result.usageMetadata || null,
70
+ thread_id: result.threadId,
71
+ }, null, 2));
72
+ }
73
+ else {
74
+ console.log(result.response);
75
+ }
76
+ }
77
+ catch (err) {
78
+ console.error(`Error: ${err.message}`);
79
+ process.exitCode = 1;
80
+ }
81
+ finally {
82
+ process.exit();
83
+ }
84
+ });
85
+ program.action(async () => {
86
+ if (!existsSync(CONFIG_PATH)) {
87
+ const { runInstall } = await import('./install.js');
88
+ await runInstall();
89
+ return;
90
+ }
91
+ const { interactiveChat } = await import('./agent/cli.js');
92
+ const { shutdownMcp } = await import('./agent/mcp/index.js');
93
+ const { shutdown } = await initApp();
94
+ process.once('SIGINT', async () => {
95
+ await shutdown();
96
+ process.exit(0);
97
+ });
98
+ await interactiveChat();
99
+ await shutdown();
41
100
  });
42
- await interactiveChat();
43
- await shutdownMcp();
101
+ program.parse();
102
+ }
103
+ import { pathToFileURL } from 'url';
104
+ // 仅在直接运行本文件时执行 main,避免被测试导入时自动执行
105
+ if (import.meta.url === pathToFileURL(process.argv[1]).href) {
106
+ main();
44
107
  }
45
- main();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "zhitalk",
3
- "version": "0.1.1",
3
+ "version": "0.1.2",
4
4
  "description": "A personal AI Agent like OpenClaw, including tools, skills, memory, hook, sub-agent, MCP server, etc. Run in the terminal.",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",