[{"data":1,"prerenderedAt":-1},["ShallowReactive",2],{"skill:01M6F6EMMZ0HTS26PCPEAV0844":3,"skill-same:01M6F6EMMZ0HTS26PCPEAV0844":81},{"skillNo":4,"name":5,"nameZh":6,"description":7,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":6,"sourceHtmlUrl":16,"categories":17,"keywords":26,"publishedAt":49,"linkOnly":50,"markdownContent":51,"markdownBody":52,"frontMatter":53,"refName":58,"commitSha":59,"skillDirectory":60,"forksCount":61,"repositoryHtmlUrl":62,"repositoryHomepageUrl":6,"ownerHtmlUrl":63,"ownerBlogUrl":64,"ownerDescription":65,"updatedAt":66,"install":67,"resources":80},"01M6F6EMMZ0HTS26PCPEAV0844","evernote-ci-integration",null,"Configure CI\u002FCD pipelines for Evernote integrations.\nUse when setting up automated testing, continuous integration,\nor deployment pipelines for Evernote projects.\nTrigger with phrases like \"evernote ci\", \"evernote github actions\",\n\"evernote pipeline\", \"automate evernote tests\".","Dicklesworthstone\u002Fpi_agent_rust","Dicklesworthstone","https:\u002F\u002Favatars.githubusercontent.com\u002Fu\u002F35050222?v=4","01M6CG26AR942CSVMN1M5E3DSE","Jeff Emanuel","01M6CG26EPP71PSHW1VDS71121",1979,"MIT","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fevernote-pack\u002Fskills\u002Fevernote-ci-integration\u002FSKILL.md",[18,23],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000008","测试与质量","SCENARIO","使用场景",{"categoryNo":24,"categoryName":25,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000011","运维与部署",[27,28,29,30,31,32,33,34,35,36,37,38,39,40,41,42,43,44,45,46,47,48],"integration","evernote","ci","evernote ci integration","integrations","when","CI\u002FCD","io","continuous integration","up","edit","write","read","testing","automate","projects","configure","Bash","pipeline","github actions","actions","deployment","2026-11-10 10:12:30",false,"---\nname: evernote-ci-integration\ndescription: |\n  Configure CI\u002FCD pipelines for Evernote integrations.\n  Use when setting up automated testing, continuous integration,\n  or deployment pipelines for Evernote projects.\n  Trigger with phrases like \"evernote ci\", \"evernote github actions\",\n  \"evernote pipeline\", \"automate evernote tests\".\nallowed-tools: Read, Write, Edit, Bash(npm:*), Grep\nversion: 1.0.0\nlicense: MIT\nauthor: Jeremy Longshore \u003Cjeremy@intentsolutions.io>\n---\n\n# Evernote CI Integration\n\n## Overview\n\nConfigure continuous integration pipelines for Evernote integrations, including test automation, credential management, and deployment workflows.\n\n## Prerequisites\n\n- Git repository set up\n- CI\u002FCD platform (GitHub Actions, GitLab CI, etc.)\n- Test suite implemented\n- Sandbox API credentials\n\n## Instructions\n\n### Step 1: GitHub Actions Workflow\n\n```yaml\n# .github\u002Fworkflows\u002Fevernote-ci.yml\nname: Evernote Integration CI\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main]\n\nenv:\n  NODE_VERSION: '20'\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Setup Node.js\n        uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run linter\n        run: npm run lint\n\n  unit-tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Setup Node.js\n        uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run unit tests\n        run: npm run test:unit\n\n      - name: Upload coverage\n        uses: codecov\u002Fcodecov-action@v3\n        with:\n          files: .\u002Fcoverage\u002Flcov.info\n\n  integration-tests:\n    runs-on: ubuntu-latest\n    needs: [lint, unit-tests]\n    # Only run on main branch to preserve rate limits\n    if: github.ref == 'refs\u002Fheads\u002Fmain'\n\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Setup Node.js\n        uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run integration tests\n        env:\n          EVERNOTE_CONSUMER_KEY: ${{ secrets.EVERNOTE_CONSUMER_KEY }}\n          EVERNOTE_CONSUMER_SECRET: ${{ secrets.EVERNOTE_CONSUMER_SECRET }}\n          EVERNOTE_ACCESS_TOKEN: ${{ secrets.EVERNOTE_SANDBOX_TOKEN }}\n          EVERNOTE_SANDBOX: 'true'\n        run: npm run test:integration\n\n  security-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Run Snyk security scan\n        uses: snyk\u002Factions\u002Fnode@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n        with:\n          args: --severity-threshold=high\n```\n\n### Step 2: Test Configuration\n\n```javascript\n\u002F\u002F jest.config.js\nmodule.exports = {\n  testEnvironment: 'node',\n  testMatch: [\n    '**\u002Ftests\u002F**\u002F*.test.js'\n  ],\n  collectCoverageFrom: [\n    'src\u002F**\u002F*.js',\n    '!src\u002F**\u002F*.test.js'\n  ],\n  coverageThreshold: {\n    global: {\n      branches: 80,\n      functions: 80,\n      lines: 80,\n      statements: 80\n    }\n  },\n  setupFilesAfterEnv: ['.\u002Ftests\u002Fsetup.js'],\n\n  \u002F\u002F Separate test suites\n  projects: [\n    {\n      displayName: 'unit',\n      testMatch: ['\u003CrootDir>\u002Ftests\u002Funit\u002F**\u002F*.test.js']\n    },\n    {\n      displayName: 'integration',\n      testMatch: ['\u003CrootDir>\u002Ftests\u002Fintegration\u002F**\u002F*.test.js'],\n      testTimeout: 30000\n    }\n  ]\n};\n```\n\n```javascript\n\u002F\u002F tests\u002Fsetup.js\nconst dotenv = require('dotenv');\n\n\u002F\u002F Load test environment\ndotenv.config({ path: '.env.test' });\n\n\u002F\u002F Global test setup\nbeforeAll(() => {\n  \u002F\u002F Verify test environment\n  if (process.env.NODE_ENV === 'production') {\n    throw new Error('Cannot run tests in production environment');\n  }\n\n  \u002F\u002F Ensure sandbox mode for Evernote tests\n  if (process.env.EVERNOTE_SANDBOX !== 'true') {\n    console.warn('WARNING: EVERNOTE_SANDBOX should be true for tests');\n  }\n});\n\n\u002F\u002F Global teardown\nafterAll(async () => {\n  \u002F\u002F Clean up test resources\n});\n```\n\n### Step 3: Mock Evernote Client for Unit Tests\n\n```javascript\n\u002F\u002F tests\u002Fmocks\u002Fevernote-mock.js\n\nclass MockNoteStore {\n  constructor() {\n    this.notebooks = [];\n    this.notes = [];\n    this.tags = [];\n  }\n\n  async listNotebooks() {\n    return this.notebooks;\n  }\n\n  async createNotebook(notebook) {\n    const created = {\n      ...notebook,\n      guid: `notebook-${Date.now()}`,\n      created: Date.now(),\n      updated: Date.now()\n    };\n    this.notebooks.push(created);\n    return created;\n  }\n\n  async listTags() {\n    return this.tags;\n  }\n\n  async createNote(note) {\n    \u002F\u002F Validate ENML\n    if (!note.content.includes('\u003C?xml version=\"1.0\"')) {\n      const error = new Error('Invalid ENML');\n      error.errorCode = 1;\n      error.parameter = 'Note.content';\n      throw error;\n    }\n\n    const created = {\n      ...note,\n      guid: `note-${Date.now()}`,\n      created: Date.now(),\n      updated: Date.now()\n    };\n    this.notes.push(created);\n    return created;\n  }\n\n  async getNote(guid, withContent, withResources) {\n    const note = this.notes.find(n => n.guid === guid);\n    if (!note) {\n      const error = new Error('Note not found');\n      error.identifier = 'Note.guid';\n      error.key = guid;\n      throw error;\n    }\n    return note;\n  }\n\n  async findNotesMetadata(filter, offset, maxNotes, spec) {\n    let filtered = [...this.notes];\n\n    if (filter.words) {\n      const words = filter.words.toLowerCase();\n      filtered = filtered.filter(n =>\n        n.title.toLowerCase().includes(words) ||\n        n.content.toLowerCase().includes(words)\n      );\n    }\n\n    return {\n      notes: filtered.slice(offset, offset + maxNotes),\n      totalNotes: filtered.length\n    };\n  }\n\n  \u002F\u002F Reset for tests\n  reset() {\n    this.notebooks = [];\n    this.notes = [];\n    this.tags = [];\n  }\n}\n\nclass MockUserStore {\n  async getUser() {\n    return {\n      id: 12345,\n      username: 'testuser',\n      email: 'test@example.com',\n      name: 'Test User',\n      privilege: 1,\n      created: Date.now() - 86400000,\n      updated: Date.now(),\n      active: true,\n      accounting: {\n        uploadLimit: 62914560,\n        uploaded: 1000000,\n        uploadLimitEnd: Date.now() + 86400000 * 30\n      }\n    };\n  }\n}\n\nclass MockEvernoteClient {\n  constructor(options = {}) {\n    this.options = options;\n    this._noteStore = new MockNoteStore();\n    this._userStore = new MockUserStore();\n  }\n\n  getNoteStore() {\n    return this._noteStore;\n  }\n\n  getUserStore() {\n    return this._userStore;\n  }\n\n  \u002F\u002F For OAuth testing\n  getRequestToken(callbackUrl, callback) {\n    callback(null, 'mock-oauth-token', 'mock-oauth-secret');\n  }\n\n  getAuthorizeUrl(oauthToken) {\n    return `https:\u002F\u002Fsandbox.evernote.com\u002FOAuth.action?oauth_token=${oauthToken}`;\n  }\n\n  getAccessToken(oauthToken, oauthTokenSecret, oauthVerifier, callback) {\n    callback(null, 'mock-access-token', 'mock-access-secret', {\n      edam_expires: String(Date.now() + 31536000000)\n    });\n  }\n}\n\nmodule.exports = {\n  MockEvernoteClient,\n  MockNoteStore,\n  MockUserStore\n};\n```\n\n### Step 4: Unit Test Examples\n\n```javascript\n\u002F\u002F tests\u002Funit\u002Fnote-service.test.js\nconst { MockEvernoteClient } = require('..\u002Fmocks\u002Fevernote-mock');\nconst NoteService = require('..\u002F..\u002Fsrc\u002Fservices\u002Fnote-service');\n\ndescribe('NoteService', () => {\n  let noteService;\n  let mockClient;\n\n  beforeEach(() => {\n    mockClient = new MockEvernoteClient();\n    noteService = new NoteService(mockClient.getNoteStore());\n  });\n\n  describe('createNote', () => {\n    it('should create a note with valid ENML', async () => {\n      const result = await noteService.createNote({\n        title: 'Test Note',\n        content: '\u003Cp>Test content\u003C\u002Fp>'\n      });\n\n      expect(result.guid).toBeDefined();\n      expect(result.title).toBe('Test Note');\n    });\n\n    it('should sanitize note title', async () => {\n      const result = await noteService.createNote({\n        title: 'Title\\nwith\\nnewlines',\n        content: '\u003Cp>Content\u003C\u002Fp>'\n      });\n\n      expect(result.title).not.toContain('\\n');\n    });\n\n    it('should reject invalid ENML', async () => {\n      \u002F\u002F Remove ENML wrapping for this test\n      const badService = {\n        noteStore: mockClient.getNoteStore(),\n        createNote: async (params) => {\n          const note = { title: params.title, content: params.content };\n          return mockClient.getNoteStore().createNote(note);\n        }\n      };\n\n      await expect(\n        badService.createNote({\n          title: 'Bad Note',\n          content: '\u003Cp>No ENML wrapper\u003C\u002Fp>'\n        })\n      ).rejects.toMatchObject({\n        errorCode: 1,\n        parameter: 'Note.content'\n      });\n    });\n  });\n\n  describe('searchNotes', () => {\n    beforeEach(async () => {\n      \u002F\u002F Seed test data\n      await noteService.createNote({ title: 'Meeting Notes', content: '\u003Cp>Q1 Review\u003C\u002Fp>' });\n      await noteService.createNote({ title: 'Todo List', content: '\u003Cp>Tasks\u003C\u002Fp>' });\n    });\n\n    it('should find notes by title', async () => {\n      const results = await noteService.search('meeting');\n\n      expect(results.notes.length).toBe(1);\n      expect(results.notes[0].title).toContain('Meeting');\n    });\n  });\n});\n```\n\n### Step 5: Integration Test Examples\n\n```javascript\n\u002F\u002F tests\u002Fintegration\u002Fevernote-api.test.js\nconst Evernote = require('evernote');\n\n\u002F\u002F Skip if no credentials\nconst hasCredentials = process.env.EVERNOTE_ACCESS_TOKEN;\n\n(hasCredentials ? describe : describe.skip)('Evernote API Integration', () => {\n  let client;\n  let noteStore;\n  let createdNoteGuids = [];\n\n  beforeAll(() => {\n    client = new Evernote.Client({\n      token: process.env.EVERNOTE_ACCESS_TOKEN,\n      sandbox: process.env.EVERNOTE_SANDBOX === 'true'\n    });\n    noteStore = client.getNoteStore();\n  });\n\n  afterAll(async () => {\n    \u002F\u002F Clean up created notes\n    for (const guid of createdNoteGuids) {\n      try {\n        await noteStore.deleteNote(guid);\n      } catch (error) {\n        console.log(`Cleanup: Could not delete ${guid}`);\n      }\n    }\n  });\n\n  it('should authenticate and get user info', async () => {\n    const userStore = client.getUserStore();\n    const user = await userStore.getUser();\n\n    expect(user.id).toBeDefined();\n    expect(user.username).toBeDefined();\n  });\n\n  it('should list notebooks', async () => {\n    const notebooks = await noteStore.listNotebooks();\n\n    expect(Array.isArray(notebooks)).toBe(true);\n    expect(notebooks.length).toBeGreaterThan(0);\n  });\n\n  it('should create and retrieve a note', async () => {\n    const testTitle = `CI Test Note - ${Date.now()}`;\n    const content = `\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003C!DOCTYPE en-note SYSTEM \"http:\u002F\u002Fxml.evernote.com\u002Fpub\u002Fenml2.dtd\">\n\u003Cen-note>\u003Cp>Integration test content\u003C\u002Fp>\u003C\u002Fen-note>`;\n\n    const note = new Evernote.Types.Note();\n    note.title = testTitle;\n    note.content = content;\n\n    const created = await noteStore.createNote(note);\n    createdNoteGuids.push(created.guid);\n\n    expect(created.guid).toBeDefined();\n    expect(created.title).toBe(testTitle);\n\n    \u002F\u002F Retrieve and verify\n    const retrieved = await noteStore.getNote(created.guid, true, false, false, false);\n    expect(retrieved.content).toContain('Integration test content');\n  });\n\n  it('should handle rate limits gracefully', async () => {\n    \u002F\u002F This test intentionally makes multiple calls\n    \u002F\u002F to verify rate limit handling works\n\n    const promises = [];\n    for (let i = 0; i \u003C 5; i++) {\n      promises.push(noteStore.listNotebooks());\n    }\n\n    const results = await Promise.all(promises);\n    expect(results.every(r => Array.isArray(r))).toBe(true);\n  }, 30000);\n});\n```\n\n### Step 6: Secrets Management\n\n```yaml\n# GitHub Actions Secrets Configuration\n# Go to Settings > Secrets and variables > Actions\n\n# Required secrets:\n# EVERNOTE_CONSUMER_KEY      - Your API consumer key\n# EVERNOTE_CONSUMER_SECRET   - Your API consumer secret\n# EVERNOTE_SANDBOX_TOKEN     - Sandbox developer token for testing\n\n# Production secrets (separate environment):\n# EVERNOTE_PROD_CONSUMER_KEY\n# EVERNOTE_PROD_CONSUMER_SECRET\n```\n\n```yaml\n# Using environments for production\n# .github\u002Fworkflows\u002Fdeploy.yml\njobs:\n  deploy-production:\n    runs-on: ubuntu-latest\n    environment: production  # Requires approval\n\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Deploy\n        env:\n          EVERNOTE_CONSUMER_KEY: ${{ secrets.EVERNOTE_PROD_CONSUMER_KEY }}\n          EVERNOTE_CONSUMER_SECRET: ${{ secrets.EVERNOTE_PROD_CONSUMER_SECRET }}\n        run: npm run deploy\n```\n\n### Step 7: Package.json Scripts\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"test:unit\": \"jest --selectProjects unit\",\n    \"test:integration\": \"jest --selectProjects integration --runInBand\",\n    \"test:coverage\": \"jest --coverage\",\n    \"lint\": \"eslint src\u002F tests\u002F\",\n    \"lint:fix\": \"eslint src\u002F tests\u002F --fix\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"ci\": \"npm run lint && npm run typecheck && npm run test:unit\",\n    \"ci:full\": \"npm run ci && npm run test:integration\"\n  }\n}\n```\n\n## Output\n\n- GitHub Actions workflow for Evernote CI\n- Comprehensive test configuration\n- Mock client for unit testing\n- Integration test examples\n- Secrets management setup\n\n## Best Practices\n\n| Practice | Reason |\n|----------|--------|\n| Run unit tests on every PR | Fast feedback, no API usage |\n| Limit integration tests | Preserve rate limits |\n| Use sandbox for all CI | Never test against production |\n| Store secrets securely | Protect API credentials |\n| Clean up test data | Don't leave garbage in sandbox |\n\n## Resources\n\n- [GitHub Actions](https:\u002F\u002Fdocs.github.com\u002Fen\u002Factions)\n- [Jest Documentation](https:\u002F\u002Fjestjs.io\u002Fdocs\u002Fgetting-started)\n- [Evernote Sandbox](https:\u002F\u002Fsandbox.evernote.com)\n\n## Next Steps\n\nFor deployment pipelines, see `evernote-deploy-integration`.\n","# Evernote CI Integration\n\n## Overview\n\nConfigure continuous integration pipelines for Evernote integrations, including test automation, credential management, and deployment workflows.\n\n## Prerequisites\n\n- Git repository set up\n- CI\u002FCD platform (GitHub Actions, GitLab CI, etc.)\n- Test suite implemented\n- Sandbox API credentials\n\n## Instructions\n\n### Step 1: GitHub Actions Workflow\n\n```yaml\n# .github\u002Fworkflows\u002Fevernote-ci.yml\nname: Evernote Integration CI\n\non:\n  push:\n    branches: [main, develop]\n  pull_request:\n    branches: [main]\n\nenv:\n  NODE_VERSION: '20'\n\njobs:\n  lint:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Setup Node.js\n        uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run linter\n        run: npm run lint\n\n  unit-tests:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Setup Node.js\n        uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run unit tests\n        run: npm run test:unit\n\n      - name: Upload coverage\n        uses: codecov\u002Fcodecov-action@v3\n        with:\n          files: .\u002Fcoverage\u002Flcov.info\n\n  integration-tests:\n    runs-on: ubuntu-latest\n    needs: [lint, unit-tests]\n    # Only run on main branch to preserve rate limits\n    if: github.ref == 'refs\u002Fheads\u002Fmain'\n\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Setup Node.js\n        uses: actions\u002Fsetup-node@v4\n        with:\n          node-version: ${{ env.NODE_VERSION }}\n          cache: 'npm'\n\n      - name: Install dependencies\n        run: npm ci\n\n      - name: Run integration tests\n        env:\n          EVERNOTE_CONSUMER_KEY: ${{ secrets.EVERNOTE_CONSUMER_KEY }}\n          EVERNOTE_CONSUMER_SECRET: ${{ secrets.EVERNOTE_CONSUMER_SECRET }}\n          EVERNOTE_ACCESS_TOKEN: ${{ secrets.EVERNOTE_SANDBOX_TOKEN }}\n          EVERNOTE_SANDBOX: 'true'\n        run: npm run test:integration\n\n  security-scan:\n    runs-on: ubuntu-latest\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Run Snyk security scan\n        uses: snyk\u002Factions\u002Fnode@master\n        env:\n          SNYK_TOKEN: ${{ secrets.SNYK_TOKEN }}\n        with:\n          args: --severity-threshold=high\n```\n\n### Step 2: Test Configuration\n\n```javascript\n\u002F\u002F jest.config.js\nmodule.exports = {\n  testEnvironment: 'node',\n  testMatch: [\n    '**\u002Ftests\u002F**\u002F*.test.js'\n  ],\n  collectCoverageFrom: [\n    'src\u002F**\u002F*.js',\n    '!src\u002F**\u002F*.test.js'\n  ],\n  coverageThreshold: {\n    global: {\n      branches: 80,\n      functions: 80,\n      lines: 80,\n      statements: 80\n    }\n  },\n  setupFilesAfterEnv: ['.\u002Ftests\u002Fsetup.js'],\n\n  \u002F\u002F Separate test suites\n  projects: [\n    {\n      displayName: 'unit',\n      testMatch: ['\u003CrootDir>\u002Ftests\u002Funit\u002F**\u002F*.test.js']\n    },\n    {\n      displayName: 'integration',\n      testMatch: ['\u003CrootDir>\u002Ftests\u002Fintegration\u002F**\u002F*.test.js'],\n      testTimeout: 30000\n    }\n  ]\n};\n```\n\n```javascript\n\u002F\u002F tests\u002Fsetup.js\nconst dotenv = require('dotenv');\n\n\u002F\u002F Load test environment\ndotenv.config({ path: '.env.test' });\n\n\u002F\u002F Global test setup\nbeforeAll(() => {\n  \u002F\u002F Verify test environment\n  if (process.env.NODE_ENV === 'production') {\n    throw new Error('Cannot run tests in production environment');\n  }\n\n  \u002F\u002F Ensure sandbox mode for Evernote tests\n  if (process.env.EVERNOTE_SANDBOX !== 'true') {\n    console.warn('WARNING: EVERNOTE_SANDBOX should be true for tests');\n  }\n});\n\n\u002F\u002F Global teardown\nafterAll(async () => {\n  \u002F\u002F Clean up test resources\n});\n```\n\n### Step 3: Mock Evernote Client for Unit Tests\n\n```javascript\n\u002F\u002F tests\u002Fmocks\u002Fevernote-mock.js\n\nclass MockNoteStore {\n  constructor() {\n    this.notebooks = [];\n    this.notes = [];\n    this.tags = [];\n  }\n\n  async listNotebooks() {\n    return this.notebooks;\n  }\n\n  async createNotebook(notebook) {\n    const created = {\n      ...notebook,\n      guid: `notebook-${Date.now()}`,\n      created: Date.now(),\n      updated: Date.now()\n    };\n    this.notebooks.push(created);\n    return created;\n  }\n\n  async listTags() {\n    return this.tags;\n  }\n\n  async createNote(note) {\n    \u002F\u002F Validate ENML\n    if (!note.content.includes('\u003C?xml version=\"1.0\"')) {\n      const error = new Error('Invalid ENML');\n      error.errorCode = 1;\n      error.parameter = 'Note.content';\n      throw error;\n    }\n\n    const created = {\n      ...note,\n      guid: `note-${Date.now()}`,\n      created: Date.now(),\n      updated: Date.now()\n    };\n    this.notes.push(created);\n    return created;\n  }\n\n  async getNote(guid, withContent, withResources) {\n    const note = this.notes.find(n => n.guid === guid);\n    if (!note) {\n      const error = new Error('Note not found');\n      error.identifier = 'Note.guid';\n      error.key = guid;\n      throw error;\n    }\n    return note;\n  }\n\n  async findNotesMetadata(filter, offset, maxNotes, spec) {\n    let filtered = [...this.notes];\n\n    if (filter.words) {\n      const words = filter.words.toLowerCase();\n      filtered = filtered.filter(n =>\n        n.title.toLowerCase().includes(words) ||\n        n.content.toLowerCase().includes(words)\n      );\n    }\n\n    return {\n      notes: filtered.slice(offset, offset + maxNotes),\n      totalNotes: filtered.length\n    };\n  }\n\n  \u002F\u002F Reset for tests\n  reset() {\n    this.notebooks = [];\n    this.notes = [];\n    this.tags = [];\n  }\n}\n\nclass MockUserStore {\n  async getUser() {\n    return {\n      id: 12345,\n      username: 'testuser',\n      email: 'test@example.com',\n      name: 'Test User',\n      privilege: 1,\n      created: Date.now() - 86400000,\n      updated: Date.now(),\n      active: true,\n      accounting: {\n        uploadLimit: 62914560,\n        uploaded: 1000000,\n        uploadLimitEnd: Date.now() + 86400000 * 30\n      }\n    };\n  }\n}\n\nclass MockEvernoteClient {\n  constructor(options = {}) {\n    this.options = options;\n    this._noteStore = new MockNoteStore();\n    this._userStore = new MockUserStore();\n  }\n\n  getNoteStore() {\n    return this._noteStore;\n  }\n\n  getUserStore() {\n    return this._userStore;\n  }\n\n  \u002F\u002F For OAuth testing\n  getRequestToken(callbackUrl, callback) {\n    callback(null, 'mock-oauth-token', 'mock-oauth-secret');\n  }\n\n  getAuthorizeUrl(oauthToken) {\n    return `https:\u002F\u002Fsandbox.evernote.com\u002FOAuth.action?oauth_token=${oauthToken}`;\n  }\n\n  getAccessToken(oauthToken, oauthTokenSecret, oauthVerifier, callback) {\n    callback(null, 'mock-access-token', 'mock-access-secret', {\n      edam_expires: String(Date.now() + 31536000000)\n    });\n  }\n}\n\nmodule.exports = {\n  MockEvernoteClient,\n  MockNoteStore,\n  MockUserStore\n};\n```\n\n### Step 4: Unit Test Examples\n\n```javascript\n\u002F\u002F tests\u002Funit\u002Fnote-service.test.js\nconst { MockEvernoteClient } = require('..\u002Fmocks\u002Fevernote-mock');\nconst NoteService = require('..\u002F..\u002Fsrc\u002Fservices\u002Fnote-service');\n\ndescribe('NoteService', () => {\n  let noteService;\n  let mockClient;\n\n  beforeEach(() => {\n    mockClient = new MockEvernoteClient();\n    noteService = new NoteService(mockClient.getNoteStore());\n  });\n\n  describe('createNote', () => {\n    it('should create a note with valid ENML', async () => {\n      const result = await noteService.createNote({\n        title: 'Test Note',\n        content: '\u003Cp>Test content\u003C\u002Fp>'\n      });\n\n      expect(result.guid).toBeDefined();\n      expect(result.title).toBe('Test Note');\n    });\n\n    it('should sanitize note title', async () => {\n      const result = await noteService.createNote({\n        title: 'Title\\nwith\\nnewlines',\n        content: '\u003Cp>Content\u003C\u002Fp>'\n      });\n\n      expect(result.title).not.toContain('\\n');\n    });\n\n    it('should reject invalid ENML', async () => {\n      \u002F\u002F Remove ENML wrapping for this test\n      const badService = {\n        noteStore: mockClient.getNoteStore(),\n        createNote: async (params) => {\n          const note = { title: params.title, content: params.content };\n          return mockClient.getNoteStore().createNote(note);\n        }\n      };\n\n      await expect(\n        badService.createNote({\n          title: 'Bad Note',\n          content: '\u003Cp>No ENML wrapper\u003C\u002Fp>'\n        })\n      ).rejects.toMatchObject({\n        errorCode: 1,\n        parameter: 'Note.content'\n      });\n    });\n  });\n\n  describe('searchNotes', () => {\n    beforeEach(async () => {\n      \u002F\u002F Seed test data\n      await noteService.createNote({ title: 'Meeting Notes', content: '\u003Cp>Q1 Review\u003C\u002Fp>' });\n      await noteService.createNote({ title: 'Todo List', content: '\u003Cp>Tasks\u003C\u002Fp>' });\n    });\n\n    it('should find notes by title', async () => {\n      const results = await noteService.search('meeting');\n\n      expect(results.notes.length).toBe(1);\n      expect(results.notes[0].title).toContain('Meeting');\n    });\n  });\n});\n```\n\n### Step 5: Integration Test Examples\n\n```javascript\n\u002F\u002F tests\u002Fintegration\u002Fevernote-api.test.js\nconst Evernote = require('evernote');\n\n\u002F\u002F Skip if no credentials\nconst hasCredentials = process.env.EVERNOTE_ACCESS_TOKEN;\n\n(hasCredentials ? describe : describe.skip)('Evernote API Integration', () => {\n  let client;\n  let noteStore;\n  let createdNoteGuids = [];\n\n  beforeAll(() => {\n    client = new Evernote.Client({\n      token: process.env.EVERNOTE_ACCESS_TOKEN,\n      sandbox: process.env.EVERNOTE_SANDBOX === 'true'\n    });\n    noteStore = client.getNoteStore();\n  });\n\n  afterAll(async () => {\n    \u002F\u002F Clean up created notes\n    for (const guid of createdNoteGuids) {\n      try {\n        await noteStore.deleteNote(guid);\n      } catch (error) {\n        console.log(`Cleanup: Could not delete ${guid}`);\n      }\n    }\n  });\n\n  it('should authenticate and get user info', async () => {\n    const userStore = client.getUserStore();\n    const user = await userStore.getUser();\n\n    expect(user.id).toBeDefined();\n    expect(user.username).toBeDefined();\n  });\n\n  it('should list notebooks', async () => {\n    const notebooks = await noteStore.listNotebooks();\n\n    expect(Array.isArray(notebooks)).toBe(true);\n    expect(notebooks.length).toBeGreaterThan(0);\n  });\n\n  it('should create and retrieve a note', async () => {\n    const testTitle = `CI Test Note - ${Date.now()}`;\n    const content = `\u003C?xml version=\"1.0\" encoding=\"UTF-8\"?>\n\u003C!DOCTYPE en-note SYSTEM \"http:\u002F\u002Fxml.evernote.com\u002Fpub\u002Fenml2.dtd\">\n\u003Cen-note>\u003Cp>Integration test content\u003C\u002Fp>\u003C\u002Fen-note>`;\n\n    const note = new Evernote.Types.Note();\n    note.title = testTitle;\n    note.content = content;\n\n    const created = await noteStore.createNote(note);\n    createdNoteGuids.push(created.guid);\n\n    expect(created.guid).toBeDefined();\n    expect(created.title).toBe(testTitle);\n\n    \u002F\u002F Retrieve and verify\n    const retrieved = await noteStore.getNote(created.guid, true, false, false, false);\n    expect(retrieved.content).toContain('Integration test content');\n  });\n\n  it('should handle rate limits gracefully', async () => {\n    \u002F\u002F This test intentionally makes multiple calls\n    \u002F\u002F to verify rate limit handling works\n\n    const promises = [];\n    for (let i = 0; i \u003C 5; i++) {\n      promises.push(noteStore.listNotebooks());\n    }\n\n    const results = await Promise.all(promises);\n    expect(results.every(r => Array.isArray(r))).toBe(true);\n  }, 30000);\n});\n```\n\n### Step 6: Secrets Management\n\n```yaml\n# GitHub Actions Secrets Configuration\n# Go to Settings > Secrets and variables > Actions\n\n# Required secrets:\n# EVERNOTE_CONSUMER_KEY      - Your API consumer key\n# EVERNOTE_CONSUMER_SECRET   - Your API consumer secret\n# EVERNOTE_SANDBOX_TOKEN     - Sandbox developer token for testing\n\n# Production secrets (separate environment):\n# EVERNOTE_PROD_CONSUMER_KEY\n# EVERNOTE_PROD_CONSUMER_SECRET\n```\n\n```yaml\n# Using environments for production\n# .github\u002Fworkflows\u002Fdeploy.yml\njobs:\n  deploy-production:\n    runs-on: ubuntu-latest\n    environment: production  # Requires approval\n\n    steps:\n      - uses: actions\u002Fcheckout@v4\n\n      - name: Deploy\n        env:\n          EVERNOTE_CONSUMER_KEY: ${{ secrets.EVERNOTE_PROD_CONSUMER_KEY }}\n          EVERNOTE_CONSUMER_SECRET: ${{ secrets.EVERNOTE_PROD_CONSUMER_SECRET }}\n        run: npm run deploy\n```\n\n### Step 7: Package.json Scripts\n\n```json\n{\n  \"scripts\": {\n    \"test\": \"jest\",\n    \"test:unit\": \"jest --selectProjects unit\",\n    \"test:integration\": \"jest --selectProjects integration --runInBand\",\n    \"test:coverage\": \"jest --coverage\",\n    \"lint\": \"eslint src\u002F tests\u002F\",\n    \"lint:fix\": \"eslint src\u002F tests\u002F --fix\",\n    \"typecheck\": \"tsc --noEmit\",\n    \"ci\": \"npm run lint && npm run typecheck && npm run test:unit\",\n    \"ci:full\": \"npm run ci && npm run test:integration\"\n  }\n}\n```\n\n## Output\n\n- GitHub Actions workflow for Evernote CI\n- Comprehensive test configuration\n- Mock client for unit testing\n- Integration test examples\n- Secrets management setup\n\n## Best Practices\n\n| Practice | Reason |\n|----------|--------|\n| Run unit tests on every PR | Fast feedback, no API usage |\n| Limit integration tests | Preserve rate limits |\n| Use sandbox for all CI | Never test against production |\n| Store secrets securely | Protect API credentials |\n| Clean up test data | Don't leave garbage in sandbox |\n\n## Resources\n\n- [GitHub Actions](https:\u002F\u002Fdocs.github.com\u002Fen\u002Factions)\n- [Jest Documentation](https:\u002F\u002Fjestjs.io\u002Fdocs\u002Fgetting-started)\n- [Evernote Sandbox](https:\u002F\u002Fsandbox.evernote.com)\n\n## Next Steps\n\nFor deployment pipelines, see `evernote-deploy-integration`.\n",{"name":5,"author":54,"license":15,"version":55,"description":56,"allowed-tools":57},"Jeremy Longshore \u003Cjeremy@intentsolutions.io>","1.0.0","Configure CI\u002FCD pipelines for Evernote integrations.\nUse when setting up automated testing, continuous integration,\nor deployment pipelines for Evernote projects.\nTrigger with phrases like \"evernote ci\", \"evernote github actions\",\n\"evernote pipeline\", \"automate evernote tests\".\n","Read, Write, Edit, Bash(npm:*), Grep","main","90b50ed0778745a80fd5bfb13b1843d45b9e82f7","tests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fevernote-pack\u002Fskills\u002Fevernote-ci-integration",231,"https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust","https:\u002F\u002Fgithub.com\u002FDicklesworthstone","https:\u002F\u002Fwww.jeffreyemanuel.com\u002F","Building in NY","2026-11-11 01:23:40",{"source":8,"skillName":5,"commands":68,"prompt":78,"zipAvailable":79},[69,72,75],{"tool":70,"command":71},"npx","npx skills add Dicklesworthstone\u002Fpi_agent_rust --skill evernote-ci-integration",{"tool":73,"command":74},"bunx","bunx skills add Dicklesworthstone\u002Fpi_agent_rust --skill evernote-ci-integration",{"tool":76,"command":77},"pnpm","pnpm dlx skills add Dicklesworthstone\u002Fpi_agent_rust --skill evernote-ci-integration","安装技能，技能地址：https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Fplugins-community\u002Fplugins\u002Fsaas-packs\u002Fevernote-pack\u002Fskills\u002Fevernote-ci-integration\u002FSKILL.md",true,[],[82,121,143,169,195,217],{"skillNo":83,"name":84,"nameZh":6,"description":85,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":86,"licenseFileUrl":87,"sourceHtmlUrl":88,"categories":89,"keywords":93,"publishedAt":49},"01M6FEGPPK0ZTKCCM0R3E3KS33","web-artifacts-builder","Suite of tools for creating elaborate, multi-component claude.ai HTML artifacts using modern frontend web technologies (React, Tailwind CSS, shadcn\u002Fui). Use for complex artifacts requiring state management, routing, or shadcn\u002Fui components - not for simple single-file HTML\u002FJSX artifacts.","Apache-2.0","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Futilities\u002Fweb-artifacts-builder\u002FSKILL.md",[90],{"categoryNo":91,"categoryName":92,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000009","前端开发",[94,95,96,97,98,99,100,101,102,103,104,105,106,107,108,109,110,111,112,113,114,115,116,117,118,119,120],"web artifacts builder","web","artifacts","artifacts builder","builder","frontend","css","components","react","component","routing","state management","state","shadcn","file","ui","Tools","Tailwind","use","html","single-file","simple","ui components","multi","for","jsx","suite",{"skillNo":122,"name":123,"nameZh":6,"description":124,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":86,"licenseFileUrl":125,"sourceHtmlUrl":126,"categories":127,"keywords":130,"publishedAt":49},"01M6FEE9VTK1W4GY21AKREJXRG","webapp-testing","Toolkit for interacting with and testing local web applications using Playwright. Supports verifying frontend functionality, debugging UI behavior, capturing browser screenshots, and viewing browser logs.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fsecurity\u002Fwebapp-testing\u002FSKILL.md",[128,129],{"categoryNo":19,"categoryName":20,"taxonomyType":21,"taxonomyTypeDesc":22},{"categoryNo":91,"categoryName":92,"taxonomyType":21,"taxonomyTypeDesc":22},[40,131,132,99,133,134,95,135,136,137,109,138,139,140,141,142,118],"webapp testing","webapp","screenshots","playwright","debugging","toolkit","browser","logs","applications","behavior","local","with",{"skillNo":144,"name":145,"nameZh":6,"description":146,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":147,"sourceHtmlUrl":148,"categories":149,"keywords":150,"publishedAt":49},"01M6FE20F7ZTQBFWKF4MPJ2SBX","neuropixels-analysis","Neuropixels neural recording analysis. Load SpikeGLX\u002FOpenEphys data, preprocess, motion correction, Kilosort4 spike sorting, quality metrics, Allen\u002FIBL curation, AI-assisted visual analysis, for Neuropixels 1.0\u002F2.0 extracellular electrophysiology. Use when working with neural recordings, spike sorting, extracellular electrophysiology, or when the user mentions Neuropixels, SpikeGLX, Open Ephys, Kilosort, quality metrics, or unit curation.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fscientific\u002Fneuropixels-analysis\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fscientific\u002Fneuropixels-analysis\u002FSKILL.md",[],[151,152,153,154,155,156,157,158,112,159,160,161,162,163,164,165,142,166,167,168,118,32],"analysis","neuropixels analysis","spike","metrics","data","open","quality","user","recordings","Neural","motion","visual","quality metrics","unit","sorting","curation","recording","load",{"skillNo":170,"name":171,"nameZh":6,"description":172,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":86,"licenseFileUrl":173,"sourceHtmlUrl":174,"categories":175,"keywords":179,"publishedAt":49},"01M6FDP0PDK6F1GGX395C7625Z","skill-creator","Guide for creating effective skills. This skill should be used when users want to create a new skill (or update an existing skill) that extends Claude's capabilities with specialized knowledge, workflows, or tool integrations.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fskill-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fskill-creator\u002FSKILL.md",[176],{"categoryNo":177,"categoryName":178,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000023","Skill与智能体开发",[180,181,182,183,184,185,186,187,31,188,189,190,191,192,142,118,193,32,194],"skill creator","creator","workflows","create","update","capabilities","knowledge","tool","specialized","guide","new skill","existing skill","users","Existing","new",{"skillNo":196,"name":197,"nameZh":6,"description":198,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":15,"licenseFileUrl":199,"sourceHtmlUrl":200,"categories":201,"keywords":203,"publishedAt":49},"01M6FDM8Y7VK7NYG9SM7QPA3PW","notebooklm","Use this skill to query your Google NotebookLM notebooks directly from Claude Code for source-grounded, citation-backed answers from Gemini. Browser automation, library management, persistent auth. Drastically reduced hallucinations through document-only responses.","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fnotebooklm\u002FLICENSE","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fproductivity\u002Fnotebooklm\u002FSKILL.md",[202],{"categoryNo":177,"categoryName":178,"taxonomyType":21,"taxonomyTypeDesc":22},[197,204,205,206,207,208,209,137,210,211,112,212,213,214,215,118,216],"google","browser automation","gemini","automation","auth","code","library","management","only","document","query","Citation","source",{"skillNo":218,"name":219,"nameZh":6,"description":220,"descriptionZh":6,"repositoryFullName":8,"ownerLogin":9,"ownerAvatarUrl":10,"ownerAccountNo":11,"ownerDisplayName":12,"repositoryNo":13,"starsCount":14,"licenseSpdxId":86,"licenseFileUrl":221,"sourceHtmlUrl":222,"categories":223,"keywords":230,"publishedAt":49},"01M6FDHPXE10J3XGTCH5HBHS1M","slack-gif-creator","Knowledge and utilities for creating animated GIFs optimized for Slack. Provides constraints, validation tools, and animation concepts. Use when users request animated GIFs for Slack like \"make me a GIF of X doing Y for Slack.\"","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fenterprise-communication\u002Fslack-gif-creator\u002FLICENSE.txt","https:\u002F\u002Fgithub.com\u002FDicklesworthstone\u002Fpi_agent_rust\u002Fblob\u002Fmain\u002Ftests\u002Fext_conformance\u002Fartifacts\u002Ftemplates-davila7\u002Fcli-tool\u002Fcomponents\u002Fskills\u002Fenterprise-communication\u002Fslack-gif-creator\u002FSKILL.md",[224,227],{"categoryNo":225,"categoryName":226,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000005","邮件与沟通",{"categoryNo":228,"categoryName":229,"taxonomyType":21,"taxonomyTypeDesc":22},"01JBSEEDCAT000000000000016","图像与多媒体",[231,232,233,181,234,110,235,236,186,112,237,192,238,239,240,118,241,32],"slack gif creator","slack","gif","validation","constraints","Animation","me","make","Utilities","concepts","request"]