KS
Killer-Skills

tfm-add-card — how to use tfm-add-card how to use tfm-add-card, tfm-add-card setup guide, tfm-add-card alternative, tfm-add-card vs TFM card implementation, what is tfm-add-card, tfm-add-card install, adding cards to TFM, TFM card development, tfm-add-card documentation

v1.0.0
GitHub

About this Skill

Perfect for TFM Developers needing streamlined card addition workflows with TypeScript and unit testing tfm-add-card is a skill that allows developers to add new cards to TFM by registering the card name, implementing the card, registering in manifest, and writing unit tests.

Features

Registers card names in src/common/cards/CardName.ts
Implements cards in src/server/cards/commission/<CardName>.ts
Registers cards in src/server/cards/commission/CommissionCardManifest.ts
Writes unit tests in tests/cards/commission/<CardName>.spec.ts
Supports CorporationCard and IProjectCard types
Requires confirmation of card name, display name, and card type

# Core Topics

Ender-Wiggin2019 Ender-Wiggin2019
[0]
[0]
Updated: 3/7/2026

Quality Score

Top 5%
42
Excellent
Based on code quality & docs
Installation
SYS Universal Install (Auto-Detect)
Cursor IDE Windsurf IDE VS Code IDE
> npx killer-skills add Ender-Wiggin2019/elo/tfm-add-card

Agent Capability Analysis

The tfm-add-card MCP Server by Ender-Wiggin2019 is an open-source Categories.community integration for Claude and other AI agents, enabling seamless task automation and capability expansion. Optimized for how to use tfm-add-card, tfm-add-card setup guide, tfm-add-card alternative.

Ideal Agent Persona

Perfect for TFM Developers needing streamlined card addition workflows with TypeScript and unit testing

Core Value

Empowers agents to extend TFM functionality by registering new card names in CardName.ts, implementing cards in CommissionCardManifest.ts, and writing unit tests in .spec.ts files, leveraging TypeScript for robust card development and integration

Capabilities Granted for tfm-add-card MCP Server

Registering new card types like CorporationCard or IProjectCard
Implementing custom card logic in src/server/cards/commission/
Writing unit tests for commission cards in tests/cards/commission/

! Prerequisites & Limits

  • Requires access to TFM source code
  • Must follow 4-step workflow for card addition
  • Limited to TFM environment with specific file structure
Project
SKILL.md
10.7 KB
.cursorrules
1.2 KB
package.json
240 B
Ready
UTF-8

# Tags

[No tags]
SKILL.md
Readonly

Add a New Card to TFM

Workflow

Adding a card requires exactly 4 steps:

  1. Register the card name in src/common/cards/CardName.ts
  2. Implement the card in src/server/cards/commission/<CardName>.ts
  3. Register in manifest in src/server/cards/commission/CommissionCardManifest.ts
  4. Write unit tests in tests/cards/commission/<CardName>.spec.ts

Before implementing, confirm with the user:

  • Card name and display name
  • Card type: CorporationCard or IProjectCard (and sub-type: ACTIVE, AUTOMATED, EVENT. Somethings User may ask for "蓝卡", "绿卡", which means: Blue -> Active, Green -> Automated, Red -> Event)
  • Tags, cost, requirements, victory points
  • Card effect/ability (exact mechanics and numerical values)
  • Card number (XB series)

If any detail is unclear or ambiguous, ask the user to clarify before proceeding.

Step 1: Register Card Name

Add an entry to the CardName enum in src/common/cards/CardName.ts.

Commission cards use 🌸 prefix/suffix in the display string. Find the commission card section (near line 626+) and append:

typescript
1// In the commission cards section of CardName enum: 2MY_NEW_CARD = '🌸My New Card🌸',

Naming conventions:

  • Enum key: UPPER_SNAKE_CASE
  • Value: '🌸Display Name🌸' (Chinese names are also acceptable, e.g. '🌸城电转能🌸')

Step 2: Implement the Card

Create a new file in src/server/cards/commission/. File name uses UpperCamelCase (e.g. MyNewCard.ts).

Determine Card Type

Read the card details to determine which base class to use:

TypeBase ClassImplementsWhen to Use
CorporationCorporationCardICorporationCardCards with startingMegaCredits, corp-level effects
Active CorpActiveCorporationCardICorporationCard + IActionCardCorp cards with per-turn action()
Project (Active)CardIProjectCardBlue cards with ongoing effects or actions
Project (Automated)CardIProjectCardGreen cards with one-time effects
Project (Event)CardIProjectCardRed cards with one-time effects
Project w/ ActionActionCardIProjectCard + has action behaviorBlue cards with data-driven action

Template: Corporation Card

