Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
36 changes: 36 additions & 0 deletions README.ja.md
Original file line number Diff line number Diff line change
Expand Up @@ -150,6 +150,42 @@ color: "#ffffff"

## CLI コマンド

### `init` - テンプレート作成

```bash
figdeck init [options]

Options:
-o, --out <path> 出力ファイルパス (default: "slides.md")
-f, --force 既存ファイルを上書き
--ai-rules [targets] AI エージェントルールを生成 (agents,claude,cursor,copilot または all)
--no-slides slides.md の生成をスキップ
-h, --help ヘルプ表示
```

サポートされている全ての Markdown 記法の例を含む `slides.md` テンプレートを作成します。

オプションで AI エージェントルールファイルを生成できます:

```bash
# 全ての AI エージェントルールを生成
figdeck init --ai-rules all

# 特定のルールのみ生成
figdeck init --ai-rules claude,cursor

# 既存プロジェクトにルールを追加(slides.md はそのまま)
figdeck init --ai-rules all --no-slides
```

生成されるファイル:
| ターゲット | ファイル | 対象ツール |
|------------|----------|------------|
| `agents` | `AGENTS.md` | Codex CLI, Cursor (AGENTS.md) |
| `claude` | `.claude/rules/figdeck.md` | Claude Code |
| `cursor` | `.cursor/rules/figdeck.mdc` | Cursor |
| `copilot` | `.github/instructions/figdeck.instructions.md` | GitHub Copilot |

### `build` - JSON 出力

```bash
Expand Down
29 changes: 26 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -174,13 +174,36 @@ Per-slide frontmatter > Global frontmatter
figdeck init [options]

Options:
-o, --out <path> Output file path (default: "slides.md")
-f, --force Overwrite existing file
-h, --help Show help
-o, --out <path> Output file path (default: "slides.md")
-f, --force Overwrite existing files
--ai-rules [targets] Generate AI agent rules (agents,claude,cursor,copilot or all)
--no-slides Skip generating slides.md
-h, --help Show help
```

Creates a template `slides.md` with examples of all supported Markdown syntax.

Optionally generate AI agent rule files with `--ai-rules`:

```bash
# Generate all AI agent rules
figdeck init --ai-rules all

# Generate specific rules only
figdeck init --ai-rules claude,cursor

# Add rules to existing project (keep existing slides.md)
figdeck init --ai-rules all --no-slides
```

Generated files:
| Target | File | Tool |
|--------|------|------|
| `agents` | `AGENTS.md` | Codex CLI, Cursor (AGENTS.md) |
| `claude` | `.claude/rules/figdeck.md` | Claude Code |
| `cursor` | `.cursor/rules/figdeck.mdc` | Cursor |
| `copilot` | `.github/instructions/figdeck.instructions.md` | GitHub Copilot |

### `build` - JSON Output

```bash
Expand Down
189 changes: 187 additions & 2 deletions packages/cli/src/cli.test.ts
Original file line number Diff line number Diff line change
@@ -1,5 +1,12 @@
import { describe, expect, it } from "bun:test";
import { existsSync, readFileSync, unlinkSync } from "node:fs";
import { afterEach, beforeEach, describe, expect, it } from "bun:test";
import {
existsSync,
mkdirSync,
readFileSync,
rmSync,
unlinkSync,
writeFileSync,
} from "node:fs";
import { join } from "node:path";
import { $ } from "bun";

Expand Down Expand Up @@ -105,4 +112,182 @@ describe("CLI", () => {
expect(result).toContain("-o");
});
});

describe("init command", () => {
const TMP_DIR = join(import.meta.dir, ".tmp-init-test");

beforeEach(() => {
if (existsSync(TMP_DIR)) {
rmSync(TMP_DIR, { recursive: true, force: true });
}
mkdirSync(TMP_DIR, { recursive: true });
});

afterEach(() => {
if (existsSync(TMP_DIR)) {
rmSync(TMP_DIR, { recursive: true, force: true });
}
});

it("should show --ai-rules and --no-slides in help", async () => {
const result = await $`bun ${CLI_PATH} init --help`.text();

expect(result).toContain("--ai-rules");
expect(result).toContain("--no-slides");
expect(result).toContain("agents,claude,cursor,copilot");
});

it("should create only slides.md by default", async () => {
const outPath = join(TMP_DIR, "slides.md");
await $`bun ${CLI_PATH} init -o ${outPath}`.text();

expect(existsSync(outPath)).toBe(true);
expect(existsSync(join(TMP_DIR, "AGENTS.md"))).toBe(false);
expect(existsSync(join(TMP_DIR, ".claude/rules/figdeck.md"))).toBe(false);
});

it("should create AGENTS.md with --ai-rules agents", async () => {
const outPath = join(TMP_DIR, "slides.md");
await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules agents`.text();

