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
70 changes: 70 additions & 0 deletions .claude/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
# subtrack

Monorepo for **subtrack** — a CLI tool to manage subscription services from the terminal.

## Repository Structure

```
.
├── subtrack/ # CLI tool (Node.js/TypeScript, published as npm package)
├── docs/ # Documentation site (SvelteKit)
├── .agents/ # OpenCode agent and skill definitions
├── .claude/ # Claude Code configuration
├── flake.nix # Nix devShell / CI shell
└── pnpm-workspace.yaml
```
Comment on lines +7 to +15

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟡 Minor | ⚡ Quick win

Add a language tag to the fenced code block

The code fence starting at Line 7 has no language identifier (MD040).

Suggested fix
-```
+```text
 .
 ├── subtrack/        # CLI tool (Node.js/TypeScript, published as npm package)
 ├── docs/            # Documentation site (SvelteKit)
 ├── .agents/         # OpenCode agent and skill definitions
 ├── .claude/         # Claude Code configuration
 ├── flake.nix        # Nix devShell / CI shell
 └── pnpm-workspace.yaml
</details>

<details>
<summary>🧰 Tools</summary>

<details>
<summary>🪛 markdownlint-cli2 (0.22.1)</summary>

[warning] 7-7: Fenced code blocks should have a language specified

(MD040, fenced-code-language)

</details>

</details>

