Files
amcs/internal/tools/crm.go
Hein 9e6d05e055
Some checks failed
CI / build-and-test (push) Failing after -31m24s
feat(ui): add content editor components for skills and thoughts
* 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
2026-05-02 19:35:27 +02:00

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
// }