@fickydev/pigent 0.1.1 → 0.1.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/.env.example +2 -2
- package/CHANGELOG.md +7 -1
- package/README.md +49 -25
- package/TODO.md +8 -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 +241 -23
- package/src/cli/setup.ts +4 -12
- 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,8 +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`.
|
|
30
35
|
- Simplified published CLI flow so `bunx @fickydev/pigent` runs setup and starts Pigent without install/start/typecheck prompts.
|
|
31
36
|
- Added automatic database migrations at daemon startup.
|
|
37
|
+
- Kept daemon process alive after startup so CLI runs do not exit after `pigent ready`.
|
|
32
38
|
|
|
33
39
|
### Changed
|
|
34
40
|
|
package/README.md
CHANGED
|
@@ -71,24 +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
|
|
102
|
+
```
|
|
103
|
+
|
|
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:
|
|
107
|
+
|
|
108
|
+
```bash
|
|
109
|
+
bunx @fickydev/pigent run
|
|
84
110
|
```
|
|
85
111
|
|
|
86
|
-
|
|
112
|
+
From a checked-out repo:
|
|
87
113
|
|
|
88
114
|
```bash
|
|
89
|
-
bun run run
|
|
90
|
-
# or, with bunx after publish:
|
|
91
|
-
bunx @fickydev/pigent my-pigent --setup
|
|
115
|
+
bun run run
|
|
92
116
|
```
|
|
93
117
|
|
|
94
118
|
The setup flow will:
|
|
@@ -119,8 +143,8 @@ TELEGRAM_BOT_TOKEN=
|
|
|
119
143
|
PIGENT_WORKSPACE_ROOT=~/.pigent
|
|
120
144
|
PIGENT_FAKE_AGENT=0
|
|
121
145
|
PIGENT_FALLBACK_FAKE_AGENT=1
|
|
122
|
-
PIGENT_AUTO_SETUP_CHATS=
|
|
123
|
-
PIGENT_AUTO_SETUP_DEFAULT_AGENT=
|
|
146
|
+
PIGENT_AUTO_SETUP_CHATS=1
|
|
147
|
+
PIGENT_AUTO_SETUP_DEFAULT_AGENT=assistant
|
|
124
148
|
```
|
|
125
149
|
|
|
126
150
|
Start daemon directly after setup:
|
|
@@ -187,9 +211,9 @@ Edit `pigent.yaml`:
|
|
|
187
211
|
telegramChats:
|
|
188
212
|
- chatId: "123456"
|
|
189
213
|
title: Ficky Private Chat
|
|
190
|
-
defaultAgent:
|
|
214
|
+
defaultAgent: assistant
|
|
191
215
|
allowedAgents:
|
|
192
|
-
-
|
|
216
|
+
- assistant
|
|
193
217
|
instructions: |
|
|
194
218
|
Be concise.
|
|
195
219
|
```
|
|
@@ -213,17 +237,17 @@ hello
|
|
|
213
237
|
```
|
|
214
238
|
|
|
215
239
|
```text
|
|
216
|
-
@
|
|
240
|
+
@assistant review this
|
|
217
241
|
```
|
|
218
242
|
|
|
219
243
|
```text
|
|
220
|
-
/agent
|
|
244
|
+
/agent assistant write a test
|
|
221
245
|
```
|
|
222
246
|
|
|
223
247
|
With `PIGENT_FAKE_AGENT=1`, expected fake runner response:
|
|
224
248
|
|
|
225
249
|
```text
|
|
226
|
-
[
|
|
250
|
+
[Assistant] fake runner received: hello
|
|
227
251
|
```
|
|
228
252
|
|
|
229
253
|
With `PIGENT_FAKE_AGENT=0`, Pigent calls the Pi SDK and returns the Pi assistant response.
|
|
@@ -233,23 +257,23 @@ With `PIGENT_FAKE_AGENT=0`, Pigent calls the Pi SDK and returns the Pi assistant
|
|
|
233
257
|
Example agent lives at:
|
|
234
258
|
|
|
235
259
|
```text
|
|
236
|
-
agents/
|
|
237
|
-
agents/
|
|
260
|
+
agents/assistant/agent.yaml
|
|
261
|
+
agents/assistant/SYSTEM.md
|
|
238
262
|
```
|
|
239
263
|
|
|
240
264
|
Example profile:
|
|
241
265
|
|
|
242
266
|
```text
|
|
243
|
-
profiles/
|
|
267
|
+
profiles/assistant.yaml
|
|
244
268
|
```
|
|
245
269
|
|
|
246
270
|
Agent config shape:
|
|
247
271
|
|
|
248
272
|
```yaml
|
|
249
|
-
id:
|
|
250
|
-
name:
|
|
251
|
-
profile:
|
|
252
|
-
workspace: ~/.pigent/workspaces/
|
|
273
|
+
id: assistant
|
|
274
|
+
name: Assistant
|
|
275
|
+
profile: generic-assistant
|
|
276
|
+
workspace: ~/.pigent/workspaces/assistant
|
|
253
277
|
systemPromptFile: ./SYSTEM.md
|
|
254
278
|
skills: []
|
|
255
279
|
extensions: []
|
|
@@ -276,13 +300,13 @@ Enable in `.env`:
|
|
|
276
300
|
|
|
277
301
|
```env
|
|
278
302
|
PIGENT_AUTO_SETUP_CHATS=1
|
|
279
|
-
PIGENT_AUTO_SETUP_DEFAULT_AGENT=
|
|
303
|
+
PIGENT_AUTO_SETUP_DEFAULT_AGENT=assistant
|
|
280
304
|
```
|
|
281
305
|
|
|
282
306
|
When a new Telegram chat sends any message Pigent receives, the daemon inserts a chat config into SQLite with:
|
|
283
307
|
|
|
284
|
-
- `defaultAgent:
|
|
285
|
-
- `allowedAgents: [
|
|
308
|
+
- `defaultAgent: assistant`
|
|
309
|
+
- `allowedAgents: [assistant]`
|
|
286
310
|
- `enabled: true`
|
|
287
311
|
|
|
288
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,9 @@
|
|
|
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
|
|
5
8
|
- [x] Remove install/migrate/typecheck prompts from published run flow
|
|
6
9
|
- [x] Add automatic startup migrations
|
|
7
10
|
- [x] Add `bunx @fickydev/pigent <dir>` scaffold/setup/run command
|
|
@@ -17,6 +20,7 @@
|
|
|
17
20
|
- [x] Add `.env.example`
|
|
18
21
|
- [x] Add `.gitignore`
|
|
19
22
|
- [x] Add basic `src/main.ts`
|
|
23
|
+
- [x] Keep daemon alive after startup
|
|
20
24
|
- [x] Add graceful shutdown handling
|
|
21
25
|
|
|
22
26
|
## Dependencies
|
|
@@ -43,9 +47,10 @@
|
|
|
43
47
|
- [x] Implement config loader
|
|
44
48
|
- [x] Load configs from `agents/*/agent.yaml`
|
|
45
49
|
- [x] Load profiles from `profiles/*.yaml`
|
|
46
|
-
- [x]
|
|
47
|
-
- [x] Add example
|
|
48
|
-
- [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
|
|
49
54
|
|
|
50
55
|
## Database
|
|
51
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,47 +1,153 @@
|
|
|
1
1
|
#!/usr/bin/env bun
|
|
2
|
+
import { $ } from "bun";
|
|
2
3
|
import { existsSync } from "node:fs";
|
|
3
|
-
import { cp, mkdir } from "node:fs/promises";
|
|
4
|
+
import { chmod, cp, mkdir, readFile, rename, rm, writeFile } from "node:fs/promises";
|
|
4
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
|
-
|
|
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;
|
|
12
31
|
|
|
13
32
|
async function main(): Promise<void> {
|
|
14
|
-
|
|
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);
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
async function installAndStartService(targetDir: string): Promise<void> {
|
|
71
|
+
await ensureConfiguredApp(targetDir, true);
|
|
15
72
|
|
|
16
|
-
if (
|
|
17
|
-
|
|
18
|
-
|
|
73
|
+
if (!(await hasSystemctl())) {
|
|
74
|
+
console.log("[pigent] systemd user service unavailable; running in foreground");
|
|
75
|
+
await runForeground(targetDir);
|
|
76
|
+
return;
|
|
19
77
|
}
|
|
20
78
|
|
|
21
|
-
|
|
79
|
+
await installCommandShim();
|
|
80
|
+
await writeServiceFile(targetDir);
|
|
81
|
+
await systemctl("daemon-reload");
|
|
82
|
+
await systemctl("enable", true, `${serviceName}.service`);
|
|
83
|
+
await systemctl("restart");
|
|
22
84
|
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
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;
|
|
29
98
|
}
|
|
30
99
|
|
|
31
|
-
await
|
|
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
|
+
}
|
|
124
|
+
|
|
125
|
+
async function ensureConfiguredApp(targetDir: string, interactive: boolean): Promise<void> {
|
|
126
|
+
await ensureProject(targetDir);
|
|
127
|
+
process.chdir(targetDir);
|
|
128
|
+
|
|
129
|
+
if (interactive || needsSetup(targetDir)) {
|
|
130
|
+
const { runSetup } = await import("./setup");
|
|
131
|
+
await runSetup();
|
|
132
|
+
}
|
|
32
133
|
}
|
|
33
134
|
|
|
34
|
-
function
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
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");
|
|
38
145
|
}
|
|
39
146
|
|
|
40
147
|
async function ensureProject(targetDir: string): Promise<void> {
|
|
41
148
|
await mkdir(targetDir, { recursive: true });
|
|
42
149
|
|
|
43
150
|
if (existsSync(join(targetDir, "package.json"))) {
|
|
44
|
-
console.log(`[pigent] using existing project ${targetDir}`);
|
|
45
151
|
return;
|
|
46
152
|
}
|
|
47
153
|
|
|
@@ -55,6 +161,105 @@ async function ensureProject(targetDir: string): Promise<void> {
|
|
|
55
161
|
});
|
|
56
162
|
}
|
|
57
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
|
+
|
|
58
263
|
function shouldCopy(sourceRoot: string, source: string): boolean {
|
|
59
264
|
const relative = source.slice(sourceRoot.length).replace(/^\/+/, "");
|
|
60
265
|
if (!relative) return true;
|
|
@@ -74,14 +279,27 @@ function shouldCopy(sourceRoot: string, source: string): boolean {
|
|
|
74
279
|
}
|
|
75
280
|
|
|
76
281
|
function needsSetup(rootDir: string): boolean {
|
|
77
|
-
|
|
78
|
-
|
|
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;
|
|
79
289
|
}
|
|
80
290
|
|
|
81
291
|
function looksLikePigentProject(rootDir: string): boolean {
|
|
82
292
|
return existsSync(join(rootDir, "pigent.yaml")) && existsSync(join(rootDir, "agents"));
|
|
83
293
|
}
|
|
84
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`);
|
|
301
|
+
}
|
|
302
|
+
|
|
85
303
|
main().catch((error) => {
|
|
86
304
|
console.error("[pigent] failed", error instanceof Error ? error.message : error);
|
|
87
305
|
process.exit(1);
|
package/src/cli/setup.ts
CHANGED
|
@@ -66,16 +66,8 @@ async function configureEnvironment(isCustom: boolean): Promise<void> {
|
|
|
66
66
|
}
|
|
67
67
|
}
|
|
68
68
|
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
env = setEnvValue(
|
|
72
|
-
env,
|
|
73
|
-
"PIGENT_AUTO_SETUP_DEFAULT_AGENT",
|
|
74
|
-
input("Auto-setup default agent", getEnvValue(env, "PIGENT_AUTO_SETUP_DEFAULT_AGENT") ?? "coder"),
|
|
75
|
-
);
|
|
76
|
-
} else {
|
|
77
|
-
env = setEnvValue(env, "PIGENT_AUTO_SETUP_CHATS", "0");
|
|
78
|
-
}
|
|
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");
|
|
79
71
|
|
|
80
72
|
if (isCustom) {
|
|
81
73
|
env = setEnvValue(env, "LOG_LEVEL", select("Log level", ["debug", "info", "warn", "error"], getEnvValue(env, "LOG_LEVEL") ?? "info"));
|
|
@@ -112,8 +104,8 @@ async function configureTelegramChat(isCustom: boolean): Promise<void> {
|
|
|
112
104
|
return;
|
|
113
105
|
}
|
|
114
106
|
|
|
115
|
-
const title = input("Chat title", "");
|
|
116
|
-
const defaultAgent = input("Default agent", "
|
|
107
|
+
const title = input("Chat title", "Home");
|
|
108
|
+
const defaultAgent = input("Default agent", "assistant") || "assistant";
|
|
117
109
|
const allowedAgentsInput = input("Allowed agents, comma-separated", defaultAgent);
|
|
118
110
|
const instructions = isCustom ? inputMultiline("Chat instructions", "") : "";
|
|
119
111
|
const allowedAgents = allowedAgentsInput
|
|
@@ -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
|