AgentSkillsCN

pfAssign

为当前 PlanFlow 项目中的某位团队成员分配一项任务

SKILL.md
--- frontmatter
name: pfAssign
description: Assign a task to a team member in the current PlanFlow project

PlanFlow Task Assignment

Assign a task to a team member or yourself in the linked cloud project.

Usage

bash
/pfAssign <task-id> <email|me>        # Assign task to team member or self
/pfAssign T2.1 jane@company.com       # Assign to specific member
/pfAssign T2.1 me                     # Assign to yourself

Step 0: Load Configuration

javascript
function getConfig() {
  const localConfigPath = "./.plan-config.json"
  let localConfig = {}
  if (fileExists(localConfigPath)) {
    try { localConfig = JSON.parse(readFile(localConfigPath)) } catch {}
  }

  const globalConfigPath = expandPath("~/.config/claude/plan-plugin-config.json")
  let globalConfig = {}
  if (fileExists(globalConfigPath)) {
    try { globalConfig = JSON.parse(readFile(globalConfigPath)) } catch {}
  }

  // Merge configs: local overrides global, cloud sections are merged
  return {
    ...globalConfig,
    ...localConfig,
    cloud: {
      ...(globalConfig.cloud || {}),
      ...(localConfig.cloud || {})
    }
  }
}

const config = getConfig()
const language = config.language || "en"
const cloudConfig = config.cloud || {}
const isAuthenticated = !!cloudConfig.apiToken
const projectId = cloudConfig.projectId
const apiUrl = cloudConfig.apiUrl || "https://api.planflow.tools"
const userEmail = cloudConfig.userEmail

const t = JSON.parse(readFile(`locales/${language}.json`))

Step 1: Parse Arguments

Parse the command arguments to extract task ID and assignee.

Pseudo-code:

javascript
const args = commandArgs.trim().split(/\s+/)

if (args.length < 2) {
  // Show usage
  showUsage(t)
  return
}

const taskId = args[0].toUpperCase()  // e.g., T2.1
let assignee = args[1]

// Validate task ID format
const taskIdRegex = /^T\d+\.\d+$/
if (!taskIdRegex.test(taskId)) {
  console.log(`${t.common.error} ${t.commands.assign.invalidTaskId}`)
  console.log(t.commands.assign.taskIdExample)
  return
}

// Handle "me" keyword
if (assignee.toLowerCase() === "me") {
  assignee = userEmail
}

// Validate email format (unless it was "me")
const emailRegex = /^[^\s@]+@[^\s@]+\.[^\s@]+$/
if (!emailRegex.test(assignee)) {
  console.log(`${t.common.error} ${t.commands.assign.invalidEmail}`)
  console.log(t.commands.assign.emailExample)
  return
}

Show Usage:

code
📋 {t.commands.assign.title}

{t.commands.assign.usage}
  /pfAssign <task-id> <email>    {t.commands.assign.usageEmail}
  /pfAssign <task-id> me         {t.commands.assign.usageMe}

{t.commands.assign.example}
  /pfAssign T2.1 jane@company.com
  /pfAssign T2.1 me

Step 2: Validate Authentication

If not authenticated:

code
{t.common.error} {t.commands.sync.notAuthenticated}

Run: /pfLogin

Step 3: Validate Project Link

If no project is linked:

code
{t.common.error} {t.commands.sync.notLinked}

Run: /pfCloudLink <project-id>

Step 4: Assign Task

API Call:

bash
curl -s -w "\n%{http_code}" \
  --connect-timeout 5 \
  --max-time 10 \
  -X PATCH \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer {TOKEN}" \
  -d '{"assignee": "{EMAIL}"}' \
  "https://api.planflow.tools/projects/{PROJECT_ID}/tasks/{TASK_ID}/assign"

Expected Success Response (200):

json
{
  "success": true,
  "data": {
    "task": {
      "id": "uuid",
      "taskId": "T2.1",
      "name": "Implement login API",
      "status": "TODO",
      "assignee": {
        "id": "uuid",
        "name": "Jane Smith",
        "email": "jane@company.com"
      },
      "assignedAt": "2026-02-02T12:00:00Z",
      "assignedBy": {
        "id": "uuid",
        "name": "John Doe",
        "email": "john@company.com"
      }
    },
    "projectName": "My Project"
  }
}

Step 5: Display Success

Pseudo-code:

javascript
let output = ""
output += `${t.commands.assign.success}\n\n`
output += `   ${t.commands.assign.task}     ${task.taskId}: ${task.name}\n`
output += `   ${t.commands.assign.assignedTo} ${task.assignee.name} (${task.assignee.email})\n`
output += `   ${t.commands.assign.project}  ${projectName}\n\n`

if (assignee === userEmail) {
  output += `${t.commands.assign.selfAssignHint}\n`
  output += `   /planUpdate ${task.taskId} start\n`
} else {
  output += `${t.commands.assign.notifyHint}\n`
}

Example Output (English):

code
Task assigned!

   Task:        T2.1: Implement login API
   Assigned to: Jane Smith (jane@company.com)
   Project:     Planflow Plugin

They'll be notified of this assignment.

