Token导航 LogoToken导航

Agent Skill · 前端设计

typescript(TypeScript 开发)

用于辅助前端页面、组件、样式和交互逻辑的开发与维护。它适合让 Agent 生成或审查 React、Next.js、Vue、Tailwind、CSS 等相关代码,整理组件结构,或定位布局和性能问题。使用时需要结合项目现有设计系统、路由和构建方式,避免只生成孤立片段;涉及页面改动时,应配合本地预览和构建检查确认视觉效果。

typescript 是前端设计类 Agent Skill,可用宿主为 Codex、Claude、Cursor、Gemini CLI,通过GitHub 仓库获取,权限为需要联网;许可证为 MIT,第三方扫描已通过,按安装量在前端设计类的 10,216 个 Skill 里排第 2,439。

总安装 2,280周安装 95GitHub Stars 4,513下载 760核验 2026-05-01

安装说明

本站只整理中文说明,不托管安装包,也不代用户安装。
GitHub
许可证
MIT

安装方式

通过对话安装

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

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

命令行安装

复制命令到本机终端执行。不同来源提供的安装方式可能略有差异;本站展示可直接复制的安装命令,安装前请核对来源页面。

skills.shnpx skills
npx skills add https://github.com/epicenterhq/epicenter --skill typescript

这个 Skill 是做什么的

前端设计类 Skill 关注页面结构、视觉规范和组件写法,输出通常是可运行的界面代码。

typescript 用于 TypeScript 代码风格和规范维护,支持项目级命名约定和控制流模式优化。适用于联合类型、判别值和类型放置等场景,可与 arktype 和 TypeBox 等运行时验证库配合使用。使用时需遵循基线 TypeScript 默认配置,再加载专项指导,确保类型安全和代码一致性。

安装前建议确认权限范围和维护状态,避免触发不必要的文件修改或依赖变更。typescript 属于前端设计类 Skill,可作为该场景下的辅助能力补充。

来源仓库内路径:skills/typescript

上手步骤

4 步
  1. 1

    确认自己的宿主工具

    公开资料标注支持 Codex、Claude、Cursor、Gemini CLI;先确认自己用的是其中之一,再按对应工具的 Skills 目录放置。

  2. 2

    拿到安装命令

    可以直接复制的命令是 npx skills add https://github.com/epicenterhq/epicenter --skill typescript;命令里的占位符换成自己的路径或密钥再执行。

  3. 3

    安装后重启宿主

    命令执行完要完全退出宿主工具再打开,让新装的 Skill 被重新扫描到,否则列表里可能看不到。

  4. 4

    按最小权限试用

    它需要需要联网,先在测试目录或测试账号里跑,确认没问题再放到正式项目。

常见问题

6 条

typescript 怎么安装?

在终端执行 npx skills add https://github.com/epicenterhq/epicenter --skill typescript。装完完全退出宿主工具再打开,让它重新扫描本地 Skill。

typescript 支持哪些 AI 工具?

公开资料标注支持 Codex、Claude、Cursor、Gemini CLI。其他工具只要能读取本地 Skill 目录,一般也能手动放进去,但要自己确认触发方式是否一致。

typescript 需要什么权限?

权限级别是需要联网。该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

typescript 是免费的吗?

来源标注的许可证是 MIT。本站不代收任何费用,也不托管安装包;能否商用、要不要署名,以许可证原文为准。

typescript 用的人多吗?

公开数据是累计安装 2,280 次,近一周安装 95 次,下载 760 次,GitHub 4,513 星。这些数字来自第三方目录统计,只能当作热度参考,不代表质量或安全性。

typescript 安全吗?

第三方扫描结果为通过,但这只覆盖已知的静态检查项,不能替代自己读一遍源码和安装命令。

值得选的地方和要先想清楚的地方

值得选的地方

  • 同时适配 4 种工具,换客户端不用重新找替代品。
  • 3 个目录同时收录,说明这个 Skill 流传比较广。
  • 许可证明确为 MIT,商用和分发边界好判断。
  • 第三方扫描已通过,常见的高风险行为没有命中。