typescript
1import {Tag} from '../../../common/cards/Tag'; 2import {IPlayer} from '../../IPlayer'; 3import {CardName} from '../../../common/cards/CardName'; 4import {CardRenderer} from '../render/CardRenderer'; 5import {CorporationCard} from '../corporation/CorporationCard'; 6 7export class MyCorpCard extends CorporationCard { 8 constructor() { 9 super({ 10 name: CardName.MY_CORP_CARD, 11 tags: [Tag.SCIENCE], 12 startingMegaCredits: 50, 13 // Optional: initialActionText for first action description 14 // initialActionText: 'Draw 1 card with a science tag', 15 16 metadata: { 17 cardNumber: 'XB??', 18 description: 'You start with 50 M€.', 19 renderData: CardRenderer.builder((b) => { 20 b.megacredits(50); 21 b.corpBox('effect', (ce) => { 22 ce.effect('description of effect', (eb) => { 23 eb.cards(1).startEffect.megacredits(1); 24 }); 25 }); 26 }), 27 }, 28 }); 29 } 30 31 // Override for first-action corps 32 // public override initialAction(player: IPlayer) { ... } 33 34 // For effects triggered when any player plays a card: 35 // public onCardPlayedByAnyPlayer(owner: IPlayer, card: ICard, currentPlayer: IPlayer) { ... } 36 37 // For effects triggered when this corp's owner plays a card: 38 // public onCardPlayedForCorps(player: IPlayer, card: ICard) { ... } 39}

Template: Project Card (IProjectCard)

typescript
1import {CardName} from '../../../common/cards/CardName'; 2import {CardType} from '../../../common/cards/CardType'; 3import {Tag} from '../../../common/cards/Tag'; 4import {CardRenderer} from '../render/CardRenderer'; 5import {Card} from '../Card'; 6import {IProjectCard} from '../IProjectCard'; 7 8export class MyProjectCard extends Card implements IProjectCard { 9 constructor() { 10 super({ 11 name: CardName.MY_PROJECT_CARD, 12 type: CardType.ACTIVE, // or AUTOMATED, EVENT 13 tags: [Tag.BUILDING], 14 cost: 15, 15 // victoryPoints: 1, // static VP 16 // requirements: {tag: Tag.SCIENCE, count: 2}, // requirements 17 18 // For simple effects, use behavior instead of bespokePlay: 19 // behavior: { 20 // production: {energy: 1}, 21 // stock: {megacredits: 3}, 22 // global: {temperature: 1}, 23 // drawCard: 1, 24 // }, 25 26 metadata: { 27 cardNumber: 'XB??', 28 // description: 'Requires 2 science tags. ...', 29 renderData: CardRenderer.builder((b) => { 30 b.effect('When you play a building tag, gain 2 M€.', (eb) => { 31 eb.tag(Tag.BUILDING).startEffect.megacredits(2); 32 }); 33 }), 34 }, 35 }); 36 } 37 38 // --- Callback methods (choose what applies) --- 39 40 // For ACTIVE cards with a player action: 41 // public canAct(player: IPlayer): boolean { return true; } 42 // public action(player: IPlayer) { return undefined; } 43 44 // For triggered effects when this card's owner plays a card: 45 // public onCardPlayed(player: IPlayer, card: IProjectCard) { ... } 46 47 // For triggered effects when any tile is placed: 48 // public onTilePlaced(cardOwner: IPlayer, activePlayer: IPlayer, space: Space) { ... } 49 50 // For card cost discounts: 51 // public override getCardDiscount(player: IPlayer, card: ICard): number { return 0; } 52 53 // For custom VP calculation: 54 // public override getVictoryPoints(player: IPlayer): number { return 0; } 55}

Common Patterns Reference

See references/card-patterns.md for detailed examples of common card patterns including:

  • Cards with behavior (declarative effects)
  • Cards with onCardPlayed / onCardPlayedByAnyPlayer callbacks
  • Cards with onTilePlaced callbacks
  • Cards with canAct() / action() (blue card actions)
  • Cards with getCardDiscount()
  • Cards with requirements
  • Corporation cards with initialAction and corpBox

Step 3: Register in Manifest

Edit src/server/cards/commission/CommissionCardManifest.ts:

  1. Add import at top:
typescript
1import {MyNewCard} from './MyNewCard';
  1. Add entry in the appropriate section of COMMISSION_CARD_MANIFEST:

For corporation cards:

typescript
1corporationCards: { 2 // ... existing entries 3 [CardName.MY_NEW_CARD]: {Factory: MyNewCard}, // XB?? 4},

For project cards:

typescript
1projectCards: { 2 // ... existing entries 3 [CardName.MY_NEW_CARD]: {Factory: MyNewCard}, // XB?? 4},

Optional: add compatibility for expansion-dependent cards:

typescript
1[CardName.MY_NEW_CARD]: {Factory: MyNewCard, compatibility: 'turmoil'},

Step 4: Write Unit Tests

Create a test file at tests/cards/commission/<CardName>.spec.ts.

Test Command

Run tests for a single card:

bash
1npx ts-mocha -p tests/tsconfig.json --reporter-option maxDiffSize=256 -r tests/testing/setup.ts tests/cards/commission/<CardName>.spec.ts

Run all commission card tests:

bash
1npx ts-mocha -p tests/tsconfig.json --reporter-option maxDiffSize=256 -r tests/testing/setup.ts 'tests/cards/commission/**/*.spec.ts'

Test Template

typescript
1import {expect} from 'chai'; 2import {MyCard} from '../../../src/server/cards/commission/MyCard'; 3import {testGame} from '../../TestGame'; 4import {TestPlayer} from '../../TestPlayer'; 5import {runAllActions, cast} from '../../TestingUtils'; 6import {IGame} from '../../../src/server/IGame'; 7import {Tag} from '../../../src/common/cards/Tag'; 8import {CardType} from '../../../src/common/cards/CardType'; 9import {Resource} from '../../../src/common/Resource'; 10 11describe('MyCard', () => { 12 let card: MyCard; 13 let player: TestPlayer; 14 let game: IGame; 15 16 beforeEach(() => { 17 card = new MyCard(); 18 [game, player] = testGame(2, {skipInitialShuffling: true}); 19 player.megaCredits = 100; 20 }); 21 22 it('basic properties', () => { 23 expect(card.cost).to.eq(15); 24 expect(card.type).to.eq(CardType.ACTIVE); 25 expect(card.tags).to.deep.eq([Tag.BUILDING]); 26 }); 27 28 it('should apply initial behavior on play', () => { 29 player.playCard(card); 30 runAllActions(game); 31 // Assert production, stock, or other changes 32 }); 33 34 // Add more tests for each card effect/ability 35});

Testing Tips

  • Use testGame(n, {skipInitialShuffling: true}) for deterministic setup
  • Use runAllActions(game) after play/actions to resolve deferred actions
  • Use cast(input, OrOptions) to type-check player input responses
  • Use player.popWaitingFor() to get pending player input
  • When setting card.resourceCount manually, do so after runAllActions(game) to avoid other card triggers (e.g. Decomposers) interfering
  • Known engine limitation: play() fires before the card is added to playedCards, so onProductionGain / other tableau callbacks won't self-trigger during initial play (the "including this" pattern)
  • For corporation card tests, use player.playCorporationCard(card) instead of player.playCard(card)
  • For testing canAct() / action(), manually set up the required state (resources, cards, etc.) then call the methods directly

Key Imports Quick Reference

typescript
1// Common imports for card implementation 2import {CardName} from '../../../common/cards/CardName'; 3import {CardType} from '../../../common/cards/CardType'; 4import {Tag} from '../../../common/cards/Tag'; 5import {Resource} from '../../../common/Resource'; 6import {CardResource} from '../../../common/CardResource'; 7import {CardRenderer} from '../render/CardRenderer'; 8import {Size} from '../../../common/cards/render/Size'; 9import {AltSecondaryTag} from '../../../common/cards/render/AltSecondaryTag'; 10import {Card} from '../Card'; 11import {IProjectCard} from '../IProjectCard'; 12import {ICard} from '../ICard'; 13import {IPlayer} from '../../IPlayer'; 14import {CorporationCard} from '../corporation/CorporationCard'; 15import {Board} from '../../boards/Board'; 16import {Space} from '../../boards/Space';

CardRenderer DSL Quick Reference

Resources: megacredits(n), steel(n), titanium(n), plants(n), energy(n), heat(n), cards(n), tr(n)

Global params: temperature(n), oxygen(n), oceans(n), venus(n)

Tiles: city(), greenery(), emptyTile(), specialTile()

Tags: tag(Tag.XXX), wild(n), noTags()

Production: production((pb) => { pb.energy(1); })

Layout: .br (line break), .nbsp, vSpace(size?), text(str), vpText(str)

Effect box: effect(desc, (eb) => { eb.cause.startEffect.result })

Action box: action(desc, (eb) => { eb.cost.startAction.result })

Corp box: corpBox('effect'|'action', (ce) => { ce.effect(...) })

Options: {all: true} any player, {secondaryTag: Tag.XXX}, {size: Size.SMALL}

Related Skills

Looking for an alternative to tfm-add-card or building a Categories.community AI Agent? Explore these related open-source MCP Servers.

View All

widget-generator

Logo of f
f

widget-generator is an open-source AI agent skill for creating widget plugins that are injected into prompt feeds on prompts.chat. It supports two rendering modes: standard prompt widgets using default PromptCard styling and custom render widgets built as full React components.

149.6k
0
Design

chat-sdk

Logo of lobehub
lobehub

chat-sdk is a unified TypeScript SDK for building chat bots across multiple platforms, providing a single interface for deploying bot logic.

73.0k
0
Communication

zustand

Logo of lobehub
lobehub

The ultimate space for work and life — to find, build, and collaborate with agent teammates that grow with you. We are taking agent harness to the next level — enabling multi-agent collaboration, effortless agent team design, and introducing agents as the unit of work interaction.

72.8k
0
Communication

data-fetching

Logo of lobehub
lobehub

The ultimate space for work and life — to find, build, and collaborate with agent teammates that grow with you. We are taking agent harness to the next level — enabling multi-agent collaboration, effortless agent team design, and introducing agents as the unit of work interaction.

72.8k
0
Communication