Some checks failed
CI / build-and-test (push) Failing after -31m24s
* Implement ContentEditorField for inline editing of content * Create ContentEditorModal for editing content in a modal * Introduce FormerShell for managing forms related to skills and thoughts * Enhance SkillsPage and ThoughtsPage with new components for better content management
241 lines
8.8 KiB
Go
241 lines
8.8 KiB
Go
package tools
|
|
|
|
// import (
|
|
// "context"
|
|
// "errors"
|
|
// "fmt"
|
|
// "strings"
|
|
// "time"
|
|
|
|
// "github.com/google/uuid"
|
|
// "github.com/jackc/pgx/v5"
|
|
// "github.com/modelcontextprotocol/go-sdk/mcp"
|
|
|
|
// "git.warky.dev/wdevs/amcs/internal/store"
|
|
// ext "git.warky.dev/wdevs/amcs/internal/types"
|
|
// )
|
|
|
|
// type CRMTool struct {
|
|
// store *store.DB
|
|
// }
|
|
|
|
// func NewCRMTool(db *store.DB) *CRMTool {
|
|
// return &CRMTool{store: db}
|
|
// }
|
|
|
|
// // add_professional_contact
|
|
|
|
// type AddContactInput struct {
|
|
// Name string `json:"name" jsonschema:"contact's full name"`
|
|
// Company string `json:"company,omitempty"`
|
|
// Title string `json:"title,omitempty" jsonschema:"job title"`
|
|
// Email string `json:"email,omitempty"`
|
|
// Phone string `json:"phone,omitempty"`
|
|
// LinkedInURL string `json:"linkedin_url,omitempty"`
|
|
// HowWeMet string `json:"how_we_met,omitempty"`
|
|
// Tags []string `json:"tags,omitempty"`
|
|
// Notes string `json:"notes,omitempty"`
|
|
// FollowUpDate *time.Time `json:"follow_up_date,omitempty"`
|
|
// }
|
|
|
|
// type AddContactOutput struct {
|
|
// Contact ext.ProfessionalContact `json:"contact"`
|
|
// }
|
|
|
|
// func (t *CRMTool) AddContact(ctx context.Context, _ *mcp.CallToolRequest, in AddContactInput) (*mcp.CallToolResult, AddContactOutput, error) {
|
|
// if strings.TrimSpace(in.Name) == "" {
|
|
// return nil, AddContactOutput{}, errRequiredField("name")
|
|
// }
|
|
// if in.Tags == nil {
|
|
// in.Tags = []string{}
|
|
// }
|
|
// contact, err := t.store.AddProfessionalContact(ctx, ext.ProfessionalContact{
|
|
// Name: strings.TrimSpace(in.Name),
|
|
// Company: strings.TrimSpace(in.Company),
|
|
// Title: strings.TrimSpace(in.Title),
|
|
// Email: strings.TrimSpace(in.Email),
|
|
// Phone: strings.TrimSpace(in.Phone),
|
|
// LinkedInURL: strings.TrimSpace(in.LinkedInURL),
|
|
// HowWeMet: strings.TrimSpace(in.HowWeMet),
|
|
// Tags: in.Tags,
|
|
// Notes: strings.TrimSpace(in.Notes),
|
|
// FollowUpDate: in.FollowUpDate,
|
|
// })
|
|
// if err != nil {
|
|
// return nil, AddContactOutput{}, err
|
|
// }
|
|
// return nil, AddContactOutput{Contact: contact}, nil
|
|
// }
|
|
|
|
// // search_contacts
|
|
|
|
// type SearchContactsInput struct {
|
|
// Query string `json:"query,omitempty" jsonschema:"search text matching name, company, title, or notes"`
|
|
// Tags []string `json:"tags,omitempty" jsonschema:"filter by tags (all must match)"`
|
|
// }
|
|
|
|
// type SearchContactsOutput struct {
|
|
// Contacts []ext.ProfessionalContact `json:"contacts"`
|
|
// }
|
|
|
|
// func (t *CRMTool) SearchContacts(ctx context.Context, _ *mcp.CallToolRequest, in SearchContactsInput) (*mcp.CallToolResult, SearchContactsOutput, error) {
|
|
// contacts, err := t.store.SearchContacts(ctx, in.Query, in.Tags)
|
|
// if err != nil {
|
|
// return nil, SearchContactsOutput{}, err
|
|
// }
|
|
// if contacts == nil {
|
|
// contacts = []ext.ProfessionalContact{}
|
|
// }
|
|
// return nil, SearchContactsOutput{Contacts: contacts}, nil
|
|
// }
|
|
|
|
// // log_interaction
|
|
|
|
// type LogInteractionInput struct {
|
|
// ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"`
|
|
// InteractionType string `json:"interaction_type" jsonschema:"one of: meeting, email, call, coffee, event, linkedin, other"`
|
|
// OccurredAt *time.Time `json:"occurred_at,omitempty" jsonschema:"when it happened (defaults to now)"`
|
|
// Summary string `json:"summary" jsonschema:"summary of the interaction"`
|
|
// FollowUpNeeded bool `json:"follow_up_needed,omitempty"`
|
|
// FollowUpNotes string `json:"follow_up_notes,omitempty"`
|
|
// }
|
|
|
|
// type LogInteractionOutput struct {
|
|
// Interaction ext.ContactInteraction `json:"interaction"`
|
|
// }
|
|
|
|
// func (t *CRMTool) LogInteraction(ctx context.Context, _ *mcp.CallToolRequest, in LogInteractionInput) (*mcp.CallToolResult, LogInteractionOutput, error) {
|
|
// if strings.TrimSpace(in.Summary) == "" {
|
|
// return nil, LogInteractionOutput{}, errRequiredField("summary")
|
|
// }
|
|
// occurredAt := time.Now()
|
|
// if in.OccurredAt != nil {
|
|
// occurredAt = *in.OccurredAt
|
|
// }
|
|
// interaction, err := t.store.LogInteraction(ctx, ext.ContactInteraction{
|
|
// ContactID: in.ContactID,
|
|
// InteractionType: in.InteractionType,
|
|
// OccurredAt: occurredAt,
|
|
// Summary: strings.TrimSpace(in.Summary),
|
|
// FollowUpNeeded: in.FollowUpNeeded,
|
|
// FollowUpNotes: strings.TrimSpace(in.FollowUpNotes),
|
|
// })
|
|
// if err != nil {
|
|
// return nil, LogInteractionOutput{}, err
|
|
// }
|
|
// return nil, LogInteractionOutput{Interaction: interaction}, nil
|
|
// }
|
|
|
|
// // get_contact_history
|
|
|
|
// type GetContactHistoryInput struct {
|
|
// ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"`
|
|
// }
|
|
|
|
// type GetContactHistoryOutput struct {
|
|
// History ext.ContactHistory `json:"history"`
|
|
// }
|
|
|
|
// func (t *CRMTool) GetHistory(ctx context.Context, _ *mcp.CallToolRequest, in GetContactHistoryInput) (*mcp.CallToolResult, GetContactHistoryOutput, error) {
|
|
// history, err := t.store.GetContactHistory(ctx, in.ContactID)
|
|
// if err != nil {
|
|
// return nil, GetContactHistoryOutput{}, err
|
|
// }
|
|
// return nil, GetContactHistoryOutput{History: history}, nil
|
|
// }
|
|
|
|
// // create_opportunity
|
|
|
|
// type CreateOpportunityInput struct {
|
|
// ContactID *uuid.UUID `json:"contact_id,omitempty"`
|
|
// Title string `json:"title" jsonschema:"opportunity title"`
|
|
// Description string `json:"description,omitempty"`
|
|
// Stage string `json:"stage,omitempty" jsonschema:"one of: identified, in_conversation, proposal, negotiation, won, lost (default: identified)"`
|
|
// Value *float64 `json:"value,omitempty" jsonschema:"monetary value"`
|
|
// ExpectedCloseDate *time.Time `json:"expected_close_date,omitempty"`
|
|
// Notes string `json:"notes,omitempty"`
|
|
// }
|
|
|
|
// type CreateOpportunityOutput struct {
|
|
// Opportunity ext.Opportunity `json:"opportunity"`
|
|
// }
|
|
|
|
// func (t *CRMTool) CreateOpportunity(ctx context.Context, _ *mcp.CallToolRequest, in CreateOpportunityInput) (*mcp.CallToolResult, CreateOpportunityOutput, error) {
|
|
// if strings.TrimSpace(in.Title) == "" {
|
|
// return nil, CreateOpportunityOutput{}, errRequiredField("title")
|
|
// }
|
|
// stage := strings.TrimSpace(in.Stage)
|
|
// if stage == "" {
|
|
// stage = "identified"
|
|
// }
|
|
// opp, err := t.store.CreateOpportunity(ctx, ext.Opportunity{
|
|
// ContactID: in.ContactID,
|
|
// Title: strings.TrimSpace(in.Title),
|
|
// Description: strings.TrimSpace(in.Description),
|
|
// Stage: stage,
|
|
// Value: in.Value,
|
|
// ExpectedCloseDate: in.ExpectedCloseDate,
|
|
// Notes: strings.TrimSpace(in.Notes),
|
|
// })
|
|
// if err != nil {
|
|
// return nil, CreateOpportunityOutput{}, err
|
|
// }
|
|
// return nil, CreateOpportunityOutput{Opportunity: opp}, nil
|
|
// }
|
|
|
|
// // get_follow_ups_due
|
|
|
|
// type GetFollowUpsDueInput struct {
|
|
// DaysAhead int `json:"days_ahead,omitempty" jsonschema:"look ahead window in days (default: 7)"`
|
|
// }
|
|
|
|
// type GetFollowUpsDueOutput struct {
|
|
// Contacts []ext.ProfessionalContact `json:"contacts"`
|
|
// }
|
|
|
|
// func (t *CRMTool) GetFollowUpsDue(ctx context.Context, _ *mcp.CallToolRequest, in GetFollowUpsDueInput) (*mcp.CallToolResult, GetFollowUpsDueOutput, error) {
|
|
// contacts, err := t.store.GetFollowUpsDue(ctx, in.DaysAhead)
|
|
// if err != nil {
|
|
// return nil, GetFollowUpsDueOutput{}, err
|
|
// }
|
|
// if contacts == nil {
|
|
// contacts = []ext.ProfessionalContact{}
|
|
// }
|
|
// return nil, GetFollowUpsDueOutput{Contacts: contacts}, nil
|
|
// }
|
|
|
|
// // link_thought_to_contact
|
|
|
|
// type LinkThoughtToContactInput struct {
|
|
// ContactID uuid.UUID `json:"contact_id" jsonschema:"id of the contact"`
|
|
// ThoughtID uuid.UUID `json:"thought_id" jsonschema:"id of the thought to link"`
|
|
// }
|
|
|
|
// type LinkThoughtToContactOutput struct {
|
|
// Contact ext.ProfessionalContact `json:"contact"`
|
|
// }
|
|
|
|
// func (t *CRMTool) LinkThought(ctx context.Context, _ *mcp.CallToolRequest, in LinkThoughtToContactInput) (*mcp.CallToolResult, LinkThoughtToContactOutput, error) {
|
|
// thought, err := t.store.GetThought(ctx, in.ThoughtID)
|
|
// if err != nil {
|
|
// if errors.Is(err, pgx.ErrNoRows) {
|
|
// return nil, LinkThoughtToContactOutput{}, errEntityNotFound("thought", "thought_id", in.ThoughtID.String())
|
|
// }
|
|
// return nil, LinkThoughtToContactOutput{}, err
|
|
// }
|
|
|
|
// appendText := fmt.Sprintf("\n\n[Linked thought %s]: %s", thought.ID, thought.Content)
|
|
// if err := t.store.AppendThoughtToContactNotes(ctx, in.ContactID, appendText); err != nil {
|
|
// return nil, LinkThoughtToContactOutput{}, err
|
|
// }
|
|
|
|
// contact, err := t.store.GetContact(ctx, in.ContactID)
|
|
// if err != nil {
|
|
// if errors.Is(err, pgx.ErrNoRows) {
|
|
// return nil, LinkThoughtToContactOutput{}, errEntityNotFound("contact", "contact_id", in.ContactID.String())
|
|
// }
|
|
// return nil, LinkThoughtToContactOutput{}, err
|
|
// }
|
|
// return nil, LinkThoughtToContactOutput{Contact: contact}, nil
|
|
// }
|