先想清楚的地方

  • 第三方目录整理,版本更新和行为细节以来源仓库为准。

适不适合你

更适合

  • 已经在用 Codex,想直接扩展它能力的人。
  • 需要处理前端设计类任务、又不想每次都手写提示词的人。
  • 需要许可证清楚、并且有第三方扫描结果的团队项目。

不太适合

  • 需要厂商级技术支持和 SLA 的生产系统。

前端设计类 Skill 对比

6 个同类
Skill总安装GitHub 星权限许可证扫描
frontend-design

前端设计 · Codex、Claude

8,823,360126,072只读MIT通过
vercel-composition-patterns

Vercel 组合模式 · Codex、Claude

3,858,81625,893需要联网MIT通过
vercel-react-native-skills

Vercel React native skills 前端 · Codex、Claude

2,468,66425,878只读MIT通过
adapt

adapt 搜索 · Codex、Claude

2,027,04023,125操作浏览器未明确通过
bolder

bolder 分析 · Codex、Claude

2,004,28823,130只读未明确通过
typescript

TypeScript 开发 · 本页

2,2804,513需要联网MIT通过

按安装量取同分类前几名;数字来自第三方目录统计,只作热度参考,不代表质量结论。

SKILL.md 原文

TypeScript Guidelines

Related Skills: See arktype for runtime type validation patterns. See typebox for TypeBox schema patterns. See testing for test file conventions.

When to Apply This Skill

Use this pattern when you need to:

  • Write or refactor TypeScript code with project-wide naming and style conventions.
  • Choose clear control-flow/value-mapping patterns for unions and discriminated values.
  • Apply baseline TypeScript defaults before loading specialized sub-topic guidance.

References

Load these on demand based on what you're working on:


