assistme 0.2.8 → 0.3.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.
@@ -0,0 +1,7 @@
1
+ import {
2
+ JobRunner
3
+ } from "./chunk-UWE5WVQI.js";
4
+ import "./chunk-TTEGHE2E.js";
5
+ export {
6
+ JobRunner
7
+ };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "assistme",
3
- "version": "0.2.8",
3
+ "version": "0.3.0",
4
4
  "description": "AssistMe CLI Agent - AI-powered assistant that controls your real browser",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -20,7 +20,6 @@
20
20
  },
21
21
  "dependencies": {
22
22
  "@anthropic-ai/claude-agent-sdk": "^0.2.63",
23
- "@supabase/supabase-js": "^2.76.0",
24
23
  "zod": "^4.0.0",
25
24
  "chalk": "^5.4.1",
26
25
  "commander": "^13.1.0",
@@ -1,4 +1,4 @@
1
- import { getSupabase } from "../db/supabase.js";
1
+ import { callMcpHandler } from "../db/api-client.js";
2
2
  import { log } from "../utils/logger.js";
3
3
 
4
4
  // ── Interfaces ─────────────────────────────────────────────────────
@@ -31,10 +31,8 @@ export interface JobRunInfo {
31
31
  // ── JobRunner ──────────────────────────────────────────────────────
32
32
 
33
33
  export class JobRunner {
34
- private userId: string;
35
-
36
- constructor(userId: string) {
37
- this.userId = userId;
34
+ constructor(_userId: string) {
35
+ // userId is no longer needed — auth is handled by the MCP token in callMcpHandler
38
36
  }
39
37
 
40
38
  /**
@@ -43,13 +41,12 @@ export class JobRunner {
43
41
  */
44
42
  async loadJob(jobName: string): Promise<JobInfo | null> {
45
43
  try {
46
- const sb = getSupabase();
47
- const { data, error } = await sb.rpc("get_job_with_skills", {
48
- p_user_id: this.userId,
49
- p_job_name: jobName,
50
- });
44
+ const data = await callMcpHandler<Array<Record<string, unknown>> | null>(
45
+ "job.get_with_skills",
46
+ { job_name: jobName },
47
+ );
51
48
 
52
- if (error || !data || (data as unknown[]).length === 0) {
49
+ if (!data || (data as unknown[]).length === 0) {
53
50
  return null;
54
51
  }
55
52
 
@@ -79,33 +76,16 @@ export class JobRunner {
79
76
  */
80
77
  async listJobs(): Promise<Array<{ id: string; name: string; description: string; skillCount: number }>> {
81
78
  try {
82
- const sb = getSupabase();
83
-
84
- const { data: jobs, error } = await sb
85
- .from("agent_jobs")
86
- .select("id, name, description")
87
- .eq("user_id", this.userId)
88
- .eq("is_active", true)
89
- .order("name");
90
-
91
- if (error || !jobs) return [];
92
-
93
- const result: Array<{ id: string; name: string; description: string; skillCount: number }> = [];
94
- for (const job of jobs) {
95
- const { count } = await sb
96
- .from("agent_job_skills")
97
- .select("id", { count: "exact", head: true })
98
- .eq("job_id", job.id);
99
-
100
- result.push({
101
- id: job.id,
102
- name: job.name,
103
- description: job.description,
104
- skillCount: count || 0,
105
- });
106
- }
107
-
108
- return result;
79
+ const data = await callMcpHandler<Array<Record<string, unknown>>>(
80
+ "job.list",
81
+ );
82
+
83
+ return (data || []).map((row) => ({
84
+ id: row.id as string,
85
+ name: row.name as string,
86
+ description: row.description as string,
87
+ skillCount: (row.skill_count as number) || 0,
88
+ }));
109
89
  } catch (err) {
110
90
  log.debug(`Failed to list jobs: ${err}`);
111
91
  return [];
@@ -122,21 +102,13 @@ export class JobRunner {
122
102
  options?: { sessionId?: string; messageId?: string; triggerType?: string }
123
103
  ): Promise<string | null> {
124
104
  try {
125
- const sb = getSupabase();
126
- const { data, error } = await sb.rpc("create_job_run", {
127
- p_user_id: this.userId,
128
- p_job_id: jobId,
129
- p_session_id: options?.sessionId || null,
130
- p_message_id: options?.messageId || null,
131
- p_trigger_type: options?.triggerType || "manual",
105
+ const data = await callMcpHandler<string>("job.create_run", {
106
+ job_id: jobId,
107
+ session_id: options?.sessionId || null,
108
+ message_id: options?.messageId || null,
109
+ trigger_type: options?.triggerType || "manual",
132
110
  });
133
-
134
- if (error) {
135
- log.debug(`Failed to create job run: ${error.message}`);
136
- return null;
137
- }
138
-
139
- return data as string;
111
+ return data;
140
112
  } catch (err) {
141
113
  log.debug(`Job run creation error: ${err}`);
142
114
  return null;
@@ -152,17 +124,11 @@ export class JobRunner {
152
124
  status: "completed" | "failed" | "cancelled" = "completed",
153
125
  summary?: string
154
126
  ): Promise<void> {
155
- const sb = getSupabase();
156
- const { error } = await sb.rpc("complete_job_run", {
157
- p_run_id: runId,
158
- p_status: status,
159
- p_summary: summary?.slice(0, 10000) || null,
160
- p_user_id: this.userId,
127
+ await callMcpHandler("job.complete_run", {
128
+ run_id: runId,
129
+ status,
130
+ summary: summary?.slice(0, 10000) || null,
161
131
  });
162
- if (error) {
163
- log.error(`Failed to mark run ${runId} as ${status}: ${error.message}`);
164
- throw new Error(`Run completion failed: ${error.message}`);
165
- }
166
132
  }
167
133
 
168
134
  /**
@@ -170,16 +136,12 @@ export class JobRunner {
170
136
  */
171
137
  async getRunHistory(jobName?: string, limit = 10): Promise<JobRunInfo[]> {
172
138
  try {
173
- const sb = getSupabase();
174
- const { data, error } = await sb.rpc("get_job_runs", {
175
- p_user_id: this.userId,
176
- p_job_name: jobName || null,
177
- p_limit: limit,
178
- });
179
-
180
- if (error || !data) return [];
139
+ const data = await callMcpHandler<Array<Record<string, unknown>>>(
140
+ "job.get_runs",
141
+ { job_name: jobName || null, limit },
142
+ );
181
143
 
182
- return (data as Array<Record<string, unknown>>).map((row) => ({
144
+ return (data || []).map((row) => ({
183
145
  runId: row.run_id as string,
184
146
  jobName: row.job_name as string,
185
147
  status: row.status as string,
@@ -8,9 +8,10 @@ import { executeTool } from "../tools/index.js";
8
8
  import { getLimiterForTool } from "../utils/rate-limiter.js";
9
9
  import { log } from "../utils/logger.js";
10
10
  import type { MemoryManager, MemoryCategory } from "./memory.js";
11
- import type { SkillManager, SkillVariable } from "./skills.js";
12
- import { substituteArguments, substituteVariables, preprocessDynamicContext } from "./skills.js";
13
- import { getSupabase, emitEvent, setActionRequest, pollActionResponse } from "../db/supabase.js";
11
+ import type { SkillManager } from "./skills.js";
12
+ import { substituteArguments, preprocessDynamicContext, validateSkillName } from "./skills.js";
13
+ import { emitEvent, setActionRequest, pollActionResponse } from "../db/supabase.js";
14
+ import { callMcpHandler } from "../db/api-client.js";
14
15
  import { JobRunner } from "./job-runner.js";
15
16
  import {
16
17
  createScheduledTask,
@@ -214,28 +215,25 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
214
215
  ),
215
216
  tool(
216
217
  "skill_create",
217
- "Create a new skill and add it to the user's collection. Returns the skill ID on success. " +
218
- "Use {{variable_name}} syntax in instructions for user-specific data (e.g. {{github_repos}}, {{slack_channel}}).",
218
+ "Create a new skill and add it to the user's collection. Returns the skill ID on success.",
219
219
  {
220
220
  name: z.string().describe("Skill name in kebab-case, e.g. 'flight-booking'"),
221
221
  description: z.string().describe("One-line description of what this skill does"),
222
- instructions: z.string().describe(
223
- "Markdown step-by-step instructions. Use {{variable_name}} placeholders for user-specific data " +
224
- "(e.g. {{github_repos}}, {{trello_board}}, {{slack_channel}}). These will be resolved with user's config at invoke time."
225
- ),
222
+ instructions: z.string().describe("Markdown step-by-step instructions"),
226
223
  emoji: z.string().optional().describe("Single emoji representing this skill"),
227
- variables: z.array(z.object({
228
- name: z.string().describe("Variable name matching {{name}} in instructions"),
229
- description: z.string().describe("Human-readable description of what this variable is for"),
230
- type: z.enum(["string", "string[]", "number", "boolean"]).describe("Data type"),
231
- required: z.boolean().describe("Whether the skill needs this to function"),
232
- default: z.string().optional().describe("Default value if user doesn't configure"),
233
- example: z.string().optional().describe("Example value to guide the user"),
234
- })).optional().describe(
235
- "Variables that need user-specific configuration. Define one for each {{variable}} used in instructions."
236
- ),
237
224
  },
238
225
  async (args) => {
226
+ // Validate skill name format
227
+ const nameError = validateSkillName(args.name);
228
+ if (nameError) {
229
+ return {
230
+ content: [{
231
+ type: "text",
232
+ text: `Invalid skill name: ${nameError}. Use lowercase kebab-case like "flight-booking".`,
233
+ }],
234
+ };
235
+ }
236
+
239
237
  // Check for duplicates in user's collection
240
238
  const existing = skillManager.findSimilar(args.name);
241
239
  if (existing) {
@@ -254,11 +252,7 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
254
252
  args.name,
255
253
  args.description,
256
254
  args.instructions,
257
- {
258
- source: "manual",
259
- emoji: args.emoji,
260
- variables: args.variables as SkillVariable[] | undefined,
261
- }
255
+ { source: "manual", emoji: args.emoji }
262
256
  );
263
257
 
264
258
  if (!result) {
@@ -277,27 +271,17 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
277
271
  source: "manual",
278
272
  emoji: args.emoji,
279
273
  sourceSkillId: result.id,
280
- variables: args.variables as SkillVariable[] | undefined,
281
274
  }
282
275
  );
283
276
 
284
- // Build response with variable info
285
- let responseText = `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`;
286
- if (args.variables && args.variables.length > 0) {
287
- const requiredVars = args.variables.filter((v) => v.required);
288
- if (requiredVars.length > 0) {
289
- responseText += `\n\n**Variables that need configuration:**\n`;
290
- for (const v of requiredVars) {
291
- const example = v.example ? ` (e.g. ${v.example})` : "";
292
- responseText += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
293
- }
294
- responseText += `\nUse \`skill_configure\` to set these values for the user, or ask the user to provide them.`;
295
- }
296
- }
297
-
298
277
  log.success(`Skill "${args.name}" created and added to collection`);
299
278
  return {
300
- content: [{ type: "text", text: responseText }],
279
+ content: [
280
+ {
281
+ type: "text",
282
+ text: `Skill "${args.name}" created and added to your collection (ID: ${result.id}).`,
283
+ },
284
+ ],
301
285
  };
302
286
  }
303
287
  ),
@@ -382,9 +366,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
382
366
 
383
367
  let content = skill.content;
384
368
 
385
- // Substitute {{variable}} placeholders with user config values
386
- content = substituteVariables(content, skill.config, skill.variables);
387
-
388
369
  // Substitute $ARGUMENTS placeholders
389
370
  if (args.arguments) {
390
371
  content = substituteArguments(content, args.arguments);
@@ -405,16 +386,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
405
386
  response += `\n\n**Allowed tools for this skill:** ${skill.allowedTools.join(", ")}\n`;
406
387
  }
407
388
 
408
- // Warn about unconfigured required variables
409
- const unconfigured = skillManager.getUnconfiguredVariables(args.name);
410
- if (unconfigured.length > 0) {
411
- response += `\n\n**⚠ Unconfigured variables — use \`skill_configure\` to set these:**\n`;
412
- for (const v of unconfigured) {
413
- const example = v.example ? ` (e.g. ${v.example})` : "";
414
- response += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
415
- }
416
- }
417
-
418
389
  log.info(`Skill invoked: "${args.name}"`);
419
390
 
420
391
  // Log invocation to DB (fire-and-forget)
@@ -457,56 +428,6 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
457
428
  return { content: [{ type: "text", text: response }] };
458
429
  }
459
430
  ),
460
- tool(
461
- "skill_configure",
462
- "Set user-specific variable values for a skill. Variables are defined in the skill template (e.g. {{github_repos}}) " +
463
- "and need to be configured with the user's actual data before the skill can work properly.",
464
- {
465
- name: z.string().describe("Name of the skill to configure"),
466
- config: z.record(z.unknown()).describe(
467
- "Key-value pairs of variable values. Keys must match variable names defined in the skill. " +
468
- "Example: {\"github_repos\": [\"octocat/hello-world\", \"myorg/myrepo\"], \"github_username\": \"octocat\"}"
469
- ),
470
- },
471
- async (args) => {
472
- const skill = skillManager.get(args.name);
473
- if (!skill) {
474
- const available = skillManager.getAll().map((s) => s.name).join(", ");
475
- return {
476
- content: [{
477
- type: "text",
478
- text: `Skill "${args.name}" not found. Available: ${available}`,
479
- }],
480
- };
481
- }
482
-
483
- const updated = await skillManager.updateConfig(args.name, args.config);
484
- if (!updated) {
485
- return {
486
- content: [{ type: "text", text: `Failed to update config for "${args.name}".` }],
487
- };
488
- }
489
-
490
- // Check if there are still unconfigured required variables
491
- const unconfigured = skillManager.getUnconfiguredVariables(args.name);
492
- let responseText = `Configuration updated for skill "${args.name}".`;
493
-
494
- if (unconfigured.length > 0) {
495
- responseText += `\n\n**Still needs configuration:**\n`;
496
- for (const v of unconfigured) {
497
- const example = v.example ? ` (e.g. ${v.example})` : "";
498
- responseText += `- \`{{${v.name}}}\`: ${v.description}${example}\n`;
499
- }
500
- } else {
501
- responseText += ` All required variables are configured.`;
502
- }
503
-
504
- log.info(`Config updated for skill "${args.name}": ${Object.keys(args.config).join(", ")}`);
505
- return {
506
- content: [{ type: "text", text: responseText }],
507
- };
508
- }
509
- ),
510
431
  tool(
511
432
  "skill_generate",
512
433
  "Prepare context for generating skills from a job description. Returns existing skills and job info " +
@@ -542,39 +463,15 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
542
463
  response += `- name: kebab-case name (e.g. "slack-message-check")\n`;
543
464
  response += `- description: one-line description\n`;
544
465
  response += `- instructions: detailed step-by-step markdown instructions the agent can follow\n`;
545
- response += `- emoji: a single emoji representing the skill\n`;
546
- response += `- variables: define user-specific data needed by the skill (see below)\n\n`;
466
+ response += `- emoji: a single emoji representing the skill\n\n`;
547
467
  response += `skill_create automatically adds the skill to the user's collection — no need to call skill_add.\n\n`;
548
468
  response += `After ALL skills are created, call \`skill_link_job\` with job_name="${args.job_name}" and the list of created skill names to link them and mark the job as analyzed.\n\n`;
549
469
  response += `**Guidelines for skill instructions:**\n`;
550
470
  response += `- Write clear, actionable markdown steps\n`;
551
471
  response += `- Reference browser tools (browser_navigate, browser_click, browser_read_page, etc.) for web tasks\n`;
552
472
  response += `- Include error handling steps\n`;
553
- response += `- Each skill should be a single, well-defined workflow (10-25 steps)\n\n`;
554
- response += `**IMPORTANT Use {{variables}} for user-specific data:**\n`;
555
- response += `Skills often need user-specific information (accounts, repos, boards, channels, etc.).\n`;
556
- response += `Instead of hardcoding or asking at runtime, use \`{{variable_name}}\` placeholders in instructions.\n\n`;
557
- response += `Example: A GitHub PR review skill should use \`{{github_repos}}\` in its instructions:\n`;
558
- response += ` "Navigate to each repository in {{github_repos}} and check for open PRs..."\n\n`;
559
- response += `For each \`{{variable}}\` used in instructions, add a matching entry in the \`variables\` array:\n`;
560
- response += `\`\`\`json\n`;
561
- response += `{\n`;
562
- response += ` "name": "github_repos",\n`;
563
- response += ` "description": "GitHub repositories to monitor (owner/repo format)",\n`;
564
- response += ` "type": "string[]",\n`;
565
- response += ` "required": true,\n`;
566
- response += ` "example": "octocat/hello-world, myorg/myrepo"\n`;
567
- response += `}\n`;
568
- response += `\`\`\`\n\n`;
569
- response += `Common variables by platform:\n`;
570
- response += `- GitHub: github_repos, github_username, github_org\n`;
571
- response += `- Trello: trello_board_url, trello_list_names\n`;
572
- response += `- Slack: slack_channels, slack_workspace_url\n`;
573
- response += `- Email: email_labels, email_filters\n`;
574
- response += `- E-commerce: store_url, competitor_urls, product_categories\n`;
575
- response += `- General: timezone, language, notification_channel\n\n`;
576
- response += `After creating skills, if any have required variables, call \`skill_configure\` to set initial values `;
577
- response += `(ask the user for the values first).\n`;
473
+ response += `- Use placeholders like {query}, {date} for variable inputs\n`;
474
+ response += `- Each skill should be a single, well-defined workflow (10-25 steps)\n`;
578
475
 
579
476
  return { content: [{ type: "text", text: response }] };
580
477
  }
@@ -719,7 +616,78 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
719
616
  }
720
617
  ),
721
618
 
722
- // ── User Confirmation Tool ─────────────────────────────────
619
+ // ── User Interaction Tools ──────────────────────────────────
620
+
621
+ tool(
622
+ "request_user_input",
623
+ "Ask the user a clarifying question and wait for their free-text response. " +
624
+ "Use this when you need information that cannot be inferred from context, memory, or the workspace — " +
625
+ "e.g. which account to use, specific preferences, ambiguous instructions, or missing parameters for a skill. " +
626
+ "Do NOT use this for information you can discover yourself (git remote, file contents, etc.).",
627
+ {
628
+ question: z.string().describe("The question to ask the user (supports markdown). Be specific about what you need and why."),
629
+ placeholder: z.string().optional().describe("Placeholder text for the input field (e.g. 'https://github.com/owner/repo')"),
630
+ timeout_seconds: z.number().optional().describe("How long to wait for response (default: 300)"),
631
+ },
632
+ async (args) => {
633
+ const actionId = `input_${Date.now()}_${Math.random().toString(36).slice(2, 8)}`;
634
+ const timeout = (args.timeout_seconds || 300) * 1000;
635
+
636
+ const actionData = {
637
+ id: actionId,
638
+ type: "input",
639
+ message: args.question,
640
+ placeholder: args.placeholder || "",
641
+ created_at: new Date().toISOString(),
642
+ };
643
+
644
+ try {
645
+ await setActionRequest(taskId, actionData);
646
+ log.info(`Input request ${actionId}: "${args.question.slice(0, 80)}..."`);
647
+
648
+ emitEvent(taskId, "user_action_request", actionData).catch(() => {});
649
+
650
+ const startTime = Date.now();
651
+ const pollInterval = 2000;
652
+
653
+ while (Date.now() - startTime < timeout) {
654
+ const response = await pollActionResponse(taskId);
655
+ // Match response to this specific request by action_id
656
+ if (response && (!response.action_id || response.action_id === actionId)) {
657
+ const text = (response.text || response.value || "") as string;
658
+ log.info(`User input received: "${text.slice(0, 80)}"`);
659
+ return {
660
+ content: [{
661
+ type: "text",
662
+ text: JSON.stringify({ status: "responded", text }),
663
+ }],
664
+ };
665
+ }
666
+
667
+ await new Promise((resolve) => setTimeout(resolve, pollInterval));
668
+ }
669
+
670
+ log.warn(`Input request ${actionId} timed out`);
671
+ return {
672
+ content: [{
673
+ type: "text",
674
+ text: JSON.stringify({
675
+ status: "timeout",
676
+ message: "User did not respond within the timeout period.",
677
+ }),
678
+ }],
679
+ };
680
+ } catch (err) {
681
+ log.error(`request_user_input failed: ${err}`);
682
+ return {
683
+ content: [{
684
+ type: "text",
685
+ text: `Failed to request user input: ${err instanceof Error ? err.message : err}`,
686
+ }],
687
+ };
688
+ }
689
+ }
690
+ ),
723
691
 
724
692
  tool(
725
693
  "request_user_confirmation",
@@ -761,7 +729,8 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
761
729
 
762
730
  while (Date.now() - startTime < timeout) {
763
731
  const response = await pollActionResponse(taskId);
764
- if (response) {
732
+ // Match response to this specific request by action_id
733
+ if (response && (!response.action_id || response.action_id === actionId)) {
765
734
  const actionKey = (response.action_key || response.action || "") as string;
766
735
  const label = (response.label || actionKey) as string;
767
736
  log.info(`User responded: ${label} (${actionKey})`);
@@ -916,11 +885,10 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
916
885
  const task = await createScheduledTask(userId, name, prompt, args.cron, tz);
917
886
 
918
887
  // Link the job_id to the scheduled task
919
- const sb = getSupabase();
920
- await sb
921
- .from("agent_scheduled_tasks")
922
- .update({ job_id: job.jobId })
923
- .eq("id", task.id);
888
+ await callMcpHandler("schedule.link_job", {
889
+ task_id: task.id,
890
+ job_id: job.jobId,
891
+ });
924
892
 
925
893
  const nextRun = task.next_run_at
926
894
  ? new Date(task.next_run_at).toLocaleString()
@@ -1034,27 +1002,19 @@ export function createAgentToolsServer(deps: AgentToolsDeps): McpSdkServerConfig
1034
1002
  // ── Helper: persist job and link skills ────────────────────────────
1035
1003
 
1036
1004
  async function saveJobToDb(
1037
- userId: string,
1005
+ _userId: string,
1038
1006
  jobName: string,
1039
1007
  jobDescription: string,
1040
1008
  createdSkillNames: string[]
1041
1009
  ): Promise<void> {
1042
1010
  try {
1043
- const sb = getSupabase();
1044
-
1045
- const { data, error } = await sb.rpc("save_job_with_skills", {
1046
- p_user_id: userId,
1047
- p_job_name: jobName,
1048
- p_job_description: jobDescription,
1049
- p_skill_names: createdSkillNames,
1011
+ const data = await callMcpHandler("job.save_with_skills", {
1012
+ job_name: jobName,
1013
+ job_description: jobDescription,
1014
+ skill_names: createdSkillNames,
1050
1015
  });
1051
1016
 
1052
- if (error) {
1053
- log.debug(`Failed to save job "${jobName}" via RPC: ${error.message}`);
1054
- return;
1055
- }
1056
-
1057
- log.debug(`Job "${jobName}" saved via RPC (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
1017
+ log.debug(`Job "${jobName}" saved via edge function (id: ${data}), ${createdSkillNames.length} skill(s) linked`);
1058
1018
  } catch (err) {
1059
1019
  log.debug(`saveJobToDb error: ${err}`);
1060
1020
  }