Token导航 LogoToken导航TokenDH.com
开发敏感数据github未标认证来源可访问许可证需确认审计提醒

m365-workflowsm365 工作流程

Agent Skill

m365-workflows 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。可结合来源仓库、安装命令和原始 README 继续核验具体用法。安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。

总安装

346

周安装

14

GitHub Stars

公开资料未说明

下载量

109
CodexClaudeCursorGemini CLI

安装说明

本站只整理中文说明和来源信息,不托管安装包,也不代用户安装。

GitHub

来源数

2

许可证

unknown

最后核验

2026-05-01

来源状态

来源可访问

安装方式

通过对话安装

复制提示词发给支持本地命令或 Skills 的 AI 助手,先确认命令和权限,再让它执行。

请帮我安装这个 Agent Skill:m365-workflows(m365 工作流程)
来源仓库:https://github.com/parandurume-labs/conductor
仓库路径:skills/m365-workflows
安装命令:
npx skills add https://github.com/parandurume-labs/conductor --skill m365-workflows
安装前请先检查当前环境是否支持对应 CLI,并向我确认将要执行的命令、安装目录、联网范围和文件读写权限;确认后再执行。

命令行安装

复制命令到本机终端执行。该命令会通过 npx skills 从第三方来源获取 Skill;本站只展示命令,不托管安装包,也不自动执行。

skills.shnpx skills
npx skills add https://github.com/parandurume-labs/conductor --skill m365-workflows

简介

m365-workflows 用于处理 GitHub 仓库、Issue、Pull Request 和代码协作信息,适合在 Codex、Claude、Cursor、Gemini CLI 中需要围绕仓库状态、代码变更或协作事项进行整理时使用。

  • 它可结合来源仓库、安装命令和原始 README 继续核验具体用法。
  • 安装前建议确认权限范围、维护状态,以及是否会触发联网、命令执行或文件读写。
  • 安装命令为 npx skills add https://github.com/parandurume-labs/conductor --skill m365-workflows。
  • 当前分类为开发,适用于 Codex、Claude、Cursor、Gemini CLI。

SKILL.md

Microsoft 365 Workflow Patterns

This skill provides rules for building reliable Microsoft 365 integrations with Teams, SharePoint, and Outlook. Rules are ordered by impact.


Learned Patterns (Auto-Updated)

Before applying the rules below, check if LESSONS.md exists in the project root. If it does, read the section tagged with m365-workflows and apply those project-specific lessons alongside the rules below.


MCP Server Configuration

When using the M365 MCP server, configure the endpoint:

Server URL: ms365-mcp.delightfulbeach-79de09ed.koreacentral.azurecontainerapps.io/mcp

Category 1: SharePoint Integration (CRITICAL)

Rule 1: Use Compound Site ID Format

Impact: CRITICAL — Simple site IDs fail in Graph API calls. The compound format is required.

Wrong:

// Using site ID alone — this will fail
const siteId = "a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const response = await graphClient
  .api(`/sites/${siteId}/lists`)
  .get();

Correct:

// Compound site ID: hostname:/path:/siteId
const siteId = "contoso.sharepoint.com:/sites/project-alpha:/a1b2c3d4-e5f6-7890-abcd-ef1234567890";
const response = await graphClient
  .api(`/sites/${siteId}/lists`)
  .get();

Format: {hostname}:/{server-relative-path}:/{site-id}

Rule 2: SharePoint Internal Field Names

Impact: CRITICAL — Display names and internal names are different. Using display names causes silent failures.

Display NameInternal NameType
TitleTitleText
CreatedCreatedDateTime
Created ByAuthorPerson
ModifiedModifiedDateTime
Modified ByEditorPerson
File NameFileLeafRefText
File SizeFileSizeDisplayText
Content TypeContentTypeIdContentType
IDIDCounter
Checked Out ToCheckoutUserPerson

Wrong:

await graphClient
  .api(`/sites/${siteId}/lists/${listId}/items`)
  .select("Created By, Modified By, File Name")
  .get();

Correct:

await graphClient
  .api(`/sites/${siteId}/lists/${listId}/items`)
  .expand("fields($select=Author,Editor,FileLeafRef)")
  .get();

Rule 3: Handle Large Document Libraries with Pagination

Impact: HIGH — SharePoint returns max 200 items per page by default. Missing pagination means missing data.

Wrong:

// Only gets first page (up to 200 items)
const items = await graphClient
  .api(`/sites/${siteId}/lists/${listId}/items`)
  .get();

Correct:

let allItems = [];
let response = await graphClient
  .api(`/sites/${siteId}/lists/${listId}/items`)
  .top(200)
  .get();

allItems.push(...response.value);

while (response["@odata.nextLink"]) {
  response = await graphClient
    .api(response["@odata.nextLink"])
    .get();
  allItems.push(...response.value);
}

Category 2: Teams Channels & Messages (CRITICAL)

Rule 4: Channel IDs in Environment Variables

Impact: CRITICAL — Hardcoded channel IDs break when channels are recreated or when moving between environments.

Wrong:

const channelId = "19:abc123def456@thread.tacv2";  // Hardcoded
await graphClient
  .api(`/teams/${teamId}/channels/${channelId}/messages`)
  .post(message);

Correct:

const channelId = process.env.TEAMS_CHANNEL_ID;
if (!channelId) throw new Error("TEAMS_CHANNEL_ID environment variable is required");

await graphClient
  .api(`/teams/${teamId}/channels/${channelId}/messages`)
  .post(message);

Rule 5: Teams Message Size Limit

Impact: HIGH — Messages over 28KB are silently truncated or rejected.

Wrong:

// Dumping entire report as a Teams message
const message = { body: { content: entireReport } };  // Could be 100KB+

Correct:

// Summary in message, full report as file attachment or link
const message = {
  body: {
    contentType: "html",
    content: `<p><strong>Report Summary</strong></p>
              <p>${summary}</p>
              <p><a href="${reportUrl}">View full report</a></p>`
  }
};

Rule 6: Use HTML Content Type for Rich Messages

Impact: MEDIUM — Plain text messages cannot include formatting, links, or mentions.

Wrong:

const message = {
  body: {
    contentType: "text",
    content: "Build #42 passed. See results at https://..."
  }
};

Correct:

const message = {
  body: {
    contentType: "html",
    content: `<p>Build <strong>#42</strong> passed ✅</p>
              <p><a href="https://...">View results</a></p>`
  }
};

Category 3: Adaptive Cards (HIGH)

Rule 7: Adaptive Card 28KB Size Limit

Impact: HIGH — Cards exceeding 28KB fail to render with no useful error message.

Wrong:

{
  "type": "AdaptiveCard",
  "body": [
    {
      "type": "TextBlock",
      "text": "... (embedding 50KB of data in the card)"
    }
  ]
}

Correct:

{
  "type": "AdaptiveCard",
  "version": "1.5",
  "body": [
    {
      "type": "TextBlock",
      "text": "Report Summary",
      "weight": "Bolder",
      "size": "Medium"
    },
    {
      "type": "TextBlock",
      "text": "Top 5 items shown. Click below for full details.",
      "wrap": true
    }
  ],
  "actions": [
    {
      "type": "Action.OpenUrl",
      "title": "View Full Report",
      "url": "https://..."
    }
  ]
}

Best practice: Keep card payload under 20KB; use links for detailed data.

Rule 8: Always Set Card Version

Impact: MEDIUM — Missing version defaults to 1.0, which lacks many features.

Wrong:

{
  "type": "AdaptiveCard",
  "body": [...]
}

Correct:

{
  "type": "AdaptiveCard",
  "version": "1.5",
  "$schema": "http://adaptivecards.io/schemas/adaptive-card.json",
  "body": [...]
}

Rule 9: Wrap Long Text in Cards

Impact: MEDIUM — Text without wrap: true is clipped on mobile devices.

Wrong:

{ "type": "TextBlock", "text": "This is a very long description that..." }

Correct:

{ "type": "TextBlock", "text": "This is a very long description that...", "wrap": true }

Category 4: Time & Locale (HIGH)

