@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 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=0
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=coder
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 coder agent and software engineer profile.
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
- From a published package, one command can create a project, run setup, and start the daemon:
74
+ Install and start Pigent as a user service:
75
75
 
76
76
  ```bash
77
- bunx @fickydev/pigent my-pigent
77
+ bunx @fickydev/pigent
78
78
  ```
79
79
 
80
- From a checked-out repo, one command handles setup when needed, then starts the daemon:
80
+ Default app directory:
81
+
82
+ ```text
83
+ ~/.pigent/app
84
+ ```
85
+
86
+ After install, use short commands:
81
87
 
82
88
  ```bash
83
- bun run run
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
- Reconfigure anytime:
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
- bun run run -- --setup
90
- # or, with bunx after publish:
91
- bunx @fickydev/pigent my-pigent --setup
109
+ bunx @fickydev/pigent run
92
110
  ```
93
111
 
94
- Skip slow setup steps when needed:
112
+ From a checked-out repo:
95
113
 
96
114
  ```bash
97
- bun run run -- --skip-install --skip-migrate --skip-typecheck
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
- - optionally install dependencies
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=0
131
- PIGENT_AUTO_SETUP_DEFAULT_AGENT=coder
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: coder
214
+ defaultAgent: assistant
199
215
  allowedAgents:
200
- - coder
216
+ - assistant
201
217
  instructions: |
202
218
  Be concise.
203
219
  ```
@@ -221,17 +237,17 @@ hello
221
237
  ```
222
238
 
223
239
  ```text
224
- @coder review this
240
+ @assistant review this
225
241
  ```
226
242
 
227
243
  ```text
228
- /agent coder write a test
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
- [Code Agent] fake runner received: hello
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/coder/agent.yaml
245
- agents/coder/SYSTEM.md
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/software-engineer.yaml
267
+ profiles/assistant.yaml
252
268
  ```
253
269
 
254
270
  Agent config shape:
255
271
 
256
272
  ```yaml
257
- id: coder
258
- name: Code Agent
259
- profile: software-engineer
260
- workspace: ~/.pigent/workspaces/coder
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=coder
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: coder`
293
- - `allowedAgents: [coder]`
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] Add example `agents/coder/agent.yaml`
45
- - [x] Add example `agents/coder/SYSTEM.md`
46
- - [x] Add example `profiles/software-engineer.yaml`
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
 
@@ -0,0 +1,3 @@
1
+ You are Assistant, a helpful general-purpose agent.
2
+
3
+ Be concise, practical, and friendly. Help with questions, planning, writing, debugging, and everyday tasks. Ask before risky or destructive actions.
@@ -1,7 +1,7 @@
1
- id: coder
2
- name: Code Agent
3
- profile: software-engineer
4
- workspace: ~/.pigent/workspaces/coder
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@fickydev/pigent",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "description": "Autonomous multi-agent daemon using Pi as core execution engine.",
package/pigent.yaml CHANGED
@@ -5,8 +5,8 @@ telegramChats: []
5
5
  # telegramChats:
6
6
  # - chatId: "123456"
7
7
  # title: Example Private Chat
8
- # defaultAgent: coder
8
+ # defaultAgent: assistant
9
9
  # allowedAgents:
10
- # - coder
10
+ # - assistant
11
11
  # instructions: |
12
12
  # Be concise.
@@ -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 flags = new Set(args.filter((arg) => arg.startsWith("--")));
10
- const targetDirArg = args.find((arg) => !arg.startsWith("--"));
11
- const forceSetup = flags.has("--setup") || flags.has("--reconfigure");
12
- const skipInstall = flags.has("--skip-install");
13
- const skipMigrate = flags.has("--skip-migrate");
14
- const skipTypecheck = flags.has("--skip-typecheck");
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
- if (targetDirArg) {
18
- const targetDir = resolve(process.cwd(), targetDirArg);
19
- await ensureProject(targetDir);
20
- process.chdir(targetDir);
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
- const { runSetup } = await import("./setup");
125
+ async function ensureConfiguredApp(targetDir: string, interactive: boolean): Promise<void> {
126
+ await ensureProject(targetDir);
127
+ process.chdir(targetDir);
24
128
 
25
- if (forceSetup || needsSetup(process.cwd())) {
26
- await runSetup({ skipInstall, skipMigrate, skipTypecheck });
129
+ if (interactive || needsSetup(targetDir)) {
130
+ const { runSetup } = await import("./setup");
131
+ await runSetup();
27
132
  }
133
+ }
28
134
 
29
- await $`bun run src/main.ts`;
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
- if (!existsSync(join(rootDir, ".env"))) return true;
70
- if (!existsSync(join(rootDir, "pigent.db"))) return true;
71
- return false;
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(options: SetupOptions = {}): Promise<void> {
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 interactive setup");
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
- if (confirm("Auto-configure new Telegram chats?", getEnvValue(env, "PIGENT_AUTO_SETUP_CHATS") === "1")) {
93
- env = setEnvValue(env, "PIGENT_AUTO_SETUP_CHATS", "1");
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", "coder") || "coder";
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
- const flags = new Set(process.argv.slice(2));
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
- : undefined;
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),
@@ -1,3 +0,0 @@
1
- You are coder agent.
2
-
3
- Focus on implementation, debugging, and code review. Keep responses concise. Ask before risky changes.
@@ -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