Example Output (Georgian):

code
ამოცანა მინიჭებულია!

   ამოცანა:      T2.1: Implement login API
   მინიჭებულია:  Jane Smith (jane@company.com)
   პროექტი:      Planflow Plugin

მათ მიიღებენ შეტყობინებას ამ მინიჭების შესახებ.

Self-Assignment Output (English):

code
Task assigned!

   Task:        T2.1: Implement login API
   Assigned to: You (john@company.com)
   Project:     Planflow Plugin

Ready to start working? Run:
   /planUpdate T2.1 start

Step 6: Update Local PROJECT_PLAN.md (Optional)

If the task exists in the local PROJECT_PLAN.md, optionally update it with the assignee info.

Pseudo-code:

javascript
// Check if PROJECT_PLAN.md exists
if (fileExists("PROJECT_PLAN.md")) {
  const planContent = readFile("PROJECT_PLAN.md")

  // Find the task section
  const taskPattern = new RegExp(`#### ${taskId}:`)
  if (taskPattern.test(planContent)) {
    // Add or update assignee line after status
    // This is optional - the cloud is the source of truth for assignments
  }
}

Error Handling

Invalid Task ID Format

code
{t.common.error} {t.commands.assign.invalidTaskId}

{t.commands.assign.taskIdExample}

Invalid Email Format

code
{t.common.error} {t.commands.assign.invalidEmail}

{t.commands.assign.emailExample}

Task Not Found (404)

code
{t.common.error} {t.commands.assign.taskNotFound}

Task ID: {taskId}

{t.commands.assign.checkTaskId}

User Not Team Member (404)

code
{t.common.error} {t.commands.assign.userNotMember}

{email} {t.commands.assign.notMemberHint}

{t.commands.assign.inviteFirst}
  /pfTeamInvite {email}

Permission Denied (403)

code
{t.common.error} {t.commands.assign.noPermission}

{t.commands.assign.noPermissionHint}

Already Assigned (409)

code
{t.common.warning} {t.commands.assign.alreadyAssigned}

Task {taskId} is already assigned to {currentAssignee}.

{t.commands.assign.reassignHint}
  /pfUnassign {taskId}
  /pfAssign {taskId} {newEmail}

Network Error

code
{t.common.error} {t.commands.team.networkError}

{t.commands.assign.tryAgain}

API Error (401 Unauthorized)

code
{t.common.error} {t.commands.team.authFailed}

Run: /pfLogin

Bash Implementation

Full Implementation:

bash
#!/bin/bash

# Load config (Claude will read from config files)
API_URL="https://api.planflow.tools"
TOKEN="$API_TOKEN"
PROJECT_ID="$PROJECT_ID"
USER_EMAIL="$USER_EMAIL"
TASK_ID="$1"
ASSIGNEE="$2"

# Validate task ID format
if ! echo "$TASK_ID" | grep -qE '^T[0-9]+\.[0-9]+$'; then
  echo " Error: Invalid task ID format"
  echo ""
  echo "Task ID should be like: T1.1, T2.3, T10.5"
  exit 1
fi

# Handle "me" keyword
if [ "$(echo "$ASSIGNEE" | tr '[:upper:]' '[:lower:]')" = "me" ]; then
  ASSIGNEE="$USER_EMAIL"
fi

# Validate email format
if ! echo "$ASSIGNEE" | grep -qE '^[^[:space:]@]+@[^[:space:]@]+\.[^[:space:]@]+$'; then
  echo " Error: Invalid email format"
  echo ""
  echo "Example: jane@company.com or use 'me'"
  exit 1
fi

# Assign task
RESPONSE=$(curl -s -w "\n%{http_code}" \
  --connect-timeout 5 \
  --max-time 10 \
  -X PATCH \
  -H "Content-Type: application/json" \
  -H "Accept: application/json" \
  -H "Authorization: Bearer $TOKEN" \
  -d "{\"assignee\": \"$ASSIGNEE\"}" \
  "${API_URL}/projects/${PROJECT_ID}/tasks/${TASK_ID}/assign")

HTTP_CODE=$(echo "$RESPONSE" | tail -n1)
BODY=$(echo "$RESPONSE" | sed '$d')

