@fickydev/pigent 0.1.0 → 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/.env.example +2 -2
- package/CHANGELOG.md +9 -1
- package/README.md +46 -30
- package/TODO.md +10 -3
- package/agents/assistant/SYSTEM.md +3 -0
- package/agents/{coder → assistant}/agent.yaml +4 -4
- package/package.json +1 -1
- package/pigent.yaml +2 -2
- package/profiles/assistant.yaml +11 -0
- package/src/cli/run.ts +248 -19
- package/src/cli/setup.ts +9 -55
- package/src/db/client.ts +3 -0
- package/src/db/repositories/TelegramRepository.ts +2 -2
- package/src/main.ts +9 -0
- package/agents/coder/SYSTEM.md +0 -3
- package/profiles/software-engineer.yaml +0 -11
package/.env.example
CHANGED
|
@@ -17,6 +17,6 @@ PIGENT_FAKE_AGENT=0
|
|
|
17
17
|
# Set to 1 to use fake responses if Pi SDK execution fails.
|
|
18
18
|
PIGENT_FALLBACK_FAKE_AGENT=1
|
|
19
19
|
# Set to 1 to auto-create DB chat routing for new Telegram chats.
|
|
20
|
-
PIGENT_AUTO_SETUP_CHATS=
|
|
20
|
+
PIGENT_AUTO_SETUP_CHATS=1
|
|
21
21
|
# Agent used when auto-creating chat routing. Defaults to first loaded agent.
|
|
22
|
-
PIGENT_AUTO_SETUP_DEFAULT_AGENT=
|
|
22
|
+
PIGENT_AUTO_SETUP_DEFAULT_AGENT=assistant
|
package/CHANGELOG.md
CHANGED
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
- Added project planning docs: `PLAN.md`, `TODO.md`, and `AGENTS.md`.
|
|
13
13
|
- Added Bun TypeScript project skeleton with startup/shutdown flow.
|
|
14
14
|
- Added config schemas and filesystem config loader.
|
|
15
|
-
- Added example
|
|
15
|
+
- Added example generic assistant agent and profile.
|
|
16
16
|
- Added Drizzle SQLite schema, migration config, and repositories.
|
|
17
17
|
- Added channel abstraction and Telegram polling adapter skeleton.
|
|
18
18
|
- Added Telegram Bot API wrapper for `getUpdates` and `sendMessage`.
|
|
@@ -27,6 +27,14 @@
|
|
|
27
27
|
- Added optional automatic Telegram chat setup via `PIGENT_AUTO_SETUP_CHATS`.
|
|
28
28
|
- Set default workspace root to `~/.pigent` and added tilde expansion for agent workspaces.
|
|
29
29
|
- Added npm package metadata and MIT license for publishing.
|
|
30
|
+
- Added automatic user service install/start flow for `bunx @fickydev/pigent`.
|
|
31
|
+
- Added `pigent update` to back up the app, refresh shipped files from the latest package, preserve user config/state, and restart the service.
|
|
32
|
+
- Changed default agent to generic `Assistant` and enabled automatic chat setup by default.
|
|
33
|
+
- Renamed default agent/profile files to `agents/assistant` and `profiles/assistant.yaml`.
|
|
34
|
+
- Added short `pigent` command shim for `status`, `logs`, `start`, `stop`, `restart`, `update`, and `setup`.
|
|
35
|
+
- Simplified published CLI flow so `bunx @fickydev/pigent` runs setup and starts Pigent without install/start/typecheck prompts.
|
|
36
|
+
- Added automatic database migrations at daemon startup.
|
|
37
|
+
- Kept daemon process alive after startup so CLI runs do not exit after `pigent ready`.
|
|
30
38
|
|
|
31
39
|
### Changed
|
|
32
40
|
|
package/README.md
CHANGED
|
@@ -71,30 +71,48 @@ src/
|
|
|
71
71
|
|
|
72
72
|
## Setup And Run
|
|
73
73
|
|
|
74
|
-
|
|
74
|
+
Install and start Pigent as a user service:
|
|
75
75
|
|
|
76
76
|
```bash
|
|
77
|
-
bunx @fickydev/pigent
|
|
77
|
+
bunx @fickydev/pigent
|
|
78
78
|
```
|
|
79
79
|
|
|
80
|
-
|
|
80
|
+
Default app directory:
|
|
81
|
+
|
|
82
|
+
```text
|
|
83
|
+
~/.pigent/app
|
|
84
|
+
```
|
|
85
|
+
|
|
86
|
+
After install, use short commands:
|
|
81
87
|
|
|
82
88
|
```bash
|
|
83
|
-
|
|
89
|
+
pigent status
|
|
90
|
+
pigent logs
|
|
91
|
+
pigent stop
|
|
92
|
+
pigent start
|
|
93
|
+
pigent restart
|
|
94
|
+
pigent update
|
|
95
|
+
pigent setup
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
Update to latest package:
|
|
99
|
+
|
|
100
|
+
```bash
|
|
101
|
+
pigent update
|
|
84
102
|
```
|
|
85
103
|
|
|
86
|
-
|
|
104
|
+
`pigent update` backs up the app, preserves `.env`, SQLite DB, `pigent.yaml`, `agents/`, and `profiles/`, refreshes shipped files, and restarts the service.
|
|
105
|
+
|
|
106
|
+
Run in foreground instead of installing a service:
|
|
87
107
|
|
|
88
108
|
```bash
|
|
89
|
-
|
|
90
|
-
# or, with bunx after publish:
|
|
91
|
-
bunx @fickydev/pigent my-pigent --setup
|
|
109
|
+
bunx @fickydev/pigent run
|
|
92
110
|
```
|
|
93
111
|
|
|
94
|
-
|
|
112
|
+
From a checked-out repo:
|
|
95
113
|
|
|
96
114
|
```bash
|
|
97
|
-
bun run run
|
|
115
|
+
bun run run
|
|
98
116
|
```
|
|
99
117
|
|
|
100
118
|
The setup flow will:
|
|
@@ -106,9 +124,7 @@ The setup flow will:
|
|
|
106
124
|
- optionally enable automatic Telegram chat setup
|
|
107
125
|
- optionally configure Telegram chat routing in `pigent.yaml`
|
|
108
126
|
- optionally configure log level and Telegram polling values in custom mode
|
|
109
|
-
|
|
110
|
-
- optionally apply database migrations
|
|
111
|
-
- optionally run typecheck
|
|
127
|
+
Database migrations run automatically at daemon startup. The published CLI does not ask users to run `bun install`, `bun start`, or `bun typecheck`.
|
|
112
128
|
|
|
113
129
|
Manual setup:
|
|
114
130
|
|
|
@@ -127,8 +143,8 @@ TELEGRAM_BOT_TOKEN=
|
|
|
127
143
|
PIGENT_WORKSPACE_ROOT=~/.pigent
|
|
128
144
|
PIGENT_FAKE_AGENT=0
|
|
129
145
|
PIGENT_FALLBACK_FAKE_AGENT=1
|
|
130
|
-
PIGENT_AUTO_SETUP_CHATS=
|
|
131
|
-
PIGENT_AUTO_SETUP_DEFAULT_AGENT=
|
|
146
|
+
PIGENT_AUTO_SETUP_CHATS=1
|
|
147
|
+
PIGENT_AUTO_SETUP_DEFAULT_AGENT=assistant
|
|
132
148
|
```
|
|
133
149
|
|
|
134
150
|
Start daemon directly after setup:
|
|
@@ -195,9 +211,9 @@ Edit `pigent.yaml`:
|
|
|
195
211
|
telegramChats:
|
|
196
212
|
- chatId: "123456"
|
|
197
213
|
title: Ficky Private Chat
|
|
198
|
-
defaultAgent:
|
|
214
|
+
defaultAgent: assistant
|
|
199
215
|
allowedAgents:
|
|
200
|
-
-
|
|
216
|
+
- assistant
|
|
201
217
|
instructions: |
|
|
202
218
|
Be concise.
|
|
203
219
|
```
|
|
@@ -221,17 +237,17 @@ hello
|
|
|
221
237
|
```
|
|
222
238
|
|
|
223
239
|
```text
|
|
224
|
-
@
|
|
240
|
+
@assistant review this
|
|
225
241
|
```
|
|
226
242
|
|
|
227
243
|
```text
|
|
228
|
-
/agent
|
|
244
|
+
/agent assistant write a test
|
|
229
245
|
```
|
|
230
246
|
|
|
231
247
|
With `PIGENT_FAKE_AGENT=1`, expected fake runner response:
|
|
232
248
|
|
|
233
249
|
```text
|
|
234
|
-
[
|
|
250
|
+
[Assistant] fake runner received: hello
|
|
235
251
|
```
|
|
236
252
|
|
|
237
253
|
With `PIGENT_FAKE_AGENT=0`, Pigent calls the Pi SDK and returns the Pi assistant response.
|
|
@@ -241,23 +257,23 @@ With `PIGENT_FAKE_AGENT=0`, Pigent calls the Pi SDK and returns the Pi assistant
|
|
|
241
257
|
Example agent lives at:
|
|
242
258
|
|
|
243
259
|
```text
|
|
244
|
-
agents/
|
|
245
|
-
agents/
|
|
260
|
+
agents/assistant/agent.yaml
|
|
261
|
+
agents/assistant/SYSTEM.md
|
|
246
262
|
```
|
|
247
263
|
|
|
248
264
|
Example profile:
|
|
249
265
|
|
|
250
266
|
```text
|
|
251
|
-
profiles/
|
|
267
|
+
profiles/assistant.yaml
|
|
252
268
|
```
|
|
253
269
|
|
|
254
270
|
Agent config shape:
|
|
255
271
|
|
|
256
272
|
```yaml
|
|
257
|
-
id:
|
|
258
|
-
name:
|
|
259
|
-
profile:
|
|
260
|
-
workspace: ~/.pigent/workspaces/
|
|
273
|
+
id: assistant
|
|
274
|
+
name: Assistant
|
|
275
|
+
profile: generic-assistant
|
|
276
|
+
workspace: ~/.pigent/workspaces/assistant
|
|
261
277
|
systemPromptFile: ./SYSTEM.md
|
|
262
278
|
skills: []
|
|
263
279
|
extensions: []
|
|
@@ -284,13 +300,13 @@ Enable in `.env`:
|
|
|
284
300
|
|
|
285
301
|
```env
|
|
286
302
|
PIGENT_AUTO_SETUP_CHATS=1
|
|
287
|
-
PIGENT_AUTO_SETUP_DEFAULT_AGENT=
|
|
303
|
+
PIGENT_AUTO_SETUP_DEFAULT_AGENT=assistant
|
|
288
304
|
```
|
|
289
305
|
|
|
290
306
|
When a new Telegram chat sends any message Pigent receives, the daemon inserts a chat config into SQLite with:
|
|
291
307
|
|
|
292
|
-
- `defaultAgent:
|
|
293
|
-
- `allowedAgents: [
|
|
308
|
+
- `defaultAgent: assistant`
|
|
309
|
+
- `allowedAgents: [assistant]`
|
|
294
310
|
- `enabled: true`
|
|
295
311
|
|
|
296
312
|
This does not write to `pigent.yaml`; it creates runtime DB config only. Keep it off for stricter production setups.
|
package/TODO.md
CHANGED
|
@@ -2,6 +2,11 @@
|
|
|
2
2
|
|
|
3
3
|
## Now
|
|
4
4
|
|
|
5
|
+
- [x] Add `pigent update` command
|
|
6
|
+
- [x] Add user service install/start/status/logs CLI
|
|
7
|
+
- [x] Add `pigent` command shim
|
|
8
|
+
- [x] Remove install/migrate/typecheck prompts from published run flow
|
|
9
|
+
- [x] Add automatic startup migrations
|
|
5
10
|
- [x] Add `bunx @fickydev/pigent <dir>` scaffold/setup/run command
|
|
6
11
|
- [x] Add one-command setup-and-run CLI
|
|
7
12
|
- [x] Add interactive setup CLI
|
|
@@ -15,6 +20,7 @@
|
|
|
15
20
|
- [x] Add `.env.example`
|
|
16
21
|
- [x] Add `.gitignore`
|
|
17
22
|
- [x] Add basic `src/main.ts`
|
|
23
|
+
- [x] Keep daemon alive after startup
|
|
18
24
|
- [x] Add graceful shutdown handling
|
|
19
25
|
|
|
20
26
|
## Dependencies
|
|
@@ -41,9 +47,10 @@
|
|
|
41
47
|
- [x] Implement config loader
|
|
42
48
|
- [x] Load configs from `agents/*/agent.yaml`
|
|
43
49
|
- [x] Load profiles from `profiles/*.yaml`
|
|
44
|
-
- [x]
|
|
45
|
-
- [x] Add example
|
|
46
|
-
- [x] Add example
|
|
50
|
+
- [x] Rename default agent/profile files to assistant
|
|
51
|
+
- [x] Add example generic assistant agent config
|
|
52
|
+
- [x] Add example generic assistant system prompt
|
|
53
|
+
- [x] Add example generic assistant profile
|
|
47
54
|
|
|
48
55
|
## Database
|
|
49
56
|
|
|
@@ -1,7 +1,7 @@
|
|
|
1
|
-
id:
|
|
2
|
-
name:
|
|
3
|
-
profile:
|
|
4
|
-
workspace: ~/.pigent/workspaces/
|
|
1
|
+
id: assistant
|
|
2
|
+
name: Assistant
|
|
3
|
+
profile: generic-assistant
|
|
4
|
+
workspace: ~/.pigent/workspaces/assistant
|
|
5
5
|
systemPromptFile: ./SYSTEM.md
|
|
6
6
|
skills: []
|
|
7
7
|
extensions: []
|
package/package.json
CHANGED
package/pigent.yaml
CHANGED
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
id: generic-assistant
|
|
2
|
+
name: Generic Assistant
|
|
3
|
+
model: null
|
|
4
|
+
instructions: |
|
|
5
|
+
You are a helpful general-purpose assistant.
|
|
6
|
+
Prefer concise, useful answers. Ask clarifying questions only when needed.
|
|
7
|
+
defaultSkills: []
|
|
8
|
+
defaultExtensions: []
|
|
9
|
+
permissions:
|
|
10
|
+
canRunShell: false
|
|
11
|
+
canEditFiles: false
|
package/src/cli/run.ts
CHANGED
|
@@ -1,39 +1,153 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
2
|
import { $ } from "bun";
|
|
3
3
|
import { existsSync } from "node:fs";
|
|
4
|
-
import { cp, mkdir } from "node:fs/promises";
|
|
4
|
+
import { chmod, cp, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
5
|
+
import { homedir } from "node:os";
|
|
5
6
|
import { dirname, join, resolve } from "node:path";
|
|
6
7
|
import { fileURLToPath } from "node:url";
|
|
7
8
|
|
|
9
|
+
const packageName = "@fickydev/pigent";
|
|
10
|
+
const serviceName = "pigent";
|
|
11
|
+
const defaultRoot = join(homedir(), ".pigent");
|
|
12
|
+
const defaultAppDir = join(defaultRoot, "app");
|
|
13
|
+
const defaultLogPath = join(defaultRoot, "pigent.log");
|
|
8
14
|
const args = process.argv.slice(2);
|
|
9
|
-
const
|
|
10
|
-
|
|
11
|
-
|
|
12
|
-
|
|
13
|
-
|
|
14
|
-
|
|
15
|
+
const commands = new Set([
|
|
16
|
+
"install",
|
|
17
|
+
"setup",
|
|
18
|
+
"start",
|
|
19
|
+
"stop",
|
|
20
|
+
"restart",
|
|
21
|
+
"status",
|
|
22
|
+
"logs",
|
|
23
|
+
"run",
|
|
24
|
+
"foreground",
|
|
25
|
+
"update",
|
|
26
|
+
"help",
|
|
27
|
+
]);
|
|
28
|
+
const command = commands.has(args[0] ?? "") ? args[0] : null;
|
|
29
|
+
const targetDirArg = args.find((arg, index) => !arg.startsWith("--") && !(index === 0 && command));
|
|
30
|
+
const appDir = targetDirArg ? resolve(process.cwd(), targetDirArg) : defaultAppDir;
|
|
15
31
|
|
|
16
32
|
async function main(): Promise<void> {
|
|
17
|
-
|
|
18
|
-
|
|
19
|
-
|
|
20
|
-
|
|
33
|
+
switch (command) {
|
|
34
|
+
case "help":
|
|
35
|
+
printHelp();
|
|
36
|
+
return;
|
|
37
|
+
case "setup":
|
|
38
|
+
await ensureConfiguredApp(appDir, true);
|
|
39
|
+
return;
|
|
40
|
+
case "install":
|
|
41
|
+
await installAndStartService(appDir);
|
|
42
|
+
return;
|
|
43
|
+
case "start":
|
|
44
|
+
await startService(appDir);
|
|
45
|
+
return;
|
|
46
|
+
case "stop":
|
|
47
|
+
await systemctl("stop");
|
|
48
|
+
return;
|
|
49
|
+
case "restart":
|
|
50
|
+
await systemctl("restart");
|
|
51
|
+
return;
|
|
52
|
+
case "status":
|
|
53
|
+
await systemctl("status", false);
|
|
54
|
+
return;
|
|
55
|
+
case "logs":
|
|
56
|
+
await showLogs();
|
|
57
|
+
return;
|
|
58
|
+
case "update":
|
|
59
|
+
await updateApp(appDir);
|
|
60
|
+
return;
|
|
61
|
+
case "run":
|
|
62
|
+
case "foreground":
|
|
63
|
+
await runForeground(resolveForegroundDir());
|
|
64
|
+
return;
|
|
65
|
+
default:
|
|
66
|
+
await installAndStartService(appDir);
|
|
21
67
|
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function installAndStartService(targetDir: string): Promise<void> {
|
|
71
|
+
await ensureConfiguredApp(targetDir, true);
|
|
72
|
+
|
|
73
|
+
if (!(await hasSystemctl())) {
|
|
74
|
+
console.log("[pigent] systemd user service unavailable; running in foreground");
|
|
75
|
+
await runForeground(targetDir);
|
|
76
|
+
return;
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
await installCommandShim();
|
|
80
|
+
await writeServiceFile(targetDir);
|
|
81
|
+
await systemctl("daemon-reload");
|
|
82
|
+
await systemctl("enable", true, `${serviceName}.service`);
|
|
83
|
+
await systemctl("restart");
|
|
84
|
+
|
|
85
|
+
console.log("\nPigent installed and running.");
|
|
86
|
+
console.log("Commands:");
|
|
87
|
+
console.log(" pigent status");
|
|
88
|
+
console.log(" pigent logs");
|
|
89
|
+
console.log(" pigent stop");
|
|
90
|
+
console.log(" pigent restart");
|
|
91
|
+
console.log(" pigent update");
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
async function startService(targetDir: string): Promise<void> {
|
|
95
|
+
if (!existsSync(servicePath())) {
|
|
96
|
+
await installAndStartService(targetDir);
|
|
97
|
+
return;
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
await systemctl("start");
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
async function updateApp(targetDir: string): Promise<void> {
|
|
104
|
+
console.log("[pigent] updating app");
|
|
105
|
+
|
|
106
|
+
if (await hasSystemctl()) {
|
|
107
|
+
await systemctl("stop", true);
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
await mkdir(defaultRoot, { recursive: true });
|
|
111
|
+
await installCommandShim();
|
|
112
|
+
await refreshProject(targetDir);
|
|
113
|
+
|
|
114
|
+
if (await hasSystemctl()) {
|
|
115
|
+
await writeServiceFile(targetDir);
|
|
116
|
+
await systemctl("daemon-reload");
|
|
117
|
+
await systemctl("enable", true, `${serviceName}.service`);
|
|
118
|
+
await systemctl("restart");
|
|
119
|
+
console.log("[pigent] updated and restarted service");
|
|
120
|
+
} else {
|
|
121
|
+
console.log("[pigent] updated. Run `pigent run` to start foreground daemon.");
|
|
122
|
+
}
|
|
123
|
+
}
|
|
22
124
|
|
|
23
|
-
|
|
125
|
+
async function ensureConfiguredApp(targetDir: string, interactive: boolean): Promise<void> {
|
|
126
|
+
await ensureProject(targetDir);
|
|
127
|
+
process.chdir(targetDir);
|
|
24
128
|
|
|
25
|
-
if (
|
|
26
|
-
|
|
129
|
+
if (interactive || needsSetup(targetDir)) {
|
|
130
|
+
const { runSetup } = await import("./setup");
|
|
131
|
+
await runSetup();
|
|
27
132
|
}
|
|
133
|
+
}
|
|
28
134
|
|
|
29
|
-
|
|
135
|
+
async function runForeground(targetDir: string): Promise<void> {
|
|
136
|
+
await ensureProject(targetDir);
|
|
137
|
+
process.chdir(targetDir);
|
|
138
|
+
|
|
139
|
+
if (needsSetup(targetDir)) {
|
|
140
|
+
const { runSetup } = await import("./setup");
|
|
141
|
+
await runSetup();
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
await import("../main");
|
|
30
145
|
}
|
|
31
146
|
|
|
32
147
|
async function ensureProject(targetDir: string): Promise<void> {
|
|
33
148
|
await mkdir(targetDir, { recursive: true });
|
|
34
149
|
|
|
35
150
|
if (existsSync(join(targetDir, "package.json"))) {
|
|
36
|
-
console.log(`[pigent] using existing project ${targetDir}`);
|
|
37
151
|
return;
|
|
38
152
|
}
|
|
39
153
|
|
|
@@ -47,6 +161,105 @@ async function ensureProject(targetDir: string): Promise<void> {
|
|
|
47
161
|
});
|
|
48
162
|
}
|
|
49
163
|
|
|
164
|
+
async function refreshProject(targetDir: string): Promise<void> {
|
|
165
|
+
const sourceRoot = resolve(dirname(fileURLToPath(import.meta.url)), "../..");
|
|
166
|
+
const backupDir = join(defaultRoot, "backups", `app-${timestamp()}`);
|
|
167
|
+
|
|
168
|
+
if (existsSync(targetDir)) {
|
|
169
|
+
await mkdir(dirname(backupDir), { recursive: true });
|
|
170
|
+
await cp(targetDir, backupDir, { recursive: true, force: false });
|
|
171
|
+
console.log(`[pigent] backup created ${backupDir}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
await mkdir(targetDir, { recursive: true });
|
|
175
|
+
|
|
176
|
+
const preserved = [".env", "pigent.db", "pigent.db-shm", "pigent.db-wal", "pigent.yaml", "agents", "profiles"];
|
|
177
|
+
const tempPreserveDir = join(defaultRoot, ".update-preserve");
|
|
178
|
+
await rm(tempPreserveDir, { recursive: true, force: true });
|
|
179
|
+
await mkdir(tempPreserveDir, { recursive: true });
|
|
180
|
+
|
|
181
|
+
for (const entry of preserved) {
|
|
182
|
+
const source = join(targetDir, entry);
|
|
183
|
+
if (existsSync(source)) {
|
|
184
|
+
await rename(source, join(tempPreserveDir, entry));
|
|
185
|
+
}
|
|
186
|
+
}
|
|
187
|
+
|
|
188
|
+
await cp(sourceRoot, targetDir, {
|
|
189
|
+
recursive: true,
|
|
190
|
+
force: true,
|
|
191
|
+
filter: (source) => shouldCopy(sourceRoot, source),
|
|
192
|
+
});
|
|
193
|
+
|
|
194
|
+
for (const entry of preserved) {
|
|
195
|
+
const source = join(tempPreserveDir, entry);
|
|
196
|
+
if (existsSync(source)) {
|
|
197
|
+
await rm(join(targetDir, entry), { recursive: true, force: true });
|
|
198
|
+
await rename(source, join(targetDir, entry));
|
|
199
|
+
}
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
await rm(tempPreserveDir, { recursive: true, force: true });
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function installCommandShim(): Promise<void> {
|
|
206
|
+
const binDir = join(homedir(), ".local", "bin");
|
|
207
|
+
const shimPath = join(binDir, "pigent");
|
|
208
|
+
await mkdir(binDir, { recursive: true });
|
|
209
|
+
await writeFile(shimPath, `#!/usr/bin/env sh\nexec bunx ${packageName}@latest "$@"\n`);
|
|
210
|
+
await chmod(shimPath, 0o755);
|
|
211
|
+
|
|
212
|
+
const pathParts = (process.env.PATH ?? "").split(":");
|
|
213
|
+
if (!pathParts.includes(binDir)) {
|
|
214
|
+
console.log(`[pigent] add ${binDir} to PATH to use the pigent command from new shells`);
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function writeServiceFile(targetDir: string): Promise<void> {
|
|
219
|
+
const pathValue = process.env.PATH ?? "";
|
|
220
|
+
const bunPath = process.execPath;
|
|
221
|
+
await mkdir(dirname(servicePath()), { recursive: true });
|
|
222
|
+
await writeFile(
|
|
223
|
+
servicePath(),
|
|
224
|
+
`[Unit]\nDescription=Pigent autonomous agent daemon\nAfter=network-online.target\nWants=network-online.target\n\n[Service]\nType=simple\nWorkingDirectory=${targetDir}\nExecStart=${bunPath} run src/main.ts\nRestart=always\nRestartSec=5\nEnvironment=NODE_ENV=production\nEnvironment=PATH=${pathValue}\n\n[Install]\nWantedBy=default.target\n`,
|
|
225
|
+
);
|
|
226
|
+
}
|
|
227
|
+
|
|
228
|
+
async function showLogs(): Promise<void> {
|
|
229
|
+
if (await hasSystemctl()) {
|
|
230
|
+
await $`systemctl --user status ${serviceName}.service`.nothrow().quiet();
|
|
231
|
+
await $`journalctl --user -u ${serviceName}.service -f`;
|
|
232
|
+
return;
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
if (existsSync(defaultLogPath)) {
|
|
236
|
+
console.log(await readFile(defaultLogPath, "utf8"));
|
|
237
|
+
return;
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
console.log("No logs found.");
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
async function systemctl(action: string, nothrow = false, extra?: string): Promise<void> {
|
|
244
|
+
const commandArgs = extra ? [action, extra] : [action, `${serviceName}.service`];
|
|
245
|
+
const proc = $`systemctl --user ${commandArgs}`;
|
|
246
|
+
if (nothrow) {
|
|
247
|
+
await proc.nothrow();
|
|
248
|
+
} else {
|
|
249
|
+
await proc;
|
|
250
|
+
}
|
|
251
|
+
}
|
|
252
|
+
|
|
253
|
+
async function hasSystemctl(): Promise<boolean> {
|
|
254
|
+
if (!existsSync("/bin/systemctl") && !existsSync("/usr/bin/systemctl")) return false;
|
|
255
|
+
const result = await $`systemctl --user show-environment`.nothrow().quiet();
|
|
256
|
+
return result.exitCode === 0;
|
|
257
|
+
}
|
|
258
|
+
|
|
259
|
+
function servicePath(): string {
|
|
260
|
+
return join(homedir(), ".config", "systemd", "user", `${serviceName}.service`);
|
|
261
|
+
}
|
|
262
|
+
|
|
50
263
|
function shouldCopy(sourceRoot: string, source: string): boolean {
|
|
51
264
|
const relative = source.slice(sourceRoot.length).replace(/^\/+/, "");
|
|
52
265
|
if (!relative) return true;
|
|
@@ -66,9 +279,25 @@ function shouldCopy(sourceRoot: string, source: string): boolean {
|
|
|
66
279
|
}
|
|
67
280
|
|
|
68
281
|
function needsSetup(rootDir: string): boolean {
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
282
|
+
return !existsSync(join(rootDir, ".env"));
|
|
283
|
+
}
|
|
284
|
+
|
|
285
|
+
function resolveForegroundDir(): string {
|
|
286
|
+
if (targetDirArg) return appDir;
|
|
287
|
+
if (looksLikePigentProject(process.cwd())) return process.cwd();
|
|
288
|
+
return defaultAppDir;
|
|
289
|
+
}
|
|
290
|
+
|
|
291
|
+
function looksLikePigentProject(rootDir: string): boolean {
|
|
292
|
+
return existsSync(join(rootDir, "pigent.yaml")) && existsSync(join(rootDir, "agents"));
|
|
293
|
+
}
|
|
294
|
+
|
|
295
|
+
function timestamp(): string {
|
|
296
|
+
return new Date().toISOString().replace(/[-:]/g, "").replace(/\.\d{3}Z$/, "Z");
|
|
297
|
+
}
|
|
298
|
+
|
|
299
|
+
function printHelp(): void {
|
|
300
|
+
console.log(`Pigent\n\nUsage:\n pigent Install/start user service in ~/.pigent/app\n pigent install Install/start user service\n pigent setup Reconfigure app\n pigent start Start service\n pigent stop Stop service\n pigent restart Restart service\n pigent status Show service status\n pigent logs Follow service logs\n pigent update Update app from latest package and restart\n pigent run Run foreground daemon\n\nWith bunx:\n bunx ${packageName}\n bunx ${packageName} status\n`);
|
|
72
301
|
}
|
|
73
302
|
|
|
74
303
|
main().catch((error) => {
|
package/src/cli/setup.ts
CHANGED
|
@@ -1,7 +1,6 @@
|
|
|
1
1
|
import { existsSync } from "node:fs";
|
|
2
2
|
import { copyFile, mkdir, readFile, writeFile } from "node:fs/promises";
|
|
3
3
|
import { dirname, join } from "node:path";
|
|
4
|
-
import { $ } from "bun";
|
|
5
4
|
import YAML from "yaml";
|
|
6
5
|
|
|
7
6
|
const rootDir = process.cwd();
|
|
@@ -15,11 +14,7 @@ export type SetupOptions = {
|
|
|
15
14
|
skipTypecheck?: boolean;
|
|
16
15
|
};
|
|
17
16
|
|
|
18
|
-
export async function runSetup(
|
|
19
|
-
const skipInstall = options.skipInstall ?? false;
|
|
20
|
-
const skipMigrate = options.skipMigrate ?? false;
|
|
21
|
-
const skipTypecheck = options.skipTypecheck ?? false;
|
|
22
|
-
|
|
17
|
+
export async function runSetup(_options: SetupOptions = {}): Promise<void> {
|
|
23
18
|
banner();
|
|
24
19
|
|
|
25
20
|
const mode = select("Setup mode", ["quick", "custom"], "quick");
|
|
@@ -29,30 +24,12 @@ export async function runSetup(options: SetupOptions = {}): Promise<void> {
|
|
|
29
24
|
await configureEnvironment(isCustom);
|
|
30
25
|
await configureTelegramChat(isCustom);
|
|
31
26
|
|
|
32
|
-
if (!skipInstall && confirm("Install dependencies?", true)) {
|
|
33
|
-
await runStep("Install dependencies", async () => {
|
|
34
|
-
await $`bun install`;
|
|
35
|
-
});
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
if (!skipMigrate && confirm("Apply database migrations?", true)) {
|
|
39
|
-
await runStep("Apply database migrations", async () => {
|
|
40
|
-
await $`bun run db:migrate`;
|
|
41
|
-
});
|
|
42
|
-
}
|
|
43
|
-
|
|
44
|
-
if (!skipTypecheck && confirm("Run typecheck?", true)) {
|
|
45
|
-
await runStep("Typecheck", async () => {
|
|
46
|
-
await $`bun run typecheck`;
|
|
47
|
-
});
|
|
48
|
-
}
|
|
49
|
-
|
|
50
27
|
summary();
|
|
51
28
|
}
|
|
52
29
|
|
|
53
30
|
function banner(): void {
|
|
54
|
-
console.log("\nPigent
|
|
55
|
-
console.log("
|
|
31
|
+
console.log("\nPigent setup");
|
|
32
|
+
console.log("------------");
|
|
56
33
|
}
|
|
57
34
|
|
|
58
35
|
async function ensureEnvFile(): Promise<void> {
|
|
@@ -89,16 +66,8 @@ async function configureEnvironment(isCustom: boolean): Promise<void> {
|
|
|
89
66
|
}
|
|
90
67
|
}
|
|
91
68
|
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
env = setEnvValue(
|
|
95
|
-
env,
|
|
96
|
-
"PIGENT_AUTO_SETUP_DEFAULT_AGENT",
|
|
97
|
-
input("Auto-setup default agent", getEnvValue(env, "PIGENT_AUTO_SETUP_DEFAULT_AGENT") ?? "coder"),
|
|
98
|
-
);
|
|
99
|
-
} else {
|
|
100
|
-
env = setEnvValue(env, "PIGENT_AUTO_SETUP_CHATS", "0");
|
|
101
|
-
}
|
|
69
|
+
env = setEnvValue(env, "PIGENT_AUTO_SETUP_CHATS", "1");
|
|
70
|
+
env = setEnvValue(env, "PIGENT_AUTO_SETUP_DEFAULT_AGENT", getEnvValue(env, "PIGENT_AUTO_SETUP_DEFAULT_AGENT") ?? "assistant");
|
|
102
71
|
|
|
103
72
|
if (isCustom) {
|
|
104
73
|
env = setEnvValue(env, "LOG_LEVEL", select("Log level", ["debug", "info", "warn", "error"], getEnvValue(env, "LOG_LEVEL") ?? "info"));
|
|
@@ -135,8 +104,8 @@ async function configureTelegramChat(isCustom: boolean): Promise<void> {
|
|
|
135
104
|
return;
|
|
136
105
|
}
|
|
137
106
|
|
|
138
|
-
const title = input("Chat title", "");
|
|
139
|
-
const defaultAgent = input("Default agent", "
|
|
107
|
+
const title = input("Chat title", "Home");
|
|
108
|
+
const defaultAgent = input("Default agent", "assistant") || "assistant";
|
|
140
109
|
const allowedAgentsInput = input("Allowed agents, comma-separated", defaultAgent);
|
|
141
110
|
const instructions = isCustom ? inputMultiline("Chat instructions", "") : "";
|
|
142
111
|
const allowedAgents = allowedAgentsInput
|
|
@@ -172,11 +141,6 @@ async function configureTelegramChat(isCustom: boolean): Promise<void> {
|
|
|
172
141
|
log(`Configured Telegram chat ${chatId}`);
|
|
173
142
|
}
|
|
174
143
|
|
|
175
|
-
async function runStep(name: string, step: () => Promise<void>): Promise<void> {
|
|
176
|
-
log(name);
|
|
177
|
-
await step();
|
|
178
|
-
}
|
|
179
|
-
|
|
180
144
|
function input(label: string, defaultValue: string): string {
|
|
181
145
|
const suffix = defaultValue ? ` [${defaultValue}]` : "";
|
|
182
146
|
const value = prompt(`${label}${suffix}:`)?.trim();
|
|
@@ -236,11 +200,7 @@ function setEnvValue(content: string, key: string, value: string): string {
|
|
|
236
200
|
}
|
|
237
201
|
|
|
238
202
|
function summary(): void {
|
|
239
|
-
console.log("\nSetup complete");
|
|
240
|
-
console.log("--------------");
|
|
241
|
-
console.log("Start daemon: bun run start");
|
|
242
|
-
console.log("Dev mode: bun run dev");
|
|
243
|
-
console.log("Typecheck: bun run typecheck");
|
|
203
|
+
console.log("\nSetup complete. Starting Pigent...\n");
|
|
244
204
|
}
|
|
245
205
|
|
|
246
206
|
function log(message: string): void {
|
|
@@ -248,13 +208,7 @@ function log(message: string): void {
|
|
|
248
208
|
}
|
|
249
209
|
|
|
250
210
|
if (import.meta.main) {
|
|
251
|
-
|
|
252
|
-
|
|
253
|
-
runSetup({
|
|
254
|
-
skipInstall: flags.has("--skip-install"),
|
|
255
|
-
skipMigrate: flags.has("--skip-migrate"),
|
|
256
|
-
skipTypecheck: flags.has("--skip-typecheck"),
|
|
257
|
-
}).catch((error) => {
|
|
211
|
+
runSetup().catch((error) => {
|
|
258
212
|
console.error("[setup] failed", error instanceof Error ? error.message : error);
|
|
259
213
|
process.exit(1);
|
|
260
214
|
});
|
package/src/db/client.ts
CHANGED
|
@@ -2,6 +2,7 @@ import { mkdirSync } from "node:fs";
|
|
|
2
2
|
import { dirname, resolve } from "node:path";
|
|
3
3
|
import { Database } from "bun:sqlite";
|
|
4
4
|
import { drizzle } from "drizzle-orm/bun-sqlite";
|
|
5
|
+
import { migrate } from "drizzle-orm/bun-sqlite/migrator";
|
|
5
6
|
import * as schema from "./schema";
|
|
6
7
|
|
|
7
8
|
function databasePathFromEnv(): string {
|
|
@@ -16,6 +17,8 @@ mkdirSync(dirname(databasePath), { recursive: true });
|
|
|
16
17
|
export const sqlite = new Database(databasePath);
|
|
17
18
|
export const db = drizzle(sqlite, { schema });
|
|
18
19
|
|
|
20
|
+
migrate(db, { migrationsFolder: "./drizzle/migrations" });
|
|
21
|
+
|
|
19
22
|
export type DbClient = typeof db;
|
|
20
23
|
|
|
21
24
|
export function getDatabasePath(): string {
|
|
@@ -63,8 +63,8 @@ export class TelegramRepository {
|
|
|
63
63
|
|
|
64
64
|
const raw = message.raw as { chat?: { title?: string; username?: string; first_name?: string; last_name?: string } } | null;
|
|
65
65
|
const title = raw?.chat
|
|
66
|
-
? raw.chat.title ?? [raw.chat.first_name, raw.chat.last_name].filter(Boolean).join(" ") ?? raw.chat.username
|
|
67
|
-
:
|
|
66
|
+
? raw.chat.title ?? [raw.chat.first_name, raw.chat.last_name].filter(Boolean).join(" ") ?? raw.chat.username ?? "Home"
|
|
67
|
+
: "Home";
|
|
68
68
|
|
|
69
69
|
await this.upsertConfiguredChat({
|
|
70
70
|
chatId: message.chatId,
|
package/src/main.ts
CHANGED
|
@@ -25,10 +25,19 @@ async function shutdown(signal: string): Promise<void> {
|
|
|
25
25
|
process.on("SIGINT", () => void shutdown("SIGINT"));
|
|
26
26
|
process.on("SIGTERM", () => void shutdown("SIGTERM"));
|
|
27
27
|
|
|
28
|
+
function keepAlive(): Promise<never> {
|
|
29
|
+
return new Promise(() => {
|
|
30
|
+
setInterval(() => {
|
|
31
|
+
// Keep daemon process alive even when no adapter currently has an active handle.
|
|
32
|
+
}, 60_000);
|
|
33
|
+
});
|
|
34
|
+
}
|
|
35
|
+
|
|
28
36
|
try {
|
|
29
37
|
logger.info("pigent starting");
|
|
30
38
|
await daemon.start();
|
|
31
39
|
logger.info("pigent ready");
|
|
40
|
+
await keepAlive();
|
|
32
41
|
} catch (error) {
|
|
33
42
|
logger.error("pigent failed to start", {
|
|
34
43
|
error: error instanceof Error ? error.message : String(error),
|
package/agents/coder/SYSTEM.md
DELETED
|
@@ -1,11 +0,0 @@
|
|
|
1
|
-
id: software-engineer
|
|
2
|
-
name: Software Engineer
|
|
3
|
-
model: null
|
|
4
|
-
instructions: |
|
|
5
|
-
You are a pragmatic software engineering agent.
|
|
6
|
-
Prefer small safe diffs, clear tests, and stable behavior.
|
|
7
|
-
defaultSkills: []
|
|
8
|
-
defaultExtensions: []
|
|
9
|
-
permissions:
|
|
10
|
-
canRunShell: false
|
|
11
|
-
canEditFiles: false
|