Core Rules

  • Always use type instead of interface in TypeScript.
  • readonly only for arrays and maps: Never use readonly on primitive properties or object properties. The modifier is shallow and provides little protection for non-collection types. Use it only where mutation is a realistic footgun: // Good - readonly only on the array type Config = {version: number; vendor: string; items: readonly string[];}; // Bad - readonly everywhere is noise type Config = {readonly version: number; readonly vendor: string; readonly items: readonly string[];}; Exception: Match upstream library types exactly (e.g., standard-schema interfaces). See docs/articles/readonly-is-mostly-noise.md for rationale.
  • Acronyms in camelCase: Treat acronyms as single words, capitalizing only the first letter: // Correct - acronyms as words parseUrl(); defineKv(); readJson(); customerId; httpClient; // Incorrect - all-caps acronyms parseURL(); defineKV(); readJSON(); customerID; HTTPClient; Exception: Match existing platform APIs (e.g., XMLHttpRequest). See docs/articles/acronyms-in-camelcase.md for rationale.
  • TypeScript 5.5+ automatically infers type predicates in .filter() callbacks. Don't add manual type assertions: // Good - TypeScript infers the narrowed type automatically const filtered = items.filter((x) => x!== undefined); // Bad - unnecessary type predicate const filtered = items.filter((x): x is NonNullable<typeof x> => x!== undefined,);
  • When moving components to new locations, always update relative imports to absolute imports (e.g., change import Component from '../Component.svelte' to import Component from '$lib/components/Component.svelte')
  • Use .js extensions in relative imports: The monorepo uses "module": "preserve" in tsconfig, which requires explicit file extensions. Always use .js (not .ts) in relative import paths—TypeScript resolves .js to the corresponding .ts file at compile time: // Good —.js extension in relative imports import {parseSkill} from './parse.js'; import type {Skill} from './types.js'; // Bad — no extension (fails with module: preserve) import {parseSkill} from './parse'; // Bad —.ts extension (non-standard, won't resolve correctly) import {parseSkill} from './parse.ts'; This does NOT apply to package imports (import {type} from 'arktype') or path aliases (import Component from '$lib/components/Foo.svelte')—only bare relative paths.
  • export {} is only for barrel files: Every symbol is exported directly at its declaration (export type, export const, export function). The export {Foo} from './bar' re-export syntax is reserved for index.ts barrel files—that's their entire job. Don't add re-exports at the bottom of implementation files "for convenience"; they go unused, leave orphaned imports, and create a false second import path. // Good — direct export at declaration export type TablesHelper<T> = {...}; export const EncryptionKey = type({...}); export function createTables(...) {...} // Good — barrel re-exports in index.ts export {createTables} from './create-tables.js'; export type {TablesHelper} from './types.js'; // Bad — re-export at bottom of create-tables.ts export type {TablesHelper, TableDefinitions};
  • When functions are only used in the return statement of a factory/creator function, use object method shorthand syntax instead of defining them separately. For example, instead of: function myFunction() {const helper = () => {/*... */}; return {helper};} Use: function myFunction() {return {helper() {/*... */},};}
  • Prefer factory functions over classes: Use function createX() {return {...}} instead of class X {...}. Closures provide structural privacy—everything above the return statement is private by position, everything inside it is the public API. Classes mix private/protected/public members in arbitrary order, forcing you to scan every member and check its modifier. See docs/articles/closures-are-better-privacy-than-keywords.md for rationale.
  • Generic type parameters use T prefix + descriptive name: Never use single letters like S, D, K. Always prefix with T and use the full name: // Good — descriptive with T prefix function validate<TSchema extends StandardSchemaV1>(schema: TSchema) {...} type MapOptions<TDefs extends Record<string, Definition>> = {...}; function get<TKey extends string & keyof TDefs>(key: TKey) {...} // Bad — single letters function validate<S extends StandardSchemaV1>(schema: S) {...} type MapOptions<D extends Record<string, Definition>> = {...}; function get<K extends string & keyof D>(key: K) {...}
  • Destructure options in function signature, not the first line of the body: ` // Good — destructure in the signature export function createThing<T>({name, value, onError,}: ThingOptions<T>) {// function body starts here} // Bad — intermediate options parameter, destructured on first line export function createThing<T>(options: ThingOptions<T>) {const {name, value, onError} = options; //...} `
  • Don't annotate return types the compiler can infer: Let TypeScript infer return types on inner/private functions. Only annotate return types on exported public API functions when the inferred type is too complex or when you need to break circular inference. // Good — inner functions let TS infer function parseValue(raw: string | null) {if (raw === null) return defaultValue; return JSON.parse(raw);} // Bad — unnecessary return type annotation function parseValue(raw: string | null): SomeType {if (raw === null) return defaultValue; return JSON.parse(raw);}

Identity Checks: Brand, Don't Probe

When isFoo(x) is asking "is this the specific thing my factory returned," use a Symbol brand stamped at the factory, not a coincidental-property probe. Shape probes collide with look-alikes and rot as the type grows; the brand is unforgeable and survives normal object spreads.

// Smell — three coincidental properties stand in for identity.
// Any object that happens to have ydoc + dispose + Symbol.dispose passes.
function isDocumentHandle(value: unknown): value is DocumentHandle<Document> {
	if (value == null || typeof value !== 'object') return false;
	const record = value as Record<string | symbol, unknown>;
	return (
		'ydoc' in record &&
		typeof record.dispose === 'function' &&
		typeof record[Symbol.dispose] === 'function'
	);
}

// Better — brand stamped by the factory, one check carries the intent.
// Use `Symbol.for('<namespace>.<thing>')` — not `Symbol(...)` — so the brand
// survives module duplication (see "Cross-package brands" below).
export const DOCUMENT_HANDLE = Symbol.for('epicenter.document-handle');

function isDocumentHandle(value: unknown): value is DocumentHandle<Document> {
	return (
		value != null &&
		typeof value === 'object' &&
		DOCUMENT_HANDLE in value
	);
}

Cross-package brands: Symbol.for, never Symbol

Any brand that has to be recognized across a module boundary — CLI-walks-user-bundles, server-adapter-walks-workspace, AI-tool-bridge-walks-actions — must use the global symbol registry. Plain Symbol('name') creates a fresh reference per module evaluation; a monorepo that ends up with two instances of @epicenter/workspace (pnpm hoisting, dual CJS/ESM publish, bundler dedup miss, test vs. app resolution) gives each instance its own brand reference. defineX from copy A stamps symbol-A; isX from copy B checks for symbol-B; the identity check silently fails.

