assurgent 0.1.0 → 0.2.0
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/cli.ts +28 -9
- package/config.example.json +26 -0
- package/package.json +2 -6
- package/src/config.test.ts +81 -2
- package/src/config.ts +10 -2
- package/src/index.ts +2 -2
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,15 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "assurgent",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"bin": {
|
|
6
6
|
"assurgent": "./cli.ts"
|
|
7
7
|
},
|
|
8
|
-
"files": [
|
|
9
|
-
"cli.ts",
|
|
10
|
-
"src",
|
|
11
|
-
"README.md"
|
|
12
|
-
],
|
|
8
|
+
"files": ["cli.ts", "src", "config.example.json", "README.md"],
|
|
13
9
|
"publishConfig": {
|
|
14
10
|
"access": "public"
|
|
15
11
|
},
|
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(
|