Metadata-Version: 2.4
Name: obsidian-code-atlas
Version: 0.5.1
Summary: Mirror GitHub activity into an Obsidian vault
Author: Michael Tisza
License-Expression: MIT
Project-URL: Homepage, https://github.com/tiszalab/obsidian-code-atlas
Project-URL: Repository, https://github.com/tiszalab/obsidian-code-atlas
Project-URL: Issues, https://github.com/tiszalab/obsidian-code-atlas/issues
Project-URL: Changelog, https://github.com/tiszalab/obsidian-code-atlas/releases
Keywords: obsidian,github,notes,knowledge-management,second-brain
Classifier: Development Status :: 4 - Beta
Classifier: Environment :: Console
Classifier: Intended Audience :: Developers
Classifier: Intended Audience :: Science/Research
Classifier: Operating System :: MacOS
Classifier: Operating System :: POSIX :: Linux
Classifier: Programming Language :: Python :: 3
Classifier: Programming Language :: Python :: 3 :: Only
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: Programming Language :: Python :: 3.13
Classifier: Topic :: Text Processing :: Markup :: Markdown
Classifier: Topic :: Utilities
Requires-Python: >=3.9
Description-Content-Type: text/markdown
License-File: LICENSE
Dynamic: license-file

# Obsidian Code Atlas