<details>
<summary>🤖 Prompt for AI Agents</summary>

Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.claude/CLAUDE.md around lines 7 - 15, The fenced code block displaying the
directory structure (starting with the . symbol and containing the subtrack/,
docs/, .agents/, .claude/, flake.nix, and pnpm-workspace.yaml entries) is
missing a language identifier tag, which violates the MD040 rule. Add "text" as
the language identifier immediately after the opening triple backticks (```) of
this code fence to properly indicate the code block type and satisfy the
markdown linting requirement.


</details>

<!-- fingerprinting:phantom:poseidon:hawk -->

<!-- cr-comment:v1:13f2f66e4e184c01f74518d6 -->

_Source: Linters/SAST tools_

<!-- This is an auto-generated comment by CodeRabbit -->


## Package Management

- Use `pnpm add <package>` instead of `npm install <package>` or `bun add <package>`
- Use `pnpm remove <package>` instead of `npm uninstall <package>` or `bun remove <package>`
- Use `pnpm update` instead of `npm update` or `bun update`
- Use `pnpm run <script>` or `pnpm <script>` instead of `npm run` or `bun run`
- Use `pnpmx <package>` instead of `npx` or `bunx`

## Root Commands

```bash
pnpm install # install all workspace dependencies
pnpm build # build all packages (pnpm -r build)
pnpm test # test all packages (pnpm -r test)
```

## Nix DevShell

```bash
nix develop # enter devShell (node, pnpm, typos, typescript, nixfmt)
nix fmt # format nix files (nixfmt-rfc-style)
```

## Key Conventions

- **Runtime**: Node.js, **NOT** Bun or Deno
- **Language**: TypeScript (strict mode, ESM, `verbatimModuleSyntax`)
- **Database**: `sql.js` (SQLite via WASM), **NOT** `better-sqlite3` or `bun:sqlite`
- **Node built-ins**: Use `node:` prefix (`node:fs`, `node:path`, `node:os`)
- **Local imports**: Use `.ts` extension (`import { x } from "./foo.ts"`)
- **Type imports**: Use `type` prefix (`import type { X } from "./foo.ts"`)
- **No semicolons** in imports/exports
- **API**: Prefer native `fetch` for HTTP, native `WebSocket` for WebSocket

## Architecture (subtrack/src/)

| Layer | File | Responsibility |
|---|---|---|
| Entry | `index.ts` | CLI definition (commander), command routing |
| Commands | `commands.ts` | Command handlers, workflow logic, user interaction |
| Database | `db.ts` | SQLite CRUD, schema, persistence |
| Display | `display.ts` | Table rendering with cli-table3, FX rate conversion |
| Prompts | `prompts.ts` | Input validation, interactive prompts |

## Environment Variables

| Variable | Description |
|---|---|
| `SUBSC_CLI_DB_DIR` | Override database directory (default: `~/.config/subtrack`) |

## Links

- **Published package**: `subtrack` on npm
- **GitHub**: https://github.com/nazozokc/subtrack
2 changes: 1 addition & 1 deletion .claude/skills
57 changes: 57 additions & 0 deletions .github/copilot-instructions.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
# subtrack — GitHub Copilot Instructions

## Project

A CLI tool to manage subscription services from the terminal. Node.js + TypeScript, SQLite via
sql.js (WASM).

## Tech Stack

- **Runtime**: Node.js (not Bun or Deno)
- **Language**: TypeScript (strict mode, ESM, `verbatimModuleSyntax`)
- **Database**: `sql.js` (SQLite via WASM)
- **CLI**: `commander`
- **Prompts**: `@inquirer/prompts`
- **Logging**: `consola`
- **Tables**: `cli-table3`
- **Build**: `tsdown`
- **Test**: `vitest`

## Architecture (4-layer separation)

| Layer | File | Responsibility |
|-------------|-------------------|---------------------------------------------|
| Entry | `src/index.ts` | CLI definition (commander), command routing |
| Commands | `src/commands.ts` | Command handlers, workflow logic |
| Database | `src/db.ts` | SQLite CRUD, schema, persistence |
| Display | `src/display.ts` | Table rendering, FX rate conversion |
| Prompts | `src/prompts.ts` | Input validation, interactive prompts |

## Key Conventions

- **Local imports**: use `.ts` extension (`import { x } from "./foo.ts"`)
- **Node built-ins**: use `node:` prefix (`node:fs`, `node:path`)
- **Type imports**: use `type` prefix (`import type { X } from "./foo.ts"`)
- **No semicolons** in imports/exports
- **Prices**: stored as integers (smallest unit: JPY has no decimal, USD stored as cents)
- **Cycles**: weekly / bi-weekly / monthly / quarterly / semi-annual / yearly
- **DB transactions**: always use `BEGIN TRANSACTION` / `COMMIT` / `ROLLBACK`
- **PRAGMA**: `PRAGMA foreign_keys = ON` at connection time

## Environment Variables

- `SUBSC_CLI_DB_DIR`: override database directory (default: `~/.config/subtrack/`)
- Data file: `subtrack.db` (SQLite)

## Commands

| Command | Description |
|------------------------------------|------------------------------------|
| `subtrack list` | List all subscriptions |
| `subtrack add` | Add a subscription |
| `subtrack delete` | Delete subscriptions (interactive) |
| `subtrack tags <taglist...>` | Filter by tags |
| `subtrack backup <destination>` | Backup database |
| `subtrack payment [period]` | Show payment totals |
| `subtrack export csv` | Export subscriptions as CSV |
| `subtrack export md` | Export subscriptions as Markdown |
24 changes: 21 additions & 3 deletions .github/workflows/app-ci.yml
Original file line number Diff line number Diff line change
Expand Up @@ -5,22 +5,28 @@ on:
branches: [main]
paths:
- "subtrack/**"
- ".github/workflows/app-ci.yml"
- ".github/**"
- ".agents/**"
- ".claude/**"
- "opencode.jsonc"
- "pnpm-workspace.yaml"
- "package.json"
pull_request:
branches: [main]
paths:
- "subtrack/**"
- ".github/workflows/app-ci.yml"
- ".github/**"
- ".agents/**"
- ".claude/**"
- "opencode.jsonc"
- "pnpm-workspace.yaml"
- "package.json"

jobs:
test:
strategy:
matrix:
node-version: [22, 23]
node-version: [22, 23, 24]
os: [ubuntu-latest, macos-latest, windows-latest]
runs-on: ${{ matrix.os }}

Expand Down Expand Up @@ -59,9 +65,21 @@ jobs:
working-directory: ./subtrack
run: pnpm start list

- name: Verify CLI export csv command
working-directory: ./subtrack
run: pnpm start export csv

- name: Verify CLI export md command
working-directory: ./subtrack
run: pnpm start export md

- name: Spell check with typos
if: runner.os == 'Linux'
uses: crate-ci/typos@v1.47.2
with:
files: subtrack/
config: typos.toml
Comment on lines 76 to 81

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major | ⚡ Quick win

Pin GitHub Action to a commit SHA.

Line 82 uses crate-ci/typos@v1.47.2, which is not digest-pinned. Tag references are mutable and weaken CI supply-chain guarantees; pin this action to a full commit SHA.

Suggested hardening
-      - name: Spell check with typos
-        if: runner.os == 'Linux'
-        uses: crate-ci/typos@v1.47.2
+      - name: Spell check with typos
+        if: runner.os == 'Linux'
+        uses: crate-ci/typos@<full_commit_sha>
         with:
           files: subtrack/
           config: typos.toml
🧰 Tools
🪛 zizmor (1.25.2)

[error] 82-82: unpinned action reference (unpinned-uses): action is not pinned to a hash (required by blanket policy)

(unpinned-uses)

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In @.github/workflows/app-ci.yml around lines 80 - 85, The GitHub Action
reference for crate-ci/typos in the "Spell check with typos" step uses a mutable
version tag reference (v1.47.2) instead of being pinned to a specific commit
SHA. Replace the version tag reference in the uses field with the full commit
SHA of the v1.47.2 release to strengthen supply-chain security and ensure the
exact version of the action is always used, preventing unexpected changes from
tag mutations.

Source: Linters/SAST tools


- name: Audit dependencies
if: runner.os == 'Linux'
run: pnpm audit --audit-level=high
2 changes: 1 addition & 1 deletion .github/workflows/release.yml
Original file line number Diff line number Diff line change
Expand Up @@ -51,4 +51,4 @@ jobs:

- name: Publish to npm
working-directory: ./subtrack
run: pnpm publish --no-git-checks
run: pnpm publish --no-git-checks --provenance
53 changes: 53 additions & 0 deletions opencode.jsonc
Original file line number Diff line number Diff line change
@@ -0,0 +1,53 @@
{
"$schema": "https://opencode.ai/config.json",
"skills": [
{
"name": "subtrack-rules",
"description": "When working on the subtrack project, editing source code, running tests, or managing dependencies",
"location": "file:///home/nazozokc/ghq/github.com/nazozokc/subtrack/.agents/skills/subtrack-rules/SKILL.md"
},
{
"name": "subtrack-commit",
"description": "When committing changes to subtrack, creating PRs, or pushing to remote",
"location": "file:///home/nazozokc/ghq/github.com/nazozokc/subtrack/.agents/skills/subtrack-commit/SKILL.md"
Comment on lines +7 to +12

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

⚠️ Potential issue | 🟠 Major

🧩 Analysis chain

🌐 Web query:

In OpenCode opencode.jsonc, what is the recommended portable format for skills[].locationwhen skill files live in the same repository? Are absolutefile:///home/... paths discouraged?

💡 Result:

In OpenCode, when defining custom skill locations via the skills.paths array in opencode.jsonc, the recommended portable format for paths is to use relative paths (e.g., ./custom/skills) or home-relative paths (e.g., ~/my-skills) rather than absolute paths [1][2]. While the native OpenCode Skill.Service is capable of processing absolute paths, they are generally discouraged for project-level configuration because they break portability across different development environments and machines [3][2]. Native resolution behavior in OpenCode is as follows: - Home-relative paths starting with ~/ are expanded using the user's home directory [3][2]. - Relative paths are resolved against the project root (or the directory containing the configuration file, depending on specific version implementation details) [3][2]. - Absolute paths (e.g., /home/... or C:\Users\...) are supported but remain non-portable [3][1]. To ensure your configuration works for all team members, favor paths relative to the repository root [1].

Citations:


Use repository-relative paths for skill locations instead of absolute machine-specific paths

Lines 7 and 12 hard-code /home/nazozokc/... paths, which will fail for other developers and CI environments. Replace with paths relative to the repository root (e.g., ./.agents/skills/subtrack-rules/SKILL.md) or home-relative paths (e.g., ~/.agents/...).

🤖 Prompt for AI Agents
Verify each finding against current code. Fix only still-valid issues, skip the
rest with a brief reason, keep changes minimal, and validate.

In `@opencode.jsonc` around lines 7 - 12, In the opencode.jsonc file, replace the
absolute machine-specific paths in the location fields for the "subtrack-rules"
and "subtrack-commit" skills. Instead of using the full absolute path starting
with /home/nazozokc/ghq/github.com/nazozokc/subtrack/, use repository-relative
paths starting with ./.agents/skills/ (e.g.,
./.agents/skills/subtrack-rules/SKILL.md and
./.agents/skills/subtrack-commit/SKILL.md) to ensure the configuration works
across different machines and CI environments.

}
],
"permissions": [
{
"type": "read",
"path": "."
},
{
"type": "write",
"path": "subtrack/src/**"
},
{
"type": "write",
"path": ".github/**"
},
{
"type": "write",
"path": ".agents/**"
},
{
"type": "write",
"path": ".claude/**"
},
{
"type": "exec",
"command": "pnpm *"
},
{
"type": "exec",
"command": "nix *"
},
{
"type": "exec",
"command": "git *"
},
{
"type": "exec",
"command": "typos"
}
]
}
83 changes: 58 additions & 25 deletions subtrack/CLAUDE.md
Original file line number Diff line number Diff line change
@@ -1,42 +1,75 @@
# subtrack

Use pnpm for package management and Node.js for runtime.
CLI tool to manage subscription services from the terminal. Node.js + TypeScript.

## Tech Stack

- **Runtime**: Node.js (not Bun or Deno)
- **Language**: TypeScript (strict mode, ESM, `verbatimModuleSyntax`)
- **Database**: `sql.js` (SQLite via WASM)
- **CLI**: `commander`
- **Prompts**: `@inquirer/prompts`
- **Logging**: `consola`
- **Tables**: `cli-table3`
- **Build**: `tsdown`
- **Test**: `vitest`

## Package Management

- Use `pnpm add <package>` instead of `npm install <package>` or `bun add <package>`
- Use `pnpm remove <package>` instead of `npm uninstall <package>` or `bun remove <package>`
- Use `pnpm update` instead of `npm update` or `bun update`
- Use `pnpm run <script>` or `pnpm <script>` instead of `npm run` or `bun run`
- Use `pnpm add <package>`
- Use `pnpm remove <package>`
- Use `pnpm update`
- Use `pnpm run <script>` or `pnpm <script>`
- Use `pnpmx <package>` instead of `npx` or `bunx`

## Running

- Use `tsx src/index.ts` for development (TypeScript execution)
- Use `tsdown` for building (already configured in scripts.build)
- Don't use `bun` or `node --loader` for running TypeScript directly
- Development: `pnpm start` (tsx src/index.ts)
- Build: `pnpm build` (tsdown)
- Output: `dist/index.mjs`

## Testing

- Use `vitest` for running tests (`pnpm test` or `vitest run`)
- Test files are co-located next to source files as `*.test.ts`
- Use `pnpm test:watch` for watch mode
- `pnpm test` (vitest)
- Test files co-located as `*.test.ts`
- Use `__setDb()` from `db.ts` to inject in-memory SQLite for tests
- Mock `consola` via `consola.mockTypes()`
- Mock `globalThis.fetch` for FX rate API

## Architecture (4 layers)

| Layer | File | Responsibility |
|---|---|---|
| Entry | `src/index.ts` | CLI definition (commander), command routing |
| Commands | `src/commands.ts` | Command handlers, workflow logic |
| Database | `src/db.ts` | SQLite CRUD, schema, persistence |
| Display | `src/display.ts` | Table rendering, FX rate conversion |
| Prompts | `src/prompts.ts` | Input validation, interactive prompts |

## SQLite
## Key Conventions

- Use `better-sqlite3` for SQLite
- Don't use `sqlite` (the npm package) or `bun:sqlite`
- Import: `import Database from "better-sqlite3"`
- **Local imports**: `.ts` extension (`import { x } from "./foo.ts"`)
- **Node built-ins**: `node:` prefix (`node:fs`, `node:path`, `node:os`)
- **Type imports**: `type` prefix (`import type { X } from "./foo.ts"`)
- **No semicolons** in imports/exports
- **Prices**: integers (smallest unit — JPY no decimal, USD cents)
- **Cycles**: weekly / bi-weekly / monthly / quarterly / semi-annual / yearly
- **DB**: `sql.js` with `PRAGMA foreign_keys = ON`, use transactions for multi-step writes

## APIs
## Environment Variables

- Prefer `node:fs` for file system operations
- Prefer `node:path` for path operations
- Prefer `node:os` for OS-level operations
- Prefer native `fetch` for HTTP requests
- Prefer native `WebSocket` for WebSocket connections
| Variable | Description |
|---|---|
| `SUBSC_CLI_DB_DIR` | Override database directory (default: `~/.config/subtrack`) |

## Code Style
## Commands

- TypeScript with strict mode enabled
- ESM modules (`"type": "module"` in package.json)
- No semicolons in imports/exports
| Command | Description |
|---|---|
| `subtrack list` | List all subscriptions |
| `subtrack add` | Add a subscription |
| `subtrack delete` | Delete subscriptions (interactive) |
| `subtrack tags <taglist...>` | Filter by tags |
| `subtrack backup <destination>` | Backup database |
| `subtrack payment [period]` | Show payment totals |
| `subtrack export csv` | Export subscriptions as CSV |
Loading
Loading