Rule 10: Default to Korea Standard Time

Impact: HIGH — Scheduled events, notifications, and timestamps are wrong if timezone is not set explicitly.

Wrong:

// No timezone specified — defaults to UTC
const event = {
  start: { dateTime: "2025-03-15T09:00:00" },
  end: { dateTime: "2025-03-15T10:00:00" }
};

Correct:

const event = {
  start: { dateTime: "2025-03-15T09:00:00", timeZone: "Korea Standard Time" },
  end: { dateTime: "2025-03-15T10:00:00", timeZone: "Korea Standard Time" }
};

Note: Microsoft Graph uses Windows timezone IDs (Korea Standard Time), not IANA (Asia/Seoul).

Rule 11: Date Format for Korean Locale

Impact: MEDIUM — Korean users expect YYYY년 MM월 DD일 format.

Wrong:

const dateStr = date.toLocaleDateString("en-US"); // "3/15/2025"

Correct:

const dateStr = date.toLocaleDateString("ko-KR", {
  year: "numeric",
  month: "long",
  day: "numeric"
}); // "2025년 3월 15일"

Category 5: Environment Variable Conventions

All M365-related environment variables should follow this naming convention:

VariablePurposeExample
M365_TENANT_IDAzure AD tenant IDa1b2c3d4-...
M365_CLIENT_IDApp registration client IDe5f6a7b8-...
TEAMS_TEAM_IDTarget team ID19:abc...@thread.tacv2
TEAMS_CHANNEL_IDTarget channel ID19:def...@thread.tacv2
TEAMS_WEBHOOK_URLIncoming webhook URLhttps://...webhook.office.com/...
SHAREPOINT_SITE_IDCompound site IDcontoso.sharepoint.com:/sites/...
SHAREPOINT_LIST_IDTarget list/library IDa1b2c3d4-...
GRAPH_API_SCOPEGraph API permission scopehttps://graph.microsoft.com/.default

Rules:

  • Never hardcode any of these values
  • Use .env files for local development (add .env to .gitignore)
  • Use Azure Key Vault or platform secrets for production
  • Validate all required variables at startup — fail fast with clear error messages

Category 6: Microsoft Graph Advanced (HIGH)

Rule 12: Graph API Batch Requests

Impact: HIGH — Making individual Graph API calls in a loop causes throttling (HTTP 429) and poor performance.

Wrong:

// N+1 problem — one API call per user
const users = ["user1@contoso.com", "user2@contoso.com", "user3@contoso.com"];
for (const email of users) {
  const profile = await graphClient.api(`/users/${email}`).get();
  results.push(profile);
}

Correct:

// Batch up to 20 requests in a single call
const batchContent = {
  requests: users.map((email, i) => ({
    id: String(i + 1),
    method: "GET",
    url: `/users/${email}?$select=displayName,mail,department`
  }))
};

const batchResponse = await graphClient.api("/$batch").post(batchContent);

for (const response of batchResponse.responses) {
  if (response.status === 200) {
    results.push(response.body);
  } else if (response.status === 429) {
    // Individual request throttled — retry after delay
    const retryAfter = response.headers["Retry-After"] || 5;
    await delay(retryAfter * 1000);
    // Retry individual request
  }
}

Why: Graph API batch endpoint accepts up to 20 requests per batch. Each request in the batch can succeed or fail independently. Always handle per-request errors, especially 429 (throttling).

Rule 13: Teams Tab SSO Authentication

Impact: HIGH — Prompting users to log in separately in a Teams tab creates friction and confusion.

Wrong:

// Redirecting to a login page inside Teams tab
window.location.href = "/login?redirect=/tab";

Correct:

import * as microsoftTeams from "@microsoft/teams-js";

