niahere 0.2.6 → 0.2.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +2 -2
- package/bin/nia +0 -2
- package/defaults/self/identity.md +2 -2
- package/package.json +3 -2
- package/skills/nia-image/SKILL.md +3 -3
- package/skills/nia-image/scripts/generate_image.py +2 -2
- package/src/cli/channels.ts +2 -5
- package/src/cli/index.ts +7 -0
- package/src/cli/status.ts +7 -2
- package/src/commands/db.ts +130 -0
- package/src/commands/init.ts +26 -13
- package/src/core/scheduler.ts +6 -0
package/README.md
CHANGED
|
@@ -67,8 +67,8 @@ All config and data lives in `~/.niahere/`:
|
|
|
67
67
|
soul.md — how the agent works
|
|
68
68
|
memory.md — persistent learnings (read/written on demand, not loaded into context)
|
|
69
69
|
images/
|
|
70
|
-
reference.
|
|
71
|
-
profile.
|
|
70
|
+
reference.webp — visual identity reference image
|
|
71
|
+
profile.webp — profile picture for Telegram/Slack
|
|
72
72
|
tmp/
|
|
73
73
|
nia.pid, daemon.log, cron-state.json, cron-audit.jsonl
|
|
74
74
|
```
|
package/bin/nia
CHANGED
|
@@ -19,9 +19,7 @@ if [ -z "$BUN_CMD" ]; then
|
|
|
19
19
|
echo "Bun install failed. Install manually: curl -fsSL https://bun.sh/install | bash"
|
|
20
20
|
exit 1
|
|
21
21
|
fi
|
|
22
|
-
echo ""
|
|
23
22
|
echo "Bun installed. Continuing..."
|
|
24
|
-
echo "(Open a new terminal for bun to be in PATH permanently)"
|
|
25
23
|
echo ""
|
|
26
24
|
else
|
|
27
25
|
echo ""
|
|
@@ -31,8 +31,8 @@ You're curious. You like understanding *why* things work, not just *that* they w
|
|
|
31
31
|
|
|
32
32
|
To generate or update your visual identity, use the `nia-image` skill.
|
|
33
33
|
|
|
34
|
-
- **Profile picture**: `~/.niahere/images/profile.
|
|
35
|
-
- **Reference image**: `~/.niahere/images/reference.
|
|
34
|
+
- **Profile picture**: `~/.niahere/images/profile.webp`
|
|
35
|
+
- **Reference image**: `~/.niahere/images/reference.webp`
|
|
36
36
|
|
|
37
37
|
## What You Don't Do
|
|
38
38
|
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "niahere",
|
|
3
|
-
"version": "0.2.
|
|
3
|
+
"version": "0.2.9",
|
|
4
4
|
"description": "A personal AI assistant daemon — scheduled jobs, chat across Telegram and Slack, persona system, and visual identity.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"scripts": {
|
|
@@ -11,7 +11,8 @@
|
|
|
11
11
|
"test": "tsc --noEmit && LOG_LEVEL=silent bun test --reporter=dots",
|
|
12
12
|
"test:bun": "bun test",
|
|
13
13
|
"typecheck": "tsc --noEmit",
|
|
14
|
-
"seed": "bun run src/db/seed.ts"
|
|
14
|
+
"seed": "bun run src/db/seed.ts",
|
|
15
|
+
"release": "npm version patch && npm publish && git push"
|
|
15
16
|
},
|
|
16
17
|
"keywords": [
|
|
17
18
|
"ai",
|
|
@@ -21,13 +21,13 @@ Generate photorealistic images of Nia with consistent identity across different
|
|
|
21
21
|
## Assets
|
|
22
22
|
|
|
23
23
|
The script looks for references in this order:
|
|
24
|
-
1. `~/.niahere/images/reference.
|
|
24
|
+
1. `~/.niahere/images/reference.webp` — user's custom reference (takes priority)
|
|
25
25
|
2. `assets/nia-reference.webp` — default shipped with niahere
|
|
26
26
|
|
|
27
27
|
| Location | Purpose |
|
|
28
28
|
|----------|---------|
|
|
29
|
-
| `~/.niahere/images/reference.
|
|
30
|
-
| `~/.niahere/images/profile.
|
|
29
|
+
| `~/.niahere/images/reference.webp` | User's reference image |
|
|
30
|
+
| `~/.niahere/images/profile.webp` | User's profile picture (for Telegram/Slack) |
|
|
31
31
|
| `~/.niahere/images/` | Output directory for new generations |
|
|
32
32
|
| `assets/nia-reference.webp` | Default reference (fallback) |
|
|
33
33
|
| `assets/nia-profile.webp` | Default profile picture (fallback) |
|
|
@@ -24,7 +24,7 @@ NIA_CONFIG = NIA_HOME / "config.yaml"
|
|
|
24
24
|
DEFAULT_MODEL = "gemini-3.1-flash-image-preview"
|
|
25
25
|
PRO_MODEL = "gemini-3-pro-image-preview"
|
|
26
26
|
BASIC_MODEL = "gemini-2.5-flash-image"
|
|
27
|
-
USER_REFERENCE = str(NIA_HOME / "images" / "reference.
|
|
27
|
+
USER_REFERENCE = str(NIA_HOME / "images" / "reference.webp")
|
|
28
28
|
DEFAULT_REFERENCE = str(PROJECT_ROOT / "assets" / "nia-reference.webp")
|
|
29
29
|
DEFAULT_OUTPUT = str(NIA_HOME / "images")
|
|
30
30
|
DEFAULT_PROMPT = (
|
|
@@ -194,7 +194,7 @@ def main() -> None:
|
|
|
194
194
|
f"or add gemini_api_key to {NIA_CONFIG}."
|
|
195
195
|
)
|
|
196
196
|
|
|
197
|
-
# Resolve reference image: user's ~/.niahere/images/reference.
|
|
197
|
+
# Resolve reference image: user's ~/.niahere/images/reference.webp > skill default > none
|
|
198
198
|
ref_path: str | None = None
|
|
199
199
|
if not args.no_reference:
|
|
200
200
|
if args.reference:
|
package/src/cli/channels.ts
CHANGED
|
@@ -1,5 +1,4 @@
|
|
|
1
1
|
import { getConfig, updateRawConfig } from "../utils/config";
|
|
2
|
-
import { withDb } from "../db/connection";
|
|
3
2
|
import { getPaths } from "../utils/paths";
|
|
4
3
|
import { errMsg } from "../utils/errors";
|
|
5
4
|
import { fail } from "../utils/cli";
|
|
@@ -21,10 +20,8 @@ export async function sendCommand(): Promise<void> {
|
|
|
21
20
|
const { sendMessage } = await import("../mcp/tools");
|
|
22
21
|
|
|
23
22
|
try {
|
|
24
|
-
await
|
|
25
|
-
|
|
26
|
-
console.log(result);
|
|
27
|
-
});
|
|
23
|
+
const result = await sendMessage(message, channel);
|
|
24
|
+
console.log(result);
|
|
28
25
|
} catch (err) {
|
|
29
26
|
fail(`Failed to send: ${errMsg(err)}`);
|
|
30
27
|
}
|
package/src/cli/index.ts
CHANGED
|
@@ -238,6 +238,12 @@ switch (command) {
|
|
|
238
238
|
break;
|
|
239
239
|
}
|
|
240
240
|
|
|
241
|
+
case "db": {
|
|
242
|
+
const { dbCommand } = await import("../commands/db");
|
|
243
|
+
await dbCommand();
|
|
244
|
+
break;
|
|
245
|
+
}
|
|
246
|
+
|
|
241
247
|
case "test": {
|
|
242
248
|
const verbose = process.argv.includes("-v") || process.argv.includes("--verbose");
|
|
243
249
|
const extraArgs = process.argv.slice(3).filter((a) => a !== "-v" && a !== "--verbose");
|
|
@@ -285,6 +291,7 @@ switch (command) {
|
|
|
285
291
|
console.log(" history [room] — recent messages");
|
|
286
292
|
console.log(" logs [-f] — daemon logs");
|
|
287
293
|
console.log(" job <sub> — manage jobs");
|
|
294
|
+
console.log(" db <sub> — database setup/status/migrate");
|
|
288
295
|
console.log(" skills — list available skills");
|
|
289
296
|
console.log(" send [-c ch] <msg> — send a message via channel");
|
|
290
297
|
console.log(" telegram <token> — configure telegram");
|
package/src/cli/status.ts
CHANGED
|
@@ -228,7 +228,10 @@ export async function statusCommand(argv: string[] = []): Promise<void> {
|
|
|
228
228
|
a.name.localeCompare(b.name),
|
|
229
229
|
);
|
|
230
230
|
|
|
231
|
-
|
|
231
|
+
// Hide completed one-shot jobs
|
|
232
|
+
const visibleJobs = sortedJobs.filter((j) => !(j.scheduleType === "once" && !j.enabled && j.lastRunAt));
|
|
233
|
+
|
|
234
|
+
for (const job of visibleJobs) {
|
|
232
235
|
const stateInfo = state[job.name];
|
|
233
236
|
const status = stateInfo?.status ?? (job.lastRunAt ? "ok" : "never");
|
|
234
237
|
const lastRun = stateInfo?.lastRun ?? job.lastRunAt ?? null;
|
|
@@ -291,8 +294,10 @@ export async function statusCommand(argv: string[] = []): Promise<void> {
|
|
|
291
294
|
const icon = info.status === "ok" ? "\u2713" : info.status === "error" ? "\u2717" : "\u2217";
|
|
292
295
|
console.log(` ${icon} ${name}: ${info.status} (last: ${last}, ${info.duration_ms}ms)`);
|
|
293
296
|
}
|
|
297
|
+
} else if (dbError) {
|
|
298
|
+
console.log(`\nJobs: database unavailable (${errMsg(dbError)})`);
|
|
294
299
|
} else {
|
|
295
|
-
console.log("\nJobs:
|
|
300
|
+
console.log("\nJobs: none");
|
|
296
301
|
}
|
|
297
302
|
}
|
|
298
303
|
|
|
@@ -0,0 +1,130 @@
|
|
|
1
|
+
import { getConfig } from "../utils/config";
|
|
2
|
+
import { runMigrations } from "../db/migrate";
|
|
3
|
+
import { closeDb, getSql } from "../db/connection";
|
|
4
|
+
import { errMsg } from "../utils/errors";
|
|
5
|
+
|
|
6
|
+
export async function dbSetup(): Promise<void> {
|
|
7
|
+
console.log("Setting up PostgreSQL...\n");
|
|
8
|
+
|
|
9
|
+
const pgCheck = Bun.spawnSync(["which", "psql"]);
|
|
10
|
+
const hasPostgres = pgCheck.exitCode === 0;
|
|
11
|
+
|
|
12
|
+
if (!hasPostgres) {
|
|
13
|
+
if (process.platform === "darwin") {
|
|
14
|
+
console.log(" PostgreSQL not found. Installing via Homebrew...");
|
|
15
|
+
const brew = Bun.spawn(["brew", "install", "postgresql@17"], { stdout: "inherit", stderr: "inherit" });
|
|
16
|
+
if (await brew.exited !== 0) {
|
|
17
|
+
console.log(" \u2717 brew install failed. Install manually: brew install postgresql@17");
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
console.log(" \u2713 PostgreSQL installed");
|
|
21
|
+
|
|
22
|
+
console.log(" Starting PostgreSQL...");
|
|
23
|
+
const start = Bun.spawn(["brew", "services", "start", "postgresql@17"], { stdout: "pipe", stderr: "pipe" });
|
|
24
|
+
if (await start.exited !== 0) {
|
|
25
|
+
console.log(" \u2717 could not start. Try: brew services start postgresql@17");
|
|
26
|
+
return;
|
|
27
|
+
}
|
|
28
|
+
console.log(" \u2713 PostgreSQL started");
|
|
29
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
30
|
+
} else {
|
|
31
|
+
console.log(" PostgreSQL not found.");
|
|
32
|
+
console.log(" Install it:");
|
|
33
|
+
console.log(" Ubuntu/Debian: sudo apt install postgresql");
|
|
34
|
+
console.log(" Fedora: sudo dnf install postgresql-server");
|
|
35
|
+
console.log(" Arch: sudo pacman -S postgresql");
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
const ready = Bun.spawnSync(["pg_isready"]);
|
|
40
|
+
if (ready.exitCode !== 0) {
|
|
41
|
+
console.log(" PostgreSQL installed but not running.");
|
|
42
|
+
if (process.platform === "darwin") {
|
|
43
|
+
console.log(" Starting...");
|
|
44
|
+
const start = Bun.spawn(["brew", "services", "start", "postgresql@17"], { stdout: "pipe", stderr: "pipe" });
|
|
45
|
+
await start.exited;
|
|
46
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
47
|
+
if (Bun.spawnSync(["pg_isready"]).exitCode === 0) {
|
|
48
|
+
console.log(" \u2713 PostgreSQL started");
|
|
49
|
+
} else {
|
|
50
|
+
console.log(" \u2717 could not start. Check: brew services list");
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
} else {
|
|
54
|
+
console.log(" Start it: sudo systemctl start postgresql");
|
|
55
|
+
return;
|
|
56
|
+
}
|
|
57
|
+
} else {
|
|
58
|
+
console.log(" \u2713 PostgreSQL running");
|
|
59
|
+
}
|
|
60
|
+
}
|
|
61
|
+
|
|
62
|
+
// Create database
|
|
63
|
+
const config = getConfig();
|
|
64
|
+
const dbName = config.database_url.split("/").pop() || "niahere";
|
|
65
|
+
const createDb = Bun.spawnSync(["createdb", dbName]);
|
|
66
|
+
if (createDb.exitCode === 0) {
|
|
67
|
+
console.log(` \u2713 database "${dbName}" created`);
|
|
68
|
+
} else {
|
|
69
|
+
const stderr = new TextDecoder().decode(createDb.stderr);
|
|
70
|
+
if (stderr.includes("already exists")) {
|
|
71
|
+
console.log(` \u2713 database "${dbName}" already exists`);
|
|
72
|
+
} else {
|
|
73
|
+
console.log(` \u2717 createdb failed: ${stderr.trim()}`);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
// Run migrations
|
|
79
|
+
try {
|
|
80
|
+
await runMigrations();
|
|
81
|
+
console.log(" \u2713 migrations done");
|
|
82
|
+
await closeDb();
|
|
83
|
+
} catch (err) {
|
|
84
|
+
console.log(` \u2717 migrations failed: ${errMsg(err)}`);
|
|
85
|
+
}
|
|
86
|
+
|
|
87
|
+
console.log("\nDatabase ready.");
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
export async function dbCommand(): Promise<void> {
|
|
91
|
+
const sub = process.argv[3];
|
|
92
|
+
|
|
93
|
+
switch (sub) {
|
|
94
|
+
case "setup":
|
|
95
|
+
await dbSetup();
|
|
96
|
+
break;
|
|
97
|
+
|
|
98
|
+
case "migrate": {
|
|
99
|
+
try {
|
|
100
|
+
await runMigrations();
|
|
101
|
+
console.log("Migrations done.");
|
|
102
|
+
await closeDb();
|
|
103
|
+
} catch (err) {
|
|
104
|
+
console.log(`Failed: ${errMsg(err)}`);
|
|
105
|
+
process.exit(1);
|
|
106
|
+
}
|
|
107
|
+
break;
|
|
108
|
+
}
|
|
109
|
+
|
|
110
|
+
case "status": {
|
|
111
|
+
try {
|
|
112
|
+
const sql = getSql();
|
|
113
|
+
await sql`SELECT 1`;
|
|
114
|
+
console.log("Database: connected");
|
|
115
|
+
await closeDb();
|
|
116
|
+
} catch (err) {
|
|
117
|
+
console.log(`Database: unavailable (${errMsg(err)})`);
|
|
118
|
+
process.exit(1);
|
|
119
|
+
}
|
|
120
|
+
break;
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
default:
|
|
124
|
+
console.log("Usage: nia db <command>\n");
|
|
125
|
+
console.log(" setup — install PostgreSQL + create database + migrate");
|
|
126
|
+
console.log(" migrate — run database migrations");
|
|
127
|
+
console.log(" status — check database connection");
|
|
128
|
+
break;
|
|
129
|
+
}
|
|
130
|
+
}
|
package/src/commands/init.ts
CHANGED
|
@@ -52,8 +52,12 @@ async function offerBeadsShellExport(rl: readline.Interface, beadsDir: string):
|
|
|
52
52
|
if (answer.toLowerCase() !== "y") return;
|
|
53
53
|
|
|
54
54
|
appendFileSync(rcFile, `\n# Beads global task DB\n${exportLine}\n`);
|
|
55
|
+
// Apply to current process so it takes effect immediately
|
|
56
|
+
const parts = exportLine.replace("$HOME", homedir()).split("=");
|
|
57
|
+
if (parts.length === 2) {
|
|
58
|
+
process.env[parts[0].replace("export ", "")] = parts[1].replace(/"/g, "");
|
|
59
|
+
}
|
|
55
60
|
console.log(` \u2713 added BEADS_DIR to ${rcFile.replace(homedir(), "~")}`);
|
|
56
|
-
console.log(` Run 'source ${rcFile.replace(homedir(), "~")}' or open a new terminal.`);
|
|
57
61
|
}
|
|
58
62
|
|
|
59
63
|
function loadTemplate(name: string, vars: Record<string, string> = {}): string {
|
|
@@ -110,7 +114,13 @@ export async function runInit(): Promise<void> {
|
|
|
110
114
|
} catch (err) {
|
|
111
115
|
const msg = errMsg(err);
|
|
112
116
|
console.log(` \u2717 could not connect: ${msg}`);
|
|
113
|
-
|
|
117
|
+
const setupDb = await ask(rl, " Set up PostgreSQL now? (y/n)", "y");
|
|
118
|
+
if (setupDb.toLowerCase() === "y") {
|
|
119
|
+
const { dbSetup } = await import("./db");
|
|
120
|
+
await dbSetup();
|
|
121
|
+
} else {
|
|
122
|
+
console.log(` (run 'nia db setup' later)\n`);
|
|
123
|
+
}
|
|
114
124
|
}
|
|
115
125
|
delete process.env.DATABASE_URL;
|
|
116
126
|
|
|
@@ -144,6 +154,7 @@ export async function runInit(): Promise<void> {
|
|
|
144
154
|
if (telegramToken) {
|
|
145
155
|
const openInput = await ask(rl, "Allow anyone to message? (y/n)", "n");
|
|
146
156
|
telegramOpen = openInput.toLowerCase() === "y";
|
|
157
|
+
console.log(" Tip: Send a message to your bot to activate outbound messaging.");
|
|
147
158
|
}
|
|
148
159
|
}
|
|
149
160
|
}
|
|
@@ -181,7 +192,8 @@ export async function runInit(): Promise<void> {
|
|
|
181
192
|
const createUrl = `https://api.slack.com/apps?new_app=1&manifest_json=${encodeURIComponent(manifest)}`;
|
|
182
193
|
|
|
183
194
|
console.log("\n Opening Slack app creation page...");
|
|
184
|
-
|
|
195
|
+
const openCmd = process.platform === "darwin" ? "open" : process.platform === "win32" ? "start" : "xdg-open";
|
|
196
|
+
Bun.spawn([openCmd, createUrl], { stdio: ["ignore", "ignore", "ignore"] });
|
|
185
197
|
console.log(" 1. Click 'Create' to create the app");
|
|
186
198
|
console.log(" 2. Go to 'OAuth & Permissions' → Install to workspace → copy Bot Token (xoxb-...)");
|
|
187
199
|
console.log(" 3. Go to 'Basic Information' → 'App-Level Tokens' → create one with connections:write → copy (xapp-...)\n");
|
|
@@ -299,7 +311,7 @@ export async function runInit(): Promise<void> {
|
|
|
299
311
|
// Visual identity
|
|
300
312
|
const imagesDir = `${home}/images`;
|
|
301
313
|
mkdirSync(imagesDir, { recursive: true });
|
|
302
|
-
const hasUserReference = existsSync(`${imagesDir}/reference.
|
|
314
|
+
const hasUserReference = existsSync(`${imagesDir}/reference.webp`);
|
|
303
315
|
const hasDefaultReference = existsSync(`${SKILL_ASSETS_DIR}/nia-reference.webp`);
|
|
304
316
|
|
|
305
317
|
if (geminiApiKey && !hasUserReference) {
|
|
@@ -317,23 +329,23 @@ export async function runInit(): Promise<void> {
|
|
|
317
329
|
"--api-key", geminiApiKey,
|
|
318
330
|
"--aspect-ratio", "9:16",
|
|
319
331
|
"--prompt", prompt,
|
|
320
|
-
"--output", `${imagesDir}/reference.
|
|
332
|
+
"--output", `${imagesDir}/reference.webp`,
|
|
321
333
|
], { stdout: "pipe", stderr: "pipe" });
|
|
322
334
|
const exitCode = await proc.exited;
|
|
323
335
|
if (exitCode === 0) {
|
|
324
|
-
console.log(` \u2713 generated reference image at ${imagesDir}/reference.
|
|
336
|
+
console.log(` \u2713 generated reference image at ${imagesDir}/reference.webp`);
|
|
325
337
|
// Also generate a profile picture
|
|
326
338
|
console.log(" Generating profile picture...");
|
|
327
339
|
const profileProc = Bun.spawn([
|
|
328
340
|
"python3", GENERATE_SCRIPT,
|
|
329
|
-
"--reference", `${imagesDir}/reference.
|
|
341
|
+
"--reference", `${imagesDir}/reference.webp`,
|
|
330
342
|
"--api-key", geminiApiKey,
|
|
331
343
|
"--aspect-ratio", "1:1",
|
|
332
344
|
"--prompt", `Photorealistic close-up portrait of the same person from the reference. Warm slight smile, direct eye contact, soft ambient side lighting, creamy bokeh background, 85mm f/1.8, shallow depth of field. Same face, same style, natural skin texture, DSLR quality, hyper-detailed.`,
|
|
333
|
-
"--output", `${imagesDir}/profile.
|
|
345
|
+
"--output", `${imagesDir}/profile.webp`,
|
|
334
346
|
], { stdout: "pipe", stderr: "pipe" });
|
|
335
347
|
if (await profileProc.exited === 0) {
|
|
336
|
-
console.log(` \u2713 generated profile picture at ${imagesDir}/profile.
|
|
348
|
+
console.log(` \u2713 generated profile picture at ${imagesDir}/profile.webp`);
|
|
337
349
|
}
|
|
338
350
|
} else {
|
|
339
351
|
const stderr = await new Response(proc.stderr).text();
|
|
@@ -342,10 +354,10 @@ export async function runInit(): Promise<void> {
|
|
|
342
354
|
} else if (hasDefaultReference) {
|
|
343
355
|
// No description — copy defaults
|
|
344
356
|
const { copyFileSync } = await import("fs");
|
|
345
|
-
copyFileSync(`${SKILL_ASSETS_DIR}/nia-reference.webp`, `${imagesDir}/reference.
|
|
357
|
+
copyFileSync(`${SKILL_ASSETS_DIR}/nia-reference.webp`, `${imagesDir}/reference.webp`);
|
|
346
358
|
console.log(` \u2713 copied default reference image`);
|
|
347
359
|
if (existsSync(`${SKILL_ASSETS_DIR}/nia-profile.webp`)) {
|
|
348
|
-
copyFileSync(`${SKILL_ASSETS_DIR}/nia-profile.webp`, `${imagesDir}/profile.
|
|
360
|
+
copyFileSync(`${SKILL_ASSETS_DIR}/nia-profile.webp`, `${imagesDir}/profile.webp`);
|
|
349
361
|
console.log(` \u2713 copied default profile picture`);
|
|
350
362
|
}
|
|
351
363
|
}
|
|
@@ -400,6 +412,7 @@ export async function runInit(): Promise<void> {
|
|
|
400
412
|
const vars = { agentName, ownerName, ownerRole, ownerLocation, ownerInterests };
|
|
401
413
|
const selfFile = (name: string) => `${paths.selfDir}/${name}`;
|
|
402
414
|
|
|
415
|
+
// Identity and owner — always write (template-driven, not user-customized)
|
|
403
416
|
writeFileSync(selfFile("identity.md"), loadTemplate("identity.md", vars));
|
|
404
417
|
console.log(` \u2713 wrote ${selfFile("identity.md")}`);
|
|
405
418
|
|
|
@@ -411,8 +424,8 @@ export async function runInit(): Promise<void> {
|
|
|
411
424
|
}
|
|
412
425
|
|
|
413
426
|
// Soul and memory — only create if missing (user may have customized)
|
|
414
|
-
writeIfMissing(selfFile("soul.md"), loadTemplate("soul.md"), selfFile("soul.md"));
|
|
415
|
-
writeIfMissing(selfFile("memory.md"), loadTemplate("memory.md"), selfFile("memory.md"));
|
|
427
|
+
writeIfMissing(selfFile("soul.md"), loadTemplate("soul.md", vars), selfFile("soul.md"));
|
|
428
|
+
writeIfMissing(selfFile("memory.md"), loadTemplate("memory.md", vars), selfFile("memory.md"));
|
|
416
429
|
|
|
417
430
|
resetConfig();
|
|
418
431
|
|
package/src/core/scheduler.ts
CHANGED
|
@@ -99,6 +99,12 @@ async function tick(): Promise<void> {
|
|
|
99
99
|
await Job.markRun(job.name, nextRun).catch((err) => {
|
|
100
100
|
log.error({ err, job: job.name }, "scheduler: failed to update next_run_at");
|
|
101
101
|
});
|
|
102
|
+
|
|
103
|
+
// Auto-disable one-shot jobs after execution
|
|
104
|
+
if (job.scheduleType === "once") {
|
|
105
|
+
await Job.update(job.name, { enabled: false }).catch(() => {});
|
|
106
|
+
log.info({ job: job.name }, "scheduler: one-shot job completed, auto-disabled");
|
|
107
|
+
}
|
|
102
108
|
}
|
|
103
109
|
}
|
|
104
110
|
|