Symbol.for('epicenter.action') talks to a process-global registry keyed by the string. Every call anywhere returns the same reference. The brand survives duplication.

// Wrong — local reference; fails under module duplication
export const ACTION_BRAND = Symbol('epicenter.action');

// Right — registry-resolved; always the same reference
export const ACTION_BRAND = Symbol.for('epicenter.action');

Convention: namespace the key (epicenter.action, epicenter.document-handle), and centralize cross-package brand keys in one brands.ts per package so the duplication-safe identity set is visible and reviewable. The brand constant itself is an implementation detail — consumers import the isX guard, never the raw symbol.

When the brand can be local: if the factory and the check both live in the same file and the type never crosses a package boundary, plain Symbol() is fine. The Symbol.for rule is specifically for cross-package identity.

This rule is narrow. It does NOT apply to:

  • Union narrowing via presence — 'data' in result / 'error' in result on a wellcrafted Result, or 'error' in response on an OAuth response union. The union *is* the contract; the presence check discriminates it.
  • Discriminated union tags — switch (change.type). The tag is already a brand.
  • Protocol / feature detection — Symbol.dispose in x, Symbol.asyncIterator in x, typeof x.then === 'function'. These check *capability*, not identity.
  • Single-or-function config — typeof baseURL === 'function' to distinguish a value from a getter. A config API pattern, not a broken contract.
  • Node error inspection — 'code' in error on NodeJS.ErrnoException. Upstream type genuinely requires it.

When a shape probe IS the smell, the fix is usually upstream. If you're about to write isFoo(x) that shape-probes an internal factory's output, the factory should stamp a brand. If you're about to shape-probe user input or JSON.parse output, validate with arktype/typebox at the boundary — the probe accepts any object that happens to match; the schema rejects anything off-contract.

Factory output: flat objects, not prototype delegation

When a factory returns a "bag of data + a few lifecycle methods," spread the data and add the methods as own enumerable properties. Don't use Object.create(bundle) to inherit the data, and don't hide methods with non-enumerable Object.defineProperties.

// Smell — data lives on the prototype, methods are non-enumerable.
// Object.keys(handle) returns []; {...handle} spreads nothing;
// callers reach through Object.getPrototypeOf(handle) to iterate.
const handle = Object.create(bundle);
Object.defineProperties(handle, {
	dispose:          { value: () => {...} },
	[Symbol.dispose]: { value: () => {...} },
});

// Better — flat, own, enumerable. Spreads, Object.keys, and debuggers all work.
return {
	...bundle,
	dispose: () => {...},
	[Symbol.dispose]: () => {...},
	[DOCUMENT_HANDLE]: true,
};

If you're reaching for Object.create to get class-like delegation, either write a class or flatten — don't simulate one with the other. The only legitimate Object.defineProperty in this repo patches a Node-owned getter (process.stdout.isTTY) in a test; normal assignment doesn't work there.

Casts: never as any, rarely as unknown as T

as any in production code is a red flag: either the callee is over-narrow (fix the signature) or the caller is passing the wrong type (fix the call). as unknown as T double-casts that mask a real type error are the same smell in disguise — e.g., generateId() as unknown as BrandedId should be as string as BrandedId, or better, fix generateId's return type.

Legitimate cast exceptions:

  • Generics ceremony in typed builders — Object.assign(handler, {...}) as unknown as Query<T, U> when Object.assign erases the generic overload inference. Acceptable when the overload signature is the real contract; keep the cast at the innermost scope.
  • Test fixtures casting mocks — acceptable in *.test.ts, never leaked out of a test file.

Optional properties: ?. over in or truthiness

When a property is optional in the type (foo?: () => void, including symbol keys like [Symbol.asyncDispose]?: () => Promise<void>), access it with optional chaining. Don't in-check, don't cast, don't truthiness-check. The type already proves the call is safe; runtime probes are redundant and invite casts.