async function getToken() {
  await microsoftTeams.app.initialize();

  try {
    // Silent SSO — no user interaction needed
    const token = await microsoftTeams.authentication.getAuthToken();

    // Exchange Teams token for your API token (server-side)
    const apiToken = await fetch("/api/auth/teams-exchange", {
      method: "POST",
      headers: { "Content-Type": "application/json" },
      body: JSON.stringify({ teamsToken: token })
    }).then(r => r.json());

    return apiToken.accessToken;
  } catch (error) {
    // Fallback: interactive consent popup
    if (error.message === "resourceRequiresConsent") {
      const token = await microsoftTeams.authentication.authenticate({
        url: `${window.location.origin}/auth-start`,
        width: 600,
        height: 535
      });
      return token;
    }
    throw error;
  }
}
# Server-side: exchange Teams token using On-Behalf-Of flow
from msal import ConfidentialClientApplication

app = ConfidentialClientApplication(
    client_id=os.environ["M365_CLIENT_ID"],
    client_credential=os.environ["M365_CLIENT_SECRET"],
    authority=f"https://login.microsoftonline.com/{os.environ['M365_TENANT_ID']}"
)

result = app.acquire_token_on_behalf_of(
    user_assertion=teams_token,
    scopes=["User.Read", "Mail.Read"]
)

Why: Teams SSO provides a seamless experience — the user is already authenticated in Teams. Use the On-Behalf-Of (OBO) flow server-side to exchange the Teams token for Graph API permissions.

Rule 14: SharePoint Framework (SPFx) Web Part Patterns

Impact: HIGH — Custom SPFx web parts with incorrect patterns cause rendering failures and memory leaks.

Wrong:

// Direct DOM manipulation in SPFx
export default class MyWebPart extends BaseClientSideWebPart<IMyProps> {
  public render(): void {
    this.domElement.innerHTML = `
      <div>${this.properties.title}</div>
      <div id="content"></div>
    `;
    // Direct fetch without error handling
    fetch("/api/data").then(r => r.json()).then(data => {
      document.getElementById("content")!.innerHTML = data.html;
    });
  }
}

Correct:

import * as React from "react";
import * as ReactDom from "react-dom";
import { BaseClientSideWebPart } from "@microsoft/sp-webpart-base";
import { SPHttpClient } from "@microsoft/sp-http";

export default class MyWebPart extends BaseClientSideWebPart<IMyProps> {
  public render(): void {
    const element = React.createElement(MyComponent, {
      title: this.properties.title,
      spHttpClient: this.context.spHttpClient,
      siteUrl: this.context.pageContext.web.absoluteUrl,
      displayMode: this.displayMode
    });
    ReactDom.render(element, this.domElement);
  }

  protected onDispose(): void {
    ReactDom.unmountComponentAtNode(this.domElement);  // Prevent memory leaks
  }
}

Why: Use React for SPFx rendering (not direct DOM manipulation). Always unmount components in onDispose(). Use SPHttpClient for authenticated SharePoint API calls — it handles token management automatically.


Category 7: Outlook & Planner Integration (MEDIUM)

Rule 15: Outlook Actionable Messages

Impact: MEDIUM — Sending plain notifications when actionable cards are available misses engagement opportunities.

Wrong:

# Plain text email notification
send_email(
    to="manager@contoso.com",
    subject="Expense Report Pending Approval",
    body="Employee Kim submitted an expense report for ₩150,000. Please approve in the system."
)

Correct:

# Actionable Message with Adaptive Card (MessageCard format is deprecated)
card_payload = {
    "type": "AdaptiveCard",
    "$schema": "https://adaptivecards.io/schemas/adaptive-card.json",
    "version": "1.5",
    "body": [
        {
            "type": "TextBlock",
            "text": "경비 보고서 승인 요청",
            "weight": "bolder",
            "size": "medium"
        },
        {
            "type": "FactSet",
            "facts": [
                {"title": "제출자", "value": "김철수"},
                {"title": "금액", "value": "₩150,000"},
                {"title": "카테고리", "value": "출장비"},
                {"title": "날짜", "value": "2026년 3월 31일"}
            ]
        }
    ],
    "actions": [
        {
            "type": "Action.Http",
            "title": "승인",
            "method": "POST",
            "url": "https://api.contoso.com/expenses/123/approve",
            "body": "{\"action\": \"approve\", \"expenseId\": \"123\"}",
            "headers": [{"name": "Content-Type", "value": "application/json"}]
        },
        {
            "type": "Action.Http",
            "title": "반려",
            "method": "POST",
            "url": "https://api.contoso.com/expenses/123/reject",
            "body": "{\"action\": \"reject\", \"expenseId\": \"123\"}"
        },
        {
            "type": "Action.OpenUrl",
            "title": "상세 보기",
            "url": "https://expenses.contoso.com/123"
        }
    ]
}

