Metadata-Version: 2.4
Name: soigia-init
Version: 0.1.2
Summary: Experimental pip package — used to learn the publish workflow end-to-end
Author-email: Soi Gia <sojgja@gmail.com>
License-Expression: BSD-3-Clause
Keywords: soigia,experiment,pip
Classifier: Development Status :: 4 - Beta
Classifier: Intended Audience :: Developers
Classifier: Operating System :: OS Independent
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3.8
Classifier: Programming Language :: Python :: 3.9
Classifier: Programming Language :: Python :: 3.10
Classifier: Programming Language :: Python :: 3.11
Classifier: Programming Language :: Python :: 3.12
Classifier: Topic :: Software Development :: Libraries :: Python Modules
Requires-Python: >=3.8
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# soigia-init

An **experimental** pip package — built to learn the publish workflow
end-to-end (scaffold → test → build → check → upload → install).
Zero dependencies, published to PyPI for real as a proof of workflow.

## Install

```bash
pip install soigia-init
```

## Usage

```bash
soigia-init --name SoiGia
# Hello, SoiGia! 👋 from soigia-init
```

Or from Python:

```python
import soigia_init
from soigia_init.cli import hello

print(soigia_init.__version__)   # prints the current version (synced with pyproject.toml)
print(hello("Soi Gia"))
```

---

# 📖 How to Build a Pip Package From Zero — Step by Step

> Everything below is the **exact workflow this package went through**.
> Follow the steps with any new package and it will end up on PyPI,
> published with **one command and no manual token typing**.

## 📁 Layout of a package (this repo is the template)

```
pip-soigia-init/                  # folder of the package
├── pyproject.toml                # package metadata + build config
├── Makefile                      # one-command workflow: test/build/publish
├── README.md                     # this guide (rendered on PyPI page too)
├── LICENSE                       # BSD-3-Clause
├── .gitignore                    # keep build artifacts & secrets out of git
├── soigia_init/                  # source code — importable package
│   ├── __init__.py               # __version__ lives here
│   └── cli.py                    # CLI entry point (optional)
├── scripts/
│   ├── release.py                # bump → build → check → upload (auto)
│   └── bump_version.py           # version bumping helper
└── tests/
    └── test_smoke.py             # pytest smoke tests
```

## Step 1 — Create the folder, learn the name rules

```bash
mkdir pip-soigia-mcp && cd pip-soigia-mcp
mkdir soigia_mcp tests scripts
```

Three different names, easy to mix up:

| What            | Example         | Rule |
|-----------------|-----------------|------|
| **Folder**      | `pip-soigia-mcp` | dash `-` — one folder per package |
| **Import name** | `soigia_mcp`     | underscore `_` — what Python imports |
| **PyPI name**   | `soigia-mcp`     | what `pip install` uses — must be **unique on PyPI** |

## Step 2 — `pyproject.toml` (the heart of the package)

```toml
[build-system]
requires = ["setuptools>=61", "wheel"]
build-backend = "setuptools.build_meta"

[project]
name = "soigia-mcp"                 # PyPI name — check it's free first!
version = "0.1.0"                   # starts at 0.1.0
description = "One-line description of the package"
readme = { file = "README.md", content-type = "text/markdown" }
license = "BSD-3-Clause"
license-files = ["LICENSE"]
authors = [{ name = "Soi Gia", email = "sojgja@gmail.com" }]
keywords = ["soigia", "mcp"]
requires-python = ">=3.8"
dependencies = []                   # e.g. ["requests>=2.31", "typer>=0.9"]

[project.scripts]
soigia-mcp = "soigia_mcp.cli:main"  # (optional) installs a CLI command

[tool.setuptools.packages.find]
include = ["soigia_mcp", "soigia_mcp.*"]

[tool.pytest.ini_options]
testpaths = ["tests"]
```

Key points:
- `version` is **static here** — keep it in sync with `__init__.py`
  (the release script does this automatically — Step 6).
- `[project.scripts]` turns your Python function into a shell command.
- Check the PyPI name is free first: `curl https://pypi.org/pypi/<name>/json`
  → `404` means the name is available.

## Step 3 — Source code

```python
# soigia_mcp/__init__.py
__version__ = "0.1.0"
```

```python
# soigia_mcp/cli.py
import argparse


def hello(name: str = "world") -> str:
    return f"Hello, {name}! 👋 from soigia-mcp"


def main() -> None:
    parser = argparse.ArgumentParser(prog="soigia-mcp")
    parser.add_argument("--name", default="world")
    args = parser.parse_args()
    print(hello(args.name))
```

## Step 4 — Tests (run before every publish)

```python
# tests/test_smoke.py
import soigia_mcp


def test_version():
    assert soigia_mcp.__version__ == "0.1.0"


def test_hello():
    assert "soigia-mcp" in soigia_mcp.cli.hello("Soi Gia")
```

Install dev tools once, then run:

```bash
python -m pip install -e . pytest build twine
make test
```

## Step 5 — Makefile (one command per job)