// Bad — runtime `in` check + cast
if (Symbol.asyncDispose in sink) {
  await (sink as AsyncDisposable)[Symbol.asyncDispose]();
}

// Bad — truthiness check before call
if (handler.onError) handler.onError(err);

// Good — optional chaining handles it
await sink[Symbol.asyncDispose]?.();
handler.onError?.(err);

Partial<AsyncDisposable> and optional-function property types compose cleanly with ?.() — no casts needed — and it works identically for string, symbol, and computed keys. Real example from the workspace-logger:

type LogSink = ((event: LogEvent) => void) & Partial<AsyncDisposable>;

for (const sink of sinks) await sink[Symbol.asyncDispose]?.();
// consoleSink has no dispose → no-op; jsonlFileSink has it → awaited

Boolean Naming: is/has/can Prefix

Boolean properties, variables, and parameters MUST use a predicate prefix that reads as a yes/no question:

  • is — state or identity: isEncrypted, isLoading, isVisible, isActive
  • has — possession or presence: hasToken, hasChildren, hasError
  • can — capability or permission: canWrite, canDelete, canUndo
// Good — reads as a question
type Config = {
	isEncrypted: boolean;
	isReadOnly: boolean;
	hasCustomTheme: boolean;
	canExport: boolean;
};

get isEncrypted() { return currentKey !== undefined; }
const isVisible = element.offsetParent !== null;
if (hasToken) { ... }

// Bad — ambiguous, doesn't read as yes/no
type Config = {
	encrypted: boolean;    // adjective without 'is'
	readOnly: boolean;     // could be a noun
	state: boolean;        // what state?
	mode: boolean;         // what mode?
};

This applies to:

  • Object/type properties (isActive: boolean)
  • Getter methods (get isEncrypted())
  • Local variables (const isValid =...)
  • Function parameters (function toggle(isEnabled: boolean))
  • Function return values when the function is a predicate (function isExpired(): boolean)

Exception: Match upstream library types exactly (e.g., tab.pinned, window.focused from APIs where the type is externally defined).

Switch Over If/Else for Value Comparison

When multiple if/else if branches compare the same variable against string literals (or other constant values), always use a switch statement instead. This applies to action types, status fields, file types, strategy names, or any discriminated value.

// Bad - if/else chain comparing the same variable
if (change.action === 'add') {
	handleAdd(change);
} else if (change.action === 'update') {
	handleUpdate(change);
} else if (change.action === 'delete') {
	handleDelete(change);
}

// Good - switch statement
switch (change.action) {
	case 'add':
		handleAdd(change);
		break;
	case 'update':
		handleUpdate(change);
		break;
	case 'delete':
		handleDelete(change);
		break;
}

Use fall-through for cases that share logic:

switch (change.action) {
	case 'add':
	case 'update': {
		applyChange(change);
		break;
	}
	case 'delete': {
		removeChange(change);
		break;
	}
}

Use block scoping ({}) when a case declares variables with let or const.

When NOT to use switch: early returns for type narrowing are fine as sequential if statements. If each branch returns immediately and the checks are narrowing a union type for subsequent code, keep them as if guards.

Exhaustiveness via default: x satisfies never

When switching over a closed type — a discriminated union, a defineErrors variant, a literal-string enum, a migration version — guard the switch with an exhaustiveness check so adding a new variant breaks the build until every site handles it.

// Good — adding a new RpcError variant fails the build here
switch (error.name) {
	case 'ActionNotFound':
		handleNotFound(error.action);
		return;
	case 'Timeout':
		handleTimeout(error.ms);
		return;
	case 'PeerOffline':
	case 'PeerLeft':
		handleDisconnect();
		return;
	case 'ActionFailed':
		handleFailure(error.cause);
		return;
	case 'Disconnected':
		handleDisconnect();
		return;
	default:
		error satisfies never;
}

Why satisfies never and not const _exhaustive: never = error; void _exhaustive;? Same compile-time guarantee, less emit, no unused-variable suppression dance.

// satisfies — type-level only, strips to the bare expression
default: error satisfies never;
// emits: default: error;