send_email(
    to="manager@contoso.com",
    subject="경비 보고서 승인 요청 - 김철수 (₩150,000)",
    body="<html><body>경비 보고서가 제출되었습니다.</body></html>",
    actionable_card=card_payload
)

Why: Actionable Messages let users approve, reject, or respond directly from Outlook without opening another app. Register your service at https://aka.ms/publishactionablecard for production use.

Rule 16: OneDrive File Operations via Graph

Impact: MEDIUM — Using SharePoint API for simple file operations is unnecessarily complex.

Wrong:

// Using SharePoint REST API for file upload
await fetch(`${siteUrl}/_api/web/GetFolderByServerRelativeUrl('/Documents')/Files/add(url='report.pdf',overwrite=true)`, {
  method: "POST",
  headers: { "Authorization": `Bearer ${token}` },
  body: fileContent
});

Correct:

// Upload file to OneDrive/SharePoint via Graph API
// Small files (< 4MB) — simple upload
await graphClient
  .api(`/drives/${driveId}/items/root:/Reports/report.pdf:/content`)
  .put(fileContent);

// Large files (> 4MB) — resumable upload session
const session = await graphClient
  .api(`/drives/${driveId}/items/root:/Reports/large-report.pdf:/createUploadSession`)
  .post({
    item: {
      "@microsoft.graph.conflictBehavior": "rename",
      name: "large-report.pdf"
    }
  });

// Upload in chunks (recommended: 5-10MB chunks)
const CHUNK_SIZE = 5 * 1024 * 1024;
for (let offset = 0; offset < fileSize; offset += CHUNK_SIZE) {
  const chunk = fileContent.slice(offset, offset + CHUNK_SIZE);
  const end = Math.min(offset + CHUNK_SIZE, fileSize) - 1;
  await fetch(session.uploadUrl, {
    method: "PUT",
    headers: {
      "Content-Range": `bytes ${offset}-${end}/${fileSize}`,
      "Content-Length": chunk.length
    },
    body: chunk
  });
}

Why: Graph API's drive endpoint works for both OneDrive and SharePoint document libraries. Use simple upload for files < 4MB, resumable upload for larger files. The upload session URL is pre-authenticated — no Bearer token needed.

Rule 17: Planner Task Management via Graph

Impact: MEDIUM — Manual task tracking when Graph API can automate Planner integration.

Correct:

// Create a Planner task
const task = await graphClient.api("/planner/tasks").post({
  planId: planId,
  bucketId: bucketId,
  title: "리뷰: 3월 경비 보고서",
  assignments: {
    [assigneeId]: {
      "@odata.type": "#microsoft.graph.plannerAssignment",
      orderHint: " !"
    }
  },
  dueDateTime: "2026-04-07T09:00:00Z",
  priority: 3  // 1=Urgent, 3=Important, 5=Medium, 9=Low
});

// Update task details (description, checklist)
await graphClient
  .api(`/planner/tasks/${task.id}/details`)
  .header("If-Match", task["@odata.etag"])
  .patch({
    description: "3월 팀 경비 보고서를 검토하고 승인해 주세요.",
    checklist: {
      [guid()]: {
        title: "금액 확인",
        isChecked: false
      },
      [guid()]: {
        title: "영수증 첨부 확인",
        isChecked: false
      }
    }
  });

Why: Planner tasks require If-Match etag header for updates (optimistic concurrency). Priority uses a 1-9 scale. Assignments use the user's ID as the key. Always include orderHint with a space-bang pattern.

Rule 18: Power Automate Cloud Flow Triggers from Code

Impact: MEDIUM — Building custom automation when Power Automate flows can handle it is over-engineering.

Correct:

import httpx