Copy the [Makefile](Makefile) from this package — the targets are generic:

| Target | What it does |
|--------|--------------|
| `make test` | run pytest |
| `make build` | build `dist/*.whl` + `dist/*.tar.gz` |
| `make check` | twine check (metadata validation) |
| `make bump-patch/minor/major` | bump version in `pyproject.toml` + `__init__.py` |
| `make publish` | **clean → test → bump patch → build → check → upload** |
| `make publish-minor/major` | same, different version bump |
| `make publish-test` | upload to TestPyPI instead of PyPI |
| `make clean` | remove build artifacts |

## Step 6 — Release scripts (the automation core)

Copy from this package, then **change one line** (the package path):

```bash
cp ../pip-soigia-init/Makefile .
mkdir scripts
cp ../pip-soigia-init/scripts/release.py scripts/
cp ../pip-soigia-init/scripts/bump_version.py scripts/
# edit scripts/release.py: init_path = root / "soigia_mcp" / "__init__.py"
# edit scripts/bump_version.py: same package name fix
```

What `release.py` does automatically:

1. Reads the current version from `pyproject.toml`
2. Bumps it (`patch` by default) in **both** `pyproject.toml` and `__init__.py`
3. Builds + validates with twine
4. Uploads with credentials from `.secret/pypi.yaml` (Step 7)
5. **On any failure: rolls the version back** — a broken publish never leaves
   your files half-bumped, and `make publish` can be re-run safely.

## Step 7 — 🔐 Credentials: `.secret/pypi.yaml` (READ THIS)

Credentials are shared by **every package** in the repo — they live **once**
at the repo root, not inside each package:

```
soigia-sdk/
├── .secret/
│   └── pypi.yaml          ← token pypi.org (real) + token testpypi (empty)
├── pip-soigia/
├── pip-soigia-init/       ← release.py finds .secret at repo root (..)
└── ...
```

`release.py` finds it automatically: the script is at `<package>/scripts/`,
so it looks at `<package>/../.secret/pypi.yaml`. **Do not copy `.secret/`
into your package folder.**

```yaml
pypi:                        # used when repository = soigia / pypi (default)
  repository: https://upload.pypi.org/legacy/
  username: __token__        # fixed — do not change
  password: pypi-...         # token from https://pypi.org/manage/account/token/

testpypi:                    # used when repository = testpypi
  repository: https://test.pypi.org/legacy/
  username: __token__
  password: ""               # token from https://test.pypi.org/manage/account/token/
```

### ⚠️ Security rules — non-negotiable

| Rule | Why |
|------|-----|
| `.secret/` is in `.gitignore` — verify with `git check-ignore .secret/pypi.yaml` | the token must **never** reach GitHub |
| Never print a token into a chat, log, or screenshot | it stays in the conversation history forever |
| One token per account: pypi.org ≠ test.pypi.org | a 403 means you used the wrong kind |
| Token leaked? **Revoke it** at the token page, generate a new one, paste it back | revoking kills the leaked one instantly |
| `git log --all -S pypi-...` shows nothing | confirms no token ever entered history |

## Step 8 — Publish (one command, no typing)

```bash
make publish          # 0.1.0 → 0.1.1 on PyPI (real)
make publish-test     # uploads to TestPyPI instead (needs testpypi token)
make publish-minor    # 0.1.0 → 0.2.0
make publish-major    # 0.1.0 → 1.0.0
```

Which credentials are used:

| `--repository` (set by Makefile) | Section read | Uploads to |
|----------------------------------|--------------|------------|
| `soigia` (default) / `pypi` | `pypi:` | https://pypi.org — **real** |
| `testpypi` | `testpypi:` | https://test.pypi.org — trial |

Success looks like:

```
Uploading soigia_mcp-0.1.1-py3-none-any.whl ...
View at: https://pypi.org/project/soigia-mcp/0.1.1/
```

## Step 9 — Verify the published package

```bash
pip install soigia-mcp       # install from PyPI (not from source!)
soigia-mcp --help            # CLI works
python -c "import soigia_mcp; print(soigia_mcp.__version__)"
```

## Step 10 — Next releases

Always run `make publish` — never bump versions by hand. Version bumps are
recorded in `pyproject.toml` **and** `__init__.py` by the script, so the
installed `__version__` always matches the release.

---

# 🛠️ Everyday Command Summary

```bash
make test          # run tests
make build         # build artifacts
make check         # validate artifacts
make publish       # release patch on PyPI (recommended everyday flow)
make publish-minor # release minor
make publish-major # release major
make clean         # remove build/
```

# 🔒 Security Checklist (quick)

- [ ] `git check-ignore .secret/pypi.yaml` → prints the file (ignored)
- [ ] `git status --short` → no `.secret/` listed
- [ ] `git log --all -S pypi-` → empty output
- [ ] Token never typed into chat/logs/screenshots
- [ ] Leaked token → revoked + replaced in `.secret/pypi.yaml`

# 📄 License

[BSD-3-Clause](LICENSE)