Mirror your GitHub code into an [Obsidian](https://obsidian.md/) vault.

> [!NOTE]
> **Who is this for?** 
> 
> People who write code, host it on GitHub, and use Obsidian as a work, lab, or personal notebook for its "Second Brain" features.
> 
> **What problem does this solve?**
> 
> As a scientist, I love having all my laboratory notes and ideas easily searchable within an Obisidian vault. But, when I write code, I often find myself clunkily searching on GitHub in the browser and/or through my local computer's files for previously written scripts. 
> 
> **Why `obsidian-code-atlas`?** 
> 
> It brings code from all my repos into easily searchable and visually appealling `Base` and `Note` objects inside an existing Obsidian vault. It updates each morning, so scripts are always fresh.

This tool generates four things from your `gh`-authenticated account:

1. **Activity.md** — recent commits across every repo you touch.
2. **Repos/** + **Repos.base** — one note per repo, browsable as an Obsidian Base database.
3. **Scripts/** + **Scripts.base** — every source file you’ve written, as searchable notes.
4. **Issues/** + **Issues.base** — every open issue in those repos (labels, assignees, milestone, comment count), one note per issue named by number (e.g. `Issues/owner-repo/42.md`).

The generated `Repos/`, `Scripts/`, `Issues/`, `Activity.md`, `GitHub Dashboard.md`, and `*.base` files are fully managed by the tool — don’t hand-edit them.

## Requirements

- [GitHub CLI (`gh`)](https://cli.github.com/) installed and authenticated (`gh auth status`).
- Python 3.9 or newer.

## Installation

Install from PyPI with pip:

```bash
python3 -m pip install obsidian-code-atlas
```

For an isolated command-line installation, use [pipx](https://pipx.pypa.io/):

```bash
pipx install obsidian-code-atlas
```

Upgrade an existing installation:

```bash
python3 -m pip install --upgrade obsidian-code-atlas
# or, for pipx:
pipx upgrade obsidian-code-atlas
```

<details>
<summary>Installing from source</summary>

Install the latest development version directly from the Git URL:

```bash
python3 -m pip install git+https://github.com/tiszalab/obsidian-code-atlas.git
```

Or from a local clone:

```bash
git clone https://github.com/tiszalab/obsidian-code-atlas.git
cd obsidian-code-atlas
python3 -m pip install .
```

</details>

The application code lives in your Python environment; generated Obsidian notes live in the vault you choose below.

## Initializing a vault

The `init` command prepares an existing Obsidian vault to receive generated notes and optionally installs a daily scheduler.

```bash
obsidian-code-atlas init "/path/to/My Vault" --output "Code Atlas" --scheduler launchd
```

Arguments:

- `VAULT_PATH` — root of an existing Obsidian vault (the directory containing `.obsidian/`).
- `--output` — name of the generated atlas folder inside the vault (default: `Code Atlas`).
- `--scheduler` — `launchd` (macOS), `cron` (Unix), or `none` (default).
- `--gitignore` — add the generated output folder to the parent Git repository’s `.gitignore` (default).
- `--no-gitignore` (alias `--track-generated`) — opt out of the default `.gitignore` entry.
- `--force` — initialize even if `.obsidian/` is missing.
- `--no-refresh` — skip the first `refresh all`.
- `--config` — path to a custom `obsidian-code-atlas.json` configuration file.

`init` is idempotent: rerunning it updates the scheduler and Git ignore entry without duplicating them or deleting existing generated notes.

### Git integration

If your vault is inside a Git worktree, `init` finds the actual repository root (even when the vault is nested below it) and adds an anchored ignore block:

```gitignore
# BEGIN obsidian-code-atlas: <stable-id>
/path/relative/to/repo/My Vault/Code Atlas/
# END obsidian-code-atlas: <stable-id>
```

This leaves unrelated `.gitignore` content untouched. Because the whole output directory is ignored, generated notes and output-local configuration will not be tracked by the parent repository. Use `--no-gitignore` (or its alias `--track-generated`) to skip this step. When the output directory *is* the worktree root there is no rule that could ignore it, so `init` and `doctor` say so instead of writing one.

### Diagnosing the setup

```bash
obsidian-code-atlas doctor --output "/path/to/My Vault/Code Atlas"
```

`doctor` checks:

- package and Python version;
- whether the output directory exists and is writable;
- whether the output is inside an Obsidian vault;
- `gh` availability and `gh auth status`;
- effective configuration file and JSON validity;
- installed scheduler status;
- parent Git worktree and whether the output is ignored.

It exits with status `1` if a condition prevents `refresh` from working. Warnings that do not block refresh keep a zero status. `doctor` never prints tokens or credentials.

## Refreshing content

Every package invocation needs an output directory. This prevents generated files from silently going into the current working directory or package installation:

```bash
obsidian-code-atlas refresh all --output "/path/to/My Vault/Code Atlas"
obsidian-code-atlas refresh activity --output "/path/to/My Vault/Code Atlas"
obsidian-code-atlas refresh repos --output "/path/to/My Vault/Code Atlas"
obsidian-code-atlas refresh scripts --output "/path/to/My Vault/Code Atlas"
obsidian-code-atlas refresh issues --output "/path/to/My Vault/Code Atlas"
```

The section defaults to `all`, so `obsidian-code-atlas refresh --output PATH` is also valid. Paths containing spaces are supported. `OBSIDIAN_CODE_ATLAS_OUTPUT` may supply the output path, but an explicit `--output` takes precedence. The command resolves the path to an absolute path and creates it when the refresh starts.

The same interface is available through Python:

```bash
python3 -m obsidian_code_atlas --version
python3 -m obsidian_code_atlas refresh all --output "/path/to/My Vault/Code Atlas"
```

## Scheduling

Installed-package users can manage a daily 08:00 refresh without depending on repository scripts:

```bash
obsidian-code-atlas scheduler install --launchd --output "/path/to/My Vault/Code Atlas"  # macOS
obsidian-code-atlas scheduler install --cron --output "/path/to/My Vault/Code Atlas"     # Unix cron
obsidian-code-atlas scheduler status --output "/path/to/My Vault/Code Atlas"
obsidian-code-atlas scheduler uninstall --output "/path/to/My Vault/Code Atlas"
```

Pass `--config PATH` to `scheduler install` to preserve an explicit configuration path in the scheduled command. The generated job uses the absolute interpreter running Obsidian Code Atlas, `refresh all`, and the absolute output path. Different output directories receive different identifiers, so multiple vaults can coexist. `--launchd` is rejected outside macOS with a recommendation to use cron.

launchd runs both at login and daily at 08:00. Cron runs daily at 08:00. Cron output is appended to `refresh.log` in the selected output directory; launchd writes `launchd.out.log` and `launchd.err.log` there. The install command creates the output directory before scheduling the first run.

`scheduler status` prints the expected identifier, installed scheduler types, schedule, and command. It returns status 1 when neither matching job is installed and 0 when at least one is found. Uninstall removes only jobs belonging to the selected output directory.

## Configuring language support

Create `obsidian-code-atlas.json` in the output directory using this structure (repository checkouts also include `obsidian-code-atlas.json.example`):

```json
{
  "excluded_repos": [
    "owner/private-repo",
    "organization/archived-repo"
  ],
  "script_extensions": {
    ".ex": {"label": "Elixir", "fence": "elixir"},
    ".exs": {"label": "Elixir Script", "fence": "elixir"},
    ".yml": null
  }
}
```

`excluded_repos` contains case-insensitive `owner/repository` names. Excluded repositories are omitted from activity, repository, script, and issue output. A `script_extensions` value of `null` removes an extension. A configuration file that cannot be parsed or validated aborts the command with an error instead of falling back to the defaults. Configuration is selected in this exact order:

1. explicit `--config PATH`;
2. `OBSIDIAN_CODE_ATLAS_CONFIG`;
3. `obsidian-code-atlas.json` in the output directory;
4. built-in defaults.

Extension environment overrides are applied after the selected file:

```bash
OBSIDIAN_CODE_ATLAS_EXTENSIONS=".ex:Elixir:elixir,.exs:Elixir:elixir,-.yml" \
  obsidian-code-atlas refresh all --output "/path/to/My Vault/Code Atlas"
```

The format is `.ext:Label:fence`; two parts (`.ext:Label`) and one part (`.ext`) are also accepted, and a leading `-` removes an extension.

## File layout

```text
|.
├── pyproject.toml
├── src/obsidian_code_atlas/      # installable package
│   ├── __init__.py
│   ├── __main__.py
│   ├── cli.py
│   ├── doctor.py
│   ├── init.py
│   └── scheduler.py
└── obsidian-code-atlas.json.example
```

## Uninstall

Remove the scheduler first, then uninstall the package. Generated notes are retained unless you delete the output folder manually.

```bash
obsidian-code-atlas scheduler uninstall --output "/path/to/My Vault/Code Atlas"
python3 -m pip uninstall obsidian-code-atlas
# or, for pipx:
pipx uninstall obsidian-code-atlas
```

## License

[MIT](LICENSE) © Michael Tisza