# Trigger a Power Automate flow via HTTP request trigger
FLOW_TRIGGER_URL = os.environ["POWER_AUTOMATE_WEBHOOK_URL"]

async def trigger_approval_flow(expense_data: dict):
    """Trigger Power Automate approval flow from application code."""
    async with httpx.AsyncClient() as client:
        response = await client.post(
            FLOW_TRIGGER_URL,
            json={
                "submitter": expense_data["submitter_name"],
                "amount": expense_data["amount"],
                "category": expense_data["category"],
                "approver_email": expense_data["approver_email"],
                "callback_url": f"{API_BASE}/api/expenses/{expense_data['id']}/status"
            },
            timeout=30.0
        )
        response.raise_for_status()
        return response.json()

Why: Power Automate excels at approval workflows, multi-step notifications, and M365 orchestration. Use HTTP request triggers to invoke flows from your code. Use HTTP response actions to return results. This avoids reimplementing approval logic, Teams notifications, and Outlook email in your application.

Rule 19: Teams Meeting Transcript Access

Impact: MEDIUM — Accessing meeting transcripts requires specific Graph API permissions and timing considerations.

Correct:

// 1. List transcripts for a meeting (available after meeting ends)
const transcripts = await graphClient
  .api(`/me/onlineMeetings/${meetingId}/transcripts`)
  .get();

// 2. Get transcript content
if (transcripts.value.length > 0) {
  const transcriptId = transcripts.value[0].id;

  // Get as VTT format (includes timestamps)
  const vttContent = await graphClient
    .api(`/me/onlineMeetings/${meetingId}/transcripts/${transcriptId}/content`)
    .header("Accept", "text/vtt")
    .get();

  // Or get as plain text (no timestamps)
  const textContent = await graphClient
    .api(`/me/onlineMeetings/${meetingId}/transcripts/${transcriptId}/content`)
    .header("Accept", "text/plain")
    .get();
}

Required permissions: OnlineMeetingTranscript.Read.All (application) or OnlineMeetingTranscript.Read (delegated).

Why: Transcripts are only available after the meeting ends and transcription was enabled. Use VTT format when you need speaker attribution and timestamps. Use plain text for summarization or search indexing.


Pre-Deployment Checklist (M365 Integrations)

Authentication:

  • App registration has minimum required Graph API permissions
  • Teams SSO configured for tabs (no separate login)
  • OAuth 2.0 used for API plugins (not API keys)

SharePoint:

  • Compound site ID format used
  • Internal field names used (not display names)
  • SPFx web parts unmount on dispose

Teams:

  • Adaptive Cards under 28KB
  • Channel IDs retrieved dynamically (not hardcoded)
  • Webhook URLs stored in Key Vault

Graph API:

  • Batch requests used for bulk operations (≤20 per batch)
  • Retry logic for HTTP 429 with Retry-After header
  • Delta queries for incremental sync

Localization:

  • Korean date format (YYYY년 MM월 DD일)
  • Korea Standard Time timezone ID used
  • Korean error messages in 합니다체

适合场景

01

用户想查找某类 Agent Skill 时

02

需要根据任务场景推荐可安装能力包时

03

需要对比不同来源的安装命令和来源信息时

能力概览

能力 1

按任务关键词查找相关 Skills

能力 2

展示可复制的安装命令

能力 3

保留来源站点、仓库和原始说明,方便继续核验

能力 4

展示第三方安全扫描或审计结果

安装后应在对应宿主中按原始 README 的触发条件使用;具体调用方式请以来源页面和 README 为准。

平台分布

Claude

33.06%
按下载量换算36

Codex

32.13%
按下载量换算35

Cursor

19.54%
按下载量换算21

Gemini CLI

9.79%
按下载量换算11

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

可疑

权限和风险

敏感数据

该 Skill 可能接触密钥、Token、环境变量或敏感配置,应进入高风险复核队列,默认不自动发布。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。来源安全扫描存在 warning/failed 结果,不能写成本站确认安全。当前只有一个来源,正式发布前建议补源仓库或其他目录站核验。

来源信息

继续浏览同类 Skills