assistme 0.3.1 → 0.3.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.
Files changed (44) hide show
  1. package/PLAN.md +14 -3
  2. package/dist/{chunk-UWE5WVQI.js → chunk-KX7ITO55.js} +20 -11
  3. package/dist/index.js +1771 -496
  4. package/dist/{job-runner-N4XAAWLJ.js → job-runner-P2L6MOOX.js} +1 -1
  5. package/package.json +5 -3
  6. package/src/agent/job-runner.ts +9 -13
  7. package/src/agent/mcp-servers.ts +6 -952
  8. package/src/agent/memory.ts +2 -11
  9. package/src/agent/processor.ts +17 -107
  10. package/src/agent/scheduler.ts +2 -3
  11. package/src/agent/session.ts +20 -36
  12. package/src/agent/skills.ts +167 -61
  13. package/src/agent/system-prompt.ts +126 -0
  14. package/src/browser/chrome-launcher.ts +555 -0
  15. package/src/browser/controller.ts +1386 -0
  16. package/src/browser/types.ts +70 -0
  17. package/src/commands/credential.ts +190 -0
  18. package/src/commands/job.ts +14 -45
  19. package/src/commands/memory.ts +16 -29
  20. package/src/commands/schedule.ts +15 -37
  21. package/src/commands/start.ts +11 -43
  22. package/src/credentials/credential-store.test.ts +162 -0
  23. package/src/credentials/credential-store.ts +266 -0
  24. package/src/credentials/encryption.test.ts +98 -0
  25. package/src/credentials/encryption.ts +82 -0
  26. package/src/credentials/index.ts +15 -0
  27. package/src/credentials/local-store.ts +89 -0
  28. package/src/db/action.ts +19 -0
  29. package/src/db/api-client.ts +3 -32
  30. package/src/db/auth-store.ts +41 -0
  31. package/src/db/auth.ts +38 -0
  32. package/src/db/conversation.ts +39 -0
  33. package/src/db/event.ts +52 -0
  34. package/src/db/job-poll.ts +18 -0
  35. package/src/db/session.ts +60 -0
  36. package/src/db/supabase.ts +40 -383
  37. package/src/db/task.ts +69 -0
  38. package/src/db/types.ts +54 -0
  39. package/src/index.ts +2 -0
  40. package/src/mcp/agent-tools-server.ts +1047 -0
  41. package/src/mcp/browser-server.ts +258 -0
  42. package/src/tools/browser.ts +28 -1208
  43. package/src/tools/index.ts +32 -263
  44. package/src/tools/web.ts +0 -73
