@hyperspell/openclaw-hyperspell 0.4.2 → 0.7.1

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/config.ts CHANGED
@@ -1,191 +1,273 @@
1
1
  export type HyperspellSource =
2
- | "collections"
3
- | "reddit"
4
- | "notion"
5
- | "slack"
6
- | "google_calendar"
7
- | "google_mail"
8
- | "box"
9
- | "google_drive"
10
- | "vault"
11
- | "web_crawler"
2
+ | "collections"
3
+ | "reddit"
4
+ | "notion"
5
+ | "slack"
6
+ | "google_calendar"
7
+ | "google_mail"
8
+ | "box"
9
+ | "google_drive"
10
+ | "vault"
11
+ | "web_crawler";
12
+
13
+ export type KnowledgeGraphConfig = {
14
+ enabled: boolean;
15
+ scanIntervalMinutes: number;
16
+ batchSize: number;
17
+ };
18
+
19
+ export type AutoTraceConfig = {
20
+ enabled: boolean;
21
+ extract: Array<"procedure" | "memory" | "mood">;
22
+ metadata?: Record<string, string | number | boolean>;
23
+ };
12
24
 
13
25
  export type HyperspellConfig = {
14
- apiKey: string
15
- userId?: string
16
- autoContext: boolean
17
- syncMemories: boolean
18
- sources: HyperspellSource[]
19
- maxResults: number
20
- debug: boolean
21
- }
26
+ apiKey: string;
27
+ userId?: string;
28
+ autoContext: boolean;
29
+ autoTrace: AutoTraceConfig;
30
+ syncMemories: boolean;
31
+ sources: HyperspellSource[];
32
+ maxResults: number;
33
+ relevanceThreshold: number;
34
+ debug: boolean;
35
+ knowledgeGraph: KnowledgeGraphConfig;
36
+ };
22
37
 
23
38
  const ALLOWED_KEYS = [
24
- "apiKey",
25
- "userId",
26
- "autoContext",
27
- "syncMemories",
28
- "sources",
29
- "maxResults",
30
- "debug",
31
- ]
39
+ "apiKey",
40
+ "userId",
41
+ "autoContext",
42
+ "autoTrace",
43
+ "syncMemories",
44
+ "sources",
45
+ "maxResults",
46
+ "relevanceThreshold",
47
+ "debug",
48
+ "knowledgeGraph",
49
+ ];
32
50
 
33
51
  const VALID_SOURCES: HyperspellSource[] = [
34
- "collections",
35
- "reddit",
36
- "notion",
37
- "slack",
38
- "google_calendar",
39
- "google_mail",
40
- "box",
41
- "google_drive",
42
- "vault",
43
- "web_crawler",
44
- ]
52
+ "collections",
53
+ "reddit",
54
+ "notion",
55
+ "slack",
56
+ "google_calendar",
57
+ "google_mail",
58
+ "box",
59
+ "google_drive",
60
+ "vault",
61
+ "web_crawler",
62
+ ];
45
63
 
