axiom-coding-agent-setup 1.0.1 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,540 @@
1
+ ---
2
+ name: fastapi-templates
3
+ description: Create production-ready FastAPI projects with async patterns, dependency injection, and comprehensive error handling. Use when building new FastAPI applications or setting up backend API projects.
4
+ ---
5
+
6
+ # FastAPI Project Templates
7
+
8
+ Production-ready FastAPI project structures with async patterns, dependency injection, middleware, and best practices for building high-performance APIs.
9
+
10
+ ## When to Use This Skill
11
+
12
+ - Starting new FastAPI projects from scratch
13
+ - Implementing async REST APIs with Python
14
+ - Building high-performance web services and microservices
15
+ - Creating async applications with PostgreSQL, MongoDB
16
+ - Setting up API projects with proper structure and testing
17
+
18
+ ## Core Concepts
19
+
20
+ ### 1. Project Structure
21
+
22
+ **Recommended Layout:**
23
+
24
+ ```
25
+ app/
26
+ ├── api/ # API routes
27
+ │ ├── v1/
28
+ │ │ ├── endpoints/
29
+ │ │ │ ├── users.py
30
+ │ │ │ ├── auth.py
31
+ │ │ │ └── items.py
32
+ │ │ └── router.py
33
+ │ └── dependencies.py # Shared dependencies
34
+ ├── core/ # Core configuration
35
+ │ ├── config.py
36
+ │ ├── security.py
37
+ │ └── database.py
38
+ ├── models/ # Database models
39
+ │ ├── user.py
40
+ │ └── item.py
41
+ ├── schemas/ # Pydantic schemas
42
+ │ ├── user.py
43
+ │ └── item.py
44
+ ├── services/ # Business logic
45
+ │ ├── user_service.py
46
+ │ └── auth_service.py
47
+ ├── repositories/ # Data access
48
+ │ ├── user_repository.py
49
+ │ └── item_repository.py
50
+ └── main.py # Application entry
51
+ ```
52
+
53
+ ### 2. Dependency Injection
54
+
55
+ FastAPI's built-in DI system using `Depends`:
56
+
57
+ - Database session management
58
+ - Authentication/authorization
59
+ - Shared business logic
60
+ - Configuration injection
61
+
62
+ ### 3. Async Patterns
63
+
64
+ Proper async/await usage:
65
+
66
+ - Async route handlers
67
+ - Async database operations
68
+ - Async background tasks
69
+ - Async middleware
70
+
71
+ ## Implementation Patterns
72
+
73
+ ### Pattern 1: Complete FastAPI Application
74
+
75
+ ```python
76
+ # main.py
77
+ from fastapi import FastAPI, Depends
78
+ from fastapi.middleware.cors import CORSMiddleware
79
+ from contextlib import asynccontextmanager
80
+
81
+ @asynccontextmanager
82
+ async def lifespan(app: FastAPI):
83
+ """Application lifespan events."""
84
+ # Startup
85
+ await database.connect()
86
+ yield
87
+ # Shutdown
88
+ await database.disconnect()
89
+
90
+ app = FastAPI(
91
+ title="API Template",
92
+ version="1.0.0",
93
+ lifespan=lifespan
94
+ )
95
+
96
+ # CORS middleware
97
+ app.add_middleware(
98
+ CORSMiddleware,
99
+ allow_origins=["*"],
100
+ allow_credentials=True,
101
+ allow_methods=["*"],
102
+ allow_headers=["*"],
103
+ )
104
+
105
+ # Include routers
106
+ from app.api.v1.router import api_router
107
+ app.include_router(api_router, prefix="/api/v1")
108
+
109
+ # core/config.py
110
+ from pydantic_settings import BaseSettings
111
+ from functools import lru_cache
112
+
113
+ class Settings(BaseSettings):
114
+ """Application settings."""
115
+ DATABASE_URL: str
116
+ SECRET_KEY: str
117
+ ACCESS_TOKEN_EXPIRE_MINUTES: int = 30
118
+ API_V1_STR: str = "/api/v1"
119
+
120
+ class Config:
121
+ env_file = ".env"
122
+
123
+ @lru_cache()
124
+ def get_settings() -> Settings:
125
+ return Settings()
126
+
127
+ # core/database.py
128
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
129
+ from sqlalchemy.ext.declarative import declarative_base
130
+ from sqlalchemy.orm import sessionmaker
131
+ from app.core.config import get_settings
132
+
133
+ settings = get_settings()
134
+
135
+ engine = create_async_engine(
136
+ settings.DATABASE_URL,
137
+ echo=True,
138
+ future=True
139
+ )
140
+
141
+ AsyncSessionLocal = sessionmaker(
142
+ engine,
143
+ class_=AsyncSession,
144
+ expire_on_commit=False
145
+ )
146
+
147
+ Base = declarative_base()
148
+
149
+ async def get_db() -> AsyncSession:
150
+ """Dependency for database session."""
151
+ async with AsyncSessionLocal() as session:
152
+ try:
153
+ yield session
154
+ await session.commit()
155
+ except Exception:
156
+ await session.rollback()
157
+ raise
158
+ finally:
159
+ await session.close()
160
+ ```
161
+
162
+ ### Pattern 2: CRUD Repository Pattern
163
+
164
+ ```python
165
+ # repositories/base_repository.py
166
+ from typing import Generic, TypeVar, Type, Optional, List
167
+ from sqlalchemy.ext.asyncio import AsyncSession
168
+ from sqlalchemy import select
169
+ from pydantic import BaseModel
170
+
171
+ ModelType = TypeVar("ModelType")
172
+ CreateSchemaType = TypeVar("CreateSchemaType", bound=BaseModel)
173
+ UpdateSchemaType = TypeVar("UpdateSchemaType", bound=BaseModel)
174
+
175
+ class BaseRepository(Generic[ModelType, CreateSchemaType, UpdateSchemaType]):
176
+ """Base repository for CRUD operations."""
177
+
178
+ def __init__(self, model: Type[ModelType]):
179
+ self.model = model
180
+
181
+ async def get(self, db: AsyncSession, id: int) -> Optional[ModelType]:
182
+ """Get by ID."""
183
+ result = await db.execute(
184
+ select(self.model).where(self.model.id == id)
185
+ )
186
+ return result.scalars().first()
187
+
188
+ async def get_multi(
189
+ self,
190
+ db: AsyncSession,
191
+ skip: int = 0,
192
+ limit: int = 100
193
+ ) -> List[ModelType]:
194
+ """Get multiple records."""
195
+ result = await db.execute(
196
+ select(self.model).offset(skip).limit(limit)
197
+ )
198
+ return result.scalars().all()
199
+
200
+ async def create(
201
+ self,
202
+ db: AsyncSession,
203
+ obj_in: CreateSchemaType
204
+ ) -> ModelType:
205
+ """Create new record."""
206
+ db_obj = self.model(**obj_in.dict())
207
+ db.add(db_obj)
208
+ await db.flush()
209
+ await db.refresh(db_obj)
210
+ return db_obj
211
+
212
+ async def update(
213
+ self,
214
+ db: AsyncSession,
215
+ db_obj: ModelType,
216
+ obj_in: UpdateSchemaType
217
+ ) -> ModelType:
218
+ """Update record."""
219
+ update_data = obj_in.dict(exclude_unset=True)
220
+ for field, value in update_data.items():
221
+ setattr(db_obj, field, value)
222
+ await db.flush()
223
+ await db.refresh(db_obj)
224
+ return db_obj
225
+
226
+ async def delete(self, db: AsyncSession, id: int) -> bool:
227
+ """Delete record."""
228
+ obj = await self.get(db, id)
229
+ if obj:
230
+ await db.delete(obj)
231
+ return True
232
+ return False
233
+
234
+ # repositories/user_repository.py
235
+ from app.repositories.base_repository import BaseRepository
236
+ from app.models.user import User
237
+ from app.schemas.user import UserCreate, UserUpdate
238
+
239
+ class UserRepository(BaseRepository[User, UserCreate, UserUpdate]):
240
+ """User-specific repository."""
241
+
242
+ async def get_by_email(self, db: AsyncSession, email: str) -> Optional[User]:
243
+ """Get user by email."""
244
+ result = await db.execute(
245
+ select(User).where(User.email == email)
246
+ )
247
+ return result.scalars().first()
248
+
249
+ async def is_active(self, db: AsyncSession, user_id: int) -> bool:
250
+ """Check if user is active."""
251
+ user = await self.get(db, user_id)
252
+ return user.is_active if user else False
253
+
254
+ user_repository = UserRepository(User)
255
+ ```
256
+
257
+ ### Pattern 3: Service Layer
258
+
259
+ ```python
260
+ # services/user_service.py
261
+ from typing import Optional
262
+ from sqlalchemy.ext.asyncio import AsyncSession
263
+ from app.repositories.user_repository import user_repository
264
+ from app.schemas.user import UserCreate, UserUpdate, User
265
+ from app.core.security import get_password_hash, verify_password
266
+
267
+ class UserService:
268
+ """Business logic for users."""
269
+
270
+ def __init__(self):
271
+ self.repository = user_repository
272
+
273
+ async def create_user(
274
+ self,
275
+ db: AsyncSession,
276
+ user_in: UserCreate
277
+ ) -> User:
278
+ """Create new user with hashed password."""
279
+ # Check if email exists
280
+ existing = await self.repository.get_by_email(db, user_in.email)
281
+ if existing:
282
+ raise ValueError("Email already registered")
283
+
284
+ # Hash password
285
+ user_in_dict = user_in.dict()
286
+ user_in_dict["hashed_password"] = get_password_hash(user_in_dict.pop("password"))
287
+
288
+ # Create user
289
+ user = await self.repository.create(db, UserCreate(**user_in_dict))
290
+ return user
291
+
292
+ async def authenticate(
293
+ self,
294
+ db: AsyncSession,
295
+ email: str,
296
+ password: str
297
+ ) -> Optional[User]:
298
+ """Authenticate user."""
299
+ user = await self.repository.get_by_email(db, email)
300
+ if not user:
301
+ return None
302
+ if not verify_password(password, user.hashed_password):
303
+ return None
304
+ return user
305
+
306
+ async def update_user(
307
+ self,
308
+ db: AsyncSession,
309
+ user_id: int,
310
+ user_in: UserUpdate
311
+ ) -> Optional[User]:
312
+ """Update user."""
313
+ user = await self.repository.get(db, user_id)
314
+ if not user:
315
+ return None
316
+
317
+ if user_in.password:
318
+ user_in_dict = user_in.dict(exclude_unset=True)
319
+ user_in_dict["hashed_password"] = get_password_hash(
320
+ user_in_dict.pop("password")
321
+ )
322
+ user_in = UserUpdate(**user_in_dict)
323
+
324
+ return await self.repository.update(db, user, user_in)
325
+
326
+ user_service = UserService()
327
+ ```
328
+
329
+ ### Pattern 4: API Endpoints with Dependencies
330
+
331
+ ```python
332
+ # api/v1/endpoints/users.py
333
+ from fastapi import APIRouter, Depends, HTTPException, status
334
+ from sqlalchemy.ext.asyncio import AsyncSession
335
+ from typing import List
336
+
337
+ from app.core.database import get_db
338
+ from app.schemas.user import User, UserCreate, UserUpdate
339
+ from app.services.user_service import user_service
340
+ from app.api.dependencies import get_current_user
341
+
342
+ router = APIRouter()
343
+
344
+ @router.post("/", response_model=User, status_code=status.HTTP_201_CREATED)
345
+ async def create_user(
346
+ user_in: UserCreate,
347
+ db: AsyncSession = Depends(get_db)
348
+ ):
349
+ """Create new user."""
350
+ try:
351
+ user = await user_service.create_user(db, user_in)
352
+ return user
353
+ except ValueError as e:
354
+ raise HTTPException(status_code=400, detail=str(e))
355
+
356
+ @router.get("/me", response_model=User)
357
+ async def read_current_user(
358
+ current_user: User = Depends(get_current_user)
359
+ ):
360
+ """Get current user."""
361
+ return current_user
362
+
363
+ @router.get("/{user_id}", response_model=User)
364
+ async def read_user(
365
+ user_id: int,
366
+ db: AsyncSession = Depends(get_db),
367
+ current_user: User = Depends(get_current_user)
368
+ ):
369
+ """Get user by ID."""
370
+ user = await user_service.repository.get(db, user_id)
371
+ if not user:
372
+ raise HTTPException(status_code=404, detail="User not found")
373
+ return user
374
+
375
+ @router.patch("/{user_id}", response_model=User)
376
+ async def update_user(
377
+ user_id: int,
378
+ user_in: UserUpdate,
379
+ db: AsyncSession = Depends(get_db),
380
+ current_user: User = Depends(get_current_user)
381
+ ):
382
+ """Update user."""
383
+ if current_user.id != user_id:
384
+ raise HTTPException(status_code=403, detail="Not authorized")
385
+
386
+ user = await user_service.update_user(db, user_id, user_in)
387
+ if not user:
388
+ raise HTTPException(status_code=404, detail="User not found")
389
+ return user
390
+
391
+ @router.delete("/{user_id}", status_code=status.HTTP_204_NO_CONTENT)
392
+ async def delete_user(
393
+ user_id: int,
394
+ db: AsyncSession = Depends(get_db),
395
+ current_user: User = Depends(get_current_user)
396
+ ):
397
+ """Delete user."""
398
+ if current_user.id != user_id:
399
+ raise HTTPException(status_code=403, detail="Not authorized")
400
+
401
+ deleted = await user_service.repository.delete(db, user_id)
402
+ if not deleted:
403
+ raise HTTPException(status_code=404, detail="User not found")
404
+ ```
405
+
406
+ ### Pattern 5: Authentication & Authorization
407
+
408
+ ```python
409
+ # core/security.py
410
+ from datetime import datetime, timedelta
411
+ from typing import Optional
412
+ from jose import JWTError, jwt
413
+ from passlib.context import CryptContext
414
+ from app.core.config import get_settings
415
+
416
+ settings = get_settings()
417
+ pwd_context = CryptContext(schemes=["bcrypt"], deprecated="auto")
418
+
419
+ ALGORITHM = "HS256"
420
+
421
+ def create_access_token(data: dict, expires_delta: Optional[timedelta] = None):
422
+ """Create JWT access token."""
423
+ to_encode = data.copy()
424
+ if expires_delta:
425
+ expire = datetime.utcnow() + expires_delta
426
+ else:
427
+ expire = datetime.utcnow() + timedelta(minutes=15)
428
+ to_encode.update({"exp": expire})
429
+ encoded_jwt = jwt.encode(to_encode, settings.SECRET_KEY, algorithm=ALGORITHM)
430
+ return encoded_jwt
431
+
432
+ def verify_password(plain_password: str, hashed_password: str) -> bool:
433
+ """Verify password against hash."""
434
+ return pwd_context.verify(plain_password, hashed_password)
435
+
436
+ def get_password_hash(password: str) -> str:
437
+ """Hash password."""
438
+ return pwd_context.hash(password)
439
+
440
+ # api/dependencies.py
441
+ from fastapi import Depends, HTTPException, status
442
+ from fastapi.security import OAuth2PasswordBearer
443
+ from jose import JWTError, jwt
444
+ from sqlalchemy.ext.asyncio import AsyncSession
445
+
446
+ from app.core.database import get_db
447
+ from app.core.security import ALGORITHM
448
+ from app.core.config import get_settings
449
+ from app.repositories.user_repository import user_repository
450
+
451
+ oauth2_scheme = OAuth2PasswordBearer(tokenUrl=f"{settings.API_V1_STR}/auth/login")
452
+
453
+ async def get_current_user(
454
+ db: AsyncSession = Depends(get_db),
455
+ token: str = Depends(oauth2_scheme)
456
+ ):
457
+ """Get current authenticated user."""
458
+ credentials_exception = HTTPException(
459
+ status_code=status.HTTP_401_UNAUTHORIZED,
460
+ detail="Could not validate credentials",
461
+ headers={"WWW-Authenticate": "Bearer"},
462
+ )
463
+
464
+ try:
465
+ payload = jwt.decode(token, settings.SECRET_KEY, algorithms=[ALGORITHM])
466
+ user_id: int = payload.get("sub")
467
+ if user_id is None:
468
+ raise credentials_exception
469
+ except JWTError:
470
+ raise credentials_exception
471
+
472
+ user = await user_repository.get(db, user_id)
473
+ if user is None:
474
+ raise credentials_exception
475
+
476
+ return user
477
+ ```
478
+
479
+ ## Testing
480
+
481
+ ```python
482
+ # tests/conftest.py
483
+ import pytest
484
+ import asyncio
485
+ from httpx import AsyncClient
486
+ from sqlalchemy.ext.asyncio import create_async_engine, AsyncSession
487
+ from sqlalchemy.orm import sessionmaker
488
+
489
+ from app.main import app
490
+ from app.core.database import get_db, Base
491
+
492
+ TEST_DATABASE_URL = "sqlite+aiosqlite:///:memory:"
493
+
494
+ @pytest.fixture(scope="session")
495
+ def event_loop():
496
+ loop = asyncio.get_event_loop_policy().new_event_loop()
497
+ yield loop
498
+ loop.close()
499
+
500
+ @pytest.fixture
501
+ async def db_session():
502
+ engine = create_async_engine(TEST_DATABASE_URL, echo=True)
503
+ async with engine.begin() as conn:
504
+ await conn.run_sync(Base.metadata.create_all)
505
+
506
+ AsyncSessionLocal = sessionmaker(
507
+ engine, class_=AsyncSession, expire_on_commit=False
508
+ )
509
+
510
+ async with AsyncSessionLocal() as session:
511
+ yield session
512
+
513
+ @pytest.fixture
514
+ async def client(db_session):
515
+ async def override_get_db():
516
+ yield db_session
517
+
518
+ app.dependency_overrides[get_db] = override_get_db
519
+
520
+ async with AsyncClient(app=app, base_url="http://test") as client:
521
+ yield client
522
+
523
+ # tests/test_users.py
524
+ import pytest
525
+
526
+ @pytest.mark.asyncio
527
+ async def test_create_user(client):
528
+ response = await client.post(
529
+ "/api/v1/users/",
530
+ json={
531
+ "email": "test@example.com",
532
+ "password": "testpass123",
533
+ "name": "Test User"
534
+ }
535
+ )
536
+ assert response.status_code == 201
537
+ data = response.json()
538
+ assert data["email"] == "test@example.com"
539
+ assert "id" in data
540
+ ```
@@ -0,0 +1,124 @@
1
+ ---
2
+ name: git-commit
3
+ description: 'Execute git commit with conventional commit message analysis, intelligent staging, and message generation. Use when user asks to commit changes, create a git commit, or mentions "/commit". Supports: (1) Auto-detecting type and scope from changes, (2) Generating conventional commit messages from diff, (3) Interactive commit with optional type/scope/description overrides, (4) Intelligent file staging for logical grouping'
4
+ license: MIT
5
+ allowed-tools: Bash
6
+ ---
7
+
8
+ # Git Commit with Conventional Commits
9
+
10
+ ## Overview
11
+
12
+ Create standardized, semantic git commits using the Conventional Commits specification. Analyze the actual diff to determine appropriate type, scope, and message.
13
+
14
+ ## Conventional Commit Format
15
+
16
+ ```
17
+ <type>[optional scope]: <description>
18
+
19
+ [optional body]
20
+
21
+ [optional footer(s)]
22
+ ```
23
+
24
+ ## Commit Types
25
+
26
+ | Type | Purpose |
27
+ | ---------- | ------------------------------ |
28
+ | `feat` | New feature |
29
+ | `fix` | Bug fix |
30
+ | `docs` | Documentation only |
31
+ | `style` | Formatting/style (no logic) |
32
+ | `refactor` | Code refactor (no feature/fix) |
33
+ | `perf` | Performance improvement |
34
+ | `test` | Add/update tests |
35
+ | `build` | Build system/dependencies |
36
+ | `ci` | CI/config changes |
37
+ | `chore` | Maintenance/misc |
38
+ | `revert` | Revert commit |
39
+
40
+ ## Breaking Changes
41
+
42
+ ```
43
+ # Exclamation mark after type/scope
44
+ feat!: remove deprecated endpoint
45
+
46
+ # BREAKING CHANGE footer
47
+ feat: allow config to extend other configs
48
+
49
+ BREAKING CHANGE: `extends` key behavior changed
50
+ ```
51
+
52
+ ## Workflow
53
+
54
+ ### 1. Analyze Diff
55
+
56
+ ```bash
57
+ # If files are staged, use staged diff
58
+ git diff --staged
59
+
60
+ # If nothing staged, use working tree diff
61
+ git diff
62
+
63
+ # Also check status
64
+ git status --porcelain
65
+ ```
66
+
67
+ ### 2. Stage Files (if needed)
68
+
69
+ If nothing is staged or you want to group changes differently:
70
+
71
+ ```bash
72
+ # Stage specific files
73
+ git add path/to/file1 path/to/file2
74
+
75
+ # Stage by pattern
76
+ git add *.test.*
77
+ git add src/components/*
78
+
79
+ # Interactive staging
80
+ git add -p
81
+ ```
82
+
83
+ **Never commit secrets** (.env, credentials.json, private keys).
84
+
85
+ ### 3. Generate Commit Message
86
+
87
+ Analyze the diff to determine:
88
+
89
+ - **Type**: What kind of change is this?
90
+ - **Scope**: What area/module is affected?
91
+ - **Description**: One-line summary of what changed (present tense, imperative mood, <72 chars)
92
+
93
+ ### 4. Execute Commit
94
+
95
+ ```bash
96
+ # Single line
97
+ git commit -m "<type>[scope]: <description>"
98
+
99
+ # Multi-line with body/footer
100
+ git commit -m "$(cat <<'EOF'
101
+ <type>[scope]: <description>
102
+
103
+ <optional body>
104
+
105
+ <optional footer>
106
+ EOF
107
+ )"
108
+ ```
109
+
110
+ ## Best Practices
111
+
112
+ - One logical change per commit
113
+ - Present tense: "add" not "added"
114
+ - Imperative mood: "fix bug" not "fixes bug"
115
+ - Reference issues: `Closes #123`, `Refs #456`
116
+ - Keep description under 72 characters
117
+
118
+ ## Git Safety Protocol
119
+
120
+ - NEVER update git config
121
+ - NEVER run destructive commands (--force, hard reset) without explicit request
122
+ - NEVER skip hooks (--no-verify) unless user asks
123
+ - NEVER force push to main/master
124
+ - If commit fails due to hooks, fix and create NEW commit (don't amend)
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "axiom-coding-agent-setup",
3
- "version": "1.0.1",
3
+ "version": "1.0.2",
4
4
  "description": "CLI tool to download AXIOM coding agent setup files into your project",
5
5
  "main": "bin/cli.js",
6
6
  "bin": {
@@ -0,0 +1,15 @@
1
+ {
2
+ "version": 1,
3
+ "skills": {
4
+ "fastapi-templates": {
5
+ "source": "wshobson/agents",
6
+ "sourceType": "github",
7
+ "computedHash": "1a5fe66bd2683afd1db9afdf37a25cd8b9195c369c98489333badf0406fc91b3"
8
+ },
9
+ "git-commit": {
10
+ "source": "github/awesome-copilot",
11
+ "sourceType": "github",
12
+ "computedHash": "2607fc60629b82b257136dd2a7a373f0a4466c0b49df7746d845d59313c99b21"
13
+ }
14
+ }
15
+ }
@@ -1,318 +0,0 @@
1
- # npx CLI Package Setup Reference
2
-
3
- Quick reference for creating and publishing an npx CLI package that downloads files from a GitHub repository.
4
-
5
- ---
6
-
7
- ## Overview
8
-
9
- This pattern creates a CLI tool that can be run via `npx your-package-name` to download specific files from a GitHub repo into the current project directory.
10
-
11
- **Use case**: Distributing coding agent instructions, project templates, config files, etc.
12
-
13
- ---
14
-
15
- ## Step-by-Step Setup
16
-
17
- ### 1. Create Project Structure
18
-
19
- ```
20
- your-project/
21
- ├── package.json # npm package configuration
22
- ├── bin/
23
- │ └── cli.js # CLI entry point (Node.js script)
24
- └── README.md # Documentation
25
- ```
26
-
27
- ### 2. Configure package.json
28
-
29
- ```json
30
- {
31
- "name": "your-package-name",
32
- "version": "1.0.0",
33
- "description": "Description of what this CLI does",
34
- "main": "bin/cli.js",
35
- "bin": {
36
- "your-package-name": "bin/cli.js",
37
- "short-alias": "bin/cli.js"
38
- },
39
- "scripts": {
40
- "test": "echo \"Error: no test specified\" && exit 1"
41
- },
42
- "keywords": ["cli", "setup", "scaffold"],
43
- "author": "your-github-username",
44
- "license": "MIT",
45
- "repository": {
46
- "type": "git",
47
- "url": "https://github.com/username/repo-name.git"
48
- },
49
- "engines": {
50
- "node": ">=14.0.0"
51
- }
52
- }
53
- ```
54
-
55
- **Key fields:**
56
- - `name`: Must be unique on npm (check availability first)
57
- - `bin`: Maps command names to the CLI script
58
- - `repository`: Links to your GitHub repo
59
-
60
- ### 3. Create the CLI Script (bin/cli.js)
61
-
62
- Template for downloading files from GitHub raw content:
63
-
64
- ```javascript
65
- #!/usr/bin/env node
66
-
67
- const https = require('https');
68
- const fs = require('fs');
69
- const path = require('path');
70
-
71
- const REPO_OWNER = 'your-github-username';
72
- const REPO_NAME = 'your-repo-name';
73
- const BRANCH = 'main';
74
-
75
- const FILES_TO_DOWNLOAD = [
76
- 'file1.md',
77
- 'folder/file2.md',
78
- 'folder/file3.md'
79
- ];
80
-
81
- const GITHUB_RAW_URL = `https://raw.githubusercontent.com/${REPO_OWNER}/${REPO_NAME}/${BRANCH}`;
82
-
83
- function downloadFile(filePath) {
84
- return new Promise((resolve, reject) => {
85
- const url = `${GITHUB_RAW_URL}/${filePath}`;
86
- const localPath = path.join(process.cwd(), filePath);
87
- const dir = path.dirname(localPath);
88
-
89
- // Create directory if it doesn't exist
90
- if (!fs.existsSync(dir)) {
91
- fs.mkdirSync(dir, { recursive: true });
92
- }
93
-
94
- const file = fs.createWriteStream(localPath);
95
-
96
- https.get(url, (response) => {
97
- if (response.statusCode === 200) {
98
- response.pipe(file);
99
- file.on('finish', () => {
100
- file.close();
101
- resolve(filePath);
102
- });
103
- } else {
104
- file.close();
105
- if (fs.existsSync(localPath)) fs.unlinkSync(localPath);
106
- reject(new Error(`Failed to download ${filePath}: ${response.statusCode}`));
107
- }
108
- }).on('error', (err) => {
109
- if (fs.existsSync(localPath)) fs.unlinkSync(localPath);
110
- reject(err);
111
- });
112
- });
113
- }
114
-
115
- async function main() {
116
- console.log('Starting download...\n');
117
-
118
- for (const file of FILES_TO_DOWNLOAD) {
119
- try {
120
- process.stdout.write(`Downloading ${file}... `);
121
- await downloadFile(file);
122
- console.log('✓');
123
- } catch (error) {
124
- console.log(`✗ (${error.message})`);
125
- }
126
- }
127
-
128
- console.log('\nDone!');
129
- }
130
-
131
- main().catch(console.error);
132
- ```
133
-
134
- ### 4. Test Locally
135
-
136
- Before publishing, test the CLI locally:
137
-
138
- ```bash
139
- node bin/cli.js
140
- ```
141
-
142
- Or link it locally:
143
-
144
- ```bash
145
- npm link
146
- your-package-name # Test the command
147
- npm unlink # Remove local link when done
148
- ```
149
-
150
- ### 5. Publish to npm
151
-
152
- #### First-time setup:
153
-
154
- 1. **Create npm account**: https://www.npmjs.com/signup
155
-
156
- 2. **Login from terminal**:
157
- ```bash
158
- npm login
159
- ```
160
-
161
- 3. **Publish**:
162
- ```bash
163
- npm publish
164
- ```
165
-
166
- #### Check if published successfully:
167
-
168
- ```bash
169
- npm view your-package-name
170
- ```
171
-
172
- Or visit: `https://www.npmjs.com/package/your-package-name`
173
-
174
- ---
175
-
176
- ## Usage After Publishing
177
-
178
- Anyone can now use your CLI (no installation required):
179
-
180
- ```bash
181
- npx your-package-name
182
- ```
183
-
184
- Or install globally:
185
-
186
- ```bash
187
- npm install -g your-package-name
188
- your-package-name # Run directly
189
- ```
190
-
191
- ---
192
-
193
- ## Updating Your Package
194
-
195
- ### Updating Content Files (the downloaded files)
196
-
197
- Since files are downloaded directly from GitHub:
198
-
199
- 1. Edit files locally
200
- 2. Commit and push:
201
- ```bash
202
- git add .
203
- git commit -m "Update instructions"
204
- git push origin main
205
- ```
206
- 3. Done! Changes are live immediately (no npm publish needed)
207
-
208
- ### Updating the CLI Script Itself
209
-
210
- If you modify `bin/cli.js` or `package.json`:
211
-
212
- 1. Update version in `package.json`:
213
- ```json
214
- "version": "1.0.1" // Increment version
215
- ```
216
-
217
- 2. Commit and push to GitHub:
218
- ```bash
219
- git add .
220
- git commit -m "Fix download logic"
221
- git push origin main
222
- ```
223
-
224
- 3. Republish to npm:
225
- ```bash
226
- npm publish
227
- ```
228
-
229
- ---
230
-
231
- ## Version Management
232
-
233
- npm follows semantic versioning:
234
-
235
- | Version change | When to use |
236
- |----------------|-------------|
237
- | `1.0.0` → `1.0.1` | Bug fixes (patch) |
238
- | `1.0.0` → `1.1.0` | New features (minor) |
239
- | `1.0.0` → `2.0.0` | Breaking changes (major) |
240
-
241
- Update manually in `package.json` before republishing.
242
-
243
- ---
244
-
245
- ## Troubleshooting
246
-
247
- ### 403 Forbidden when publishing
248
- - Package name might already exist on npm
249
- - Try a more unique name (e.g., `@username/package-name` for scoped packages)
250
-
251
- ### 404 when downloading files
252
- - Check GitHub repo is public
253
- - Verify `REPO_OWNER` and `REPO_NAME` match your GitHub URL
254
- - Ensure files exist on the `main` branch
255
-
256
- ### Command not found after publishing
257
- - Wait a few minutes for npm registry to propagate
258
- - Try `npx your-package-name@latest`
259
-
260
- ### Local changes not reflected
261
- - Clear npx cache: `npx clear-npx-cache`
262
- - Or specify version: `npx your-package-name@1.0.1`
263
-
264
- ---
265
-
266
- ## Scoped Packages (Optional)
267
-
268
- If the package name is taken, use your username as a scope:
269
-
270
- ```json
271
- {
272
- "name": "@username/package-name"
273
- }
274
- ```
275
-
276
- Publish with:
277
- ```bash
278
- npm publish --access public
279
- ```
280
-
281
- Use with:
282
- ```bash
283
- npx @username/package-name
284
- ```
285
-
286
- ---
287
-
288
- ## Quick Checklist
289
-
290
- - [ ] Create `package.json` with `bin` entry
291
- - [ ] Create `bin/cli.js` with shebang (`#!/usr/bin/env node`)
292
- - [ ] Set correct `REPO_OWNER`, `REPO_NAME`, and `FILES_TO_DOWNLOAD`
293
- - [ ] Test locally with `node bin/cli.js`
294
- - [ ] Create npm account
295
- - [ ] Run `npm login`
296
- - [ ] Run `npm publish`
297
- - [ ] Test with `npx your-package-name`
298
-
299
- ---
300
-
301
- ## Example Commands Reference
302
-
303
- ```bash
304
- # Setup
305
- npm login # Login to npm
306
- npm publish # Publish package
307
- npm version patch # Bump version (1.0.0 → 1.0.1)
308
-
309
- # Maintenance
310
- npm view your-package-name # Check package info
311
- npm unpublish your-package-name@version # Remove specific version
312
- npm deprecate your-package-name@version # Deprecate version
313
-
314
- # Usage
315
- npx your-package-name # Run via npx
316
- npx your-package-name@latest # Force latest version
317
- npx clear-npx-cache # Clear npx cache
318
- ```