// const form — declares a real binding, needs `void` to silence unused-var
default: {
	const _exhaustive: never = error;
	void _exhaustive;
}
// emits: default: { const _exhaustive = error; void _exhaustive; }

satisfies (TS 4.9+) is the blessed idiom for "assert conformance without producing a value."

When NOT to add an exhaustive check:

  • Switches over open input — wire bytes (messageType from a binary protocol), HTTP status codes, file extensions from user paths, error names from external libraries you don't control. These need real default: handling (throw, return null, etc.) because unknown values are reachable at runtime.
  • Switches whose default: is doing intentional fallback (e.g., "anything else gets the noop").

The rule of thumb: if the type checker proves the input is one of N closed values AND adding an N+1th value should require updating this site, add satisfies never. Otherwise, leave the switch alone.

See docs/articles/switch-over-if-else-for-value-comparison.md for rationale.

Record Lookup Over Nested Ternaries

When an expression maps a finite set of known values to outputs, use a satisfies Record lookup instead of nested ternaries. This is the expression-level counterpart to "Switch Over If/Else": switch handles statements with side effects, record lookup handles value mappings.

// Bad - nested ternary
const tooltip = status === 'connected'
	? 'Connected'
	: status === 'connecting'
		? 'Connecting…'
		: 'Offline';

// Good - record lookup with exhaustive type checking
const tooltip = ({
	connected: 'Connected',
	connecting: 'Connecting…',
	offline: 'Offline',
} satisfies Record<SyncStatus, string>)[status];

satisfies Record<SyncStatus, string> gives you compile-time exhaustiveness: if SyncStatus gains a fourth value, TypeScript errors because the record is missing a key. Nested ternaries silently fall through to the else branch.

as const is unnecessary here. satisfies already validates the shape and value types. as const would narrow values to literal types ('Connected' instead of string), which adds no value when the output is just rendered or passed as a string.

When the record is used once, inline it. When it's shared or has 5+ entries, extract to a named constant.

See docs/articles/record-lookup-over-nested-ternaries.md for rationale.

Silent Fallback Smell

Not all ?? expressions are safe defaults. When the fallback creates state that other systems depend on, the nullish coalescing hides a broken invariant.

// Safe default — divergence doesn't matter
const timeout = options.timeout ?? 5000;

// SMELL — fallback creates divergent identity
// Two machines importing the same data silently get different IDs
const id = parsedId ?? generateId();

The test: does the fallback create state that must be consistent across systems? If yes, the ?? is masking a problem. Fix it by:

  • Self-healing: generate the value and write it back to the source, so the fallback never fires again
  • Throwing: make the invariant explicit—if the value should exist, its absence is an error
  • Warning: at minimum, make the fallback visible so silent divergence doesn't go unnoticed

Round-Trip Invariant

If you serialize and then deserialize, identity properties must survive:

// This must hold for any entity with stable identity:
const exported = serialize(entity);
const reimported = deserialize(exported);
assert(reimported.id === entity.id);

If an ID doesn't survive a full cycle, every system that references it by ID is broken—document handles, foreign keys, cache entries. The round-trip test is: "If I export to disk and import on a fresh machine, does everything still match?"

When designing parse/serialize pairs, decide which fields are identity (must survive round-trips) vs derived (can be recomputed). Persist identity fields explicitly—don't rely on matching by secondary keys to recover them.

平台分布

Claude Code

30.2%
按下载量换算230

OpenCode

21.97%
按下载量换算167

Gemini CLI

15.84%
按下载量换算120

Antigravity

13.66%
按下载量换算104

Codex

6.87%
按下载量换算52

Cursor

3.74%
按下载量换算28

安全审计

Gen Agent Trust Hub

通过

Socket

通过

Snyk

通过

权限和风险

需要联网

该 Skill 可能需要联网访问来源站点、仓库或外部 API;具体网络访问范围需要结合源码和 README 复核。

安装前确认

本站仅展示第三方公开信息,不托管安装包,不提供自动安装或运行环境。安装前应自行审查源码、依赖和命令行为。

继续浏览同类 Skills