{"owner":"CareyWang","repo":"sub-web","hasSkills":true,"totalSkillsCount":1,"totalTokensCount":2416,"categories":["root-instruction"],"hasMcp":false,"mcpConfig":null,"found":["AGENTS.md"],"skills":{"AGENTS.md":"# AGENTS GUIDE\n\nVue 3 + Vite 8 SPA with Element Plus. Keep changes small, follow existing patterns, avoid refactors during fixes.\n\n## Quick Facts\n\n- Framework: Vue 3 (Options API)\n- Build tool: Vite 8\n- UI: Element Plus 2 (icons via `@element-plus/icons-vue`)\n- Router: Vue Router 4 (`createWebHistory`, base from `import.meta.env.BASE_URL`)\n- Node: 24.x\n- Regression tests use Node’s built-in test runner with the existing Vite loader; no additional test dependency\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `yarn install` | Install dependencies |\n| `yarn dev` | Start dev server (host: 0.0.0.0) |\n| `yarn build` | Production build |\n| `yarn preview` | Preview production build locally |\n| `yarn lint` | ESLint check |\n| `node --test tests/subscription-workflows.test.mjs` | Subscription workflow regression tests |\n\n## CI / Workflows\n\n- **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)\n- **docker-build-push.yml**: triggers on push to `master` — builds and pushes multi-arch image (`linux/amd64`, `linux/arm64`) to `careywong/subweb:latest`\n\n## Repository Layout\n\n```\nsrc/\n├── main.js                      # App bootstrap, plugin registration, Vue mount\n├── App.vue\n├── router/index.js              # Vue Router (history mode)\n├── views/Subconverter.vue       # Main page\n├── components/\n│   ├── ConfigUploadDialog.vue   # Config upload dialog\n│   ├── UrlParseDialog.vue       # URL parse dialog\n│   └── SvgIcon/index.vue        # SVG icon wrapper component\n├── composables/\n│   ├── useSubscription.js       # Form defaults + URL generation and parsing rules\n│   ├── useSubscriptionForm.js   # Reactive form state + addCustomParam + saveSubUrl\n│   └── useGeneratedLinks.js     # Generated long/short link association + pending requests\n├── services/\n│   ├── backendService.js        # BackendService.getBackendVersion()\n│   ├── shortUrlService.js       # ShortUrlService.generateShortUrl(), resolveUrl()\n│   └── configUploadService.js   # Upload + copy attempt, returns { url, copied }\n├── config/\n│   ├── constants.js             # CONSTANTS (env-backed, DEFAULT_CLIENT_TYPE='clash')\n│   ├── client-types.js          # CLIENT_TYPES map (display label → target value)\n│   └── remote-configs.js        # REMOTE_CONFIGS grouped options array\n├── utils/\n│   ├── storage.js               # getLocalStorageItem / setLocalStorageItem (TTL-based)\n│   ├── validators.js            # validateSubUrl → { valid, message } | validateForm → boolean\n│   ├── formatters.js            # formatVersion, formatErrorMessage, processSubUrl\n│   ├── clipboard.js             # copyText (Clipboard API + execCommand fallback)\n│   └── search.js                # Backend autocomplete search helper\n├── plugins/                     # setupXxx(app) registrations (element-plus, axios, device)\n└── icons/\n    ├── index.js                 # Registers SVG sprite\n    └── svg/                     # SVG source files (e.g., github.svg)\nservices/                        # Docker Compose stack (subweb + myurls + redis)\n```\n\n## Key Modules\n\n### `src/config/constants.js`\nAll values read from `import.meta.env` with `VITE_` prefix. Key constants:\n- `DEFAULT_BACKEND` — appends `/sub?` to `VITE_SUBCONVERTER_DEFAULT_BACKEND`\n- `DEFAULT_CLIENT_TYPE` — hardcoded `'clash'`\n- `SHORT_URL_API`, `CONFIG_UPLOAD_API`, `PROJECT`, `BOT_LINK`, etc.\n\n### `src/composables/useSubscriptionForm.js`\nReturns 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.\n\n### `src/composables/useSubscription.js`\n`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.\n\n### `src/composables/useGeneratedLinks.js`\nStore `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.\n\n### `src/services/`\nClasses 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`.\n\n### `src/utils/storage.js`\nTTL 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.\n\n## Code Style\n\n- Indentation: 2 spaces\n- Quotes: single quotes preferred\n- Semicolons: none. ESLint’s `semi: 0` disables enforcement, so review new code for this convention\n- Vue component names: single-word allowed (`vue/multi-word-component-names: off`)\n- `no-console` / `no-debugger`: error in production, off in dev\n- ESLint extends: `plugin:vue/vue3-essential`, `eslint:recommended`\n- Parser: `@babel/eslint-parser` with `requireConfigFile: false`\n\n## Imports & Modules\n\n- ES modules (`import`/`export`) throughout\n- Absolute alias `@` maps to `src/` (see `vite.config.js`)\n- Import order: core libs → local config/utils → services → components\n- Dynamic imports only for route lazy-load\n\n## Vue Patterns\n\n- Options API everywhere; do not introduce Composition API or `<script setup>`\n- Component structure: `<template>`, `<script>`, `<style>`\n- Reactive state in `data()`; derived state in `computed`\n- Spread `...useSubscription(CONSTANTS.DEFAULT_BACKEND)` in `methods`; store generated-link state in `data()`\n- `useSubscriptionForm()` spread via `...subscriptionForm` in `data()`\n- Globals registered on `app.config.globalProperties` (`$axios`, `$getOS`, `$message`, `$notify`)\n- Named slots only (`<template #header>`); `slot=\"x\"` and `$listeners` do not exist in Vue 3\n- Element Plus icon components exposed via `computed` (not `data`) to avoid reactive wrapping\n\n## Icons\n\n- SVG sprites via `vite-plugin-svg-icons`; icon dirs: `src/icons/svg`\n- Usage: `<svg-icon icon-class=\"name\" />`\n- Symbol ID format: `icon-[name]`\n- UI icons come from `@element-plus/icons-vue` as components; there are no `el-icon-*` font classes\n\n## Environment Variables\n\n- All env vars use `VITE_` prefix; access via `import.meta.env`\n- Do not commit `.env.local`, `.env.*.local`\n- Constants centralised in `src/config/constants.js` — do not scatter `import.meta.env` calls\n\n## Input validation\n\n- Use `src/utils/validators.js` for user-facing checks\n- `validateSubUrl` returns `{ valid, message }`; `validateForm` returns boolean\n- Do not throw for validation flow\n\n## Error Handling\n\n- Views display notifications through `this.$message.*` or `this.$notify`; other modules may return error messages or throw descriptive errors for operational failures\n- Preserve the original `cause` when wrapping an operational error\n- Silent failures acceptable only when UX demands it (e.g., backend version fetch)\n- Use `formatErrorMessage` from `src/utils/formatters.js` for consistent error strings\n\n## Docker\n\n- Base images: `node:24-alpine` (build), `nginx:1.24-alpine` (runtime)\n- Build: `yarn install && yarn build`, output copied to `/usr/share/nginx/html`\n- Services compose stack in `services/` includes myurls + Redis\n\n## Git Hygiene\n\n- Do not commit `dist/`, `node_modules/`, `.env.local`, `.env.*.local`\n- Avoid adding generated files\n\n## Frontend Safety\n\n- Avoid inline styles unless already present in nearby code\n- Prefer Element Plus components and existing patterns\n- Keep UI message strings consistent (mostly Chinese)\n- `.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\n\n## Performance\n\n- Do not introduce heavy dependencies; prefer existing utilities\n- Keep all network calls in `src/services/`\n\n## Example Patterns\n\n```js\n// Route lazy-load\ncomponent: () => import('../views/Subconverter.vue')\n\n// Plugin registration\nexport function setupAxios(app) {\n  app.config.globalProperties.$axios = axios\n}\n\n// Service class\nexport class BackendService {\n  static async getBackendVersion($axios) { ... }\n}\n\n// Composable (Options API style)\nexport function useSubscription(defaultBackend) {\n  // Generation and parsing share private parameter rules.\n  return { makeUrl, parseUrl }\n}\n\n// Spread composable into methods\nmethods: {\n  ...useSubscription(CONSTANTS.DEFAULT_BACKEND)\n}\n\n// Spread form state into data()\ndata() {\n  return { ...useSubscriptionForm(), otherField: '' }\n}\n```\n\n## Verification\n\nFor import, generation, or upload compatibility reviews, read [Subscription workflow behavior changes](docs/changes/2026-09-06-subscription-workflows.md).\n\n- `node --test tests/subscription-workflows.test.mjs`\n- Single case: `node --test --test-name-pattern=\"invalid import leaves\" tests/subscription-workflows.test.mjs`\n- `yarn lint`\n- `yarn build`\n- Run `yarn dev` and smoke the main screen\n\n## Notes for Agents\n\n- Follow existing patterns; minimise scope\n- No large refactors unless explicitly requested\n- Do not introduce TypeScript or new tooling without approval\n- Keep regression tests at module interfaces and page workflows; use controlled network and clipboard adapters\n"},"files":{"AGENTS.md":"# AGENTS GUIDE\n\nVue 3 + Vite 8 SPA with Element Plus. Keep changes small, follow existing patterns, avoid refactors during fixes.\n\n## Quick Facts\n\n- Framework: Vue 3 (Options API)\n- Build tool: Vite 8\n- UI: Element Plus 2 (icons via `@element-plus/icons-vue`)\n- Router: Vue Router 4 (`createWebHistory`, base from `import.meta.env.BASE_URL`)\n- Node: 24.x\n- Regression tests use Node’s built-in test runner with the existing Vite loader; no additional test dependency\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `yarn install` | Install dependencies |\n| `yarn dev` | Start dev server (host: 0.0.0.0) |\n| `yarn build` | Production build |\n| `yarn preview` | Preview production build locally |\n| `yarn lint` | ESLint check |\n| `node --test tests/subscription-workflows.test.mjs` | Subscription workflow regression tests |\n\n## CI / Workflows\n\n- **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)\n- **docker-build-push.yml**: triggers on push to `master` — builds and pushes multi-arch image (`linux/amd64`, `linux/arm64`) to `careywong/subweb:latest`\n\n## Repository Layout\n\n```\nsrc/\n├── main.js                      # App bootstrap, plugin registration, Vue mount\n├── App.vue\n├── router/index.js              # Vue Router (history mode)\n├── views/Subconverter.vue       # Main page\n├── components/\n│   ├── ConfigUploadDialog.vue   # Config upload dialog\n│   ├── UrlParseDialog.vue       # URL parse dialog\n│   └── SvgIcon/index.vue        # SVG icon wrapper component\n├── composables/\n│   ├── useSubscription.js       # Form defaults + URL generation and parsing rules\n│   ├── useSubscriptionForm.js   # Reactive form state + addCustomParam + saveSubUrl\n│   └── useGeneratedLinks.js     # Generated long/short link association + pending requests\n├── services/\n│   ├── backendService.js        # BackendService.getBackendVersion()\n│   ├── shortUrlService.js       # ShortUrlService.generateShortUrl(), resolveUrl()\n│   └── configUploadService.js   # Upload + copy attempt, returns { url, copied }\n├── config/\n│   ├── constants.js             # CONSTANTS (env-backed, DEFAULT_CLIENT_TYPE='clash')\n│   ├── client-types.js          # CLIENT_TYPES map (display label → target value)\n│   └── remote-configs.js        # REMOTE_CONFIGS grouped options array\n├── utils/\n│   ├── storage.js               # getLocalStorageItem / setLocalStorageItem (TTL-based)\n│   ├── validators.js            # validateSubUrl → { valid, message } | validateForm → boolean\n│   ├── formatters.js            # formatVersion, formatErrorMessage, processSubUrl\n│   ├── clipboard.js             # copyText (Clipboard API + execCommand fallback)\n│   └── search.js                # Backend autocomplete search helper\n├── plugins/                     # setupXxx(app) registrations (element-plus, axios, device)\n└── icons/\n    ├── index.js                 # Registers SVG sprite\n    └── svg/                     # SVG source files (e.g., github.svg)\nservices/                        # Docker Compose stack (subweb + myurls + redis)\n```\n\n## Key Modules\n\n### `src/config/constants.js`\nAll values read from `import.meta.env` with `VITE_` prefix. Key constants:\n- `DEFAULT_BACKEND` — appends `/sub?` to `VITE_SUBCONVERTER_DEFAULT_BACKEND`\n- `DEFAULT_CLIENT_TYPE` — hardcoded `'clash'`\n- `SHORT_URL_API`, `CONFIG_UPLOAD_API`, `PROJECT`, `BOT_LINK`, etc.\n\n### `src/composables/useSubscriptionForm.js`\nReturns 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.\n\n### `src/composables/useSubscription.js`\n`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.\n\n### `src/composables/useGeneratedLinks.js`\nStore `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.\n\n### `src/services/`\nClasses 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`.\n\n### `src/utils/storage.js`\nTTL 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.\n\n## Code Style\n\n- Indentation: 2 spaces\n- Quotes: single quotes preferred\n- Semicolons: none. ESLint’s `semi: 0` disables enforcement, so review new code for this convention\n- Vue component names: single-word allowed (`vue/multi-word-component-names: off`)\n- `no-console` / `no-debugger`: error in production, off in dev\n- ESLint extends: `plugin:vue/vue3-essential`, `eslint:recommended`\n- Parser: `@babel/eslint-parser` with `requireConfigFile: false`\n\n## Imports & Modules\n\n- ES modules (`import`/`export`) throughout\n- Absolute alias `@` maps to `src/` (see `vite.config.js`)\n- Import order: core libs → local config/utils → services → components\n- Dynamic imports only for route lazy-load\n\n## Vue Patterns\n\n- Options API everywhere; do not introduce Composition API or `<script setup>`\n- Component structure: `<template>`, `<script>`, `<style>`\n- Reactive state in `data()`; derived state in `computed`\n- Spread `...useSubscription(CONSTANTS.DEFAULT_BACKEND)` in `methods`; store generated-link state in `data()`\n- `useSubscriptionForm()` spread via `...subscriptionForm` in `data()`\n- Globals registered on `app.config.globalProperties` (`$axios`, `$getOS`, `$message`, `$notify`)\n- Named slots only (`<template #header>`); `slot=\"x\"` and `$listeners` do not exist in Vue 3\n- Element Plus icon components exposed via `computed` (not `data`) to avoid reactive wrapping\n\n## Icons\n\n- SVG sprites via `vite-plugin-svg-icons`; icon dirs: `src/icons/svg`\n- Usage: `<svg-icon icon-class=\"name\" />`\n- Symbol ID format: `icon-[name]`\n- UI icons come from `@element-plus/icons-vue` as components; there are no `el-icon-*` font classes\n\n## Environment Variables\n\n- All env vars use `VITE_` prefix; access via `import.meta.env`\n- Do not commit `.env.local`, `.env.*.local`\n- Constants centralised in `src/config/constants.js` — do not scatter `import.meta.env` calls\n\n## Input validation\n\n- Use `src/utils/validators.js` for user-facing checks\n- `validateSubUrl` returns `{ valid, message }`; `validateForm` returns boolean\n- Do not throw for validation flow\n\n## Error Handling\n\n- Views display notifications through `this.$message.*` or `this.$notify`; other modules may return error messages or throw descriptive errors for operational failures\n- Preserve the original `cause` when wrapping an operational error\n- Silent failures acceptable only when UX demands it (e.g., backend version fetch)\n- Use `formatErrorMessage` from `src/utils/formatters.js` for consistent error strings\n\n## Docker\n\n- Base images: `node:24-alpine` (build), `nginx:1.24-alpine` (runtime)\n- Build: `yarn install && yarn build`, output copied to `/usr/share/nginx/html`\n- Services compose stack in `services/` includes myurls + Redis\n\n## Git Hygiene\n\n- Do not commit `dist/`, `node_modules/`, `.env.local`, `.env.*.local`\n- Avoid adding generated files\n\n## Frontend Safety\n\n- Avoid inline styles unless already present in nearby code\n- Prefer Element Plus components and existing patterns\n- Keep UI message strings consistent (mostly Chinese)\n- `.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\n\n## Performance\n\n- Do not introduce heavy dependencies; prefer existing utilities\n- Keep all network calls in `src/services/`\n\n## Example Patterns\n\n```js\n// Route lazy-load\ncomponent: () => import('../views/Subconverter.vue')\n\n// Plugin registration\nexport function setupAxios(app) {\n  app.config.globalProperties.$axios = axios\n}\n\n// Service class\nexport class BackendService {\n  static async getBackendVersion($axios) { ... }\n}\n\n// Composable (Options API style)\nexport function useSubscription(defaultBackend) {\n  // Generation and parsing share private parameter rules.\n  return { makeUrl, parseUrl }\n}\n\n// Spread composable into methods\nmethods: {\n  ...useSubscription(CONSTANTS.DEFAULT_BACKEND)\n}\n\n// Spread form state into data()\ndata() {\n  return { ...useSubscriptionForm(), otherField: '' }\n}\n```\n\n## Verification\n\nFor import, generation, or upload compatibility reviews, read [Subscription workflow behavior changes](docs/changes/2026-09-06-subscription-workflows.md).\n\n- `node --test tests/subscription-workflows.test.mjs`\n- Single case: `node --test --test-name-pattern=\"invalid import leaves\" tests/subscription-workflows.test.mjs`\n- `yarn lint`\n- `yarn build`\n- Run `yarn dev` and smoke the main screen\n\n## Notes for Agents\n\n- Follow existing patterns; minimise scope\n- No large refactors unless explicitly requested\n- Do not introduce TypeScript or new tooling without approval\n- Keep regression tests at module interfaces and page workflows; use controlled network and clipboard adapters\n"},"items":[{"name":"AGENTS.md","path":"AGENTS.md","rawUrl":"https://raw.githubusercontent.com/CareyWang/sub-web/HEAD/AGENTS.md","title":"AI Agent Protocol & Instructions","category":"root-instruction","format":"markdown","content":"# AGENTS GUIDE\n\nVue 3 + Vite 8 SPA with Element Plus. Keep changes small, follow existing patterns, avoid refactors during fixes.\n\n## Quick Facts\n\n- Framework: Vue 3 (Options API)\n- Build tool: Vite 8\n- UI: Element Plus 2 (icons via `@element-plus/icons-vue`)\n- Router: Vue Router 4 (`createWebHistory`, base from `import.meta.env.BASE_URL`)\n- Node: 24.x\n- Regression tests use Node’s built-in test runner with the existing Vite loader; no additional test dependency\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `yarn install` | Install dependencies |\n| `yarn dev` | Start dev server (host: 0.0.0.0) |\n| `yarn build` | Production build |\n| `yarn preview` | Preview production build locally |\n| `yarn lint` | ESLint check |\n| `node --test tests/subscription-workflows.test.mjs` | Subscription workflow regression tests |\n\n## CI / Workflows\n\n- **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)\n- **docker-build-push.yml**: triggers on push to `master` — builds and pushes multi-arch image (`linux/amd64`, `linux/arm64`) to `careywong/subweb:latest`\n\n## Repository Layout\n\n```\nsrc/\n├── main.js                      # App bootstrap, plugin registration, Vue mount\n├── App.vue\n├── router/index.js              # Vue Router (history mode)\n├── views/Subconverter.vue       # Main page\n├── components/\n│   ├── ConfigUploadDialog.vue   # Config upload dialog\n│   ├── UrlParseDialog.vue       # URL parse dialog\n│   └── SvgIcon/index.vue        # SVG icon wrapper component\n├── composables/\n│   ├── useSubscription.js       # Form defaults + URL generation and parsing rules\n│   ├── useSubscriptionForm.js   # Reactive form state + addCustomParam + saveSubUrl\n│   └── useGeneratedLinks.js     # Generated long/short link association + pending requests\n├── services/\n│   ├── backendService.js        # BackendService.getBackendVersion()\n│   ├── shortUrlService.js       # ShortUrlService.generateShortUrl(), resolveUrl()\n│   └── configUploadService.js   # Upload + copy attempt, returns { url, copied }\n├── config/\n│   ├── constants.js             # CONSTANTS (env-backed, DEFAULT_CLIENT_TYPE='clash')\n│   ├── client-types.js          # CLIENT_TYPES map (display label → target value)\n│   └── remote-configs.js        # REMOTE_CONFIGS grouped options array\n├── utils/\n│   ├── storage.js               # getLocalStorageItem / setLocalStorageItem (TTL-based)\n│   ├── validators.js            # validateSubUrl → { valid, message } | validateForm → boolean\n│   ├── formatters.js            # formatVersion, formatErrorMessage, processSubUrl\n│   ├── clipboard.js             # copyText (Clipboard API + execCommand fallback)\n│   └── search.js                # Backend autocomplete search helper\n├── plugins/                     # setupXxx(app) registrations (element-plus, axios, device)\n└── icons/\n    ├── index.js                 # Registers SVG sprite\n    └── svg/                     # SVG source files (e.g., github.svg)\nservices/                        # Docker Compose stack (subweb + myurls + redis)\n```\n\n## Key Modules\n\n### `src/config/constants.js`\nAll values read from `import.meta.env` with `VITE_` prefix. Key constants:\n- `DEFAULT_BACKEND` — appends `/sub?` to `VITE_SUBCONVERTER_DEFAULT_BACKEND`\n- `DEFAULT_CLIENT_TYPE` — hardcoded `'clash'`\n- `SHORT_URL_API`, `CONFIG_UPLOAD_API`, `PROJECT`, `BOT_LINK`, etc.\n\n### `src/composables/useSubscriptionForm.js`\nReturns 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.\n\n### `src/composables/useSubscription.js`\n`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.\n\n### `src/composables/useGeneratedLinks.js`\nStore `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.\n\n### `src/services/`\nClasses 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`.\n\n### `src/utils/storage.js`\nTTL 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.\n\n## Code Style\n\n- Indentation: 2 spaces\n- Quotes: single quotes preferred\n- Semicolons: none. ESLint’s `semi: 0` disables enforcement, so review new code for this convention\n- Vue component names: single-word allowed (`vue/multi-word-component-names: off`)\n- `no-console` / `no-debugger`: error in production, off in dev\n- ESLint extends: `plugin:vue/vue3-essential`, `eslint:recommended`\n- Parser: `@babel/eslint-parser` with `requireConfigFile: false`\n\n## Imports & Modules\n\n- ES modules (`import`/`export`) throughout\n- Absolute alias `@` maps to `src/` (see `vite.config.js`)\n- Import order: core libs → local config/utils → services → components\n- Dynamic imports only for route lazy-load\n\n## Vue Patterns\n\n- Options API everywhere; do not introduce Composition API or `<script setup>`\n- Component structure: `<template>`, `<script>`, `<style>`\n- Reactive state in `data()`; derived state in `computed`\n- Spread `...useSubscription(CONSTANTS.DEFAULT_BACKEND)` in `methods`; store generated-link state in `data()`\n- `useSubscriptionForm()` spread via `...subscriptionForm` in `data()`\n- Globals registered on `app.config.globalProperties` (`$axios`, `$getOS`, `$message`, `$notify`)\n- Named slots only (`<template #header>`); `slot=\"x\"` and `$listeners` do not exist in Vue 3\n- Element Plus icon components exposed via `computed` (not `data`) to avoid reactive wrapping\n\n## Icons\n\n- SVG sprites via `vite-plugin-svg-icons`; icon dirs: `src/icons/svg`\n- Usage: `<svg-icon icon-class=\"name\" />`\n- Symbol ID format: `icon-[name]`\n- UI icons come from `@element-plus/icons-vue` as components; there are no `el-icon-*` font classes\n\n## Environment Variables\n\n- All env vars use `VITE_` prefix; access via `import.meta.env`\n- Do not commit `.env.local`, `.env.*.local`\n- Constants centralised in `src/config/constants.js` — do not scatter `import.meta.env` calls\n\n## Input validation\n\n- Use `src/utils/validators.js` for user-facing checks\n- `validateSubUrl` returns `{ valid, message }`; `validateForm` returns boolean\n- Do not throw for validation flow\n\n## Error Handling\n\n- Views display notifications through `this.$message.*` or `this.$notify`; other modules may return error messages or throw descriptive errors for operational failures\n- Preserve the original `cause` when wrapping an operational error\n- Silent failures acceptable only when UX demands it (e.g., backend version fetch)\n- Use `formatErrorMessage` from `src/utils/formatters.js` for consistent error strings\n\n## Docker\n\n- Base images: `node:24-alpine` (build), `nginx:1.24-alpine` (runtime)\n- Build: `yarn install && yarn build`, output copied to `/usr/share/nginx/html`\n- Services compose stack in `services/` includes myurls + Redis\n\n## Git Hygiene\n\n- Do not commit `dist/`, `node_modules/`, `.env.local`, `.env.*.local`\n- Avoid adding generated files\n\n## Frontend Safety\n\n- Avoid inline styles unless already present in nearby code\n- Prefer Element Plus components and existing patterns\n- Keep UI message strings consistent (mostly Chinese)\n- `.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\n\n## Performance\n\n- Do not introduce heavy dependencies; prefer existing utilities\n- Keep all network calls in `src/services/`\n\n## Example Patterns\n\n```js\n// Route lazy-load\ncomponent: () => import('../views/Subconverter.vue')\n\n// Plugin registration\nexport function setupAxios(app) {\n  app.config.globalProperties.$axios = axios\n}\n\n// Service class\nexport class BackendService {\n  static async getBackendVersion($axios) { ... }\n}\n\n// Composable (Options API style)\nexport function useSubscription(defaultBackend) {\n  // Generation and parsing share private parameter rules.\n  return { makeUrl, parseUrl }\n}\n\n// Spread composable into methods\nmethods: {\n  ...useSubscription(CONSTANTS.DEFAULT_BACKEND)\n}\n\n// Spread form state into data()\ndata() {\n  return { ...useSubscriptionForm(), otherField: '' }\n}\n```\n\n## Verification\n\nFor import, generation, or upload compatibility reviews, read [Subscription workflow behavior changes](docs/changes/2026-09-06-subscription-workflows.md).\n\n- `node --test tests/subscription-workflows.test.mjs`\n- Single case: `node --test --test-name-pattern=\"invalid import leaves\" tests/subscription-workflows.test.mjs`\n- `yarn lint`\n- `yarn build`\n- Run `yarn dev` and smoke the main screen\n\n## Notes for Agents\n\n- Follow existing patterns; minimise scope\n- No large refactors unless explicitly requested\n- Do not introduce TypeScript or new tooling without approval\n- Keep regression tests at module interfaces and page workflows; use controlled network and clipboard adapters\n","isInternal":false,"tokens":2416,"sizeBytes":10389}],"systemPromptSnippet":"<yakaai_skills repo=\"CareyWang/sub-web\">\n<!-- File: AGENTS.md (Tokens: ~2416 | Category: root-instruction) -->\n# AGENTS GUIDE\n\nVue 3 + Vite 8 SPA with Element Plus. Keep changes small, follow existing patterns, avoid refactors during fixes.\n\n## Quick Facts\n\n- Framework: Vue 3 (Options API)\n- Build tool: Vite 8\n- UI: Element Plus 2 (icons via `@element-plus/icons-vue`)\n- Router: Vue Router 4 (`createWebHistory`, base from `import.meta.env.BASE_URL`)\n- Node: 24.x\n- Regression tests use Node’s built-in test runner with the existing Vite loader; no additional test dependency\n\n## Commands\n\n| Command | Description |\n|---------|-------------|\n| `yarn install` | Install dependencies |\n| `yarn dev` | Start dev server (host: 0.0.0.0) |\n| `yarn build` | Production build |\n| `yarn preview` | Preview production build locally |\n| `yarn lint` | ESLint check |\n| `node --test tests/subscription-workflows.test.mjs` | Subscription workflow regression tests |\n\n## CI / Workflows\n\n- **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)\n- **docker-build-push.yml**: triggers on push to `master` — builds and pushes multi-arch image (`linux/amd64`, `linux/arm64`) to `careywong/subweb:latest`\n\n## Repository Layout\n\n```\nsrc/\n├── main.js                      # App bootstrap, plugin registration, Vue mount\n├── App.vue\n├── router/index.js              # Vue Router (history mode)\n├── views/Subconverter.vue       # Main page\n├── components/\n│   ├── ConfigUploadDialog.vue   # Config upload dialog\n│   ├── UrlParseDialog.vue       # URL parse dialog\n│   └── SvgIcon/index.vue        # SVG icon wrapper component\n├── composables/\n│   ├── useSubscription.js       # Form defaults + URL generation and parsing rules\n│   ├── useSubscriptionForm.js   # Reactive form state + addCustomParam + saveSubUrl\n│   └── useGeneratedLinks.js     # Generated long/short link association + pending requests\n├── services/\n│   ├── backendService.js        # BackendService.getBackendVersion()\n│   ├── shortUrlService.js       # ShortUrlService.generateShortUrl(), resolveUrl()\n│   └── configUploadService.js   # Upload + copy attempt, returns { url, copied }\n├── config/\n│   ├── constants.js             # CONSTANTS (env-backed, DEFAULT_CLIENT_TYPE='clash')\n│   ├── client-types.js          # CLIENT_TYPES map (display label → target value)\n│   └── remote-configs.js        # REMOTE_CONFIGS grouped options array\n├── utils/\n│   ├── storage.js               # getLocalStorageItem / setLocalStorageItem (TTL-based)\n│   ├── validators.js            # validateSubUrl → { valid, message } | validateForm → boolean\n│   ├── formatters.js            # formatVersion, formatErrorMessage, processSubUrl\n│   ├── clipboard.js             # copyText (Clipboard API + execCommand fallback)\n│   └── search.js                # Backend autocomplete search helper\n├── plugins/                     # setupXxx(app) registrations (element-plus, axios, device)\n└── icons/\n    ├── index.js                 # Registers SVG sprite\n    └── svg/                     # SVG source files (e.g., github.svg)\nservices/                        # Docker Compose stack (subweb + myurls + redis)\n```\n\n## Key Modules\n\n### `src/config/constants.js`\nAll values read from `import.meta.env` with `VITE_` prefix. Key constants:\n- `DEFAULT_BACKEND` — appends `/sub?` to `VITE_SUBCONVERTER_DEFAULT_BACKEND`\n- `DEFAULT_CLIENT_TYPE` — hardcoded `'clash'`\n- `SHORT_URL_API`, `CONFIG_UPLOAD_API`, `PROJECT`, `BOT_LINK`, etc.\n\n### `src/composables/useSubscriptionForm.js`\nReturns 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.\n\n### `src/composables/useSubscription.js`\n`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.\n\n### `src/composables/useGeneratedLinks.js`\nStore `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.\n\n### `src/services/`\nClasses 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`.\n\n### `src/utils/storage.js`\nTTL 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.\n\n## Code Style\n\n- Indentation: 2 spaces\n- Quotes: single quotes preferred\n- Semicolons: none. ESLint’s `semi: 0` disables enforcement, so review new code for this convention\n- Vue component names: single-word allowed (`vue/multi-word-component-names: off`)\n- `no-console` / `no-debugger`: error in production, off in dev\n- ESLint extends: `plugin:vue/vue3-essential`, `eslint:recommended`\n- Parser: `@babel/eslint-parser` with `requireConfigFile: false`\n\n## Imports & Modules\n\n- ES modules (`import`/`export`) throughout\n- Absolute alias `@` maps to `src/` (see `vite.config.js`)\n- Import order: core libs → local config/utils → services → components\n- Dynamic imports only for route lazy-load\n\n## Vue Patterns\n\n- Options API everywhere; do not introduce Composition API or `<script setup>`\n- Component structure: `<template>`, `<script>`, `<style>`\n- Reactive state in `data()`; derived state in `computed`\n- Spread `...useSubscription(CONSTANTS.DEFAULT_BACKEND)` in `methods`; store generated-link state in `data()`\n- `useSubscriptionForm()` spread via `...subscriptionForm` in `data()`\n- Globals registered on `app.config.globalProperties` (`$axios`, `$getOS`, `$message`, `$notify`)\n- Named slots only (`<template #header>`); `slot=\"x\"` and `$listeners` do not exist in Vue 3\n- Element Plus icon components exposed via `computed` (not `data`) to avoid reactive wrapping\n\n## Icons\n\n- SVG sprites via `vite-plugin-svg-icons`; icon dirs: `src/icons/svg`\n- Usage: `<svg-icon icon-class=\"name\" />`\n- Symbol ID format: `icon-[name]`\n- UI icons come from `@element-plus/icons-vue` as components; there are no `el-icon-*` font classes\n\n## Environment Variables\n\n- All env vars use `VITE_` prefix; access via `import.meta.env`\n- Do not commit `.env.local`, `.env.*.local`\n- Constants centralised in `src/config/constants.js` — do not scatter `import.meta.env` calls\n\n## Input validation\n\n- Use `src/utils/validators.js` for user-facing checks\n- `validateSubUrl` returns `{ valid, message }`; `validateForm` returns boolean\n- Do not throw for validation flow\n\n## Error Handling\n\n- Views display notifications through `this.$message.*` or `this.$notify`; other modules may return error messages or throw descriptive errors for operational failures\n- Preserve the original `cause` when wrapping an operational error\n- Silent failures acceptable only when UX demands it (e.g., backend version fetch)\n- Use `formatErrorMessage` from `src/utils/formatters.js` for consistent error strings\n\n## Docker\n\n- Base images: `node:24-alpine` (build), `nginx:1.24-alpine` (runtime)\n- Build: `yarn install && yarn build`, output copied to `/usr/share/nginx/html`\n- Services compose stack in `services/` includes myurls + Redis\n\n## Git Hygiene\n\n- Do not commit `dist/`, `node_modules/`, `.env.local`, `.env.*.local`\n- Avoid adding generated files\n\n## Frontend Safety\n\n- Avoid inline styles unless already present in nearby code\n- Prefer Element Plus components and existing patterns\n- Keep UI message strings consistent (mostly Chinese)\n- `.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\n\n## Performance\n\n- Do not introduce heavy dependencies; prefer existing utilities\n- Keep all network calls in `src/services/`\n\n## Example Patterns\n\n```js\n// Route lazy-load\ncomponent: () => import('../views/Subconverter.vue')\n\n// Plugin registration\nexport function setupAxios(app) {\n  app.config.globalProperties.$axios = axios\n}\n\n// Service class\nexport class BackendService {\n  static async getBackendVersion($axios) { ... }\n}\n\n// Composable (Options API style)\nexport function useSubscription(defaultBackend) {\n  // Generation and parsing share private parameter rules.\n  return { makeUrl, parseUrl }\n}\n\n// Spread composable into methods\nmethods: {\n  ...useSubscription(CONSTANTS.DEFAULT_BACKEND)\n}\n\n// Spread form state into data()\ndata() {\n  return { ...useSubscriptionForm(), otherField: '' }\n}\n```\n\n## Verification\n\nFor import, generation, or upload compatibility reviews, read [Subscription workflow behavior changes](docs/changes/2026-09-06-subscription-workflows.md).\n\n- `node --test tests/subscription-workflows.test.mjs`\n- Single case: `node --test --test-name-pattern=\"invalid import leaves\" tests/subscription-workflows.test.mjs`\n- `yarn lint`\n- `yarn build`\n- Run `yarn dev` and smoke the main screen\n\n## Notes for Agents\n\n- Follow existing patterns; minimise scope\n- No large refactors unless explicitly requested\n- Do not introduce TypeScript or new tooling without approval\n- Keep regression tests at module interfaces and page workflows; use controlled network and clipboard adapters\n</yakaai_skills>"}