chore: rebrand runtime and docs to EJClaw
@@ -1,11 +1,11 @@
|
|||||||
---
|
---
|
||||||
name: update-nanoclaw
|
name: update-ejclaw
|
||||||
description: Safely merge upstream NanoClaw changes into a customized install with minimal drift.
|
description: Safely merge upstream EJClaw changes into a customized install with minimal drift.
|
||||||
---
|
---
|
||||||
|
|
||||||
# Update NanoClaw
|
# Update EJClaw
|
||||||
|
|
||||||
커스텀된 NanoClaw에 upstream 변경을 가져올 때 쓰는 절차입니다. 현재 기준 중요 경로는 `src/`, `runners/`, `setup/`, `.claude/skills/`, `groups/`, `README.md` 입니다.
|
커스텀된 EJClaw에 upstream 변경을 가져올 때 쓰는 절차입니다. 현재 기준 중요 경로는 `src/`, `runners/`, `setup/`, `.claude/skills/`, `groups/`, `README.md` 입니다.
|
||||||
|
|
||||||
## 원칙
|
## 원칙
|
||||||
|
|
||||||
@@ -76,16 +76,16 @@ git cherry-pick <hash1> <hash2>
|
|||||||
## 6. 검증
|
## 6. 검증
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run typecheck
|
pnpm typecheck
|
||||||
npm test
|
pnpm test
|
||||||
npm run build:runners
|
pnpm build:runners
|
||||||
npm run build
|
pnpm build
|
||||||
```
|
```
|
||||||
|
|
||||||
런타임 경로가 바뀌었으면 추가로 확인합니다.
|
런타임 경로가 바뀌었으면 추가로 확인합니다.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
npm run setup -- --step verify
|
pnpm setup -- --step verify
|
||||||
```
|
```
|
||||||
|
|
||||||
## 7. 요약
|
## 7. 요약
|
||||||
@@ -1,8 +1,8 @@
|
|||||||
# Changelog
|
# Changelog
|
||||||
|
|
||||||
All notable changes to NanoClaw will be documented in this file.
|
All notable changes to EJClaw will be documented in this file.
|
||||||
|
|
||||||
## [1.2.0](https://github.com/qwibitai/nanoclaw/compare/v1.1.6...v1.2.0)
|
## [1.2.0](https://github.com/phj1081/EJClaw/compare/v1.1.6...v1.2.0)
|
||||||
|
|
||||||
[BREAKING] WhatsApp removed from core, now a skill. Run `/add-whatsapp` to re-add (existing auth/groups preserved).
|
[BREAKING] WhatsApp removed from core, now a skill. Run `/add-whatsapp` to re-add (existing auth/groups preserved).
|
||||||
- **fix:** Prevent scheduled tasks from executing twice when container runtime exceeds poll interval (#138, #669)
|
- **fix:** Prevent scheduled tasks from executing twice when container runtime exceeds poll interval (#138, #669)
|
||||||
|
|||||||
24
CLAUDE.md
@@ -1,10 +1,10 @@
|
|||||||
# NanoClaw
|
# EJClaw
|
||||||
|
|
||||||
Dual-agent AI assistant (Claude Code + Codex) over Discord. Based on [qwibitai/nanoclaw](https://github.com/qwibitai/nanoclaw).
|
Dual-agent AI assistant (Claude Code + Codex) over Discord. Originally derived from [qwibitai/nanoclaw](https://github.com/qwibitai/nanoclaw).
|
||||||
|
|
||||||
## Quick Context
|
## Quick Context
|
||||||
|
|
||||||
Two systemd services (`nanoclaw`, `nanoclaw-codex`) share the same codebase but run with separate stores, data, and groups (will be unified — DB supports shared access via WAL mode + service partitioning). Agents run as direct host processes (no containers). Claude Code uses the Agent SDK; Codex uses the Codex SDK (`codex exec`). Auth via `CLAUDE_CODE_OAUTH_TOKEN` in `.env` (1-year token from `claude setup-token`).
|
Two systemd services (`ejclaw`, `ejclaw-codex`) share the same codebase but run with separate stores, data, and groups (will be unified — DB supports shared access via WAL mode + service partitioning). Agents run as direct host processes (no containers). Claude Code uses the Agent SDK; Codex uses the Codex SDK (`codex exec`). Auth via `CLAUDE_CODE_OAUTH_TOKEN` in `.env` (1-year token from `claude setup-token`).
|
||||||
|
|
||||||
## Key Files
|
## Key Files
|
||||||
|
|
||||||
@@ -29,7 +29,7 @@ Two systemd services (`nanoclaw`, `nanoclaw-codex`) share the same codebase but
|
|||||||
| `/setup` | First-time installation, authentication, service configuration |
|
| `/setup` | First-time installation, authentication, service configuration |
|
||||||
| `/customize` | Adding channels, integrations, changing behavior |
|
| `/customize` | Adding channels, integrations, changing behavior |
|
||||||
| `/debug` | Agent issues, logs, troubleshooting |
|
| `/debug` | Agent issues, logs, troubleshooting |
|
||||||
| `/update-nanoclaw` | Bring upstream NanoClaw updates into a customized install |
|
| `/update-ejclaw` | Bring upstream EJClaw updates into a customized install |
|
||||||
| `/qodo-pr-resolver` | Fetch and fix Qodo PR review issues interactively or in batch |
|
| `/qodo-pr-resolver` | Fetch and fix Qodo PR review issues interactively or in batch |
|
||||||
| `/get-qodo-rules` | Load org- and repo-level coding rules from Qodo before code tasks |
|
| `/get-qodo-rules` | Load org- and repo-level coding rules from Qodo before code tasks |
|
||||||
|
|
||||||
@@ -45,17 +45,17 @@ npm run dev # Dev mode with hot reload
|
|||||||
|
|
||||||
Service management (Linux):
|
Service management (Linux):
|
||||||
```bash
|
```bash
|
||||||
systemctl --user restart nanoclaw nanoclaw-codex # Restart both
|
systemctl --user restart ejclaw ejclaw-codex # Restart both
|
||||||
systemctl --user status nanoclaw # Check status
|
systemctl --user status ejclaw # Check status
|
||||||
journalctl --user -u nanoclaw -f # Follow logs
|
journalctl --user -u ejclaw -f # Follow logs
|
||||||
```
|
```
|
||||||
|
|
||||||
Deploy to server: `scp dist/*.js clone-ej@100.64.185.108:~/nanoclaw/dist/`
|
Deploy to server: `scp dist/*.js clone-ej@100.64.185.108:~/EJClaw/dist/`
|
||||||
|
|
||||||
## Dual-Service Architecture
|
## Dual-Service Architecture
|
||||||
|
|
||||||
- `nanoclaw.service` — Claude Code bot (`@claude`), `SERVICE_ID=claude`, `SERVICE_AGENT_TYPE=claude-code`
|
- `ejclaw.service` — Claude Code bot (`@claude`), `SERVICE_ID=claude`, `SERVICE_AGENT_TYPE=claude-code`
|
||||||
- `nanoclaw-codex.service` — Codex bot (`@codex`), `SERVICE_ID=codex`, `SERVICE_AGENT_TYPE=codex`
|
- `ejclaw-codex.service` — Codex bot (`@codex`), `SERVICE_ID=codex`, `SERVICE_AGENT_TYPE=codex`
|
||||||
- Both share the same codebase (`dist/index.js`), differentiated by env vars
|
- Both share the same codebase (`dist/index.js`), differentiated by env vars
|
||||||
- Unified dirs (`store/`, `groups/`, `data/` shared by both services):
|
- Unified dirs (`store/`, `groups/`, `data/` shared by both services):
|
||||||
- `router_state`: keys prefixed with `{SERVICE_ID}:` (e.g., `claude:last_timestamp`)
|
- `router_state`: keys prefixed with `{SERVICE_ID}:` (e.g., `claude:last_timestamp`)
|
||||||
@@ -70,8 +70,8 @@ Unified DB + directories (both services share `store/`, `groups/`, `data/`):
|
|||||||
| 항목 | 경로 |
|
| 항목 | 경로 |
|
||||||
|------|------|
|
|------|------|
|
||||||
| **DB** | `store/messages.db` (공유, WAL 모드) |
|
| **DB** | `store/messages.db` (공유, WAL 모드) |
|
||||||
| 서비스 로그 (Claude) | `journalctl --user -u nanoclaw -f` 또는 `logs/nanoclaw.log` |
|
| 서비스 로그 (Claude) | `journalctl --user -u ejclaw -f` 또는 `logs/ejclaw.log` |
|
||||||
| 서비스 로그 (Codex) | `journalctl --user -u nanoclaw-codex -f` 또는 `logs/nanoclaw-codex.log` |
|
| 서비스 로그 (Codex) | `journalctl --user -u ejclaw-codex -f` 또는 `logs/ejclaw-codex.log` |
|
||||||
| 그룹별 로그 | `groups/{name}/logs/` (공유 채널은 양쪽 봇 로그가 같은 폴더) |
|
| 그룹별 로그 | `groups/{name}/logs/` (공유 채널은 양쪽 봇 로그가 같은 폴더) |
|
||||||
| Claude 세션 | `data/sessions/{name}/.claude/` |
|
| Claude 세션 | `data/sessions/{name}/.claude/` |
|
||||||
| Codex 세션 | `data/sessions/{name}/.codex/` |
|
| Codex 세션 | `data/sessions/{name}/.codex/` |
|
||||||
|
|||||||
@@ -1,6 +1,6 @@
|
|||||||
# Contributors
|
# Contributors
|
||||||
|
|
||||||
Thanks to everyone who has contributed to NanoClaw!
|
Thanks to everyone who has contributed to EJClaw!
|
||||||
|
|
||||||
- [Alakazam03](https://github.com/Alakazam03) — Vaibhav Aggarwal
|
- [Alakazam03](https://github.com/Alakazam03) — Vaibhav Aggarwal
|
||||||
- [tydev-new](https://github.com/tydev-new)
|
- [tydev-new](https://github.com/tydev-new)
|
||||||
|
|||||||
12
README.md
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
Dual-agent AI assistant (Claude Code + Codex) over Discord.
|
Dual-agent AI assistant (Claude Code + Codex) over Discord.
|
||||||
|
|
||||||
Forked from [qwibitai/nanoclaw](https://github.com/qwibitai/nanoclaw), heavily customized for production use.
|
Originally derived from [qwibitai/nanoclaw](https://github.com/qwibitai/nanoclaw), now maintained as EJClaw for personal production use.
|
||||||
|
|
||||||
## Overview
|
## Overview
|
||||||
|
|
||||||
@@ -77,12 +77,12 @@ GROQ_API_KEY= # Voice transcription (Groq Whisper)
|
|||||||
### Systemd Services (Linux)
|
### Systemd Services (Linux)
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
systemctl --user enable nanoclaw nanoclaw-codex
|
systemctl --user enable ejclaw ejclaw-codex
|
||||||
systemctl --user start nanoclaw nanoclaw-codex
|
systemctl --user start ejclaw ejclaw-codex
|
||||||
|
|
||||||
# Logs
|
# Logs
|
||||||
journalctl --user -u nanoclaw -f
|
journalctl --user -u ejclaw -f
|
||||||
journalctl --user -u nanoclaw-codex -f
|
journalctl --user -u ejclaw-codex -f
|
||||||
```
|
```
|
||||||
|
|
||||||
## Development
|
## Development
|
||||||
@@ -96,4 +96,4 @@ npm test # Run tests
|
|||||||
|
|
||||||
## License
|
## License
|
||||||
|
|
||||||
MIT — Based on [qwibitai/nanoclaw](https://github.com/qwibitai/nanoclaw)
|
MIT — Originally derived from [qwibitai/nanoclaw](https://github.com/qwibitai/nanoclaw)
|
||||||
|
|||||||
200
README_zh.md
@@ -1,200 +0,0 @@
|
|||||||
<p align="center">
|
|
||||||
<img src="assets/nanoclaw-logo.png" alt="NanoClaw" width="400">
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
NanoClaw —— 您的专属 Claude 助手,在容器中安全运行。它轻巧易懂,并能根据您的个人需求灵活定制。
|
|
||||||
</p>
|
|
||||||
|
|
||||||
<p align="center">
|
|
||||||
<a href="https://nanoclaw.dev">nanoclaw.dev</a> •
|
|
||||||
<a href="README.md">English</a> •
|
|
||||||
<a href="https://discord.gg/VDdww8qS42"><img src="https://img.shields.io/discord/1470188214710046894?label=Discord&logo=discord&v=2" alt="Discord" valign="middle"></a> •
|
|
||||||
<a href="repo-tokens"><img src="repo-tokens/badge.svg" alt="34.9k tokens, 17% of context window" valign="middle"></a>
|
|
||||||
</p>
|
|
||||||
通过 Claude Code,NanoClaw 可以动态重写自身代码,根据您的需求定制功能。
|
|
||||||
|
|
||||||
**新功能:** 首个支持 [Agent Swarms(智能体集群)](https://code.claude.com/docs/en/agent-teams) 的 AI 助手。可轻松组建智能体团队,在您的聊天中高效协作。
|
|
||||||
|
|
||||||
## 我为什么创建这个项目
|
|
||||||
|
|
||||||
[上游项目](https://github.com/openclaw/openclaw) 是一个令人印象深刻的项目,但我无法安心使用一个我不了解却能访问我个人隐私的软件。该上游项目有近 50 万行代码、53 个配置文件和 70+ 个依赖项。其安全性是应用级别的(通过白名单、配对码实现),而非操作系统级别的隔离。所有东西都在一个共享内存的 Node 进程中运行。
|
|
||||||
|
|
||||||
NanoClaw 用一个您能快速理解的代码库,为您提供了同样的核心功能。只有一个进程,少数几个文件。智能体(Agent)运行在具有文件系统隔离的真实 Linux 容器中,而不是依赖于权限检查。
|
|
||||||
|
|
||||||
## 快速开始
|
|
||||||
|
|
||||||
```bash
|
|
||||||
git clone https://github.com/qwibitai/nanoclaw.git
|
|
||||||
cd nanoclaw
|
|
||||||
claude
|
|
||||||
```
|
|
||||||
|
|
||||||
然后运行 `/setup`。Claude Code 会处理一切:依赖安装、身份验证、容器设置、服务配置。
|
|
||||||
|
|
||||||
> **注意:** 以 `/` 开头的命令(如 `/setup`、`/add-whatsapp`)是 [Claude Code 技能](https://code.claude.com/docs/en/skills)。请在 `claude` CLI 提示符中输入,而非在普通终端中。
|
|
||||||
|
|
||||||
## 设计哲学
|
|
||||||
|
|
||||||
**小巧易懂:** 单一进程,少量源文件。无微服务、无消息队列、无复杂抽象层。让 Claude Code 引导您轻松上手。
|
|
||||||
|
|
||||||
**通过隔离保障安全:** 智能体运行在 Linux 容器(在 macOS 上是 Apple Container,或 Docker)中。它们只能看到被明确挂载的内容。即便通过 Bash 访问也十分安全,因为所有命令都在容器内执行,不会直接操作您的宿主机。
|
|
||||||
|
|
||||||
**为单一用户打造:** 这不是一个框架,是一个完全符合您个人需求的、可工作的软件。您可以 Fork 本项目,然后让 Claude Code 根据您的精确需求进行修改和适配。
|
|
||||||
|
|
||||||
**定制即代码修改:** 没有繁杂的配置文件。想要不同的行为?直接修改代码。代码库足够小,这样做是安全的。
|
|
||||||
|
|
||||||
**AI 原生:** 无安装向导(由 Claude Code 指导安装)。无需监控仪表盘,直接询问 Claude 即可了解系统状况。无调试工具(描述问题,Claude 会修复它)。
|
|
||||||
|
|
||||||
**技能(Skills)优于功能(Features):** 贡献者不应该向代码库添加新功能(例如支持 Telegram)。相反,他们应该贡献像 `/add-telegram` 这样的 [Claude Code 技能](https://code.claude.com/docs/en/skills),这些技能可以改造您的 fork。最终,您得到的是只做您需要事情的整洁代码。
|
|
||||||
|
|
||||||
**最好的工具套件,最好的模型:** 本项目运行在 Claude Agent SDK 之上,这意味着您直接运行的就是 Claude Code。Claude Code 高度强大,其编码和问题解决能力使其能够修改和扩展 NanoClaw,为每个用户量身定制。
|
|
||||||
|
|
||||||
## 功能支持
|
|
||||||
|
|
||||||
- **多渠道消息** - 通过 WhatsApp、Telegram、Discord、Slack 或 Gmail 与您的助手对话。使用 `/add-whatsapp` 或 `/add-telegram` 等技能添加渠道,可同时运行一个或多个。
|
|
||||||
- **隔离的群组上下文** - 每个群组都拥有独立的 `CLAUDE.md` 记忆和隔离的文件系统。它们在各自的容器沙箱中运行,且仅挂载所需的文件系统。
|
|
||||||
- **主频道** - 您的私有频道(self-chat),用于管理控制;其他所有群组都完全隔离
|
|
||||||
- **计划任务** - 运行 Claude 的周期性作业,并可以给您回发消息
|
|
||||||
- **网络访问** - 搜索和抓取网页内容
|
|
||||||
- **容器隔离** - 智能体在 Apple Container (macOS) 或 Docker (macOS/Linux) 的沙箱中运行
|
|
||||||
- **智能体集群(Agent Swarms)** - 启动多个专业智能体团队,协作完成复杂任务(首个支持此功能的个人 AI 助手)
|
|
||||||
- **可选集成** - 通过技能添加 Gmail (`/add-gmail`) 等更多功能
|
|
||||||
|
|
||||||
## 使用方法
|
|
||||||
|
|
||||||
使用触发词(默认为 `@Andy`)与您的助手对话:
|
|
||||||
|
|
||||||
```
|
|
||||||
@Andy 每周一到周五早上9点,给我发一份销售渠道的概览(需要访问我的 Obsidian vault 文件夹)
|
|
||||||
@Andy 每周五回顾过去一周的 git 历史,如果与 README 有出入,就更新它
|
|
||||||
@Andy 每周一早上8点,从 Hacker News 和 TechCrunch 收集关于 AI 发展的资讯,然后发给我一份简报
|
|
||||||
```
|
|
||||||
|
|
||||||
在主频道(您的self-chat)中,可以管理群组和任务:
|
|
||||||
```
|
|
||||||
@Andy 列出所有群组的计划任务
|
|
||||||
@Andy 暂停周一简报任务
|
|
||||||
@Andy 加入"家庭聊天"群组
|
|
||||||
```
|
|
||||||
|
|
||||||
## 定制
|
|
||||||
|
|
||||||
没有需要学习的配置文件。直接告诉 Claude Code 您想要什么:
|
|
||||||
|
|
||||||
- "把触发词改成 @Bob"
|
|
||||||
- "记住以后回答要更简短直接"
|
|
||||||
- "当我说早上好的时候,加一个自定义的问候"
|
|
||||||
- "每周存储一次对话摘要"
|
|
||||||
|
|
||||||
或者运行 `/customize` 进行引导式修改。
|
|
||||||
|
|
||||||
代码库足够小,Claude 可以安全地修改它。
|
|
||||||
|
|
||||||
## 贡献
|
|
||||||
|
|
||||||
**不要添加功能,而是添加技能。**
|
|
||||||
|
|
||||||
如果您想添加 Telegram 支持,不要创建一个 PR 同时添加 Telegram 和 WhatsApp。而是贡献一个技能文件 (`.claude/skills/add-telegram/SKILL.md`),教 Claude Code 如何改造一个 NanoClaw 安装以使用 Telegram。
|
|
||||||
|
|
||||||
然后用户在自己的 fork 上运行 `/add-telegram`,就能得到只做他们需要事情的整洁代码,而不是一个试图支持所有用例的臃肿系统。
|
|
||||||
|
|
||||||
### RFS (技能征集)
|
|
||||||
|
|
||||||
我们希望看到的技能:
|
|
||||||
|
|
||||||
**通信渠道**
|
|
||||||
- `/add-signal` - 添加 Signal 作为渠道
|
|
||||||
|
|
||||||
**会话管理**
|
|
||||||
- `/clear` - 添加一个 `/clear` 命令,用于压缩会话(在同一会话中总结上下文,同时保留关键信息)。这需要研究如何通过 Claude Agent SDK 以编程方式触发压缩。
|
|
||||||
|
|
||||||
## 系统要求
|
|
||||||
|
|
||||||
- macOS 或 Linux
|
|
||||||
- Node.js 20+
|
|
||||||
- [Claude Code](https://claude.ai/download)
|
|
||||||
- [Apple Container](https://github.com/apple/container) (macOS) 或 [Docker](https://docker.com/products/docker-desktop) (macOS/Linux)
|
|
||||||
|
|
||||||
## 架构
|
|
||||||
|
|
||||||
```
|
|
||||||
渠道 --> SQLite --> 轮询循环 --> 容器 (Claude Agent SDK) --> 响应
|
|
||||||
```
|
|
||||||
|
|
||||||
单一 Node.js 进程。渠道通过技能添加,启动时自注册 — 编排器连接具有凭据的渠道。智能体在具有文件系统隔离的 Linux 容器中执行。每个群组的消息队列带有并发控制。通过文件系统进行 IPC。
|
|
||||||
|
|
||||||
完整架构详情请见 [docs/SPEC.md](docs/SPEC.md)。
|
|
||||||
|
|
||||||
关键文件:
|
|
||||||
- `src/index.ts` - 编排器:状态管理、消息循环、智能体调用
|
|
||||||
- `src/channels/registry.ts` - 渠道注册表(启动时自注册)
|
|
||||||
- `src/ipc.ts` - IPC 监听与任务处理
|
|
||||||
- `src/router.ts` - 消息格式化与出站路由
|
|
||||||
- `src/group-queue.ts` - 带全局并发限制的群组队列
|
|
||||||
- `src/container-runner.ts` - 生成流式智能体容器
|
|
||||||
- `src/task-scheduler.ts` - 运行计划任务
|
|
||||||
- `src/db.ts` - SQLite 操作(消息、群组、会话、状态)
|
|
||||||
- `groups/*/CLAUDE.md` - 各群组的记忆
|
|
||||||
|
|
||||||
## FAQ
|
|
||||||
|
|
||||||
**为什么是 Docker?**
|
|
||||||
|
|
||||||
Docker 提供跨平台支持(macOS 和 Linux)和成熟的生态系统。在 macOS 上,您可以选择通过运行 `/convert-to-apple-container` 切换到 Apple Container,以获得更轻量级的原生运行时体验。
|
|
||||||
|
|
||||||
**我可以在 Linux 上运行吗?**
|
|
||||||
|
|
||||||
可以。Docker 是默认的容器运行时,在 macOS 和 Linux 上都可以使用。只需运行 `/setup`。
|
|
||||||
|
|
||||||
**这个项目安全吗?**
|
|
||||||
|
|
||||||
智能体在容器中运行,而不是在应用级别的权限检查之后。它们只能访问被明确挂载的目录。您仍然应该审查您运行的代码,但这个代码库小到您真的可以做到。完整的安全模型请见 [docs/SECURITY.md](docs/SECURITY.md)。
|
|
||||||
|
|
||||||
**为什么没有配置文件?**
|
|
||||||
|
|
||||||
我们不希望配置泛滥。每个用户都应该定制它,让代码完全符合他们的需求,而不是去配置一个通用的系统。如果您喜欢用配置文件,告诉 Claude 让它加上。
|
|
||||||
|
|
||||||
**我可以使用第三方或开源模型吗?**
|
|
||||||
|
|
||||||
可以。NanoClaw 支持任何 API 兼容的模型端点。在 `.env` 文件中设置以下环境变量:
|
|
||||||
|
|
||||||
```bash
|
|
||||||
ANTHROPIC_BASE_URL=https://your-api-endpoint.com
|
|
||||||
ANTHROPIC_AUTH_TOKEN=your-token-here
|
|
||||||
```
|
|
||||||
|
|
||||||
这使您能够使用:
|
|
||||||
- 通过 [Ollama](https://ollama.ai) 配合 API 代理运行的本地模型
|
|
||||||
- 托管在 [Together AI](https://together.ai)、[Fireworks](https://fireworks.ai) 等平台上的开源模型
|
|
||||||
- 兼容 Anthropic API 格式的自定义模型部署
|
|
||||||
|
|
||||||
注意:为获得最佳兼容性,模型需支持 Anthropic API 格式。
|
|
||||||
|
|
||||||
**我该如何调试问题?**
|
|
||||||
|
|
||||||
问 Claude Code。"为什么计划任务没有运行?" "最近的日志里有什么?" "为什么这条消息没有得到回应?" 这就是 AI 原生的方法。
|
|
||||||
|
|
||||||
**为什么我的安装不成功?**
|
|
||||||
|
|
||||||
如果遇到问题,安装过程中 Claude 会尝试动态修复。如果问题仍然存在,运行 `claude`,然后运行 `/debug`。如果 Claude 发现一个可能影响其他用户的问题,请开一个 PR 来修改 setup SKILL.md。
|
|
||||||
|
|
||||||
**什么样的代码更改会被接受?**
|
|
||||||
|
|
||||||
安全修复、bug 修复,以及对基础配置的明确改进。仅此而已。
|
|
||||||
|
|
||||||
其他一切(新功能、操作系统兼容性、硬件支持、增强功能)都应该作为技能来贡献。
|
|
||||||
|
|
||||||
这使得基础系统保持最小化,并让每个用户可以定制他们的安装,而无需继承他们不想要的功能。
|
|
||||||
|
|
||||||
## 社区
|
|
||||||
|
|
||||||
有任何疑问或建议?欢迎[加入 Discord 社区](https://discord.gg/VDdww8qS42)与我们交流。
|
|
||||||
|
|
||||||
## 更新日志
|
|
||||||
|
|
||||||
破坏性变更和迁移说明请见 [CHANGELOG.md](CHANGELOG.md)。
|
|
||||||
|
|
||||||
## 许可证
|
|
||||||
|
|
||||||
MIT
|
|
||||||
|
Before Width: | Height: | Size: 878 KiB After Width: | Height: | Size: 878 KiB |
|
Before Width: | Height: | Size: 516 KiB After Width: | Height: | Size: 516 KiB |
|
Before Width: | Height: | Size: 206 KiB After Width: | Height: | Size: 206 KiB |
|
Before Width: | Height: | Size: 212 KiB After Width: | Height: | Size: 212 KiB |
|
Before Width: | Height: | Size: 5.6 MiB After Width: | Height: | Size: 5.6 MiB |
|
Before Width: | Height: | Size: 513 KiB After Width: | Height: | Size: 513 KiB |
@@ -23,11 +23,11 @@
|
|||||||
<string>{{HOME}}</string>
|
<string>{{HOME}}</string>
|
||||||
<key>ASSISTANT_NAME</key>
|
<key>ASSISTANT_NAME</key>
|
||||||
<string>Andy-Codex</string>
|
<string>Andy-Codex</string>
|
||||||
<key>NANOCLAW_STORE_DIR</key>
|
<key>EJCLAW_STORE_DIR</key>
|
||||||
<string>{{PROJECT_ROOT}}/store-codex</string>
|
<string>{{PROJECT_ROOT}}/store-codex</string>
|
||||||
<key>NANOCLAW_GROUPS_DIR</key>
|
<key>EJCLAW_GROUPS_DIR</key>
|
||||||
<string>{{PROJECT_ROOT}}/groups-codex</string>
|
<string>{{PROJECT_ROOT}}/groups-codex</string>
|
||||||
<key>NANOCLAW_DATA_DIR</key>
|
<key>EJCLAW_DATA_DIR</key>
|
||||||
<string>{{PROJECT_ROOT}}/data-codex</string>
|
<string>{{PROJECT_ROOT}}/data-codex</string>
|
||||||
</dict>
|
</dict>
|
||||||
<key>StandardOutPath</key>
|
<key>StandardOutPath</key>
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
{
|
{
|
||||||
"name": "nanoclaw",
|
"name": "ejclaw",
|
||||||
"version": "1.2.12",
|
"version": "1.2.12",
|
||||||
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
|
"description": "Personal Claude assistant. Lightweight, secure, customizable.",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ A GitHub Action that calculates the size of your codebase in terms of tokens and
|
|||||||
## Usage
|
## Usage
|
||||||
|
|
||||||
```yaml
|
```yaml
|
||||||
- uses: qwibitai/nanoclaw/repo-tokens@v1
|
- uses: phj1081/EJClaw/repo-tokens@v1
|
||||||
with:
|
with:
|
||||||
include: 'src/**/*.ts'
|
include: 'src/**/*.ts'
|
||||||
exclude: 'src/**/*.test.ts'
|
exclude: 'src/**/*.test.ts'
|
||||||
@@ -34,7 +34,7 @@ Repos using repo-tokens:
|
|||||||
|
|
||||||
| Repo | Badge |
|
| Repo | Badge |
|
||||||
|------|-------|
|
|------|-------|
|
||||||
| [NanoClaw](https://github.com/qwibitai/NanoClaw) |  |
|
| [EJClaw](https://github.com/phj1081/EJClaw) |  |
|
||||||
|
|
||||||
### Full workflow example
|
### Full workflow example
|
||||||
|
|
||||||
@@ -59,7 +59,7 @@ jobs:
|
|||||||
with:
|
with:
|
||||||
python-version: '3.12'
|
python-version: '3.12'
|
||||||
|
|
||||||
- uses: qwibitai/nanoclaw/repo-tokens@v1
|
- uses: phj1081/EJClaw/repo-tokens@v1
|
||||||
id: tokens
|
id: tokens
|
||||||
with:
|
with:
|
||||||
include: 'src/**/*.ts'
|
include: 'src/**/*.ts'
|
||||||
|
|||||||
@@ -114,7 +114,7 @@ runs:
|
|||||||
with open(readme_path, "r", encoding="utf-8") as f:
|
with open(readme_path, "r", encoding="utf-8") as f:
|
||||||
content = f.read()
|
content = f.read()
|
||||||
|
|
||||||
repo_tokens_url = "https://github.com/qwibitai/nanoclaw/tree/main/repo-tokens"
|
repo_tokens_url = "https://github.com/phj1081/EJClaw/tree/main/repo-tokens"
|
||||||
linked_badge = f'<a href="{repo_tokens_url}">{badge}</a>'
|
linked_badge = f'<a href="{repo_tokens_url}">{badge}</a>'
|
||||||
new_content = marker_re.sub(rf"\1{linked_badge}\2", content)
|
new_content = marker_re.sub(rf"\1{linked_badge}\2", content)
|
||||||
|
|
||||||
@@ -148,7 +148,7 @@ runs:
|
|||||||
lx = label_w // 2
|
lx = label_w // 2
|
||||||
vx = label_w + value_w // 2
|
vx = label_w + value_w // 2
|
||||||
|
|
||||||
repo_tokens_url = "https://github.com/qwibitai/nanoclaw/tree/main/repo-tokens"
|
repo_tokens_url = "https://github.com/phj1081/EJClaw/tree/main/repo-tokens"
|
||||||
|
|
||||||
svg = f'''<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{total_w}" height="20" role="img" aria-label="{full_desc}">
|
svg = f'''<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" width="{total_w}" height="20" role="img" aria-label="{full_desc}">
|
||||||
<title>{full_desc}</title>
|
<title>{full_desc}</title>
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
<clipPath id="r">
|
<clipPath id="r">
|
||||||
<rect width="97" height="20" rx="3" fill="#fff"/>
|
<rect width="97" height="20" rx="3" fill="#fff"/>
|
||||||
</clipPath>
|
</clipPath>
|
||||||
<a xlink:href="https://github.com/qwibitai/nanoclaw/tree/main/repo-tokens">
|
<a xlink:href="https://github.com/phj1081/EJClaw/tree/main/repo-tokens">
|
||||||
<g clip-path="url(#r)">
|
<g clip-path="url(#r)">
|
||||||
<rect width="52" height="20" fill="#555"/>
|
<rect width="52" height="20" fill="#555"/>
|
||||||
<rect x="52" width="45" height="20" fill="#4c1"/>
|
<rect x="52" width="45" height="20" fill="#4c1"/>
|
||||||
|
|||||||
|
Before Width: | Height: | Size: 1.1 KiB After Width: | Height: | Size: 1.1 KiB |
3
runners/agent-runner/package-lock.json
generated
@@ -724,6 +724,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
"resolved": "https://registry.npmjs.org/express/-/express-5.2.1.tgz",
|
||||||
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
"integrity": "sha512-hIS4idWWai69NezIdRt2xFVofaF4j+6INOpJlVOLDO8zXGpUVEVzIYk12UUi2JzjEzWL3IOAxcTubgz9Po0yXw==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"accepts": "^2.0.0",
|
"accepts": "^2.0.0",
|
||||||
"body-parser": "^2.2.1",
|
"body-parser": "^2.2.1",
|
||||||
@@ -928,6 +929,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.8.tgz",
|
"resolved": "https://registry.npmjs.org/hono/-/hono-4.11.8.tgz",
|
||||||
"integrity": "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==",
|
"integrity": "sha512-eVkB/CYCCei7K2WElZW9yYQFWssG0DhaDhVvr7wy5jJ22K+ck8fWW0EsLpB0sITUTvPnc97+rrbQqIr5iqiy9Q==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"engines": {
|
"engines": {
|
||||||
"node": ">=16.9.0"
|
"node": ">=16.9.0"
|
||||||
}
|
}
|
||||||
@@ -1507,6 +1509,7 @@
|
|||||||
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
"resolved": "https://registry.npmjs.org/zod/-/zod-4.3.6.tgz",
|
||||||
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
"integrity": "sha512-rftlrkhHZOcjDwkGlnUtZZkvaPHCsDATp4pGpuOOMDaTdDDXF91wuVDJoWoPsKX/3YPQ5fHuF3STjcYyKr+Qhg==",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
|
"peer": true,
|
||||||
"funding": {
|
"funding": {
|
||||||
"url": "https://github.com/sponsors/colinhacks"
|
"url": "https://github.com/sponsors/colinhacks"
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,10 +1,10 @@
|
|||||||
/**
|
/**
|
||||||
* NanoClaw Agent Runner
|
* EJClaw Agent Runner
|
||||||
* Runs as a child process, receives config via stdin, outputs result to stdout
|
* Runs as a child process, receives config via stdin, outputs result to stdout
|
||||||
*
|
*
|
||||||
* Input protocol:
|
* Input protocol:
|
||||||
* Stdin: Full RunnerInput JSON (read until EOF, like before)
|
* Stdin: Full RunnerInput JSON (read until EOF, like before)
|
||||||
* IPC: Follow-up messages written as JSON files to $NANOCLAW_IPC_DIR/input/
|
* IPC: Follow-up messages written as JSON files to $EJCLAW_IPC_DIR/input/
|
||||||
* Files: {type:"message", text:"..."}.json — polled and consumed
|
* Files: {type:"message", text:"..."}.json — polled and consumed
|
||||||
* Sentinel: /workspace/ipc/input/_close — signals session end
|
* Sentinel: /workspace/ipc/input/_close — signals session end
|
||||||
*
|
*
|
||||||
@@ -60,10 +60,10 @@ interface SDKUserMessage {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Paths configurable via env vars.
|
// Paths configurable via env vars.
|
||||||
const GROUP_DIR = process.env.NANOCLAW_GROUP_DIR || '/workspace/group';
|
const GROUP_DIR = process.env.EJCLAW_GROUP_DIR || '/workspace/group';
|
||||||
const IPC_DIR = process.env.NANOCLAW_IPC_DIR || '/workspace/ipc';
|
const IPC_DIR = process.env.EJCLAW_IPC_DIR || '/workspace/ipc';
|
||||||
// Optional: override cwd (agent works in this directory instead of GROUP_DIR)
|
// Optional: override cwd (agent works in this directory instead of GROUP_DIR)
|
||||||
const WORK_DIR = process.env.NANOCLAW_WORK_DIR || '';
|
const WORK_DIR = process.env.EJCLAW_WORK_DIR || '';
|
||||||
|
|
||||||
const IPC_INPUT_DIR = path.join(IPC_DIR, 'input');
|
const IPC_INPUT_DIR = path.join(IPC_DIR, 'input');
|
||||||
const IPC_INPUT_CLOSE_SENTINEL = path.join(IPC_INPUT_DIR, '_close');
|
const IPC_INPUT_CLOSE_SENTINEL = path.join(IPC_INPUT_DIR, '_close');
|
||||||
@@ -161,8 +161,8 @@ async function readStdin(): Promise<string> {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
|
const OUTPUT_START_MARKER = '---EJCLAW_OUTPUT_START---';
|
||||||
const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
|
const OUTPUT_END_MARKER = '---EJCLAW_OUTPUT_END---';
|
||||||
|
|
||||||
function writeOutput(output: ContainerOutput): void {
|
function writeOutput(output: ContainerOutput): void {
|
||||||
console.log(OUTPUT_START_MARKER);
|
console.log(OUTPUT_START_MARKER);
|
||||||
@@ -469,22 +469,22 @@ async function runQuery(
|
|||||||
'TeamCreate', 'TeamDelete', 'SendMessage',
|
'TeamCreate', 'TeamDelete', 'SendMessage',
|
||||||
'TodoWrite', 'ToolSearch', 'Skill',
|
'TodoWrite', 'ToolSearch', 'Skill',
|
||||||
'NotebookEdit',
|
'NotebookEdit',
|
||||||
'mcp__nanoclaw__*'
|
'mcp__ejclaw__*'
|
||||||
],
|
],
|
||||||
env: sdkEnv,
|
env: sdkEnv,
|
||||||
permissionMode: 'bypassPermissions',
|
permissionMode: 'bypassPermissions',
|
||||||
allowDangerouslySkipPermissions: true,
|
allowDangerouslySkipPermissions: true,
|
||||||
settingSources: ['project', 'user'],
|
settingSources: ['project', 'user'],
|
||||||
mcpServers: {
|
mcpServers: {
|
||||||
nanoclaw: {
|
ejclaw: {
|
||||||
command: 'node',
|
command: 'node',
|
||||||
args: [mcpServerPath],
|
args: [mcpServerPath],
|
||||||
env: {
|
env: {
|
||||||
NANOCLAW_CHAT_JID: containerInput.chatJid,
|
EJCLAW_CHAT_JID: containerInput.chatJid,
|
||||||
NANOCLAW_GROUP_FOLDER: containerInput.groupFolder,
|
EJCLAW_GROUP_FOLDER: containerInput.groupFolder,
|
||||||
NANOCLAW_IS_MAIN: containerInput.isMain ? '1' : '0',
|
EJCLAW_IS_MAIN: containerInput.isMain ? '1' : '0',
|
||||||
NANOCLAW_AGENT_TYPE:
|
EJCLAW_AGENT_TYPE:
|
||||||
process.env.NANOCLAW_AGENT_TYPE || 'claude-code',
|
process.env.EJCLAW_AGENT_TYPE || 'claude-code',
|
||||||
},
|
},
|
||||||
},
|
},
|
||||||
...(process.env.MEMENTO_MCP_SSE_URL
|
...(process.env.MEMENTO_MCP_SSE_URL
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Stdio MCP Server for NanoClaw
|
* Stdio MCP Server for EJClaw
|
||||||
* Standalone process that agent teams subagents can inherit.
|
* Standalone process that agent teams subagents can inherit.
|
||||||
* Reads context from environment variables, writes IPC files for the host.
|
* Reads context from environment variables, writes IPC files for the host.
|
||||||
*/
|
*/
|
||||||
@@ -16,15 +16,15 @@ import {
|
|||||||
normalizeWatchCiIntervalSeconds,
|
normalizeWatchCiIntervalSeconds,
|
||||||
} from './watch-ci.js';
|
} from './watch-ci.js';
|
||||||
|
|
||||||
const IPC_DIR = process.env.NANOCLAW_IPC_DIR || '/workspace/ipc';
|
const IPC_DIR = process.env.EJCLAW_IPC_DIR || '/workspace/ipc';
|
||||||
const MESSAGES_DIR = path.join(IPC_DIR, 'messages');
|
const MESSAGES_DIR = path.join(IPC_DIR, 'messages');
|
||||||
const TASKS_DIR = path.join(IPC_DIR, 'tasks');
|
const TASKS_DIR = path.join(IPC_DIR, 'tasks');
|
||||||
|
|
||||||
// Context from environment variables (set by the agent runner)
|
// Context from environment variables (set by the agent runner)
|
||||||
const chatJid = process.env.NANOCLAW_CHAT_JID!;
|
const chatJid = process.env.EJCLAW_CHAT_JID!;
|
||||||
const groupFolder = process.env.NANOCLAW_GROUP_FOLDER!;
|
const groupFolder = process.env.EJCLAW_GROUP_FOLDER!;
|
||||||
const isMain = process.env.NANOCLAW_IS_MAIN === '1';
|
const isMain = process.env.EJCLAW_IS_MAIN === '1';
|
||||||
const agentType = process.env.NANOCLAW_AGENT_TYPE || 'claude-code';
|
const agentType = process.env.EJCLAW_AGENT_TYPE || 'claude-code';
|
||||||
const allowGenericScheduling = agentType !== 'codex';
|
const allowGenericScheduling = agentType !== 'codex';
|
||||||
|
|
||||||
function writeIpcFile(dir: string, data: object): string {
|
function writeIpcFile(dir: string, data: object): string {
|
||||||
@@ -42,7 +42,7 @@ function writeIpcFile(dir: string, data: object): string {
|
|||||||
}
|
}
|
||||||
|
|
||||||
const server = new McpServer({
|
const server = new McpServer({
|
||||||
name: 'nanoclaw',
|
name: 'ejclaw',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
});
|
});
|
||||||
|
|
||||||
|
|||||||
4
runners/codex-runner/package-lock.json
generated
@@ -1,11 +1,11 @@
|
|||||||
{
|
{
|
||||||
"name": "nanoclaw-codex-runner",
|
"name": "ejclaw-codex-runner",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"lockfileVersion": 3,
|
"lockfileVersion": 3,
|
||||||
"requires": true,
|
"requires": true,
|
||||||
"packages": {
|
"packages": {
|
||||||
"": {
|
"": {
|
||||||
"name": "nanoclaw-codex-runner",
|
"name": "ejclaw-codex-runner",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"dependencies": {
|
"dependencies": {
|
||||||
"@openai/codex": "^0.115.0"
|
"@openai/codex": "^0.115.0"
|
||||||
|
|||||||
@@ -1,8 +1,8 @@
|
|||||||
{
|
{
|
||||||
"name": "nanoclaw-codex-runner",
|
"name": "ejclaw-codex-runner",
|
||||||
"version": "1.0.0",
|
"version": "1.0.0",
|
||||||
"type": "module",
|
"type": "module",
|
||||||
"description": "Codex app-server runner for NanoClaw",
|
"description": "Codex app-server runner for EJClaw",
|
||||||
"main": "dist/index.js",
|
"main": "dist/index.js",
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"build": "tsc",
|
"build": "tsc",
|
||||||
|
|||||||
@@ -144,8 +144,8 @@ export class CodexAppServerClient {
|
|||||||
|
|
||||||
await this.request('initialize', {
|
await this.request('initialize', {
|
||||||
clientInfo: {
|
clientInfo: {
|
||||||
name: 'nanoclaw_codex_runner',
|
name: 'ejclaw_codex_runner',
|
||||||
title: 'NanoClaw Codex Runner',
|
title: 'EJClaw Codex Runner',
|
||||||
version: '1.0.0',
|
version: '1.0.0',
|
||||||
},
|
},
|
||||||
capabilities: {
|
capabilities: {
|
||||||
@@ -182,7 +182,7 @@ export class CodexAppServerClient {
|
|||||||
model: options.model,
|
model: options.model,
|
||||||
approvalPolicy: 'never',
|
approvalPolicy: 'never',
|
||||||
sandbox: 'danger-full-access',
|
sandbox: 'danger-full-access',
|
||||||
serviceName: 'nanoclaw',
|
serviceName: 'ejclaw',
|
||||||
};
|
};
|
||||||
|
|
||||||
const result = sessionId
|
const result = sessionId
|
||||||
@@ -362,7 +362,7 @@ export class CodexAppServerClient {
|
|||||||
this.respondError(
|
this.respondError(
|
||||||
message.id,
|
message.id,
|
||||||
-32601,
|
-32601,
|
||||||
`NanoClaw does not handle server request ${message.method}`,
|
`EJClaw does not handle server request ${message.method}`,
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -1,11 +1,11 @@
|
|||||||
/**
|
/**
|
||||||
* NanoClaw Codex Runner
|
* EJClaw Codex Runner
|
||||||
*
|
*
|
||||||
* App-server only runtime.
|
* App-server only runtime.
|
||||||
*
|
*
|
||||||
* Input protocol:
|
* Input protocol:
|
||||||
* Stdin: Full ContainerInput JSON (read until EOF)
|
* Stdin: Full ContainerInput JSON (read until EOF)
|
||||||
* IPC: Follow-up messages as JSON files in $NANOCLAW_IPC_DIR/input/
|
* IPC: Follow-up messages as JSON files in $EJCLAW_IPC_DIR/input/
|
||||||
* Sentinel: _close — signals session end
|
* Sentinel: _close — signals session end
|
||||||
*
|
*
|
||||||
* Stdout protocol:
|
* Stdout protocol:
|
||||||
@@ -43,15 +43,15 @@ interface ContainerOutput {
|
|||||||
|
|
||||||
// ── Constants ──────────────────────────────────────────────────────
|
// ── Constants ──────────────────────────────────────────────────────
|
||||||
|
|
||||||
const GROUP_DIR = process.env.NANOCLAW_GROUP_DIR || '/workspace/group';
|
const GROUP_DIR = process.env.EJCLAW_GROUP_DIR || '/workspace/group';
|
||||||
const IPC_DIR = process.env.NANOCLAW_IPC_DIR || '/workspace/ipc';
|
const IPC_DIR = process.env.EJCLAW_IPC_DIR || '/workspace/ipc';
|
||||||
const WORK_DIR = process.env.NANOCLAW_WORK_DIR || '';
|
const WORK_DIR = process.env.EJCLAW_WORK_DIR || '';
|
||||||
const IPC_INPUT_DIR = path.join(IPC_DIR, 'input');
|
const IPC_INPUT_DIR = path.join(IPC_DIR, 'input');
|
||||||
const IPC_INPUT_CLOSE_SENTINEL = path.join(IPC_INPUT_DIR, '_close');
|
const IPC_INPUT_CLOSE_SENTINEL = path.join(IPC_INPUT_DIR, '_close');
|
||||||
const IPC_POLL_MS = 500;
|
const IPC_POLL_MS = 500;
|
||||||
|
|
||||||
const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
|
const OUTPUT_START_MARKER = '---EJCLAW_OUTPUT_START---';
|
||||||
const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
|
const OUTPUT_END_MARKER = '---EJCLAW_OUTPUT_END---';
|
||||||
|
|
||||||
const EFFECTIVE_CWD = WORK_DIR || GROUP_DIR;
|
const EFFECTIVE_CWD = WORK_DIR || GROUP_DIR;
|
||||||
const CODEX_MODEL = process.env.CODEX_MODEL || '';
|
const CODEX_MODEL = process.env.CODEX_MODEL || '';
|
||||||
|
|||||||
@@ -4,15 +4,15 @@
|
|||||||
*
|
*
|
||||||
* Run AFTER stopping both services.
|
* Run AFTER stopping both services.
|
||||||
*/
|
*/
|
||||||
const Database = require('/home/clone-ej/nanoclaw/node_modules/better-sqlite3');
|
const Database = require('/home/clone-ej/EJClaw/node_modules/better-sqlite3');
|
||||||
const fs = require('fs');
|
const fs = require('fs');
|
||||||
const path = require('path');
|
const path = require('path');
|
||||||
|
|
||||||
const BASE = '/home/clone-ej/nanoclaw';
|
const BASE = '/home/clone-ej/EJClaw';
|
||||||
const CLAUDE_DB = path.join(BASE, 'store/messages.db');
|
const CLAUDE_DB = path.join(BASE, 'store/messages.db');
|
||||||
const CODEX_DB = path.join(BASE, 'store-codex/messages.db');
|
const CODEX_DB = path.join(BASE, 'store-codex/messages.db');
|
||||||
|
|
||||||
console.log('=== NanoClaw Data Unification ===\n');
|
console.log('=== EJClaw Data Unification ===\n');
|
||||||
|
|
||||||
// 1. Open both DBs
|
// 1. Open both DBs
|
||||||
const primary = new Database(CLAUDE_DB);
|
const primary = new Database(CLAUDE_DB);
|
||||||
@@ -215,6 +215,6 @@ console.log('');
|
|||||||
|
|
||||||
console.log('=== Migration complete ===');
|
console.log('=== Migration complete ===');
|
||||||
console.log('Next steps:');
|
console.log('Next steps:');
|
||||||
console.log('1. Update .env.codex to remove NANOCLAW_STORE_DIR, NANOCLAW_DATA_DIR, NANOCLAW_GROUPS_DIR');
|
console.log('1. Update .env.codex to remove EJCLAW_STORE_DIR, EJCLAW_DATA_DIR, EJCLAW_GROUPS_DIR');
|
||||||
console.log('2. Restart both services');
|
console.log('2. Restart both services');
|
||||||
console.log('3. Verify, then rename old dirs to .bak');
|
console.log('3. Verify, then rename old dirs to .bak');
|
||||||
|
|||||||
4
setup.sh
@@ -1,7 +1,7 @@
|
|||||||
#!/bin/bash
|
#!/bin/bash
|
||||||
set -euo pipefail
|
set -euo pipefail
|
||||||
|
|
||||||
# setup.sh — Bootstrap script for NanoClaw
|
# setup.sh — Bootstrap script for EJClaw
|
||||||
# Handles Node.js/npm setup, then hands off to the Node.js setup modules.
|
# Handles Node.js/npm setup, then hands off to the Node.js setup modules.
|
||||||
# This is the only bash script in the setup flow.
|
# This is the only bash script in the setup flow.
|
||||||
|
|
||||||
@@ -136,7 +136,7 @@ elif [ "$NATIVE_OK" = "false" ]; then
|
|||||||
fi
|
fi
|
||||||
|
|
||||||
cat <<EOF
|
cat <<EOF
|
||||||
=== NANOCLAW SETUP: BOOTSTRAP ===
|
=== EJCLAW SETUP: BOOTSTRAP ===
|
||||||
PLATFORM: $PLATFORM
|
PLATFORM: $PLATFORM
|
||||||
IS_WSL: $IS_WSL
|
IS_WSL: $IS_WSL
|
||||||
IS_ROOT: $IS_ROOT
|
IS_ROOT: $IS_ROOT
|
||||||
|
|||||||
@@ -1,7 +1,7 @@
|
|||||||
/**
|
/**
|
||||||
* Step: register — Write channel registration config, create group folders.
|
* Step: register — Write channel registration config, create group folders.
|
||||||
*
|
*
|
||||||
* NanoClaw is Discord-only, so registrations must target Discord channel IDs.
|
* EJClaw is Discord-only, so registrations must target Discord channel IDs.
|
||||||
* Uses parameterized SQL queries to prevent injection.
|
* Uses parameterized SQL queries to prevent injection.
|
||||||
*/
|
*/
|
||||||
import fs from 'fs';
|
import fs from 'fs';
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ export function emitStatus(
|
|||||||
step: string,
|
step: string,
|
||||||
fields: Record<string, string | number | boolean>,
|
fields: Record<string, string | number | boolean>,
|
||||||
): void {
|
): void {
|
||||||
const lines = [`=== NANOCLAW SETUP: ${step} ===`];
|
const lines = [`=== EJCLAW SETUP: ${step} ===`];
|
||||||
for (const [key, value] of Object.entries(fields)) {
|
for (const [key, value] of Object.entries(fields)) {
|
||||||
lines.push(`${key}: ${value}`);
|
lines.push(`${key}: ${value}`);
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -29,9 +29,9 @@ export async function run(_args: string[]): Promise<void> {
|
|||||||
if (mgr === 'launchd') {
|
if (mgr === 'launchd') {
|
||||||
try {
|
try {
|
||||||
const output = execSync('launchctl list', { encoding: 'utf-8' });
|
const output = execSync('launchctl list', { encoding: 'utf-8' });
|
||||||
if (output.includes('com.nanoclaw')) {
|
if (output.includes('com.ejclaw')) {
|
||||||
// Check if it has a PID (actually running)
|
// Check if it has a PID (actually running)
|
||||||
const line = output.split('\n').find((l) => l.includes('com.nanoclaw'));
|
const line = output.split('\n').find((l) => l.includes('com.ejclaw'));
|
||||||
if (line) {
|
if (line) {
|
||||||
const pidField = line.trim().split(/\s+/)[0];
|
const pidField = line.trim().split(/\s+/)[0];
|
||||||
service = pidField !== '-' && pidField ? 'running' : 'stopped';
|
service = pidField !== '-' && pidField ? 'running' : 'stopped';
|
||||||
@@ -43,14 +43,14 @@ export async function run(_args: string[]): Promise<void> {
|
|||||||
} else if (mgr === 'systemd') {
|
} else if (mgr === 'systemd') {
|
||||||
const prefix = isRoot() ? 'systemctl' : 'systemctl --user';
|
const prefix = isRoot() ? 'systemctl' : 'systemctl --user';
|
||||||
try {
|
try {
|
||||||
execSync(`${prefix} is-active nanoclaw`, { stdio: 'ignore' });
|
execSync(`${prefix} is-active ejclaw`, { stdio: 'ignore' });
|
||||||
service = 'running';
|
service = 'running';
|
||||||
} catch {
|
} catch {
|
||||||
try {
|
try {
|
||||||
const output = execSync(`${prefix} list-unit-files`, {
|
const output = execSync(`${prefix} list-unit-files`, {
|
||||||
encoding: 'utf-8',
|
encoding: 'utf-8',
|
||||||
});
|
});
|
||||||
if (output.includes('nanoclaw')) {
|
if (output.includes('ejclaw')) {
|
||||||
service = 'stopped';
|
service = 'stopped';
|
||||||
}
|
}
|
||||||
} catch {
|
} catch {
|
||||||
@@ -59,7 +59,7 @@ export async function run(_args: string[]): Promise<void> {
|
|||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
// Check for nohup PID file
|
// Check for nohup PID file
|
||||||
const pidFile = path.join(projectRoot, 'nanoclaw.pid');
|
const pidFile = path.join(projectRoot, 'ejclaw.pid');
|
||||||
if (fs.existsSync(pidFile)) {
|
if (fs.existsSync(pidFile)) {
|
||||||
try {
|
try {
|
||||||
const raw = fs.readFileSync(pidFile, 'utf-8').trim();
|
const raw = fs.readFileSync(pidFile, 'utf-8').trim();
|
||||||
|
|||||||
@@ -87,14 +87,14 @@ function buildBaseRunnerEnv(args: {
|
|||||||
: currentPath,
|
: currentPath,
|
||||||
TZ: TIMEZONE,
|
TZ: TIMEZONE,
|
||||||
HOME: os.homedir(),
|
HOME: os.homedir(),
|
||||||
NANOCLAW_GROUP_DIR: args.groupDir,
|
EJCLAW_GROUP_DIR: args.groupDir,
|
||||||
NANOCLAW_IPC_DIR: args.groupIpcDir,
|
EJCLAW_IPC_DIR: args.groupIpcDir,
|
||||||
NANOCLAW_GLOBAL_DIR: args.globalDir,
|
EJCLAW_GLOBAL_DIR: args.globalDir,
|
||||||
...(args.group.workDir ? { NANOCLAW_WORK_DIR: args.group.workDir } : {}),
|
...(args.group.workDir ? { EJCLAW_WORK_DIR: args.group.workDir } : {}),
|
||||||
NANOCLAW_CHAT_JID: args.chatJid,
|
EJCLAW_CHAT_JID: args.chatJid,
|
||||||
NANOCLAW_GROUP_FOLDER: args.group.folder,
|
EJCLAW_GROUP_FOLDER: args.group.folder,
|
||||||
NANOCLAW_IS_MAIN: args.isMain ? '1' : '0',
|
EJCLAW_IS_MAIN: args.isMain ? '1' : '0',
|
||||||
NANOCLAW_AGENT_TYPE: args.agentType,
|
EJCLAW_AGENT_TYPE: args.agentType,
|
||||||
CLAUDE_CONFIG_DIR: args.groupSessionsDir,
|
CLAUDE_CONFIG_DIR: args.groupSessionsDir,
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
@@ -240,22 +240,22 @@ function prepareCodexSessionEnvironment(args: {
|
|||||||
let toml = fs.existsSync(sessionConfigPath)
|
let toml = fs.existsSync(sessionConfigPath)
|
||||||
? fs.readFileSync(sessionConfigPath, 'utf-8')
|
? fs.readFileSync(sessionConfigPath, 'utf-8')
|
||||||
: '';
|
: '';
|
||||||
toml = toml.replace(/\n?\[mcp_servers\.nanoclaw\][\s\S]*?(?=\n\[|$)/, '');
|
toml = toml.replace(/\n?\[mcp_servers\.ejclaw\][\s\S]*?(?=\n\[|$)/, '');
|
||||||
toml = toml.replace(
|
toml = toml.replace(
|
||||||
/\n?\[mcp_servers\.memento-mcp\][\s\S]*?(?=\n\[|$)/,
|
/\n?\[mcp_servers\.memento-mcp\][\s\S]*?(?=\n\[|$)/,
|
||||||
'',
|
'',
|
||||||
);
|
);
|
||||||
const mcpSection = `
|
const mcpSection = `
|
||||||
[mcp_servers.nanoclaw]
|
[mcp_servers.ejclaw]
|
||||||
command = "node"
|
command = "node"
|
||||||
args = [${JSON.stringify(mcpServerPath)}]
|
args = [${JSON.stringify(mcpServerPath)}]
|
||||||
|
|
||||||
[mcp_servers.nanoclaw.env]
|
[mcp_servers.ejclaw.env]
|
||||||
NANOCLAW_IPC_DIR = ${JSON.stringify(args.env.NANOCLAW_IPC_DIR)}
|
EJCLAW_IPC_DIR = ${JSON.stringify(args.env.EJCLAW_IPC_DIR)}
|
||||||
NANOCLAW_CHAT_JID = ${JSON.stringify(args.chatJid)}
|
EJCLAW_CHAT_JID = ${JSON.stringify(args.chatJid)}
|
||||||
NANOCLAW_GROUP_FOLDER = ${JSON.stringify(args.group.folder)}
|
EJCLAW_GROUP_FOLDER = ${JSON.stringify(args.group.folder)}
|
||||||
NANOCLAW_IS_MAIN = ${JSON.stringify(args.isMain ? '1' : '0')}
|
EJCLAW_IS_MAIN = ${JSON.stringify(args.isMain ? '1' : '0')}
|
||||||
NANOCLAW_AGENT_TYPE = ${JSON.stringify(args.env.NANOCLAW_AGENT_TYPE)}
|
EJCLAW_AGENT_TYPE = ${JSON.stringify(args.env.EJCLAW_AGENT_TYPE)}
|
||||||
`;
|
`;
|
||||||
const mementoSseUrl =
|
const mementoSseUrl =
|
||||||
args.envVars.MEMENTO_MCP_SSE_URL || process.env.MEMENTO_MCP_SSE_URL;
|
args.envVars.MEMENTO_MCP_SSE_URL || process.env.MEMENTO_MCP_SSE_URL;
|
||||||
|
|||||||
@@ -5,15 +5,15 @@ import fs from 'fs';
|
|||||||
import { spawn } from 'child_process';
|
import { spawn } from 'child_process';
|
||||||
|
|
||||||
// Sentinel markers must match agent-runner.ts
|
// Sentinel markers must match agent-runner.ts
|
||||||
const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
|
const OUTPUT_START_MARKER = '---EJCLAW_OUTPUT_START---';
|
||||||
const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
|
const OUTPUT_END_MARKER = '---EJCLAW_OUTPUT_END---';
|
||||||
|
|
||||||
// Mock config
|
// Mock config
|
||||||
vi.mock('./config.js', () => ({
|
vi.mock('./config.js', () => ({
|
||||||
AGENT_MAX_OUTPUT_SIZE: 10485760,
|
AGENT_MAX_OUTPUT_SIZE: 10485760,
|
||||||
AGENT_TIMEOUT: 1800000, // 30min
|
AGENT_TIMEOUT: 1800000, // 30min
|
||||||
DATA_DIR: '/tmp/nanoclaw-test-data',
|
DATA_DIR: '/tmp/ejclaw-test-data',
|
||||||
GROUPS_DIR: '/tmp/nanoclaw-test-groups',
|
GROUPS_DIR: '/tmp/ejclaw-test-groups',
|
||||||
IDLE_TIMEOUT: 1800000, // 30min
|
IDLE_TIMEOUT: 1800000, // 30min
|
||||||
TIMEZONE: 'America/Los_Angeles',
|
TIMEZONE: 'America/Los_Angeles',
|
||||||
}));
|
}));
|
||||||
@@ -287,7 +287,7 @@ describe('agent-runner timeout behavior', () => {
|
|||||||
const spawnEnv = vi.mocked(spawn).mock.calls[0]?.[2]?.env as
|
const spawnEnv = vi.mocked(spawn).mock.calls[0]?.[2]?.env as
|
||||||
| Record<string, string>
|
| Record<string, string>
|
||||||
| undefined;
|
| undefined;
|
||||||
expect(spawnEnv?.NANOCLAW_CHAT_JID).toBe(testInput.chatJid);
|
expect(spawnEnv?.EJCLAW_CHAT_JID).toBe(testInput.chatJid);
|
||||||
|
|
||||||
const tomlWrite = vi
|
const tomlWrite = vi
|
||||||
.mocked(fs.writeFileSync)
|
.mocked(fs.writeFileSync)
|
||||||
@@ -295,7 +295,7 @@ describe('agent-runner timeout behavior', () => {
|
|||||||
String(call[0]).endsWith('/.codex/config.toml'),
|
String(call[0]).endsWith('/.codex/config.toml'),
|
||||||
);
|
);
|
||||||
expect(String(tomlWrite?.[1])).toContain(
|
expect(String(tomlWrite?.[1])).toContain(
|
||||||
`NANOCLAW_CHAT_JID = ${JSON.stringify(testInput.chatJid)}`,
|
`EJCLAW_CHAT_JID = ${JSON.stringify(testInput.chatJid)}`,
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -322,11 +322,11 @@ describe('agent-runner timeout behavior', () => {
|
|||||||
const spawnEnv = vi.mocked(spawn).mock.calls.at(-1)?.[2]?.env as
|
const spawnEnv = vi.mocked(spawn).mock.calls.at(-1)?.[2]?.env as
|
||||||
| Record<string, string>
|
| Record<string, string>
|
||||||
| undefined;
|
| undefined;
|
||||||
expect(spawnEnv?.NANOCLAW_IPC_DIR).toBe(
|
expect(spawnEnv?.EJCLAW_IPC_DIR).toBe(
|
||||||
'/tmp/nanoclaw-test-data/ipc/test-group/tasks/task-123',
|
'/tmp/ejclaw-test-data/ipc/test-group/tasks/task-123',
|
||||||
);
|
);
|
||||||
expect(spawnEnv?.CLAUDE_CONFIG_DIR).toBe(
|
expect(spawnEnv?.CLAUDE_CONFIG_DIR).toBe(
|
||||||
'/tmp/nanoclaw-test-data/sessions/test-group/tasks/task-123/.claude',
|
'/tmp/ejclaw-test-data/sessions/test-group/tasks/task-123/.claude',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -353,11 +353,11 @@ describe('agent-runner timeout behavior', () => {
|
|||||||
const spawnEnv = vi.mocked(spawn).mock.calls.at(-1)?.[2]?.env as
|
const spawnEnv = vi.mocked(spawn).mock.calls.at(-1)?.[2]?.env as
|
||||||
| Record<string, string>
|
| Record<string, string>
|
||||||
| undefined;
|
| undefined;
|
||||||
expect(spawnEnv?.NANOCLAW_IPC_DIR).toBe(
|
expect(spawnEnv?.EJCLAW_IPC_DIR).toBe(
|
||||||
'/tmp/nanoclaw-test-data/ipc/test-group/tasks/task-watch-group',
|
'/tmp/ejclaw-test-data/ipc/test-group/tasks/task-watch-group',
|
||||||
);
|
);
|
||||||
expect(spawnEnv?.CLAUDE_CONFIG_DIR).toBe(
|
expect(spawnEnv?.CLAUDE_CONFIG_DIR).toBe(
|
||||||
'/tmp/nanoclaw-test-data/sessions/test-group/.claude',
|
'/tmp/ejclaw-test-data/sessions/test-group/.claude',
|
||||||
);
|
);
|
||||||
});
|
});
|
||||||
|
|
||||||
@@ -365,9 +365,9 @@ describe('agent-runner timeout behavior', () => {
|
|||||||
vi.useRealTimers();
|
vi.useRealTimers();
|
||||||
fakeProc = createFakeProcess();
|
fakeProc = createFakeProcess();
|
||||||
const overlayPath =
|
const overlayPath =
|
||||||
'/tmp/nanoclaw-test-groups/test-group/.codex/config.toml';
|
'/tmp/ejclaw-test-groups/test-group/.codex/config.toml';
|
||||||
const sessionConfigPath =
|
const sessionConfigPath =
|
||||||
'/tmp/nanoclaw-test-data/sessions/test-group/.codex/config.toml';
|
'/tmp/ejclaw-test-data/sessions/test-group/.codex/config.toml';
|
||||||
let sessionToml = `model = "gpt-5.4"\n`;
|
let sessionToml = `model = "gpt-5.4"\n`;
|
||||||
|
|
||||||
vi.mocked(fs.existsSync).mockImplementation((p: fs.PathLike) => {
|
vi.mocked(fs.existsSync).mockImplementation((p: fs.PathLike) => {
|
||||||
@@ -428,7 +428,7 @@ OUROBOROS_LLM_BACKEND = "codex"
|
|||||||
const toml = String(tomlWrite?.[1]);
|
const toml = String(tomlWrite?.[1]);
|
||||||
expect(toml).toContain('[mcp_servers.ouroboros]');
|
expect(toml).toContain('[mcp_servers.ouroboros]');
|
||||||
expect(toml).toContain('OUROBOROS_AGENT_RUNTIME = "codex"');
|
expect(toml).toContain('OUROBOROS_AGENT_RUNTIME = "codex"');
|
||||||
expect(toml).toContain('[mcp_servers.nanoclaw]');
|
expect(toml).toContain('[mcp_servers.ejclaw]');
|
||||||
});
|
});
|
||||||
|
|
||||||
it('waits for queued streamed output before resolving an error exit', async () => {
|
it('waits for queued streamed output before resolving an error exit', async () => {
|
||||||
|
|||||||
@@ -1,5 +1,5 @@
|
|||||||
/**
|
/**
|
||||||
* Agent Process Runner for NanoClaw
|
* Agent Process Runner for EJClaw
|
||||||
* Spawns agent execution as direct host processes and handles IPC
|
* Spawns agent execution as direct host processes and handles IPC
|
||||||
*/
|
*/
|
||||||
import { ChildProcess, spawn } from 'child_process';
|
import { ChildProcess, spawn } from 'child_process';
|
||||||
@@ -21,8 +21,8 @@ import { logger } from './logger.js';
|
|||||||
import { AgentType, RegisteredGroup } from './types.js';
|
import { AgentType, RegisteredGroup } from './types.js';
|
||||||
|
|
||||||
// Sentinel markers for robust output parsing (must match agent-runner)
|
// Sentinel markers for robust output parsing (must match agent-runner)
|
||||||
const OUTPUT_START_MARKER = '---NANOCLAW_OUTPUT_START---';
|
const OUTPUT_START_MARKER = '---EJCLAW_OUTPUT_START---';
|
||||||
const OUTPUT_END_MARKER = '---NANOCLAW_OUTPUT_END---';
|
const OUTPUT_END_MARKER = '---EJCLAW_OUTPUT_END---';
|
||||||
|
|
||||||
export interface AgentInput {
|
export interface AgentInput {
|
||||||
prompt: string;
|
prompt: string;
|
||||||
@@ -75,12 +75,12 @@ export async function runAgentProcess(
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (input.runId) {
|
if (input.runId) {
|
||||||
env.NANOCLAW_RUN_ID = input.runId;
|
env.EJCLAW_RUN_ID = input.runId;
|
||||||
}
|
}
|
||||||
|
|
||||||
const safeName = group.folder.replace(/[^a-zA-Z0-9-]/g, '-');
|
const safeName = group.folder.replace(/[^a-zA-Z0-9-]/g, '-');
|
||||||
const processSuffix = input.runId || `${Date.now()}`;
|
const processSuffix = input.runId || `${Date.now()}`;
|
||||||
const processName = `nanoclaw-${safeName}-${processSuffix}`;
|
const processName = `ejclaw-${safeName}-${processSuffix}`;
|
||||||
|
|
||||||
// Check if runner is built
|
// Check if runner is built
|
||||||
const distEntry = path.join(runnerDir, 'dist', 'index.js');
|
const distEntry = path.join(runnerDir, 'dist', 'index.js');
|
||||||
@@ -119,7 +119,7 @@ export async function runAgentProcess(
|
|||||||
env,
|
env,
|
||||||
});
|
});
|
||||||
|
|
||||||
onProcess(proc, processName, env.NANOCLAW_IPC_DIR);
|
onProcess(proc, processName, env.EJCLAW_IPC_DIR);
|
||||||
|
|
||||||
let stdout = '';
|
let stdout = '';
|
||||||
let stderr = '';
|
let stderr = '';
|
||||||
|
|||||||
@@ -12,8 +12,8 @@ vi.mock('../env.js', () => ({ readEnvFile: vi.fn(() => ({})) }));
|
|||||||
vi.mock('../config.js', () => ({
|
vi.mock('../config.js', () => ({
|
||||||
ASSISTANT_NAME: 'Andy',
|
ASSISTANT_NAME: 'Andy',
|
||||||
TRIGGER_PATTERN: /^@Andy\b/i,
|
TRIGGER_PATTERN: /^@Andy\b/i,
|
||||||
DATA_DIR: '/tmp/nanoclaw-test-data',
|
DATA_DIR: '/tmp/ejclaw-test-data',
|
||||||
CACHE_DIR: '/tmp/nanoclaw-test-cache',
|
CACHE_DIR: '/tmp/ejclaw-test-cache',
|
||||||
}));
|
}));
|
||||||
|
|
||||||
// Mock logger
|
// Mock logger
|
||||||
|
|||||||
@@ -40,17 +40,17 @@ const HOME_DIR = process.env.HOME || os.homedir();
|
|||||||
export const SENDER_ALLOWLIST_PATH = path.join(
|
export const SENDER_ALLOWLIST_PATH = path.join(
|
||||||
HOME_DIR,
|
HOME_DIR,
|
||||||
'.config',
|
'.config',
|
||||||
'nanoclaw',
|
'ejclaw',
|
||||||
'sender-allowlist.json',
|
'sender-allowlist.json',
|
||||||
);
|
);
|
||||||
export const STORE_DIR = path.resolve(
|
export const STORE_DIR = path.resolve(
|
||||||
process.env.NANOCLAW_STORE_DIR || path.join(PROJECT_ROOT, 'store'),
|
process.env.EJCLAW_STORE_DIR || path.join(PROJECT_ROOT, 'store'),
|
||||||
);
|
);
|
||||||
export const GROUPS_DIR = path.resolve(
|
export const GROUPS_DIR = path.resolve(
|
||||||
process.env.NANOCLAW_GROUPS_DIR || path.join(PROJECT_ROOT, 'groups'),
|
process.env.EJCLAW_GROUPS_DIR || path.join(PROJECT_ROOT, 'groups'),
|
||||||
);
|
);
|
||||||
export const DATA_DIR = path.resolve(
|
export const DATA_DIR = path.resolve(
|
||||||
process.env.NANOCLAW_DATA_DIR || path.join(PROJECT_ROOT, 'data'),
|
process.env.EJCLAW_DATA_DIR || path.join(PROJECT_ROOT, 'data'),
|
||||||
);
|
);
|
||||||
// Shared cache directory (same across both services for dedup)
|
// Shared cache directory (same across both services for dedup)
|
||||||
export const CACHE_DIR = path.join(PROJECT_ROOT, 'cache');
|
export const CACHE_DIR = path.join(PROJECT_ROOT, 'cache');
|
||||||
|
|||||||
@@ -474,7 +474,7 @@ const isDirectRun =
|
|||||||
|
|
||||||
if (isDirectRun) {
|
if (isDirectRun) {
|
||||||
main().catch((err) => {
|
main().catch((err) => {
|
||||||
logger.error({ err }, 'Failed to start NanoClaw');
|
logger.error({ err }, 'Failed to start EJClaw');
|
||||||
process.exit(1);
|
process.exit(1);
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@ vi.mock('./agent-runner.js', () => ({
|
|||||||
}));
|
}));
|
||||||
|
|
||||||
vi.mock('./config.js', () => ({
|
vi.mock('./config.js', () => ({
|
||||||
DATA_DIR: '/tmp/nanoclaw-test-data',
|
DATA_DIR: '/tmp/ejclaw-test-data',
|
||||||
isSessionCommandSenderAllowed: vi.fn(() => false),
|
isSessionCommandSenderAllowed: vi.fn(() => false),
|
||||||
}));
|
}));
|
||||||
|
|
||||||
|
|||||||