@modusensus/dsh-mneme 0.1.6 → 0.2.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/settings.js CHANGED
@@ -1,142 +1,142 @@
1
- // User-configurable settings: profile (user self-description), rules (behavior
2
- // rules the agent must follow), and custom slash commands. Stored in the same
3
- // SQLite database via dedicated tables, isolated from the memories store.
4
- import { randomUUID } from "node:crypto";
5
-
6
- const SCHEMA = `
7
- CREATE TABLE IF NOT EXISTS user_settings (
8
- key TEXT PRIMARY KEY,
9
- value TEXT NOT NULL
10
- );
11
- CREATE TABLE IF NOT EXISTS custom_commands (
12
- id TEXT PRIMARY KEY,
13
- name TEXT NOT NULL UNIQUE,
14
- description TEXT NOT NULL DEFAULT '',
15
- instruction TEXT NOT NULL,
16
- created_at TEXT NOT NULL,
17
- updated_at TEXT NOT NULL
18
- );
19
- `;
20
-
21
- // DSH command names must match this (lowercase, start with a letter).
22
- const COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
23
-
24
- /** Parse a JSON array out of a stored string, tolerant of corruption. */
25
- function parseList(raw) {
26
- try {
27
- const value = JSON.parse(raw);
28
- return Array.isArray(value) ? value : [];
29
- } catch {
30
- return [];
31
- }
32
- }
33
-
34
- export function createSettings(db) {
35
- db.exec(SCHEMA);
36
-
37
- function getSetting(key) {
38
- const row = db.prepare("SELECT value FROM user_settings WHERE key = ?").get(key);
39
- return row?.value ?? undefined;
40
- }
41
-
42
- function setSetting(key, value) {
43
- db.prepare(
44
- `INSERT INTO user_settings (key, value) VALUES (?, ?)
45
- ON CONFLICT(key) DO UPDATE SET value = excluded.value`
46
- ).run(key, value);
47
- }
48
-
49
- function toCommand(row) {
50
- if (!row) return undefined;
51
- return {
52
- id: row.id,
53
- name: row.name,
54
- description: row.description,
55
- instruction: row.instruction,
56
- created_at: row.created_at,
57
- updated_at: row.updated_at
58
- };
59
- }
60
-
61
- return {
62
- /** The user's self-description (free text) or "" when unset. */
63
- getProfile() {
64
- return getSetting("profile") ?? "";
65
- },
66
- setProfile(text) {
67
- setSetting("profile", String(text ?? ""));
68
- },
69
-
70
- /** Behavior rules as an array of strings. */
71
- getRules() {
72
- return parseList(getSetting("rules") ?? "[]").filter((r) => typeof r === "string");
73
- },
74
- setRules(rules) {
75
- const list = Array.isArray(rules) ? rules.filter((r) => typeof r === "string") : [];
76
- setSetting("rules", JSON.stringify(list));
77
- },
78
-
79
- /** All custom commands, sorted by name. */
80
- listCommands() {
81
- const rows = db.prepare("SELECT * FROM custom_commands ORDER BY name ASC").all();
82
- return rows.map(toCommand);
83
- },
84
-
85
- /**
86
- * Add or replace a custom command by name.
87
- * @returns the stored command.
88
- * @throws when name is invalid or does not match DSH's command-name grammar.
89
- */
90
- addCommand({ name, description = "", instruction }) {
91
- const cmdName = String(name ?? "").trim();
92
- if (!COMMAND_NAME.test(cmdName)) {
93
- throw new Error(`invalid command name "${cmdName}": must match /^[a-z][a-z0-9_-]*$/`);
94
- }
95
- if (typeof instruction !== "string" || !instruction.trim()) {
96
- throw new Error("command instruction must be a non-empty string");
97
- }
98
- const now = new Date().toISOString();
99
- const existing = db.prepare("SELECT id FROM custom_commands WHERE name = ?").get(cmdName);
100
- if (existing) {
101
- db.prepare(
102
- "UPDATE custom_commands SET description = ?, instruction = ?, updated_at = ? WHERE id = ?"
103
- ).run(String(description ?? ""), instruction, now, existing.id);
104
- return toCommand(db.prepare("SELECT * FROM custom_commands WHERE id = ?").get(existing.id));
105
- }
106
- const id = randomUUID();
107
- db.prepare(
108
- `INSERT INTO custom_commands (id, name, description, instruction, created_at, updated_at)
109
- VALUES (?, ?, ?, ?, ?, ?)`
110
- ).run(id, cmdName, String(description ?? ""), instruction, now, now);
111
- return toCommand(db.prepare("SELECT * FROM custom_commands WHERE id = ?").get(id));
112
- },
113
-
114
- /** Remove a custom command by id; returns true when removed. */
115
- removeCommand(id) {
116
- const result = db.prepare("DELETE FROM custom_commands WHERE id = ?").run(id);
117
- return result.changes > 0;
118
- },
119
-
120
- /** Vector-search provider config (OpenAI-compatible embeddings endpoint). */
121
- getVectorConfig() {
122
- const raw = getSetting("vector");
123
- if (!raw) return undefined;
124
- try {
125
- const cfg = JSON.parse(raw);
126
- return typeof cfg === "object" && cfg !== null ? cfg : undefined;
127
- } catch {
128
- return undefined;
129
- }
130
- },
131
- setVectorConfig({ enabled, baseUrl, apiKey, model }) {
132
- const cfg = {
133
- enabled: enabled === true || enabled === 1,
134
- baseUrl: String(baseUrl ?? "").trim().replace(/\/+$/, ""),
135
- apiKey: String(apiKey ?? "").trim(),
136
- model: String(model ?? "").trim()
137
- };
138
- setSetting("vector", JSON.stringify(cfg));
139
- return cfg;
140
- }
141
- };
142
- }
1
+ // User-configurable settings: profile (user self-description), rules (behavior
2
+ // rules the agent must follow), and custom slash commands. Stored in the same
3
+ // SQLite database via dedicated tables, isolated from the memories store.
4
+ import { randomUUID } from "node:crypto";
5
+
6
+ const SCHEMA = `
7
+ CREATE TABLE IF NOT EXISTS user_settings (
8
+ key TEXT PRIMARY KEY,
9
+ value TEXT NOT NULL
10
+ );
11
+ CREATE TABLE IF NOT EXISTS custom_commands (
12
+ id TEXT PRIMARY KEY,
13
+ name TEXT NOT NULL UNIQUE,
14
+ description TEXT NOT NULL DEFAULT '',
15
+ instruction TEXT NOT NULL,
16
+ created_at TEXT NOT NULL,
17
+ updated_at TEXT NOT NULL
18
+ );
19
+ `;
20
+
21
+ // DSH command names must match this (lowercase, start with a letter).
22
+ const COMMAND_NAME = /^[a-z][a-z0-9_-]*$/;
23
+
24
+ /** Parse a JSON array out of a stored string, tolerant of corruption. */
25
+ function parseList(raw) {
26
+ try {
27
+ const value = JSON.parse(raw);
28
+ return Array.isArray(value) ? value : [];
29
+ } catch {
30
+ return [];
31
+ }
32
+ }
33
+
34
+ export function createSettings(db) {
35
+ db.exec(SCHEMA);
36
+
37
+ function getSetting(key) {
38
+ const row = db.prepare("SELECT value FROM user_settings WHERE key = ?").get(key);
39
+ return row?.value ?? undefined;
40
+ }
41
+
42
+ function setSetting(key, value) {
43
+ db.prepare(
44
+ `INSERT INTO user_settings (key, value) VALUES (?, ?)
45
+ ON CONFLICT(key) DO UPDATE SET value = excluded.value`
46
+ ).run(key, value);
47
+ }
48
+
49
+ function toCommand(row) {
50
+ if (!row) return undefined;
51
+ return {
52
+ id: row.id,
53
+ name: row.name,
54
+ description: row.description,
55
+ instruction: row.instruction,
56
+ created_at: row.created_at,
57
+ updated_at: row.updated_at
58
+ };
59
+ }
60
+
61
+ return {
62
+ /** The user's self-description (free text) or "" when unset. */
63
+ getProfile() {
64
+ return getSetting("profile") ?? "";
65
+ },
66
+ setProfile(text) {
67
+ setSetting("profile", String(text ?? ""));
68
+ },
69
+
70
+ /** Behavior rules as an array of strings. */
71
+ getRules() {
72
+ return parseList(getSetting("rules") ?? "[]").filter((r) => typeof r === "string");
73
+ },
74
+ setRules(rules) {
75
+ const list = Array.isArray(rules) ? rules.filter((r) => typeof r === "string") : [];
76
+ setSetting("rules", JSON.stringify(list));
77
+ },
78
+
79
+ /** All custom commands, sorted by name. */
80
+ listCommands() {
81
+ const rows = db.prepare("SELECT * FROM custom_commands ORDER BY name ASC").all();
82
+ return rows.map(toCommand);
83
+ },
84
+
85
+ /**
86
+ * Add or replace a custom command by name.
87
+ * @returns the stored command.
88
+ * @throws when name is invalid or does not match DSH's command-name grammar.
89
+ */
90
+ addCommand({ name, description = "", instruction }) {
91
+ const cmdName = String(name ?? "").trim();
92
+ if (!COMMAND_NAME.test(cmdName)) {
93
+ throw new Error(`invalid command name "${cmdName}": must match /^[a-z][a-z0-9_-]*$/`);
94
+ }
95
+ if (typeof instruction !== "string" || !instruction.trim()) {
96
+ throw new Error("command instruction must be a non-empty string");
97
+ }
98
+ const now = new Date().toISOString();
99
+ const existing = db.prepare("SELECT id FROM custom_commands WHERE name = ?").get(cmdName);
100
+ if (existing) {
101
+ db.prepare(
102
+ "UPDATE custom_commands SET description = ?, instruction = ?, updated_at = ? WHERE id = ?"
103
+ ).run(String(description ?? ""), instruction, now, existing.id);
104
+ return toCommand(db.prepare("SELECT * FROM custom_commands WHERE id = ?").get(existing.id));
105
+ }
106
+ const id = randomUUID();
107
+ db.prepare(
108
+ `INSERT INTO custom_commands (id, name, description, instruction, created_at, updated_at)
109
+ VALUES (?, ?, ?, ?, ?, ?)`
110
+ ).run(id, cmdName, String(description ?? ""), instruction, now, now);
111
+ return toCommand(db.prepare("SELECT * FROM custom_commands WHERE id = ?").get(id));
112
+ },
113
+
114
+ /** Remove a custom command by id; returns true when removed. */
115
+ removeCommand(id) {
116
+ const result = db.prepare("DELETE FROM custom_commands WHERE id = ?").run(id);
117
+ return result.changes > 0;
118
+ },
119
+
120
+ /** Vector-search provider config (OpenAI-compatible embeddings endpoint). */
121
+ getVectorConfig() {
122
+ const raw = getSetting("vector");
123
+ if (!raw) return undefined;
124
+ try {
125
+ const cfg = JSON.parse(raw);
126
+ return typeof cfg === "object" && cfg !== null ? cfg : undefined;
127
+ } catch {
128
+ return undefined;
129
+ }
130
+ },
131
+ setVectorConfig({ enabled, baseUrl, apiKey, model }) {
132
+ const cfg = {
133
+ enabled: enabled === true || enabled === 1,
134
+ baseUrl: String(baseUrl ?? "").trim().replace(/\/+$/, ""),
135
+ apiKey: String(apiKey ?? "").trim(),
136
+ model: String(model ?? "").trim()
137
+ };
138
+ setSetting("vector", JSON.stringify(cfg));
139
+ return cfg;
140
+ }
141
+ };
142
+ }