sub-web Agent Skills & AI Rules

GitHub

JavaScript repository by @CareyWang. AI agent rules & system prompts with 6.4k stars.

6,404 stars JavaScript JavaScript 1 file Β· ~896 tokens
RAW Rules
1 Discovered Rules β€’ ~2,416 Estimated Tokens
Quick:
Loading skill content...

AI Agent Protocol & Instructions

AGENTS.md root-instruction ~2,416 tokens
Raw
# AGENTS GUIDE

Vue 3 + Vite 8 SPA with Element Plus. Keep changes small, follow existing patterns, avoid refactors during fixes.

## Quick Facts

- Framework: Vue 3 (Options API)
- Build tool: Vite 8
- UI: Element Plus 2 (icons via `@element-plus/icons-vue`)
- Router: Vue Router 4 (`createWebHistory`, base from `import.meta.env.BASE_URL`)
- Node: 24.x
- Regression tests use Node’s built-in test runner with the existing Vite loader; no additional test dependency

## Commands

| Command | Description |
|---------|-------------|
| `yarn install` | Install dependencies |
| `yarn dev` | Start dev server (host: 0.0.0.0) |
| `yarn build` | Production build |
| `yarn preview` | Preview production build locally |
| `yarn lint` | ESLint check |
| `node --test tests/subscription-workflows.test.mjs` | Subscription workflow regression tests |

## CI / Workflows

- **build.yml**: triggers on push/PR to `master` and `dev` β€” runs `yarn install --frozen-lockfile` + `yarn lint` + Node regression tests + `yarn build`, then uploads `dist/` as artifact (7-day retention)
- **docker-build-push.yml**: triggers on push to `master` β€” builds and pushes multi-arch image (`linux/amd64`, `linux/arm64`) to `careywong/subweb:latest`

## Repository Layout

```
src/
β”œβ”€β”€ main.js                      # App bootstrap, plugin registration, Vue mount
β”œβ”€β”€ App.vue
β”œβ”€β”€ router/index.js              # Vue Router (history mode)
β”œβ”€β”€ views/Subconverter.vue       # Main page
β”œβ”€β”€ components/
β”‚   β”œβ”€β”€ ConfigUploadDialog.vue   # Config upload dialog
β”‚   β”œβ”€β”€ UrlParseDialog.vue       # URL parse dialog
β”‚   └── SvgIcon/index.vue        # SVG icon wrapper component
β”œβ”€β”€ composables/
β”‚   β”œβ”€β”€ useSubscription.js       # Form defaults + URL generation and parsing rules
β”‚   β”œβ”€β”€ useSubscriptionForm.js   # Reactive form state + addCustomParam + saveSubUrl
β”‚   └── useGeneratedLinks.js     # Generated long/short link association + pending requests
β”œβ”€β”€ services/
β”‚   β”œβ”€β”€ backendService.js        # BackendService.getBackendVersion()
β”‚   β”œβ”€β”€ shortUrlService.js       # ShortUrlService.generateShortUrl(), resolveUrl()
β”‚   └── configUploadService.js   # Upload + copy attempt, returns { url, copied }
β”œβ”€β”€ config/
β”‚   β”œβ”€β”€ constants.js             # CONSTANTS (env-backed, DEFAULT_CLIENT_TYPE='clash')
β”‚   β”œβ”€β”€ client-types.js          # CLIENT_TYPES map (display label β†’ target value)
β”‚   └── remote-configs.js        # REMOTE_CONFIGS grouped options array
β”œβ”€β”€ utils/
β”‚   β”œβ”€β”€ storage.js               # getLocalStorageItem / setLocalStorageItem (TTL-based)
β”‚   β”œβ”€β”€ validators.js            # validateSubUrl β†’ { valid, message } | validateForm β†’ boolean
β”‚   β”œβ”€β”€ formatters.js            # formatVersion, formatErrorMessage, processSubUrl
β”‚   β”œβ”€β”€ clipboard.js             # copyText (Clipboard API + execCommand fallback)
β”‚   └── search.js                # Backend autocomplete search helper
β”œβ”€β”€ plugins/                     # setupXxx(app) registrations (element-plus, axios, device)
└── icons/
    β”œβ”€β”€ index.js                 # Registers SVG sprite
    └── svg/                     # SVG source files (e.g., github.svg)
services/                        # Docker Compose stack (subweb + myurls + redis)
```

## Key Modules

### `src/config/constants.js`
All values read from `import.meta.env` with `VITE_` prefix. Key constants:
- `DEFAULT_BACKEND` β€” appends `/sub?` to `VITE_SUBCONVERTER_DEFAULT_BACKEND`
- `DEFAULT_CLIENT_TYPE` β€” hardcoded `'clash'`
- `SHORT_URL_API`, `CONFIG_UPLOAD_API`, `PROJECT`, `BOT_LINK`, etc.

### `src/composables/useSubscriptionForm.js`
Returns a plain object merged into `data()` via spread. Form defaults come from `createSubscriptionForm()` in `useSubscription.js`. Default mode is advanced (`advanced: '2'`). `form.udp` is `null` when unspecified, or `true`/`false` when explicitly set.

### `src/composables/useSubscription.js`
`useSubscription(defaultBackend)` returns `makeUrl(form, advanced, customParams)` and synchronous `parseUrl(url)`. Generation owns backend selection and source normalization; invalid required fields produce an empty string. Parsing returns `{ success: true, form, customParams }` or `{ success: false, message }` without mutating existing state. Apply the returned form only on success. Missing Boolean options retain the existing import behavior (`false`); explicit UDP false differs from absence.