expect(existsSync(outPath)).toBe(true);
expect(existsSync(join(TMP_DIR, "AGENTS.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".claude/rules/figdeck.md"))).toBe(false);
});

it("should create .claude/rules/figdeck.md with --ai-rules claude", async () => {
const outPath = join(TMP_DIR, "slides.md");
await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules claude`.text();

expect(existsSync(outPath)).toBe(true);
expect(existsSync(join(TMP_DIR, ".claude/rules/figdeck.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, "AGENTS.md"))).toBe(false);
});

it("should create .cursor/rules/figdeck.mdc with --ai-rules cursor", async () => {
const outPath = join(TMP_DIR, "slides.md");
await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules cursor`.text();

expect(existsSync(outPath)).toBe(true);
expect(existsSync(join(TMP_DIR, ".cursor/rules/figdeck.mdc"))).toBe(true);
});

it("should create .github/instructions/figdeck.instructions.md with --ai-rules copilot", async () => {
const outPath = join(TMP_DIR, "slides.md");
await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules copilot`.text();

expect(existsSync(outPath)).toBe(true);
expect(
existsSync(
join(TMP_DIR, ".github/instructions/figdeck.instructions.md"),
),
).toBe(true);
});

it("should create two files with --ai-rules claude,cursor", async () => {
const outPath = join(TMP_DIR, "slides.md");
await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules claude,cursor`.text();

expect(existsSync(outPath)).toBe(true);
expect(existsSync(join(TMP_DIR, ".claude/rules/figdeck.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".cursor/rules/figdeck.mdc"))).toBe(true);
expect(existsSync(join(TMP_DIR, "AGENTS.md"))).toBe(false);
expect(
existsSync(
join(TMP_DIR, ".github/instructions/figdeck.instructions.md"),
),
).toBe(false);
});

it("should create all files with --ai-rules all", async () => {
const outPath = join(TMP_DIR, "slides.md");
await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules all`.text();

expect(existsSync(outPath)).toBe(true);
expect(existsSync(join(TMP_DIR, "AGENTS.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".claude/rules/figdeck.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".cursor/rules/figdeck.mdc"))).toBe(true);
expect(
existsSync(
join(TMP_DIR, ".github/instructions/figdeck.instructions.md"),
),
).toBe(true);
});

it("should create all files with --ai-rules (no value)", async () => {
const outPath = join(TMP_DIR, "slides.md");
await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules`.text();

expect(existsSync(outPath)).toBe(true);
expect(existsSync(join(TMP_DIR, "AGENTS.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".claude/rules/figdeck.md"))).toBe(true);
expect(existsSync(join(TMP_DIR, ".cursor/rules/figdeck.mdc"))).toBe(true);
expect(
existsSync(
join(TMP_DIR, ".github/instructions/figdeck.instructions.md"),
),
).toBe(true);
});

it("should fail with invalid --ai-rules value", async () => {
const outPath = join(TMP_DIR, "slides.md");
const result =
await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules invalid`
.text()
.catch((e) => e.stderr?.toString() || "error");

expect(result).toContain("Invalid --ai-rules targets");
expect(existsSync(outPath)).toBe(false);
});

it("should fail without --force when file exists", async () => {
const outPath = join(TMP_DIR, "slides.md");
writeFileSync(outPath, "existing content", "utf-8");

const result = await $`bun ${CLI_PATH} init -o ${outPath}`
.text()
.catch((e) => e.stderr?.toString() || "error");

expect(result).toContain("already exist");
// Existing content should be preserved
expect(readFileSync(outPath, "utf-8")).toBe("existing content");
});

it("should overwrite with --force", async () => {
const outPath = join(TMP_DIR, "slides.md");
writeFileSync(outPath, "existing content", "utf-8");

await $`bun ${CLI_PATH} init -o ${outPath} --force`.text();

expect(existsSync(outPath)).toBe(true);
// Content should be overwritten
expect(readFileSync(outPath, "utf-8")).not.toBe("existing content");
});

it("should skip slides.md with --no-slides", async () => {
const outPath = join(TMP_DIR, "slides.md");
writeFileSync(outPath, "existing content", "utf-8");

await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules agents --no-slides`.text();

// slides.md should not be changed
expect(readFileSync(outPath, "utf-8")).toBe("existing content");
// AGENTS.md should be created
expect(existsSync(join(TMP_DIR, "AGENTS.md"))).toBe(true);
});

it("should replace {{slidesPath}} placeholder in templates", async () => {
const outPath = join(TMP_DIR, "deck/my-slides.md");
mkdirSync(join(TMP_DIR, "deck"), { recursive: true });

await $`bun ${CLI_PATH} init -o ${outPath} --ai-rules claude`.text();

const claudeContent = readFileSync(
join(TMP_DIR, "deck/.claude/rules/figdeck.md"),
"utf-8",
);
expect(claudeContent).toContain("deck/my-slides.md");
expect(claudeContent).not.toContain("{{slidesPath}}");
});
});
});
Loading