46
64
  function assertAllowedKeys(
47
- value: Record<string, unknown>,
48
- allowed: string[],
49
- label: string,
65
+ value: Record<string, unknown>,
66
+ allowed: string[],
67
+ label: string,
50
68
  ): void {
51
- const unknown = Object.keys(value).filter((k) => !allowed.includes(k))
52
- if (unknown.length > 0) {
53
- throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`)
54
- }
69
+ const unknown = Object.keys(value).filter((k) => !allowed.includes(k));
70
+ if (unknown.length > 0) {
71
+ throw new Error(`${label} has unknown keys: ${unknown.join(", ")}`);
72
+ }
55
73
  }
56
74
 
57
75
  function resolveEnvVars(value: string): string {
58
- return value.replace(/\$\{([^}]+)\}/g, (_, envVar: string) => {
59
- const envValue = process.env[envVar]
60
- if (!envValue) {
61
- throw new Error(`Environment variable ${envVar} is not set`)
62
- }
63
- return envValue
64
- })
76
+ return value.replace(/\$\{([^}]+)\}/g, (_, envVar: string) => {
77
+ const envValue = process.env[envVar];
78
+ if (!envValue) {
79
+ throw new Error(`Environment variable ${envVar} is not set`);
80
+ }
81
+ return envValue;
82
+ });
65
83
  }
66
84
 
67
85
  function parseSources(raw: string | string[] | undefined): HyperspellSource[] {
68
- if (!raw) {
69
- return []
70
- }
71
-
72
- // Handle array input
73
- if (Array.isArray(raw)) {
74
- const sources = raw
75
- .map((s) => String(s).trim().toLowerCase())
76
- .filter((s) => s.length > 0) as HyperspellSource[]
77
-
78
- for (const source of sources) {
79
- if (!VALID_SOURCES.includes(source)) {
80
- throw new Error(
81
- `Invalid source "${source}". Valid sources: ${VALID_SOURCES.join(", ")}`,
82
- )
83
- }
84
- }
85
-
86
- return sources
87
- }
88
-
89
- // Handle string input (comma-separated)
90
- if (typeof raw === "string" && raw.trim() === "") {
91
- return []
92
- }
93
-
94
- const sources = raw
95
- .split(",")
96
- .map((s) => s.trim().toLowerCase())
97
- .filter((s) => s.length > 0) as HyperspellSource[]
98
-
99
- for (const source of sources) {
100
- if (!VALID_SOURCES.includes(source)) {
101
- throw new Error(
102
- `Invalid source "${source}". Valid sources: ${VALID_SOURCES.join(", ")}`,
103
- )
104
- }
105
- }
106
-
107
- return sources
86
+ if (!raw) {
87
+ return [];
88
+ }
89
+
90
+ // Handle array input
91
+ if (Array.isArray(raw)) {
92
+ const sources = raw
93
+ .map((s) => String(s).trim().toLowerCase())
94
+ .filter((s) => s.length > 0) as HyperspellSource[];
95
+
96
+ for (const source of sources) {
97
+ if (!VALID_SOURCES.includes(source)) {
98
+ throw new Error(
99
+ `Invalid source "${source}". Valid sources: ${VALID_SOURCES.join(", ")}`,
100
+ );
101
+ }
102
+ }
103
+
104
+ return sources;
105
+ }
106
+
107
+ // Handle string input (comma-separated)
108
+ if (typeof raw === "string" && raw.trim() === "") {
109
+ return [];
110
+ }
111
+
112
+ const sources = raw
113
+ .split(",")
114
+ .map((s) => s.trim().toLowerCase())
115
+ .filter((s) => s.length > 0) as HyperspellSource[];
116
+
117
+ for (const source of sources) {
118
+ if (!VALID_SOURCES.includes(source)) {
119
+ throw new Error(
120
+ `Invalid source "${source}". Valid sources: ${VALID_SOURCES.join(", ")}`,
121
+ );
122
+ }
123
+ }
124
+
125
+ return sources;
108
126
  }
109
127
 
110
128
  export function parseConfig(raw: unknown): HyperspellConfig {
111
- const cfg =
112
- raw && typeof raw === "object" && !Array.isArray(raw)
113
- ? (raw as Record<string, unknown>)
114
- : {}
115
-
116
- if (Object.keys(cfg).length > 0) {
117
- assertAllowedKeys(cfg, ALLOWED_KEYS, "hyperspell config")
118
- }
119
-
120
- const apiKey =
121
- typeof cfg.apiKey === "string" && cfg.apiKey.length > 0
122
- ? resolveEnvVars(cfg.apiKey)
123
- : process.env.HYPERSPELL_API_KEY
124
-
125
- if (!apiKey) {
126
- throw new Error(
127
- "hyperspell: apiKey is required (set in plugin config or HYPERSPELL_API_KEY env var)",
128
- )
129
- }
130
-
131
- return {
132
- apiKey,
133
- userId: cfg.userId as string | undefined,
134
- autoContext: (cfg.autoContext as boolean) ?? true,
135
- syncMemories: (cfg.syncMemories as boolean) ?? false,
136
- sources: parseSources(cfg.sources as string | string[] | undefined),
137
- maxResults: (cfg.maxResults as number) ?? 10,
138
- debug: (cfg.debug as boolean) ?? false,
139
- }
129
+ const cfg =
130
+ raw && typeof raw === "object" && !Array.isArray(raw)
131
+ ? (raw as Record<string, unknown>)
132
+ : {};
133
+
134
+ if (Object.keys(cfg).length > 0) {
135
+ assertAllowedKeys(cfg, ALLOWED_KEYS, "hyperspell config");
136
+ }
137
+
138
+ const apiKey =
139
+ typeof cfg.apiKey === "string" && cfg.apiKey.length > 0
140
+ ? resolveEnvVars(cfg.apiKey)
141
+ : process.env.HYPERSPELL_API_KEY;
142
+
143
+ if (!apiKey) {
144
+ throw new Error(
145
+ "hyperspell: apiKey is required (set in plugin config or HYPERSPELL_API_KEY env var)",
146
+ );
147
+ }
148
+
149
+ const kgRaw = (cfg.knowledgeGraph ?? {}) as Record<string, unknown>;
150
+ const atRaw = (cfg.autoTrace ?? {}) as Record<string, unknown>;
151
+
152
+ return {
153
+ apiKey,
154
+ userId: cfg.userId as string | undefined,
155
+ autoContext: (cfg.autoContext as boolean) ?? true,
156
+ autoTrace: {
157
+ enabled: (atRaw.enabled as boolean) ?? false,
158
+ extract: (atRaw.extract as Array<"procedure" | "memory" | "mood">) ?? [
159
+ "procedure",
160
+ ],
161
+ metadata: atRaw.metadata as
162
+ | Record<string, string | number | boolean>
163
+ | undefined,
164
+ },
165
+ syncMemories: (cfg.syncMemories as boolean) ?? false,
166
+ sources: parseSources(cfg.sources as string | string[] | undefined),
167
+ maxResults: (cfg.maxResults as number) ?? 10,
168
+ relevanceThreshold: (cfg.relevanceThreshold as number) ?? 0.6,
169
+ debug: (cfg.debug as boolean) ?? false,
170
+ knowledgeGraph: {
171
+ enabled: (kgRaw.enabled as boolean) ?? false,
172
+ scanIntervalMinutes: (kgRaw.scanIntervalMinutes as number) ?? 60,
173
+ batchSize: (kgRaw.batchSize as number) ?? 20,
174
+ },
175
+ };
140
176
  }
141
177
 
142
178
  export const hyperspellConfigSchema = {
143
- parse: parseConfig,
179
+ parse: parseConfig,
180
+ };
181
+
182
+ /**
183
+ * Resolve OpenClaw state directory (matches OpenClaw's own logic).
184
+ */
185
+ export function resolveStateDir(): string {
186
+ const { homedir } = require("node:os");
187
+ const path = require("node:path");
188
+
189
+ const override =
190
+ process.env.OPENCLAW_STATE_DIR?.trim() ||
191
+ process.env.CLAWDBOT_STATE_DIR?.trim();
192
+ if (override) {
193
+ return override.startsWith("~")
194
+ ? override.replace(/^~(?=$|[\\/])/, homedir())
195
+ : path.resolve(override);
196
+ }
197
+ return path.join(homedir(), ".openclaw");
198
+ }
199
+
200
+ /**
201
+ * Resolve OpenClaw config file path (matches OpenClaw's own logic).
202
+ */
203
+ export function resolveConfigPath(): string {
204
+ const path = require("node:path");
205
+
206
+ const override =
207
+ process.env.OPENCLAW_CONFIG_PATH?.trim() ||
208
+ process.env.CLAWDBOT_CONFIG_PATH?.trim();
209
+ if (override) {
210
+ const { homedir } = require("node:os");
211
+ return override.startsWith("~")
212
+ ? override.replace(/^~(?=$|[\\/])/, homedir())
213
+ : path.resolve(override);
214
+ }
215
+ return path.join(resolveStateDir(), "openclaw.json");
144
216
  }
145
217
 
146
218
  /**
147
219
  * Get the workspace directory from OpenClaw config
148
220
  */
149
221
  export function getWorkspaceDir(): string {
150
- const { homedir } = require("node:os")
151
- const fs = require("node:fs")
152
- const path = require("node:path")
153
-
154
- // Resolve config path
155
- const override = process.env.OPENCLAW_CONFIG_PATH?.trim() || process.env.CLAWDBOT_CONFIG_PATH?.trim()
156
- let configPath: string
157
- if (override) {
158
- configPath = override.startsWith("~")
159
- ? override.replace(/^~(?=$|[\\/])/, homedir())
160
- : path.resolve(override)
161
- } else {
162
- const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim()
163
- const resolvedStateDir = stateDir
164
- ? (stateDir.startsWith("~") ? stateDir.replace(/^~(?=$|[\\/])/, homedir()) : path.resolve(stateDir))
165
- : path.join(homedir(), ".openclaw")
166
- configPath = path.join(resolvedStateDir, "openclaw.json")
167
- }
168
-
169
- // Read workspace from config
170
- if (fs.existsSync(configPath)) {
171
- try {
172
- const content = fs.readFileSync(configPath, "utf-8")
173
- const config = JSON.parse(content)
174
- const workspace = config?.agents?.defaults?.workspace
175
- if (workspace) {
176
- return workspace.startsWith("~")
177
- ? workspace.replace(/^~(?=$|[\\/])/, homedir())
178
- : workspace
179
- }
180
- } catch (_e) {
181
- // Fall back to default
182
- }
183
- }
184
-
185
- // Default workspace
186
- const stateDir = process.env.OPENCLAW_STATE_DIR?.trim() || process.env.CLAWDBOT_STATE_DIR?.trim()
187
- const resolvedStateDir = stateDir
188
- ? (stateDir.startsWith("~") ? stateDir.replace(/^~(?=$|[\\/])/, homedir()) : path.resolve(stateDir))
189
- : path.join(homedir(), ".openclaw")
190
- return path.join(resolvedStateDir, "workspace")
222
+ const { homedir } = require("node:os");
223
+ const fs = require("node:fs");
224
+ const path = require("node:path");
225
+
226
+ // Resolve config path
227
+ const override =
228
+ process.env.OPENCLAW_CONFIG_PATH?.trim() ||
229
+ process.env.CLAWDBOT_CONFIG_PATH?.trim();
230
+ let configPath: string;
231
+ if (override) {
232
+ configPath = override.startsWith("~")
233
+ ? override.replace(/^~(?=$|[\\/])/, homedir())
234
+ : path.resolve(override);
235
+ } else {
236
+ const stateDir =
237
+ process.env.OPENCLAW_STATE_DIR?.trim() ||
238
+ process.env.CLAWDBOT_STATE_DIR?.trim();
239
+ const resolvedStateDir = stateDir
240
+ ? stateDir.startsWith("~")
241
+ ? stateDir.replace(/^~(?=$|[\\/])/, homedir())
242
+ : path.resolve(stateDir)
243
+ : path.join(homedir(), ".openclaw");
244
+ configPath = path.join(resolvedStateDir, "openclaw.json");
245
+ }
246
+
247
+ // Read workspace from config
248
+ if (fs.existsSync(configPath)) {
249
+ try {
250
+ const content = fs.readFileSync(configPath, "utf-8");
251
+ const config = JSON.parse(content);
252
+ const workspace = config?.agents?.defaults?.workspace;
253
+ if (workspace) {
254
+ return workspace.startsWith("~")
255
+ ? workspace.replace(/^~(?=$|[\\/])/, homedir())
256
+ : workspace;
257
+ }
258
+ } catch (_e) {
259
+ // Fall back to default
260
+ }
261
+ }
262
+
263
+ // Default workspace
264
+ const stateDir =
265
+ process.env.OPENCLAW_STATE_DIR?.trim() ||
266
+ process.env.CLAWDBOT_STATE_DIR?.trim();
267
+ const resolvedStateDir = stateDir
268
+ ? stateDir.startsWith("~")
269
+ ? stateDir.replace(/^~(?=$|[\\/])/, homedir())
270
+ : path.resolve(stateDir)
271
+ : path.join(homedir(), ".openclaw");
272
+ return path.join(resolvedStateDir, "workspace");
191
273
  }