@@ -26,10 +26,6 @@ export interface Memory {
26
26
  // ── Memory Manager ──────────────────────────────────────────────────
27
27
 
28
28
  export class MemoryManager {
29
- constructor(_userId: string) {
30
- // userId is no longer needed — auth is handled by the MCP token in callMcpHandler
31
- }
32
-
33
29
  /**
34
30
  * Store a new memory. Called by the agent after completing tasks
35
31
  * to remember important facts about the user.
@@ -45,9 +41,7 @@ export class MemoryManager {
45
41
  }
46
42
  ): Promise<Memory> {
47
43
  const expiresAt = options?.expiresInDays
48
- ? new Date(
49
- Date.now() + options.expiresInDays * 86400_000
50
- ).toISOString()
44
+ ? new Date(Date.now() + options.expiresInDays * 86400_000).toISOString()
51
45
  : null;
52
46
 
53
47
  const data = await callMcpHandler<Memory>("memory.store", {
@@ -132,10 +126,7 @@ export class MemoryManager {
132
126
 
133
127
  // ── CRUD for CLI ────────────────────────────────────────────
134
128
 
135
- async list(
136
- category?: MemoryCategory,
137
- limit = 20
138
- ): Promise<Memory[]> {
129
+ async list(category?: MemoryCategory, limit = 20): Promise<Memory[]> {
139
130
  const data = await callMcpHandler<Memory[]>("memory.list", {
140
131
  category: category || null,
141
132
  limit,
@@ -29,101 +29,7 @@ import {
29
29
  BROWSER_TOOL_NAMES,
30
30
  } from "./mcp-servers.js";
31
31
  import { createEventHooks } from "./event-hooks.js";
32
-
33
- const BASE_SYSTEM_PROMPT = `You are AssistMe, an AI assistant that operates like a real human on the user's computer. You control the user's actual Chrome browser and work with their real files.
34
-
35
- KEY PRINCIPLE: You operate the user's real browser, not a headless sandbox. This means:
36
- - The browser has the user's real cookies, logins, and sessions
37
- - When you navigate to amazon.com, you see the user's logged-in Amazon
38
- - If a site needs login, the browser will auto-detect the login page and prompt the user
39
- - After the user logs in, their session is saved in the persistent browser profile (~/.assistme/browser-profile)
40
- - Saved sessions persist across assistme restarts — the user only needs to log in once per site
41
- - You are like a human assistant sitting at the user's computer
42
- - Chrome is automatically managed — just call browser_connect and it will auto-launch if needed
43
- - NEVER ask the user to manually start Chrome or run any terminal commands for browser setup
44
-
45
- Available capabilities:
46
- 1. BROWSER CONTROL (user's real Chrome via CDP):
47
- - Use browser tools (browser_connect, browser_navigate, browser_read_page, browser_screenshot, browser_click, browser_type, browser_press_key, browser_scroll, browser_get_elements, browser_evaluate, browser_list_tabs, browser_switch_tab, browser_new_tab) to control the user's real Chrome
48
- - If auth is needed: use browser_request_user_action to ask the user to log in
49
-
50
- 2. FILE OPERATIONS & SHELL:
51
- - Read, Write, Edit tools for file operations
52
- - Bash tool for shell commands
53
- - Glob and Grep for file search
54
-
55
- 3. MEMORY:
56
- - You can remember things about the user using memory_store
57
- - Use this when you learn preferences, important facts, or standing instructions
58
- - Your stored memories persist across conversations
59
- - PROACTIVELY use memory_store during tasks when you discover user preferences, habits, or important context
60
- - Before completing a task, consider if anything learned should be remembered for future conversations
61
-
62
- 4. SKILL-AWARE EXECUTION (CRITICAL — follow this for EVERY task):
63
- Step A — Search: Before executing ANY task, check if an existing skill matches (use skill_invoke or skill_search).
64
- Step B — If skill found: load it with skill_invoke and follow its instructions precisely. If the instructions are incomplete or wrong, adapt and improve as you go — note what changed.
65
- Step C — If NO skill found: BEFORE executing, draft a skill plan following the Agent Skills format:
66
- Skill Draft: [kebab-case-name]
67
- Description: [what this skill does and when to use it]
68
- Steps:
69
- 1. [first step]
70
- 2. [second step]
71
- ...
72
- The draft should be a reusable workflow, not specific to this one request. Use generic placeholders where the user provided specific values.
73
- Step D — Execute: Follow the skill draft (or loaded skill) step by step. Refine the draft as you discover better approaches, edge cases, or missing steps.
74
- Step E — After execution: The system will automatically evaluate whether to save the skill. You do NOT need to call skill_create manually.
75
-
76
- Agent Skills format reference (agentskills.io):
77
- - name: 1-64 chars, lowercase kebab-case (a-z, 0-9, hyphens), no leading/trailing/consecutive hyphens
78
- - description: 1-1024 chars, describe what the skill does AND when to use it, include keywords for discoverability
79
- - body: markdown step-by-step instructions, examples, edge cases. Keep under 500 lines.
80
- - Progressive disclosure: metadata (~100 tokens) → instructions (<5000 tokens) → references (on demand)
81
-
82
- 5. JOB AUTOMATION:
83
- - When the user describes their job/role/daily work, use skill_generate to decompose it into automatable skills
84
- - ALWAYS use ask_user to get user approval before creating skills — never create skills without approval
85
- - Use job_run to start a job — it gives you the job's goal and available skills as capabilities
86
- - When running a job, be AGENTIC: decide dynamically what to do based on what you discover
87
- - Do NOT follow a fixed sequence — if checking Slack reveals a task that needs GitHub, go do GitHub immediately
88
- - Chain actions intelligently: one skill's findings should inform your next move
89
- - Skip irrelevant skills, use tools directly when skills aren't needed
90
- - Use job_schedule for recurring automation (e.g., "run my SE job every weekday morning")
91
- - Use job_status to check run history
92
-
93
- 6. SKILL MARKETPLACE:
94
- - Use skill_browse to discover community-published skills
95
- - Use skill_add to add marketplace skills to the user's collection
96
- - Use skill_publish to share the user's skills with the community
97
-
98
- Workflow for web tasks (e.g. "查一下 kindle 最新款价格"):
99
- 1. browser_connect → connect to user's Chrome
100
- 2. browser_new_tab → open a new tab
101
- 3. browser_navigate → go to the website (login pages are auto-detected — the user will be prompted and their session saved)
102
- 4. browser_read_page or browser_screenshot → read the content
103
- 5. If login is needed but not auto-detected → use browser_request_user_action to ask the user
104
- 6. Repeat across multiple sites as needed
105
- 7. Summarize findings
106
-
107
- Guidelines:
108
- - Always use the real browser for web tasks, never try to fetch URLs programmatically
109
- - Use browser_screenshot when you need to see the visual layout
110
- - Use browser_get_elements to find clickable elements before clicking
111
- - Login pages are auto-detected after navigation — the user is prompted and sessions are saved automatically
112
- - If auto-detection misses a login page, use browser_request_user_action manually
113
- - Be thorough: check multiple sources when comparing prices/products
114
- - Summarize results clearly at the end
115
- - When you learn something about the user (preferences, habits), use memory_store to remember it
116
-
117
- CRITICAL — Ask before you guess:
118
- - Before executing a task, verify you have all required information. If anything is ambiguous or missing, use ask_user to ask.
119
- - First try to resolve unknowns yourself: check memories, read workspace files (e.g. git remote, config files), or infer from conversation history.
120
- - If you still lack a critical piece of information after self-resolution, ASK the user via ask_user. Do NOT guess, assume defaults, or proceed with incomplete information.
121
- - When asking, provide suggested options as buttons whenever possible — the user can always type a custom answer instead.
122
- - Examples of when to ask: which account/repo/project to target, what format the user wants, which of multiple options to choose, credentials or URLs that cannot be inferred.
123
- - Keep questions specific and actionable. Explain what you already know and what exactly you need.
124
- - After receiving the answer, store it with memory_store if it is likely to be useful in future conversations.
125
-
126
- Workspace path: {workspace_path}`;
32
+ import { BASE_SYSTEM_PROMPT } from "./system-prompt.js";
127
33
 
128
34
  const MAX_HISTORY_ENTRIES = 10;
129
35
  const MAX_RESPONSE_LENGTH = 1500;
@@ -131,7 +37,6 @@ const MAX_RESPONSE_LENGTH = 1500;
131
37
  export class TaskProcessor {
132
38
  private memoryManager: MemoryManager | null = null;
133
39
  private skillManager: SkillManager;
134
- private userId: string | null = null;
135
40
  private sessionId: string | null = null;
136
41
  /** In-memory conversation history, keyed by conversation_id */
137
42
  private historyCache: Map<string, HistoryEntry[]> = new Map();
@@ -140,9 +45,8 @@ export class TaskProcessor {
140
45
  this.skillManager = new SkillManager();
141
46
  }
142
47
 
143
- setUserId(userId: string): void {
144
- this.userId = userId;
145
- this.memoryManager = new MemoryManager(userId);
48
+ init(userId: string): void {
49
+ this.memoryManager = new MemoryManager();
146
50
  this.skillManager.setUserId(userId);
147
51
  // Load DB skills asynchronously (non-blocking)
148
52
  this.skillManager.loadFromDb().catch((err) => {
@@ -159,10 +63,7 @@ export class TaskProcessor {
159
63
  * to create/update a skill. The agent already has full context from
160
64
  * the task it just completed — no need to re-describe anything.
161
65
  */
162
- private async evaluateSkillPostTask(
163
- agentSessionId: string,
164
- model: string
165
- ): Promise<void> {
66
+ private async evaluateSkillPostTask(agentSessionId: string, model: string): Promise<void> {
166
67
  await evaluateAndMaybeCreateSkill({
167
68
  sessionId: agentSessionId,
168
69
  skillManager: this.skillManager,
@@ -249,7 +150,6 @@ export class TaskProcessor {
249
150
  skillManager: this.skillManager,
250
151
  taskId: task.id,
251
152
  sessionId: this.sessionId || undefined,
252
- userId: this.userId || undefined,
253
153
  });
254
154
 
255
155
  // Create event hooks for Supabase event emission
@@ -283,6 +183,11 @@ export class TaskProcessor {
283
183
  "mcp__assistme-agent__job_run",
284
184
  "mcp__assistme-agent__job_schedule",
285
185
  "mcp__assistme-agent__job_status",
186
+ // Credential tools (local storage)
187
+ "mcp__assistme-agent__credential_get",
188
+ "mcp__assistme-agent__credential_set",
189
+ "mcp__assistme-agent__credential_list",
190
+ "mcp__assistme-agent__credential_remove",
286
191
  ];
287
192
 
288
193
  // Build async generator for prompt (required for MCP tools)
@@ -385,7 +290,11 @@ export class TaskProcessor {
385
290
 
386
291
  default:
387
292
  // Capture session ID from init message for post-task session resume
388
- if (message.type === "system" && "subtype" in message && (message as Record<string, unknown>).subtype === "init") {
293
+ if (
294
+ message.type === "system" &&
295
+ "subtype" in message &&
296
+ (message as Record<string, unknown>).subtype === "init"
297
+ ) {
389
298
  agentSessionId = (message as Record<string, unknown>).session_id as string;
390
299
  }
391
300
  log.debug(`SDK message type: ${message.type}`);
@@ -416,8 +325,9 @@ export class TaskProcessor {
416
325
 
417
326
  // Post-task: resume the same session to evaluate skill creation (fire-and-forget)
418
327
  if (agentSessionId) {
419
- this.evaluateSkillPostTask(agentSessionId, config.model)
420
- .catch((err) => log.debug(`Post-task skill evaluation skipped: ${err}`));
328
+ this.evaluateSkillPostTask(agentSessionId, config.model).catch((err) =>
329
+ log.debug(`Post-task skill evaluation skipped: ${err}`)
330
+ );
421
331
  }
422
332
  } catch (err) {
423
333
  const errorMsg = err instanceof Error ? err.message : String(err);
@@ -174,7 +174,6 @@ export class Scheduler {
174
174
  // ── CRUD helpers for CLI commands ──────────────────────────────────
175
175
 
176
176
  export async function createScheduledTask(
177
- _userId: string,
178
177
  name: string,
179
178
  prompt: string,
180
179
  cronExpression: string,
@@ -191,7 +190,7 @@ export async function createScheduledTask(
191
190
  });
192
191
  }
193
192
 
194
- export async function listScheduledTasks(_userId: string): Promise<ScheduledTask[]> {
193
+ export async function listScheduledTasks(): Promise<ScheduledTask[]> {
195
194
  return callMcpHandler<ScheduledTask[]>("schedule.list");
196
195
  }
197
196
 
@@ -202,7 +201,7 @@ export async function toggleScheduledTask(taskId: string, enabled: boolean): Pro
202
201
  // Need to recalculate next run when re-enabling
203
202
  const taskData = await callMcpHandler<{ cron_expression: string; timezone: string }>(
204
203
  "schedule.get_task",
205
- { task_id: taskId },
204
+ { task_id: taskId }
206
205
  );
207
206
  if (taskData) {
208
207
  const nextRun = getNextRunTime(taskData.cron_expression, taskData.timezone);
@@ -52,23 +52,13 @@ export class SessionManager {
52
52
  this.onTask = onTask;
53
53
  this.userId = userId;
54
54
 
55
- this.session = await createSession(
56
- userId,
57
- config.sessionName,
58
- config.workspacePath,
59
- "0.1.0"
60
- );
55
+ this.session = await createSession(config.sessionName, config.workspacePath, "0.1.0");
61
56
 
62
57
  // Get or create CLI conversation for this session
63
- this.conversationId = await getOrCreateCliConversation(
64
- userId,
65
- this.session.id
66
- );
58
+ this.conversationId = await getOrCreateCliConversation();
67
59
 
68
60
  this.running = true;
69
- log.success(
70
- `Session started: ${this.session.id} (${config.sessionName})`
71
- );
61
+ log.success(`Session started: ${this.session.id} (${config.sessionName})`);
72
62
  log.info(`Workspace: ${config.workspacePath}`);
73
63
 
74
64
  // Start heartbeat
@@ -107,44 +97,43 @@ export class SessionManager {
107
97
  this.pollTimer = setTimeout(() => this.pollForTasks(), delay);
108
98
  }
109
99
 
110
- private async executeScheduledTask(
111
- scheduledTask: ScheduledTask
112
- ): Promise<void> {
100
+ private async executeScheduledTask(scheduledTask: ScheduledTask): Promise<void> {
113
101
  if (!this.session || !this.userId || !this.conversationId) return;
114
102
 
115
103
  log.info(`Running scheduled task: "${scheduledTask.name}"`);
116
104
 
117
105
  try {
118
- await this.submitTask(
119
- `[Scheduled: ${scheduledTask.name}] ${scheduledTask.prompt}`
120
- );
106
+ await this.submitTask(`[Scheduled: ${scheduledTask.name}] ${scheduledTask.prompt}`);
121
107
  } catch (err) {
122
108
  log.error(`Scheduled task error: ${err}`);
123
109
  }
124
110
  }
125
111
 
126
112
  private async executeJobRun(jobRun: PendingJobRun): Promise<void> {
127
- if (!this.session || !this.userId || !this.conversationId || !this.onTask)
128
- return;
113
+ if (!this.session || !this.userId || !this.conversationId || !this.onTask) return;
129
114
 
130
- log.info(
131
- `Executing job run: ${jobRun.job_name} (${jobRun.id.slice(0, 8)}...)`
132
- );
115
+ log.info(`Executing job run: ${jobRun.job_name} (${jobRun.id.slice(0, 8)}...)`);
133
116
 
134
- const runner = new JobRunner(this.userId);
117
+ const runner = new JobRunner();
135
118
  const job = await runner.loadJob(jobRun.job_name);
136
119
 
137
120
  if (!job) {
138
121
  log.error(`Job "${jobRun.job_name}" not found, marking run as failed`);
139
- try { await runner.completeRun(jobRun.id, "failed", `Job "${jobRun.job_name}" not found`); }
140
- catch { /* already logged inside completeRun */ }
122
+ try {
123
+ await runner.completeRun(jobRun.id, "failed", `Job "${jobRun.job_name}" not found`);
124
+ } catch {
125
+ /* already logged inside completeRun */
126
+ }
141
127
  return;
142
128
  }
143
129
 
144
130
  if (job.skills.length === 0) {
145
131
  log.error(`Job "${jobRun.job_name}" has no skills, marking run as failed`);
146
- try { await runner.completeRun(jobRun.id, "failed", "Job has no linked skills"); }
147
- catch { /* already logged inside completeRun */ }
132
+ try {
133
+ await runner.completeRun(jobRun.id, "failed", "Job has no linked skills");
134
+ } catch {
135
+ /* already logged inside completeRun */
136
+ }
148
137
  return;
149
138
  }
150
139
 
@@ -214,7 +203,7 @@ export class SessionManager {
214
203
  }
215
204
  } else if (this.userId) {
216
205
  // Priority 2: Pending job runs (triggered from web UI)
217
- const jobRun = await pollAndClaimJobRun(this.userId);
206
+ const jobRun = await pollAndClaimJobRun();
218
207
 
219
208
  if (jobRun) {
220
209
  this.processingDepth++;
@@ -267,12 +256,7 @@ export class SessionManager {
267
256
  await setSessionBusy(this.session.id, true);
268
257
 
269
258
  try {
270
- const task = await createTask(
271
- this.conversationId,
272
- this.userId,
273
- this.session.id,
274
- prompt
275
- );
259
+ const task = await createTask(this.conversationId, this.session.id, prompt);
276
260
 
277
261
  // Claim immediately so poll can't grab it even after depth decrements
278
262
  await claimTask(task.id);
@@ -6,18 +6,113 @@ import { callMcpHandler } from "../db/api-client.js";
6
6
 
7
7
  // Common English stop words to filter out of matching
8
8
  const STOP_WORDS = new Set([
9
- "the", "a", "an", "is", "are", "was", "were", "be", "been", "being",
10
- "have", "has", "had", "do", "does", "did", "will", "would", "could",
11
- "should", "may", "might", "shall", "can", "need", "dare", "ought",
12
- "used", "to", "of", "in", "for", "on", "with", "at", "by", "from",
13
- "as", "into", "through", "during", "before", "after", "above", "below",
14
- "between", "out", "off", "over", "under", "again", "further", "then",
15
- "once", "here", "there", "when", "where", "why", "how", "all", "each",
16
- "every", "both", "few", "more", "most", "other", "some", "such", "no",
17
- "nor", "not", "only", "own", "same", "so", "than", "too", "very",
18
- "and", "but", "or", "if", "this", "that", "these", "those", "it",
19
- "its", "i", "me", "my", "we", "our", "you", "your", "he", "him",
20
- "she", "her", "they", "them", "what", "which", "who", "whom",
9
+ "the",
10
+ "a",
11
+ "an",
12
+ "is",
13
+ "are",
14
+ "was",
15
+ "were",
16
+ "be",
17
+ "been",
18
+ "being",
19
+ "have",
20
+ "has",
21
+ "had",
22
+ "do",
23
+ "does",
24
+ "did",
25
+ "will",
26
+ "would",
27
+ "could",
28
+ "should",
29
+ "may",
30
+ "might",
31
+ "shall",
32
+ "can",
33
+ "need",
34
+ "dare",
35
+ "ought",
36
+ "used",
37
+ "to",
38
+ "of",
39
+ "in",
40
+ "for",
41
+ "on",
42
+ "with",
43
+ "at",
44
+ "by",
45
+ "from",
46
+ "as",
47
+ "into",
48
+ "through",
49
+ "during",
50
+ "before",
51
+ "after",
52
+ "above",
53
+ "below",
54
+ "between",
55
+ "out",
56
+ "off",
57
+ "over",
58
+ "under",
59
+ "again",
60
+ "further",
61
+ "then",
62
+ "once",
63
+ "here",
64
+ "there",
65
+ "when",
66
+ "where",
67
+ "why",
68
+ "how",
69
+ "all",
70
+ "each",
71
+ "every",
72
+ "both",
73
+ "few",
74
+ "more",
75
+ "most",
76
+ "other",
77
+ "some",
78
+ "such",
79
+ "no",
80
+ "nor",
81
+ "not",
82
+ "only",
83
+ "own",
84
+ "same",
85
+ "so",
86
+ "than",
87
+ "too",
88
+ "very",
89
+ "and",
90
+ "but",
91
+ "or",
92
+ "if",
93
+ "this",
94
+ "that",
95
+ "these",
96
+ "those",
97
+ "it",
98
+ "its",
99
+ "i",
100
+ "me",
101
+ "my",
102
+ "we",
103
+ "our",
104
+ "you",
105
+ "your",
106
+ "he",
107
+ "him",
108
+ "she",
109
+ "her",
110
+ "they",
111
+ "them",
112
+ "what",
113
+ "which",
114
+ "who",
115
+ "whom",
21
116
  ]);
22
117
 
23
118
  /**
@@ -48,6 +143,13 @@ function bigrams(tokens: string[]): Set<string> {
48
143
 
49
144
  // ── Skill Interfaces ────────────────────────────────────────────────
50
145
 
146
+ export interface SkillCredentialRequirement {
147
+ name: string;
148
+ type: "api_key" | "oauth_token" | "login" | "secret" | "custom";
149
+ description: string;
150
+ required: boolean;
151
+ }
152
+
51
153
  export interface SkillMetadata {
52
154
  requires?: {
53
155
  bins?: string[];
@@ -60,6 +162,7 @@ export interface SkillMetadata {
60
162
  os?: string[];
61
163
  always?: boolean;
62
164
  skillKey?: string;
165
+ credentials?: SkillCredentialRequirement[];
63
166
  }
64
167
 
65
168
  export interface Skill {
@@ -92,6 +195,7 @@ function parseDbMetadata(raw: unknown): SkillMetadata {
92
195
  os: openclaw.os as string[] | undefined,
93
196
  always: openclaw.always as boolean | undefined,
94
197
  skillKey: openclaw.skillKey as string | undefined,
198
+ credentials: openclaw.credentials as SkillCredentialRequirement[] | undefined,
95
199
  };
96
200
  }
97
201
 
@@ -165,7 +269,8 @@ export class SkillManager {
165
269
  const docFreq = new Map<string, number>();
166
270
  const totalSkills = this.skills.size || 1;
167
271
  for (const skill of this.skills.values()) {
168
- const allText = `${skill.name} ${skill.description} ${skill.content} ${skill.keywords.join(" ")}`.toLowerCase();
272
+ const allText =
273
+ `${skill.name} ${skill.description} ${skill.content} ${skill.keywords.join(" ")}`.toLowerCase();
169
274
  const words = new Set(tokenize(allText));
170
275
  for (const w of words) {
171
276
  docFreq.set(w, (docFreq.get(w) || 0) + 1);
@@ -266,8 +371,10 @@ export class SkillManager {
266
371
 
267
372
  let budget = this.DESCRIPTION_BUDGET_CHARS;
268
373
  let prompt = "\n\n## Your Skills\n";
269
- prompt += "These are your approved skills. Use skill_invoke to load full instructions when a task matches.\n";
270
- prompt += "If no skill matches but the task is a reusable pattern, consider creating one with skill_create.\n\n";
374
+ prompt +=
375
+ "These are your approved skills. Use skill_invoke to load full instructions when a task matches.\n";
376
+ prompt +=
377
+ "If no skill matches but the task is a reusable pattern, consider creating one with skill_create.\n\n";
271
378
  let included = 0;
272
379
 
273
380
  for (const skill of skills) {
@@ -287,24 +394,6 @@ export class SkillManager {
287
394
  return prompt;
288
395
  }
289
396
 
290
- /** @deprecated Use buildSkillDescriptions() + skill_invoke tool instead. */
291
- buildSkillPrompt(taskPrompt: string): string {
292
- const relevant = this.findRelevant(taskPrompt);
293
- if (relevant.length === 0) return "";
294
-
295
- let prompt = "\n\n## Available Skills\n";
296
- prompt += "The following skills provide detailed instructions for this type of task:\n\n";
297
-
298
- for (const skill of relevant) {
299
- const emoji = skill.metadata.emoji || "";
300
- prompt += `### ${emoji ? emoji + " " : ""}${skill.name}\n`;
301
- prompt += `*${skill.description}*\n\n`;
302
- prompt += skill.content + "\n\n";
303
- }
304
-
305
- return prompt;
306
- }
307
-
308
397
  async create(
309
398
  name: string,
310
399
  description: string,
@@ -327,7 +416,7 @@ export class SkillManager {
327
416
  emoji: options?.emoji || null,
328
417
  keywords: options?.keywords || [],
329
418
  metadata,
330
- },
419
+ }
331
420
  );
332
421
 
333
422
  const row = (Array.isArray(data) ? data[0] : data) as Record<string, unknown> | null;
@@ -373,13 +462,13 @@ export class SkillManager {
373
462
  try {
374
463
  const result = await callMcpHandler<{ skill: Record<string, unknown>; agent_skill: unknown }>(
375
464
  "skill.fetch_and_add",
376
- { skill_id: skillId },
465
+ { skill_id: skillId }
377
466
  );
378
467
 
379
468
  const row = result.skill;
380
- const agentSkillRow = (result.agent_skill && typeof result.agent_skill === "object"
381
- ? result.agent_skill
382
- : row) as Record<string, unknown>;
469
+ const agentSkillRow = (
470
+ result.agent_skill && typeof result.agent_skill === "object" ? result.agent_skill : row
471
+ ) as Record<string, unknown>;
383
472
  const skill = this.rowToSkill({
384
473
  ...agentSkillRow,
385
474
  name: row.name,
@@ -425,7 +514,10 @@ export class SkillManager {
425
514
  const normalizedName = name.toLowerCase().replace(/[^a-z0-9]/g, "");
426
515
  for (const [existingName, skill] of this.skills) {
427
516
  const normalizedExisting = existingName.toLowerCase().replace(/[^a-z0-9]/g, "");
428
- if (normalizedName.includes(normalizedExisting) || normalizedExisting.includes(normalizedName)) {
517
+ if (
518
+ normalizedName.includes(normalizedExisting) ||
519
+ normalizedExisting.includes(normalizedName)
520
+ ) {
429
521
  return skill;
430
522
  }
431
523
  const nameWords = new Set(name.split("-"));
@@ -546,16 +638,24 @@ export class SkillManager {
546
638
  }
547
639
  }
548
640
 
549
- async searchDb(query: string, limit = 10): Promise<Array<{
550
- name: string;
551
- description: string;
552
- emoji: string;
553
- source: string;
554
- invocationCount: number;
555
- }>> {
641
+ async searchDb(
642
+ query: string,
643
+ limit = 10
644
+ ): Promise<
645
+ Array<{
646
+ name: string;
647
+ description: string;
648
+ emoji: string;
649
+ source: string;
650
+ invocationCount: number;
651
+ }>
652
+ > {
556
653
  if (this.userId) {
557
654
  try {
558
- const data = await callMcpHandler<Record<string, unknown>[]>("skill.search", { query, limit });
655
+ const data = await callMcpHandler<Record<string, unknown>[]>("skill.search", {
656
+ query,
657
+ limit,
658
+ });
559
659
  if (data) {
560
660
  return data.map((row) => ({
561
661
  name: row.name as string,
@@ -565,7 +665,9 @@ export class SkillManager {
565
665
  invocationCount: (row.invocation_count as number) || 0,
566
666
  }));
567
667
  }
568
- } catch { /* Fall through to in-memory search */ }
668
+ } catch {
669
+ /* Fall through to in-memory search */
670
+ }
569
671
  }
570
672
 
571
673
  const results = this.findRelevant(query, limit);
@@ -582,7 +684,9 @@ export class SkillManager {
582
684
  if (!this.userId) return;
583
685
  try {
584
686
  await callMcpHandler("skill.remove", { name });
585
- } catch { /* Non-critical */ }
687
+ } catch {
688
+ /* Non-critical */
689
+ }
586
690
  }
587
691
 
588
692
  // ── Marketplace ────────────────────────────────────────────────────
@@ -630,18 +734,20 @@ export class SkillManager {
630
734
  sort?: "popular" | "recent" | "rating";
631
735
  limit?: number;
632
736
  offset?: number;
633
- }): Promise<Array<{
634
- id: string;
635
- name: string;
636
- description: string;
637
- emoji: string;
638
- version: string;
639
- authorName: string;
640
- category: string;
641
- installCount: number;
642
- avgRating: number | null;
643
- ratingCount: number;
644
- }>> {
737
+ }): Promise<
738
+ Array<{
739
+ id: string;
740
+ name: string;
741
+ description: string;
742
+ emoji: string;
743
+ version: string;
744
+ authorName: string;
745
+ category: string;
746
+ installCount: number;
747
+ avgRating: number | null;
748
+ ratingCount: number;
749
+ }>
750
+ > {
645
751
  try {
646
752
  const data = await callMcpHandler<Record<string, unknown>[]>("skill.browse", {
647
753
  query: options?.query || null,