diff --git a/.clinerules b/.clinerules deleted file mode 120000 index ac534a310..000000000 --- a/.clinerules +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/.cursor/rules/AGENT.md b/.cursor/rules/AGENT.md deleted file mode 120000 index ec2887d9c..000000000 --- a/.cursor/rules/AGENT.md +++ /dev/null @@ -1 +0,0 @@ -../../AGENT.md \ No newline at end of file diff --git a/.cursorrules b/.cursorrules deleted file mode 120000 index ac534a310..000000000 --- a/.cursorrules +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/.env.example b/.env.example index ce2fc13a7..2d2cc7785 100644 --- a/.env.example +++ b/.env.example @@ -10,3 +10,5 @@ NUXT_CF_API_TOKEN=CloudflareAPIToken NUXT_DATASET=sink NUXT_AI_MODEL="@cf/meta/llama-3-8b-instruct" NUXT_AI_PROMPT="You are a URL shortening assistant......" +NUXT_DISABLE_AUTO_BACKUP=false +NUXT_NOT_FOUND_REDIRECT=/your-own-404-page diff --git a/.github/FUNDING.yml b/.github/FUNDING.yml index f57d70ee6..a8f8b6589 100644 --- a/.github/FUNDING.yml +++ b/.github/FUNDING.yml @@ -1,2 +1,2 @@ -github: ccbikai +github: miantiao-me buy_me_a_coffee: miantiao diff --git a/.github/copilot-instructions.md b/.github/copilot-instructions.md deleted file mode 120000 index 471d0a2c6..000000000 --- a/.github/copilot-instructions.md +++ /dev/null @@ -1 +0,0 @@ -../AGENT.md \ No newline at end of file diff --git a/.github/workflows/issue-assistant.yml b/.github/workflows/issue-assistant.yml new file mode 100644 index 000000000..d52d3ff32 --- /dev/null +++ b/.github/workflows/issue-assistant.yml @@ -0,0 +1,54 @@ +name: issue-assistant + +on: + issues: + types: [opened] + +jobs: + issue-assistant: + runs-on: ubuntu-latest + permissions: + contents: read + issues: write + steps: + - name: Checkout repository + uses: actions/checkout@v4 + with: + fetch-depth: 1 + + - name: Set up Bun + uses: oven-sh/setup-bun@v2 + + - name: Install opencode + run: curl -fsSL https://opencode.ai/install | bash + + - name: Analyze issue + env: + OPENCODE_API_KEY: ${{ secrets.OPENCODE_API_KEY }} + GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }} + OPENCODE_PERMISSION: | + { + "bash": { + "*": "deny", + "gh issue*": "allow" + }, + "webfetch": "deny" + } + run: | + opencode run -m ${{ secrets.OPENCODE_MODEL }} "A new issue #${{ github.event.issue.number }} has been created. Lookup this issue, then do the following: + + 1. Read README.md and all files under docs/ directory to see if any documentation answers the issue. + 2. Search existing issues (excluding #${{ github.event.issue.number }}) for potential duplicates by similarity in title, description, error messages, or functionality. + + Based on your findings, post AT MOST ONE comment on #${{ github.event.issue.number }}. Combine all relevant info into a single comment. If nothing relevant found, do not comment at all. + + Comment format example: + '👋 Here are some resources that may help: + + **📖 From docs:** [brief explanation with relevant docs content] + + **🔗 Related issues:** + + - #123: [brief similarity] + + Hope this helps! Feel free to provide more details if these don't address your case.'" diff --git a/.gitignore b/.gitignore index a6ee4c2d0..a2d2b6b8e 100644 --- a/.gitignore +++ b/.gitignore @@ -28,3 +28,8 @@ cache public/world.json .dev.* .kiro/steering/locale.md + +.claude +.opencode +.agents +opencode.json diff --git a/.kiro/steering/AGENT.md b/.kiro/steering/AGENT.md deleted file mode 120000 index ec2887d9c..000000000 --- a/.kiro/steering/AGENT.md +++ /dev/null @@ -1 +0,0 @@ -../../AGENT.md \ No newline at end of file diff --git a/.mcp.json b/.mcp.json new file mode 100644 index 000000000..e01fd62a9 --- /dev/null +++ b/.mcp.json @@ -0,0 +1,12 @@ +{ + "mcpServers": { + "shadcn-vue": { + "command": "npx", + "args": ["shadcn-vue@latest", "mcp"] + }, + "nuxt": { + "type": "http", + "url": "https://nuxt.com/mcp" + } + } +} diff --git a/.node-version b/.node-version index 7c897d99e..2bd5a0a98 100644 --- a/.node-version +++ b/.node-version @@ -1 +1 @@ -22.15.1 \ No newline at end of file +22 diff --git a/.npmrc b/.npmrc index c483022c0..f855d962b 100644 --- a/.npmrc +++ b/.npmrc @@ -1 +1,5 @@ -shamefully-hoist=true \ No newline at end of file +shamefully-hoist=false +strict-peer-dependencies=true +auto-install-peers=true +prefer-frozen-lockfile=true +save-dev=true diff --git a/.windsurfrules b/.windsurfrules deleted file mode 120000 index ac534a310..000000000 --- a/.windsurfrules +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/AGENT.md b/AGENT.md deleted file mode 100644 index 185ae27e2..000000000 --- a/AGENT.md +++ /dev/null @@ -1,122 +0,0 @@ -# AGENT.md - -This file provides guidance for AI assistants working with the Sink project. - -## Project Overview - -Sink is a URL shortening service built with Nuxt 3 and Cloudflare Workers. It provides analytics, real-time tracking, and a modern web interface. - -## Key Technologies - -- **Frontend**: Nuxt 3, Vue 3, TypeScript, Tailwind CSS, shadcn/ui -- **Backend**: Cloudflare Workers, H3 (Nitro) -- **Database**: Cloudflare KV -- **Analytics**: Cloudflare Analytics Engine -- **Deployment**: Cloudflare Pages/Workers - -## Project Structure - -```txt -Sink/ -├── app/ # Nuxt 3 application -│ ├── components/ # Vue components -│ ├── pages/ # File-based routing -│ ├── layouts/ # Layout components -│ ├── utils/ # Utility functions -│ └── middleware/ # Route middleware -├── server/ # Server-side API -│ ├── api/ # API endpoints -│ ├── middleware/ # Server middleware -│ └── utils/ # Server utilities -├── public/ # Static assets -├── schemas/ # TypeScript schemas -├── tests/ # Test files -└── docs/ # Documentation -``` - -## Development Commands - -- `pnpm dev` - Start development server -- `pnpm build` - Build for production -- `pnpm preview` - Preview production build -- `pnpm test` - Run tests -- `pnpm lint` - Run ESLint - -## Key Conventions - -### Code Style - -- Use TypeScript for all new code -- Follow Vue 3 Composition API patterns -- Use PascalCase for component names -- Use camelCase for variables and functions -- Use kebab-case for CSS classes - -### API Structure - -- RESTful endpoints in `/server/api/` -- Use Zod for schema validation in `/schemas/` -- Implement proper error handling -- Use Cloudflare environment variables for configuration - -### Database - -- Use Cloudflare KV - -### Testing - -- Use Vitest for unit tests -- Test files should be co-located with source files -- Use the existing test utilities in `/tests/utils.ts` - -## Environment Variables - -Key environment variables to be aware of: - -- `NUXT_CF_ACCOUNT_ID` - Cloudflare account ID -- `NUXT_CF_API_TOKEN` - Cloudflare API token -- `NUXT_DATASET` - Analytics engine ID -- `NUXT_SITE_TOKEN` - Site token for management - -## Common Tasks - -### Adding a New API Endpoint - -1. Create file in `/server/api/` with appropriate HTTP method -2. Add Zod schema validation in `/schemas/` -3. Add tests in `/tests/api/` -4. Update documentation if needed - -### Adding a New Component - -1. Use existing UI components from `/app/components/ui/` -2. Follow the established component patterns -3. Add TypeScript interfaces for props -4. Consider adding to storybook if applicable - -## Security Guidelines - -- Never commit secrets or API keys -- Use environment variables for sensitive configuration -- Validate all user input with Zod schemas -- Implement rate limiting on API endpoints -- Follow OWASP guidelines for web security - -## Performance Considerations - -- Use Cloudflare's edge caching where appropriate -- Minimize bundle size by using dynamic imports -- Optimize images and assets -- Use the existing analytics for monitoring - -## Troubleshooting - -- Check Cloudflare dashboard for worker logs -- Use `wrangler tail` for real-time logs -- Verify environment variables are properly set - -## Deployment - -- Staging: Automatic on push to `main` branch -- Production: Manual trigger via Cloudflare dashboard -- Use `wrangler deploy` for manual deployments diff --git a/AGENTS.md b/AGENTS.md deleted file mode 120000 index ac534a310..000000000 --- a/AGENTS.md +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/AGENTS.md b/AGENTS.md new file mode 100644 index 000000000..93eafd8f8 --- /dev/null +++ b/AGENTS.md @@ -0,0 +1,198 @@ +# Repository Guidelines + +Guidelines for agentic coding agents operating in the Sink codebase. + +## Project Overview + +Sink is a link shortener with analytics, running 100% on Cloudflare. Uses Nuxt 4 frontend and Cloudflare Workers backend. + +**All documentation and comments must be in English.** + +## Project Structure + +``` +app/ # Nuxt 4 application (main app layer) + ├── components/ # Vue components (PascalCase) + │ └── ui/ # shadcn-vue components (DO NOT EDIT - auto-generated) + ├── composables/ # Vue composables (camelCase, use* prefix) + ├── pages/ # File-based routing + ├── types/ # TypeScript types (re-exports from shared/) + ├── utils/ # Utility functions + └── lib/ # Shared helpers +layers/dashboard/ # Dashboard layer (extends app/) + └── app/components/dashboard/ # Dashboard-specific components +shared/ # Shared code (client + server) + ├── schemas/ # Zod validation schemas + └── types/ # Shared TypeScript types +server/ # Nitro server (Cloudflare Workers) + ├── api/ # API endpoints (method suffix: create.post.ts) + └── utils/ # Server utilities (auto-imported) +tests/ # Vitest tests (Cloudflare Workers pool) +``` + +## Commands + +Use **pnpm** (v10+) with **Node.js 22+**. + +```bash +# Development +pnpm dev # Start dev server (port 7465) +pnpm build # Production build +pnpm preview # Worker preview via wrangler +pnpm lint:fix # ESLint with auto-fix (ALWAYS run before commit) +pnpm types:check # TypeScript type check + +# Testing (Vitest + @cloudflare/vitest-pool-workers) +pnpm vitest # Watch mode +pnpm vitest run # CI mode (run once) +pnpm vitest tests/api/link.spec.ts # Single test file +pnpm vitest -t "creates new link" # Match test name pattern + +# Deployment +pnpm deploy:pages # Deploy to Cloudflare Pages +pnpm deploy:worker # Deploy to Cloudflare Workers +``` + +## Code Style + +Uses `@antfu/eslint-config` with `eslint-plugin-better-tailwindcss`. Run `pnpm lint:fix` before committing. + +**Formatting**: 2-space indent | Single quotes | No semicolons | Trailing commas + +### TypeScript + +- Use TypeScript everywhere; prefer `interface` for objects, `type` for unions/aliases +- Avoid `any`; use proper types or `unknown` +- Use Zod for runtime validation in `shared/schemas/` +- Export types with `export type` for type-only exports + +```typescript +// shared/schemas/link.ts - shared validation +export const LinkSchema = z.object({ + id: z.string().trim().max(26), + url: z.string().trim().url().max(2048), + slug: z.string().trim().max(2048).regex(slugRegex), +}) +export type Link = z.infer +``` + +### Vue Components + +Use ` + + +``` + +### Imports + +- **Prefer Nuxt auto-imports**: `ref`, `computed`, `useFetch`, `useState`, `useRuntimeConfig`, etc. +- **Explicit imports for**: external libs, types (`import type { Link } from '@/types'`), icons (`import { Copy } from 'lucide-vue-next'`) +- **Server utils are auto-imported**: Functions in `server/utils/` are available globally in server code + +### Naming Conventions + +| Item | Convention | Example | +| -------------- | ---------------- | ------------------ | +| Components | PascalCase | `LinkEditor.vue` | +| Composables | `use` prefix | `useAuthToken()` | +| API routes | method suffix | `create.post.ts` | +| Directories | kebab-case | `dashboard/links/` | +| Functions/vars | camelCase | `getLink` | +| Constants | UPPER_SNAKE_CASE | `TOKEN_KEY` | + +### Error Handling + +```typescript +// Server API - use createError for HTTP errors +export default eventHandler(async (event) => { + const link = await readValidatedBody(event, LinkSchema.parse) + if (existingLink) { + throw createError({ status: 409, statusText: 'Link already exists' }) + } +}) +``` + +## Cloudflare Bindings + +Access via destructuring `event.context`: + +```typescript +const { cloudflare } = event.context +const { KV, ANALYTICS, AI, R2 } = cloudflare.env +``` + +| Binding | Type | Purpose | +| ----------- | ---------------- | ---------------------------- | +| `KV` | Workers KV | Link storage (`link:{slug}`) | +| `ANALYTICS` | Analytics Engine | Click tracking & analytics | +| `AI` | Workers AI | AI-powered slug generation | +| `R2` | R2 Bucket | Image uploads & backup | + +## Testing Patterns + +Tests use `@cloudflare/vitest-pool-workers` with real Cloudflare bindings (single worker, shared storage). + +```typescript +import { generateMock } from '@anatine/zod-mock' +import { describe, expect, it } from 'vitest' +import { fetchWithAuth, postJson } from '../utils' + +describe.sequential('/api/link/create', () => { + it('creates new link with valid data', async () => { + const response = await postJson('/api/link/create', { url: 'https://example.com', slug: 'test' }) + expect(response.status).toBe(201) + }) +}) +``` + +**Test utilities** (`tests/utils.ts`): + +- `fetchWithAuth(path, options)` - GET with auth header +- `postJson(path, body, withAuth?)` - POST JSON with optional auth +- `putJson(path, body, withAuth?)` - PUT JSON with optional auth +- `fetch(path, options)` - Raw fetch without auth + +Use `describe.sequential` for tests that share state (most API tests). + +## UI Components + +- Use shadcn-vue from `app/components/ui/` - **Never edit** (auto-generated) +- Use `ResponsiveModal` for mobile-optimized dialogs +- Use Tailwind CSS v4 for styling +- Use static English for `aria-label` (no `$t()` translations) +- Icons from `lucide-vue-next` + +## Commits + +Follow Conventional Commits: `feat:`, `fix:`, `docs:`, `chore:`, `refactor:` + +## Pre-commit + +`simple-git-hooks` runs `lint-staged` on commit, auto-runs `eslint --fix` on staged files. + +## API Route Patterns + +API routes use method suffix convention: + +- `create.post.ts` → `POST /api/link/create` +- `query.get.ts` → `GET /api/link/query` +- `edit.put.ts` → `PUT /api/link/edit` + +Server utils in `server/utils/` are auto-imported: + +- `getLink(event, slug)` - Fetch link from KV +- `putLink(event, link)` - Store link in KV +- `deleteLink(event, slug)` - Remove link from KV +- `normalizeSlug(event, slug)` - Case normalization +- `buildShortLink(event, slug)` - Construct full URL diff --git a/CLAUDE.md b/CLAUDE.md index ac534a310..47dc3e3d8 120000 --- a/CLAUDE.md +++ b/CLAUDE.md @@ -1 +1 @@ -AGENT.md \ No newline at end of file +AGENTS.md \ No newline at end of file diff --git a/GEMINI.md b/GEMINI.md deleted file mode 120000 index ac534a310..000000000 --- a/GEMINI.md +++ /dev/null @@ -1 +0,0 @@ -AGENT.md \ No newline at end of file diff --git a/README.md b/README.md index e4b10e30b..170c8492d 100644 --- a/README.md +++ b/README.md @@ -5,7 +5,7 @@ ccbikai/Sink | Trendshift @@ -35,7 +35,7 @@ /> -[DeepWiki](https://deepwiki.com/ccbikai/Sink) +[DeepWiki](https://deepwiki.com/miantiao-me/Sink) ![Cloudflare](https://img.shields.io/badge/Cloudflare-F69652?style=flat&logo=cloudflare&logoColor=white) ![Nuxt](https://img.shields.io/badge/Nuxt-00DC82?style=flat&logo=nuxtdotjs&logoColor=white) ![Tailwind CSS](https://img.shields.io/badge/Tailwind%20CSS-06B6D4?style=flat&logo=tailwindcss&logoColor=white) @@ -47,12 +47,19 @@ ## ✨ Features -- **URL Shortening:** Compress your URLs to their minimal length. -- **Analytics:** Monitor link analytics and gather insightful statistics. -- **Serverless:** Deploy without the need for traditional servers. -- **Customizable Slug:** Support for personalized slugs and case sensitivity. +- **🔗 URL Shortening:** Compress your URLs to their minimal length. +- **📈 Analytics:** Monitor link analytics and gather insightful statistics. +- **☁️ Serverless:** Deploy without the need for traditional servers. +- **🎨 Customizable Slug:** Support for personalized slugs and case sensitivity. - **🪄 AI Slug:** Leverage AI to generate slugs. -- **Link Expiration:** Set expiration dates for your links. +- **⏰ Link Expiration:** Set expiration dates for your links. +- **📱 Device Routing:** Redirect iOS/Android users to different URLs (App Store links). +- **🖼️ OpenGraph Preview:** Custom social media previews with title, description, and image. +- **📊 Real-time Analytics:** Live 3D globe visualization and real-time event logs. +- **🔲 QR Code:** Generate QR codes for your short links. +- **📦 Import/Export:** Bulk migration via JSON/CSV files. +- **🌍 Multi-language:** Full i18n support for the dashboard. +- **🌙 Dark Mode:** Light, dark, and system theme support. ## 🪧 Demo @@ -83,6 +90,7 @@ Site Token: SinkCool We welcome your contributions and PRs. - [x] Browser Extension - [Sink Tool](https://github.com/zhuzhuyule/sink-extension) +- [x] Chrome Extension - [Sink Quick Shorten](https://chromewebstore.google.com/detail/sink-quick-shorten/emlojomjpenjgkaphajcokijobpkejih) - [x] Raycast Extension - [Raycast-Sink](https://github.com/foru17/raycast-sink) - [x] Apple Shortcuts - [Sink Shortcuts](https://s.search1api.com/sink001) - [x] iOS App - [Sink](https://apps.apple.com/app/id6745417598) @@ -105,6 +113,14 @@ We currently support deployment to [Cloudflare Workers](./docs/deployment/worker [API Docs](./docs/api.md) +## 🤖 AI Skills + +Install Sink AI Skills for enhanced coding assistance: + +```bash +npx skills add miantiao-me/sink +``` + ## 🧰 MCP We currently do not support native MCP Server, but we have OpenAPI documentation, and you can use the following method to support MCP. @@ -124,7 +140,7 @@ We currently do not support native MCP Server, but we have OpenAPI documentation "env": { "OPENAPI_SPEC_URL": "https://sink.cool/_docs/openapi.json", "API_KEY": "SinkCool", - "TOOL_WHITELIST": "/api/link/create" + "TOOL_WHITELIST": "/api/link" } } } @@ -140,8 +156,9 @@ We currently do not support native MCP Server, but we have OpenAPI documentation 1. [**Cloudflare**](https://www.cloudflare.com/) 2. [**NuxtHub**](https://hub.nuxt.com/) 3. [**Astroship**](https://astroship.web3templates.com/) +4. [**Tailark**](https://tailark.com/) ## ☕ Sponsor -1. [Follow Me on X(Twitter)](https://404.li/kai). -2. [Become a sponsor to on GitHub](https://github.com/sponsors/ccbikai). +1. [Follow Me on X(Twitter)](https://404.li/x). +2. [Become a sponsor to on GitHub](https://github.com/sponsors/miantiao-me). diff --git a/app/app.config.ts b/app/app.config.ts index 2d16db23f..64a604c6f 100644 --- a/app/app.config.ts +++ b/app/app.config.ts @@ -1,11 +1,9 @@ export default defineAppConfig({ title: 'Sink', - email: 'sink.cool@miantiao.me', - github: 'https://github.com/ccbikai/sink', - twitter: 'https://sink.cool/kai', + github: 'https://github.com/miantiao-me/sink', + coffee: 'https://sink.cool/coffee', + twitter: 'https://sink.cool/x', telegram: 'https://sink.cool/telegram', - mastodon: 'https://sink.cool/mastodon', - blog: 'https://sink.cool/blog', description: 'A Simple / Speedy / Secure Link Shortener with Analytics, 100% run on Cloudflare.', image: 'https://sink.cool/banner.png', previewTTL: 300, // 5 minutes diff --git a/app/app.vue b/app/app.vue index f2897c7bb..f079b2846 100644 --- a/app/app.vue +++ b/app/app.vue @@ -1,8 +1,11 @@ - + + diff --git a/app/components/SwitchLanguage.vue b/app/components/SwitchLanguage.vue index 183a2d4dd..59fcfdea3 100644 --- a/app/components/SwitchLanguage.vue +++ b/app/components/SwitchLanguage.vue @@ -1,21 +1,14 @@ -