case $HTTP_CODE in
  200)
    # Parse response
    TASK_NAME=$(echo "$BODY" | grep -o '"name":"[^"]*"' | head -1 | cut -d'"' -f4)
    ASSIGNEE_NAME=$(echo "$BODY" | grep -o '"assignee":{[^}]*"name":"[^"]*"' | grep -o '"name":"[^"]*"' | cut -d'"' -f4)

    echo " Task assigned!"
    echo ""
    echo "   Task:        $TASK_ID: $TASK_NAME"
    echo "   Assigned to: $ASSIGNEE_NAME ($ASSIGNEE)"
    echo ""

    if [ "$ASSIGNEE" = "$USER_EMAIL" ]; then
      echo " Ready to start working? Run:"
      echo "   /planUpdate $TASK_ID start"
    else
      echo " They'll be notified of this assignment."
    fi
    ;;
  404)
    # Check if task or user not found
    if echo "$BODY" | grep -q "task"; then
      echo " Task not found: $TASK_ID"
      echo ""
      echo "Make sure the task exists in the cloud project."
      echo "Run /pfSyncPush to sync your local tasks first."
    else
      echo " User not found: $ASSIGNEE"
      echo ""
      echo "This user is not a team member."
      echo "Invite them first:"
      echo "   /pfTeamInvite $ASSIGNEE"
    fi
    ;;
  409)
    CURRENT_ASSIGNEE=$(echo "$BODY" | grep -o '"email":"[^"]*"' | head -1 | cut -d'"' -f4)
    echo " Task $TASK_ID is already assigned to $CURRENT_ASSIGNEE"
    echo ""
    echo "To reassign, first unassign:"
    echo "   /pfUnassign $TASK_ID"
    echo "   /pfAssign $TASK_ID $ASSIGNEE"
    ;;
  403)
    echo " You don't have permission to assign tasks."
    echo ""
    echo "Only editors and above can assign tasks."
    ;;
  401)
    echo " Authentication failed. Your session may have expired."
    echo ""
    echo "Run: /pfLogin"
    ;;
  *)
    echo " Failed to assign task (HTTP $HTTP_CODE)"
    echo ""
    echo "Please try again later."
    ;;
esac

Translation Keys Required

Add these to locales/en.json under commands.assign:

English:

json
{
  "commands": {
    "assign": {
      "title": "Task Assignment",
      "usage": "Usage:",
      "usageEmail": "Assign to team member by email",
      "usageMe": "Assign to yourself",
      "example": "Examples:",
      "success": "Task assigned!",
      "task": "Task:",
      "assignedTo": "Assigned to:",
      "project": "Project:",
      "selfAssignHint": "Ready to start working? Run:",
      "notifyHint": "They'll be notified of this assignment.",
      "invalidTaskId": "Invalid task ID format.",
      "taskIdExample": "Task ID should be like: T1.1, T2.3, T10.5",
      "invalidEmail": "Invalid email format.",
      "emailExample": "Example: jane@company.com or use 'me'",
      "taskNotFound": "Task not found.",
      "checkTaskId": "Make sure the task exists. Run /pfSyncPush to sync your local tasks.",
      "userNotMember": "User is not a team member.",
      "notMemberHint": "is not part of this project.",
      "inviteFirst": "Invite them first:",
      "noPermission": "You don't have permission to assign tasks.",
      "noPermissionHint": "Only editors and above can assign tasks.",
      "alreadyAssigned": "Task is already assigned.",
      "reassignHint": "To reassign:",
      "tryAgain": "Please check your connection and try again."
    }
  }
}

Georgian:

json
{
  "commands": {
    "assign": {
      "title": "ამოცანის მინიჭება",
      "usage": "გამოყენება:",
      "usageEmail": "მინიჭება გუნდის წევრისთვის ელ-ფოსტით",
      "usageMe": "მინიჭება საკუთარ თავზე",
      "example": "მაგალითები:",
      "success": "ამოცანა მინიჭებულია!",
      "task": "ამოცანა:",
      "assignedTo": "მინიჭებულია:",
      "project": "პროექტი:",
      "selfAssignHint": "მზად ხართ მუშაობის დასაწყებად? გაუშვით:",
      "notifyHint": "მათ მიიღებენ შეტყობინებას ამ მინიჭების შესახებ.",
      "invalidTaskId": "არასწორი ამოცანის ID ფორმატი.",
      "taskIdExample": "ამოცანის ID უნდა იყოს: T1.1, T2.3, T10.5",
      "invalidEmail": "არასწორი ელ-ფოსტის ფორმატი.",
      "emailExample": "მაგალითი: jane@company.com ან 'me'",
      "taskNotFound": "ამოცანა ვერ მოიძებნა.",
      "checkTaskId": "დარწმუნდით რომ ამოცანა არსებობს. გაუშვით /pfSyncPush ლოკალური ამოცანების სასინქრონიზაციოდ.",
      "userNotMember": "მომხმარებელი არ არის გუნდის წევრი.",
      "notMemberHint": "არ არის ამ პროექტის ნაწილი.",
      "inviteFirst": "ჯერ მოიწვიეთ:",
      "noPermission": "თქვენ არ გაქვთ უფლება ამოცანების მინიჭების.",
      "noPermissionHint": "მხოლოდ რედაქტორებს და ზემოთ შეუძლიათ ამოცანების მინიჭება.",
      "alreadyAssigned": "ამოცანა უკვე მინიჭებულია.",
      "reassignHint": "ხელახლა მინიჭებისთვის:",
      "tryAgain": "გთხოვთ შეამოწმოთ კავშირი და სცადოთ ხელახლა."
    }
  }
}

Notes

  • The "me" keyword is case-insensitive (Me, ME, me all work)
  • Task IDs are case-insensitive but will be normalized to uppercase (t2.1 → T2.1)
  • Only team members can be assigned tasks (use /pfTeamInvite first)
  • A task can only have one assignee at a time
  • To change assignee, use /pfUnassign first then /pfAssign
  • Assignment notifications are sent via email (if enabled in user settings)
  • Assigning to yourself shows a prompt to start working on the task