assurgent 0.1.0 → 0.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +80 -100
- package/cli.ts +28 -9
- package/config.example.json +26 -0
- package/package.json +7 -2
- package/src/config.test.ts +81 -2
- package/src/config.ts +10 -2
- package/src/index.ts +2 -2
package/README.md
CHANGED
|
@@ -1,14 +1,22 @@
|
|
|
1
|
-
# assurgent
|
|
1
|
+
# assurgent
|
|
2
2
|
|
|
3
|
-
A lightweight
|
|
3
|
+
A lightweight bridge between any **chat platform** and any **coding agent** — talk to your AI coding agent from anywhere.
|
|
4
4
|
|
|
5
|
-
>
|
|
5
|
+
> **Warning:** This project is under active development. APIs may introduce breaking changes at any time. Use at your own risk.
|
|
6
|
+
|
|
7
|
+
> The name *assurgent* carries two meanings: a botanical term for a branch that grows upward (like this project, still growing), and a nod to *agent* hiding in plain sight — **assur·gent**.
|
|
6
8
|
|
|
7
9
|
```
|
|
8
|
-
You (
|
|
10
|
+
You (Chat) → assurgent → Coding Agent → Response → You (Chat)
|
|
9
11
|
```
|
|
10
12
|
|
|
11
|
-
|
|
13
|
+
Not an agent itself. A thin bridge with automatic session management, pluggable on both sides.
|
|
14
|
+
|
|
15
|
+
### Currently Supported
|
|
16
|
+
|
|
17
|
+
| Chat Platform | Coding Agent |
|
|
18
|
+
|---|---|
|
|
19
|
+
| Telegram | Claude Code CLI |
|
|
12
20
|
|
|
13
21
|
## Quick Start
|
|
14
22
|
|
|
@@ -16,22 +24,15 @@ It is not an agent itself. It's a bridge between Telegram and the Claude Code CL
|
|
|
16
24
|
|
|
17
25
|
- [Bun](https://bun.sh) runtime
|
|
18
26
|
- [Claude Code CLI](https://docs.anthropic.com/en/docs/claude-code) installed and authenticated
|
|
19
|
-
- A Telegram bot token
|
|
27
|
+
- A Telegram bot token from [@BotFather](https://t.me/BotFather)
|
|
20
28
|
|
|
21
|
-
### Install
|
|
29
|
+
### Install & Configure
|
|
22
30
|
|
|
23
31
|
```bash
|
|
24
|
-
|
|
25
|
-
bun install
|
|
32
|
+
bunx assurgent init
|
|
26
33
|
```
|
|
27
34
|
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
```bash
|
|
31
|
-
cp config.example.json config.json
|
|
32
|
-
```
|
|
33
|
-
|
|
34
|
-
Edit `config.json`:
|
|
35
|
+
This creates `~/.assurgent/config.json` from the bundled template. Edit it:
|
|
35
36
|
|
|
36
37
|
```json
|
|
37
38
|
{
|
|
@@ -39,11 +40,7 @@ Edit `config.json`:
|
|
|
39
40
|
"adapter": "telegram",
|
|
40
41
|
"telegram": {
|
|
41
42
|
"botToken": "YOUR_BOT_TOKEN_HERE",
|
|
42
|
-
"allowedUserIds": ["YOUR_TELEGRAM_USER_ID"]
|
|
43
|
-
"placeholder": {
|
|
44
|
-
"enabled": true,
|
|
45
|
-
"text": "thinking..."
|
|
46
|
-
}
|
|
43
|
+
"allowedUserIds": ["YOUR_TELEGRAM_USER_ID"]
|
|
47
44
|
}
|
|
48
45
|
},
|
|
49
46
|
"agent": {
|
|
@@ -51,125 +48,108 @@ Edit `config.json`:
|
|
|
51
48
|
"claude-code": {
|
|
52
49
|
"model": "sonnet",
|
|
53
50
|
"maxTurns": 10,
|
|
54
|
-
"flags": ["--dangerously-skip-permissions"]
|
|
55
|
-
"claudePath": "claude"
|
|
51
|
+
"flags": ["--dangerously-skip-permissions"]
|
|
56
52
|
}
|
|
57
53
|
},
|
|
58
54
|
"session": {
|
|
59
55
|
"turnLimit": 20
|
|
60
56
|
},
|
|
61
|
-
"workspacePath": "/path/to/
|
|
57
|
+
"workspacePath": "/path/to/your/workspace"
|
|
62
58
|
}
|
|
63
59
|
```
|
|
64
60
|
|
|
65
61
|
To find your Telegram user ID, message [@userinfobot](https://t.me/userinfobot).
|
|
66
62
|
|
|
67
|
-
>
|
|
63
|
+
> If `claude` is not in your PATH (common on servers), set `claudePath` to the full path, e.g. `/home/user/.local/bin/claude`.
|
|
68
64
|
|
|
69
65
|
### Run
|
|
70
66
|
|
|
71
67
|
```bash
|
|
72
|
-
|
|
68
|
+
bunx assurgent
|
|
73
69
|
```
|
|
74
70
|
|
|
75
71
|
Then message your bot on Telegram.
|
|
76
72
|
|
|
73
|
+
### Custom Config Location
|
|
74
|
+
|
|
75
|
+
Set `ASSURGENT_HOME` to use a different config directory:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
ASSURGENT_HOME=/custom/path bunx assurgent
|
|
79
|
+
```
|
|
80
|
+
|
|
81
|
+
Default: `~/.assurgent/`
|
|
82
|
+
|
|
77
83
|
## Bot Commands
|
|
78
84
|
|
|
79
|
-
| Command
|
|
80
|
-
|
|
81
|
-
| `/new`
|
|
82
|
-
| `/extend [N]`
|
|
83
|
-
| `/model [opus\|sonnet\|haiku\|default]` | Show or change model for current session
|
|
84
|
-
| `/session list`
|
|
85
|
-
| `/session info`
|
|
86
|
-
| `/session resume <name>`
|
|
87
|
-
| `/session rename <name>`
|
|
88
|
-
| `/help`
|
|
85
|
+
| Command | Description |
|
|
86
|
+
|---|---|
|
|
87
|
+
| `/new` | Archive current session, start fresh |
|
|
88
|
+
| `/extend [N]` | Extend session by N turns (default: turnLimit) |
|
|
89
|
+
| `/model [opus\|sonnet\|haiku\|default]` | Show or change model for current session |
|
|
90
|
+
| `/session list` | List all sessions with turn usage |
|
|
91
|
+
| `/session info` | Show current session details |
|
|
92
|
+
| `/session resume <name>` | Resume a session by name |
|
|
93
|
+
| `/session rename <name>` | Rename current session |
|
|
94
|
+
| `/help` | Show all commands |
|
|
89
95
|
|
|
90
|
-
Any other text
|
|
96
|
+
Any other text is forwarded to the coding agent in the current session.
|
|
91
97
|
|
|
92
98
|
## Sessions
|
|
93
99
|
|
|
94
|
-
Sessions
|
|
100
|
+
Sessions resume automatically until you start a new one with `/new`.
|
|
95
101
|
|
|
96
|
-
When
|
|
97
|
-
- `/extend 20` to add more turns and keep the same session
|
|
98
|
-
- `/new` to archive and start fresh
|
|
102
|
+
When turns reach the configured `turnLimit`, the bot pauses and asks you to `/extend` or `/new`.
|
|
99
103
|
|
|
100
|
-
Session names are auto-generated from the date and first message (e.g. `2026-03-27-fix-bug`).
|
|
104
|
+
Session names are auto-generated from the date and first message (e.g. `2026-03-27-fix-bug`). Override the model per-session with `/model opus` — persists until reset or new session.
|
|
101
105
|
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
Sessions persist across restarts via `state/sessions.json` (relative to `workspacePath`).
|
|
106
|
+
Sessions persist across restarts in `~/.assurgent/state/sessions.json`.
|
|
105
107
|
|
|
106
108
|
## Config Reference
|
|
107
109
|
|
|
108
|
-
| Field
|
|
109
|
-
|
|
110
|
-
| `chat.adapter`
|
|
111
|
-
| `chat.telegram.botToken`
|
|
112
|
-
| `chat.telegram.allowedUserIds`
|
|
113
|
-
| `chat.telegram.placeholder.enabled` | Show
|
|
114
|
-
| `chat.telegram.placeholder.text`
|
|
115
|
-
| `agent.adapter`
|
|
116
|
-
| `agent.claude-code.model`
|
|
117
|
-
| `agent.claude-code.maxTurns`
|
|
118
|
-
| `agent.claude-code.flags`
|
|
119
|
-
| `agent.claude-code.claudePath`
|
|
120
|
-
| `session.turnLimit`
|
|
121
|
-
| `workspacePath`
|
|
122
|
-
|
|
123
|
-
## Environment Variables Passed to Agent
|
|
124
|
-
|
|
125
|
-
The wrapper sets these env vars on every Claude Code invocation:
|
|
126
|
-
|
|
127
|
-
| Variable | Description |
|
|
128
|
-
| ------------------ | ---------------------------------------------------- |
|
|
129
|
-
| `AGENT_SESSION_ID` | The Claude Code session UUID for the current session |
|
|
110
|
+
| Field | Description |
|
|
111
|
+
|---|---|
|
|
112
|
+
| `chat.adapter` | Chat platform (`"telegram"`) |
|
|
113
|
+
| `chat.telegram.botToken` | Telegram bot token |
|
|
114
|
+
| `chat.telegram.allowedUserIds` | Array of allowed Telegram user IDs |
|
|
115
|
+
| `chat.telegram.placeholder.enabled` | Show placeholder while agent thinks |
|
|
116
|
+
| `chat.telegram.placeholder.text` | Placeholder text (default: `"thinking..."`) |
|
|
117
|
+
| `agent.adapter` | Agent backend (`"claude-code"`) |
|
|
118
|
+
| `agent.claude-code.model` | Default model (`"opus"`, `"sonnet"`, `"haiku"`) |
|
|
119
|
+
| `agent.claude-code.maxTurns` | Max agent turns per invocation |
|
|
120
|
+
| `agent.claude-code.flags` | Extra CLI flags |
|
|
121
|
+
| `agent.claude-code.claudePath` | Path to `claude` binary (default: `"claude"`) |
|
|
122
|
+
| `session.turnLimit` | Pause after N turns, ask to extend or start new |
|
|
123
|
+
| `workspacePath` | Absolute path to workspace for Claude Code |
|
|
130
124
|
|
|
131
125
|
## Development
|
|
132
126
|
|
|
133
127
|
```bash
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
bun
|
|
128
|
+
git clone https://github.com/thaitype/assurgent.git
|
|
129
|
+
cd assurgent
|
|
130
|
+
bun install
|
|
131
|
+
```
|
|
132
|
+
|
|
133
|
+
```bash
|
|
134
|
+
bun run dev # Start with --watch
|
|
135
|
+
bun run typecheck # tsc --noEmit
|
|
137
136
|
bun test # Run tests
|
|
138
|
-
bun run lint #
|
|
139
|
-
bun run lint:fix # Auto-fix
|
|
137
|
+
bun run lint # Biome check
|
|
138
|
+
bun run lint:fix # Auto-fix
|
|
140
139
|
```
|
|
141
140
|
|
|
142
|
-
|
|
141
|
+
For local development, config is read from `~/.assurgent/config.json` (same as production). Use `ASSURGENT_HOME` to point to a dev-specific config.
|
|
142
|
+
|
|
143
|
+
### Architecture
|
|
143
144
|
|
|
144
145
|
```
|
|
145
|
-
ChatAdapter (Telegram)
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
(name <-> UUID)
|
|
146
|
+
ChatAdapter (e.g. Telegram) → Wrapper Core → AgentAdapter (e.g. Claude Code CLI)
|
|
147
|
+
│
|
|
148
|
+
Session Manager
|
|
149
149
|
```
|
|
150
150
|
|
|
151
|
-
Both
|
|
151
|
+
Both sides are pluggable interfaces. Adding a new chat platform or coding agent is just implementing an adapter — no changes to the core.
|
|
152
152
|
|
|
153
|
-
|
|
153
|
+
## License
|
|
154
154
|
|
|
155
|
-
|
|
156
|
-
app/
|
|
157
|
-
├── src/
|
|
158
|
-
│ ├── index.ts # Entry point
|
|
159
|
-
│ ├── config.ts # Config loader + validation
|
|
160
|
-
│ ├── core/
|
|
161
|
-
│ │ ├── wrapper.ts # Core orchestrator
|
|
162
|
-
│ │ └── session-manager.ts
|
|
163
|
-
│ ├── interfaces/
|
|
164
|
-
│ │ ├── chat-adapter.ts
|
|
165
|
-
│ │ └── agent-adapter.ts
|
|
166
|
-
│ ├── chat/
|
|
167
|
-
│ │ └── telegram.ts # Telegram adapter (grammy)
|
|
168
|
-
│ └── agent/
|
|
169
|
-
│ └── claude-code.ts # Claude Code adapter (execa)
|
|
170
|
-
├── config.json # Runtime config (gitignored)
|
|
171
|
-
├── config.example.json # Committed template
|
|
172
|
-
├── package.json
|
|
173
|
-
├── tsconfig.json
|
|
174
|
-
└── biome.json
|
|
175
|
-
```
|
|
155
|
+
MIT
|
package/cli.ts
CHANGED
|
@@ -1,8 +1,8 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
// Entry point for `bunx assurgent`
|
|
3
3
|
|
|
4
|
-
import { readFileSync } from "node:fs";
|
|
5
|
-
import { join } from "node:path";
|
|
4
|
+
import { copyFileSync, existsSync, mkdirSync, readFileSync } from "node:fs";
|
|
5
|
+
import { dirname, join } from "node:path";
|
|
6
6
|
|
|
7
7
|
const args = process.argv.slice(2);
|
|
8
8
|
|
|
@@ -11,15 +11,16 @@ if (args.includes("--help") || args.includes("-h")) {
|
|
|
11
11
|
assurgent - Telegram bot bridge to Claude Code CLI
|
|
12
12
|
|
|
13
13
|
Usage:
|
|
14
|
-
assurgent
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
--
|
|
18
|
-
--version, -v Print the version number
|
|
14
|
+
assurgent Start the bot
|
|
15
|
+
assurgent init Scaffold config.json into ASSURGENT_HOME
|
|
16
|
+
assurgent --help Show this help message
|
|
17
|
+
assurgent --version Print the version number
|
|
19
18
|
|
|
20
19
|
Configuration:
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
Config is loaded from $ASSURGENT_HOME/config.json.
|
|
21
|
+
ASSURGENT_HOME defaults to ~/.assurgent/ if not set.
|
|
22
|
+
|
|
23
|
+
Run "assurgent init" to create the config file, then edit it with your settings.
|
|
23
24
|
`);
|
|
24
25
|
process.exit(0);
|
|
25
26
|
}
|
|
@@ -31,4 +32,22 @@ if (args.includes("--version") || args.includes("-v")) {
|
|
|
31
32
|
process.exit(0);
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
if (args[0] === "init") {
|
|
36
|
+
const { getAssurgentHome } = await import("./src/config.ts");
|
|
37
|
+
const target = join(getAssurgentHome(), "config.json");
|
|
38
|
+
|
|
39
|
+
if (existsSync(target)) {
|
|
40
|
+
console.error(
|
|
41
|
+
`Config already exists at ${target}. Edit it directly or delete it to re-initialize.`,
|
|
42
|
+
);
|
|
43
|
+
process.exit(1);
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
mkdirSync(dirname(target), { recursive: true });
|
|
47
|
+
const source = join(import.meta.dirname, "config.example.json");
|
|
48
|
+
copyFileSync(source, target);
|
|
49
|
+
console.log(`Created config at ${target}. Edit it with your settings, then run "assurgent".`);
|
|
50
|
+
process.exit(0);
|
|
51
|
+
}
|
|
52
|
+
|
|
34
53
|
await import("./src/index.ts");
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"chat": {
|
|
3
|
+
"adapter": "telegram",
|
|
4
|
+
"telegram": {
|
|
5
|
+
"botToken": "123456:ABC-DEF...",
|
|
6
|
+
"allowedUserIds": ["YOUR_TELEGRAM_USER_ID"],
|
|
7
|
+
"placeholder": {
|
|
8
|
+
"enabled": true,
|
|
9
|
+
"text": "thinking..."
|
|
10
|
+
}
|
|
11
|
+
}
|
|
12
|
+
},
|
|
13
|
+
"agent": {
|
|
14
|
+
"adapter": "claude-code",
|
|
15
|
+
"claude-code": {
|
|
16
|
+
"model": "sonnet",
|
|
17
|
+
"maxTurns": 10,
|
|
18
|
+
"flags": ["--dangerously-skip-permissions"],
|
|
19
|
+
"claudePath": "claude"
|
|
20
|
+
}
|
|
21
|
+
},
|
|
22
|
+
"session": {
|
|
23
|
+
"turnLimit": 20
|
|
24
|
+
},
|
|
25
|
+
"workspacePath": "/path/to/your/workspace"
|
|
26
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assurgent",
|
|
3
|
-
"version": "0.1
|
|
3
|
+
"version": "0.2.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"assurgent": "./cli.ts"
|
|
@@ -8,6 +8,7 @@
|
|
|
8
8
|
"files": [
|
|
9
9
|
"cli.ts",
|
|
10
10
|
"src",
|
|
11
|
+
"config.example.json",
|
|
11
12
|
"README.md"
|
|
12
13
|
],
|
|
13
14
|
"publishConfig": {
|
|
@@ -18,7 +19,10 @@
|
|
|
18
19
|
"typecheck": "tsc --noEmit",
|
|
19
20
|
"test": "bun test",
|
|
20
21
|
"lint": "biome check .",
|
|
21
|
-
"lint:fix": "biome check . --fix"
|
|
22
|
+
"lint:fix": "biome check . --fix",
|
|
23
|
+
"release:patch": "release-it patch",
|
|
24
|
+
"release:minor": "release-it minor",
|
|
25
|
+
"release:major": "release-it major"
|
|
22
26
|
},
|
|
23
27
|
"dependencies": {
|
|
24
28
|
"execa": "^9.5.2",
|
|
@@ -27,6 +31,7 @@
|
|
|
27
31
|
"devDependencies": {
|
|
28
32
|
"@biomejs/biome": "^1.9.4",
|
|
29
33
|
"@types/bun": "latest",
|
|
34
|
+
"release-it": "^19.2.4",
|
|
30
35
|
"typescript": "^5.8.2"
|
|
31
36
|
}
|
|
32
37
|
}
|
package/src/config.test.ts
CHANGED
|
@@ -1,5 +1,8 @@
|
|
|
1
|
-
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import
|
|
1
|
+
import { afterEach, beforeEach, describe, expect, test } from "bun:test";
|
|
2
|
+
import fs from "node:fs";
|
|
3
|
+
import os from "node:os";
|
|
4
|
+
import path from "node:path";
|
|
5
|
+
import { getAssurgentHome, loadConfig, validateConfig } from "./config";
|
|
3
6
|
import type { Config } from "./config";
|
|
4
7
|
|
|
5
8
|
function validConfig(): Config {
|
|
@@ -26,6 +29,82 @@ function validConfig(): Config {
|
|
|
26
29
|
};
|
|
27
30
|
}
|
|
28
31
|
|
|
32
|
+
describe("getAssurgentHome", () => {
|
|
33
|
+
const originalEnv = process.env.ASSURGENT_HOME;
|
|
34
|
+
|
|
35
|
+
afterEach(() => {
|
|
36
|
+
if (originalEnv === undefined) {
|
|
37
|
+
process.env.ASSURGENT_HOME = undefined;
|
|
38
|
+
} else {
|
|
39
|
+
process.env.ASSURGENT_HOME = originalEnv;
|
|
40
|
+
}
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("returns ~/.assurgent when ASSURGENT_HOME is not set", () => {
|
|
44
|
+
process.env.ASSURGENT_HOME = undefined;
|
|
45
|
+
const expected = path.join(os.homedir(), ".assurgent");
|
|
46
|
+
expect(getAssurgentHome()).toBe(expected);
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
test("returns ASSURGENT_HOME env var value when set", () => {
|
|
50
|
+
process.env.ASSURGENT_HOME = "/custom/assurgent/home";
|
|
51
|
+
expect(getAssurgentHome()).toBe("/custom/assurgent/home");
|
|
52
|
+
});
|
|
53
|
+
});
|
|
54
|
+
|
|
55
|
+
describe("loadConfig", () => {
|
|
56
|
+
let tempDir: string;
|
|
57
|
+
|
|
58
|
+
beforeEach(() => {
|
|
59
|
+
tempDir = fs.mkdtempSync(path.join(os.tmpdir(), "assurgent-test-"));
|
|
60
|
+
});
|
|
61
|
+
|
|
62
|
+
afterEach(() => {
|
|
63
|
+
fs.rmSync(tempDir, { recursive: true, force: true });
|
|
64
|
+
process.env.ASSURGENT_HOME = undefined;
|
|
65
|
+
});
|
|
66
|
+
|
|
67
|
+
function writeConfig(dir: string, config: Config): string {
|
|
68
|
+
const configPath = path.join(dir, "config.json");
|
|
69
|
+
fs.writeFileSync(configPath, JSON.stringify(config), "utf-8");
|
|
70
|
+
return configPath;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
test("reads config from explicit path", () => {
|
|
74
|
+
const configPath = writeConfig(tempDir, validConfig());
|
|
75
|
+
const config = loadConfig(configPath);
|
|
76
|
+
expect(config.chat.adapter).toBe("telegram");
|
|
77
|
+
expect(config.session.turnLimit).toBe(20);
|
|
78
|
+
});
|
|
79
|
+
|
|
80
|
+
test("reads config from $ASSURGENT_HOME/config.json when no explicit path", () => {
|
|
81
|
+
process.env.ASSURGENT_HOME = tempDir;
|
|
82
|
+
writeConfig(tempDir, validConfig());
|
|
83
|
+
const config = loadConfig();
|
|
84
|
+
expect(config.chat.adapter).toBe("telegram");
|
|
85
|
+
});
|
|
86
|
+
|
|
87
|
+
test("throws with helpful error when config is missing and explicit path given", () => {
|
|
88
|
+
const missingPath = path.join(tempDir, "nonexistent.json");
|
|
89
|
+
expect(() => loadConfig(missingPath)).toThrow("Config file not found");
|
|
90
|
+
});
|
|
91
|
+
|
|
92
|
+
test("throws mentioning 'assurgent init' when config is missing", () => {
|
|
93
|
+
const missingPath = path.join(tempDir, "nonexistent.json");
|
|
94
|
+
expect(() => loadConfig(missingPath)).toThrow("assurgent init");
|
|
95
|
+
});
|
|
96
|
+
|
|
97
|
+
test("throws mentioning 'ASSURGENT_HOME' when config is missing", () => {
|
|
98
|
+
const missingPath = path.join(tempDir, "nonexistent.json");
|
|
99
|
+
expect(() => loadConfig(missingPath)).toThrow("ASSURGENT_HOME");
|
|
100
|
+
});
|
|
101
|
+
|
|
102
|
+
test("throws with config path in error message when config is missing", () => {
|
|
103
|
+
process.env.ASSURGENT_HOME = tempDir;
|
|
104
|
+
expect(() => loadConfig()).toThrow(path.join(tempDir, "config.json"));
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
|
|
29
108
|
describe("validateConfig", () => {
|
|
30
109
|
test("accepts valid config", () => {
|
|
31
110
|
expect(() => validateConfig(validConfig())).not.toThrow();
|
package/src/config.ts
CHANGED
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import fs from "node:fs";
|
|
2
|
+
import os from "node:os";
|
|
2
3
|
import path from "node:path";
|
|
3
4
|
|
|
4
5
|
/** Runtime configuration for the bot. */
|
|
@@ -31,6 +32,11 @@ export interface Config {
|
|
|
31
32
|
workspacePath: string;
|
|
32
33
|
}
|
|
33
34
|
|
|
35
|
+
/** Returns the resolved ASSURGENT_HOME path. */
|
|
36
|
+
export function getAssurgentHome(): string {
|
|
37
|
+
return process.env.ASSURGENT_HOME ?? path.join(os.homedir(), ".assurgent");
|
|
38
|
+
}
|
|
39
|
+
|
|
34
40
|
/** Fail fast with clear errors if required config fields are missing or invalid. */
|
|
35
41
|
export function validateConfig(config: Config): void {
|
|
36
42
|
const errors: string[] = [];
|
|
@@ -71,10 +77,12 @@ export function validateConfig(config: Config): void {
|
|
|
71
77
|
|
|
72
78
|
/** Load and validate config from a JSON file. */
|
|
73
79
|
export function loadConfig(configPath?: string): Config {
|
|
74
|
-
const resolved = configPath ?? path.
|
|
80
|
+
const resolved = configPath ?? path.join(getAssurgentHome(), "config.json");
|
|
75
81
|
|
|
76
82
|
if (!fs.existsSync(resolved)) {
|
|
77
|
-
throw new Error(
|
|
83
|
+
throw new Error(
|
|
84
|
+
`Config file not found: ${resolved}\nRun "assurgent init" to create one, or set ASSURGENT_HOME to point to an existing config directory.`,
|
|
85
|
+
);
|
|
78
86
|
}
|
|
79
87
|
|
|
80
88
|
const raw = JSON.parse(fs.readFileSync(resolved, "utf-8"));
|
package/src/index.ts
CHANGED
|
@@ -2,7 +2,7 @@ import path from "node:path";
|
|
|
2
2
|
import { ClaudeCodeAdapter } from "./agent/claude-code";
|
|
3
3
|
import { TelegramAdapter } from "./chat/telegram";
|
|
4
4
|
import type { Config } from "./config";
|
|
5
|
-
import { loadConfig } from "./config";
|
|
5
|
+
import { getAssurgentHome, loadConfig } from "./config";
|
|
6
6
|
import { SessionManager } from "./core/session-manager";
|
|
7
7
|
import { Wrapper } from "./core/wrapper";
|
|
8
8
|
import type { AgentAdapter } from "./interfaces/agent-adapter";
|
|
@@ -33,7 +33,7 @@ function createAgentAdapter(cfg: Config): AgentAdapter {
|
|
|
33
33
|
const chat = createChatAdapter(config);
|
|
34
34
|
const agent = createAgentAdapter(config);
|
|
35
35
|
const sessions = new SessionManager({
|
|
36
|
-
statePath: path.join(
|
|
36
|
+
statePath: path.join(getAssurgentHome(), "state"),
|
|
37
37
|
});
|
|
38
38
|
|
|
39
39
|
const wrapper = new Wrapper(
|