### `src/composables/useGeneratedLinks.js`
Store `useGeneratedLinks()` in `data()` and invoke its methods through that reactive object. `setLongUrl(url)` invalidates the old short link and pending results. `shorten(generateShortUrl)` ignores obsolete results and errors. `importUrl` selects the matching short link or the current long link.

### `src/services/`
Classes expose static methods. Axios operations take `$axios` first. `ShortUrlService.resolveUrl(input, fetchUrl = fetch)` owns redirect expansion; `target` query parameters identify direct conversion links. Upload response shape: `{ code: 0, data: { url }, msg }`; `uploadConfig($axios, content, copyText)` returns `{ url, copied }` and preserves the URL if copying fails. Short URL response shape: `{ Code: 1, ShortUrl, Message }`. See [Error handling](#error-handling) for notification ownership. Silent failures remain acceptable for `getBackendVersion`.

### `src/utils/storage.js`
TTL stored inside the JSON value as `{ setTime, ttl, expire, value }`. `expire` checked on every read; expired entries are removed automatically. TTL value comes from `VITE_CACHE_TTL` env var.

## Code Style

- Indentation: 2 spaces
- Quotes: single quotes preferred
- Semicolons: none. ESLint’s `semi: 0` disables enforcement, so review new code for this convention
- Vue component names: single-word allowed (`vue/multi-word-component-names: off`)
- `no-console` / `no-debugger`: error in production, off in dev
- ESLint extends: `plugin:vue/vue3-essential`, `eslint:recommended`
- Parser: `@babel/eslint-parser` with `requireConfigFile: false`

## Imports & Modules

- ES modules (`import`/`export`) throughout
- Absolute alias `@` maps to `src/` (see `vite.config.js`)
- Import order: core libs β†’ local config/utils β†’ services β†’ components
- Dynamic imports only for route lazy-load

## Vue Patterns

- Options API everywhere; do not introduce Composition API or `<script setup>`
- Component structure: `<template>`, `<script>`, `<style>`
- Reactive state in `data()`; derived state in `computed`
- Spread `...useSubscription(CONSTANTS.DEFAULT_BACKEND)` in `methods`; store generated-link state in `data()`
- `useSubscriptionForm()` spread via `...subscriptionForm` in `data()`
- Globals registered on `app.config.globalProperties` (`$axios`, `$getOS`, `$message`, `$notify`)
- Named slots only (`<template #header>`); `slot="x"` and `$listeners` do not exist in Vue 3
- Element Plus icon components exposed via `computed` (not `data`) to avoid reactive wrapping

## Icons

- SVG sprites via `vite-plugin-svg-icons`; icon dirs: `src/icons/svg`
- Usage: `<svg-icon icon-class="name" />`
- Symbol ID format: `icon-[name]`
- UI icons come from `@element-plus/icons-vue` as components; there are no `el-icon-*` font classes

## Environment Variables

- All env vars use `VITE_` prefix; access via `import.meta.env`
- Do not commit `.env.local`, `.env.*.local`
- Constants centralised in `src/config/constants.js` β€” do not scatter `import.meta.env` calls

## Input validation

- Use `src/utils/validators.js` for user-facing checks
- `validateSubUrl` returns `{ valid, message }`; `validateForm` returns boolean
- Do not throw for validation flow

## Error Handling

- Views display notifications through `this.$message.*` or `this.$notify`; other modules may return error messages or throw descriptive errors for operational failures
- Preserve the original `cause` when wrapping an operational error
- Silent failures acceptable only when UX demands it (e.g., backend version fetch)
- Use `formatErrorMessage` from `src/utils/formatters.js` for consistent error strings

## Docker

- Base images: `node:24-alpine` (build), `nginx:1.24-alpine` (runtime)
- Build: `yarn install && yarn build`, output copied to `/usr/share/nginx/html`
- Services compose stack in `services/` includes myurls + Redis

## Git Hygiene

- Do not commit `dist/`, `node_modules/`, `.env.local`, `.env.*.local`
- Avoid adding generated files

## Frontend Safety

- Avoid inline styles unless already present in nearby code
- Prefer Element Plus components and existing patterns
- Keep UI message strings consistent (mostly Chinese)
- `.el-form-item__content` and `el-row` are flex containers in Element Plus; use `justify-content` / explicit widths rather than `text-align` to position controls

## Performance

- Do not introduce heavy dependencies; prefer existing utilities
- Keep all network calls in `src/services/`

## Example Patterns

```js
// Route lazy-load
component: () => import('../views/Subconverter.vue')

// Plugin registration
export function setupAxios(app) {
  app.config.globalProperties.$axios = axios
}

// Service class
export class BackendService {
  static async getBackendVersion($axios) { ... }
}

// Composable (Options API style)
export function useSubscription(defaultBackend) {
  // Generation and parsing share private parameter rules.
  return { makeUrl, parseUrl }
}

// Spread composable into methods
methods: {
  ...useSubscription(CONSTANTS.DEFAULT_BACKEND)
}

// Spread form state into data()
data() {
  return { ...useSubscriptionForm(), otherField: '' }
}
```

## Verification

For import, generation, or upload compatibility reviews, read [Subscription workflow behavior changes](docs/changes/2026-09-06-subscription-workflows.md).

- `node --test tests/subscription-workflows.test.mjs`
- Single case: `node --test --test-name-pattern="invalid import leaves" tests/subscription-workflows.test.mjs`
- `yarn lint`
- `yarn build`
- Run `yarn dev` and smoke the main screen

## Notes for Agents

- Follow existing patterns; minimise scope
- No large refactors unless explicitly requested
- Do not introduce TypeScript or new tooling without approval
- Keep regression tests at module interfaces and page workflows; use controlled network and clipboard adapters