gfunk 0.1.0__tar.gz
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.
- gfunk-0.1.0/.claude/CLAUDE.md +53 -0
- gfunk-0.1.0/.claude/settings.json +10 -0
- gfunk-0.1.0/.github/dependabot.yml +53 -0
- gfunk-0.1.0/.github/workflows/ci.yml +159 -0
- gfunk-0.1.0/.github/workflows/publish.yml +47 -0
- gfunk-0.1.0/.gitignore +15 -0
- gfunk-0.1.0/.pre-commit-config.yaml +73 -0
- gfunk-0.1.0/.ruff.toml +80 -0
- gfunk-0.1.0/CHANGELOG.md +75 -0
- gfunk-0.1.0/Justfile +21 -0
- gfunk-0.1.0/LICENSE +21 -0
- gfunk-0.1.0/PKG-INFO +13 -0
- gfunk-0.1.0/README.md +340 -0
- gfunk-0.1.0/docs/screenshots/dubs_groups.svg +238 -0
- gfunk-0.1.0/docs/screenshots/grind_expanded.svg +243 -0
- gfunk-0.1.0/docs/screenshots/grind_week.svg +241 -0
- gfunk-0.1.0/docs/screenshots/holla_filter.svg +239 -0
- gfunk-0.1.0/docs/screenshots/holla_labels.svg +236 -0
- gfunk-0.1.0/docs/screenshots/holla_messages.svg +238 -0
- gfunk-0.1.0/docs/screenshots/holla_preview.svg +238 -0
- gfunk-0.1.0/docs/screenshots/holla_sorted.svg +238 -0
- gfunk-0.1.0/docs/screenshots/regulate_audit.svg +238 -0
- gfunk-0.1.0/docs/screenshots/snoop_browse.svg +241 -0
- gfunk-0.1.0/docs/screenshots/snoop_preview.svg +241 -0
- gfunk-0.1.0/docs/screenshots/vibe_search.svg +238 -0
- gfunk-0.1.0/docs/screenshots/vibe_table.svg +237 -0
- gfunk-0.1.0/pyproject.toml +60 -0
- gfunk-0.1.0/scripts/capture_screenshots.py +155 -0
- gfunk-0.1.0/scripts/release.py +111 -0
- gfunk-0.1.0/src/gfunk/__init__.py +3 -0
- gfunk-0.1.0/src/gfunk/__main__.py +4 -0
- gfunk-0.1.0/src/gfunk/auth.py +119 -0
- gfunk-0.1.0/src/gfunk/bootstrap.py +183 -0
- gfunk-0.1.0/src/gfunk/browser.py +69 -0
- gfunk-0.1.0/src/gfunk/cache.py +123 -0
- gfunk-0.1.0/src/gfunk/cli.py +2089 -0
- gfunk-0.1.0/src/gfunk/demo_data.py +693 -0
- gfunk-0.1.0/src/gfunk/dubs.py +110 -0
- gfunk-0.1.0/src/gfunk/dubs_tui.py +316 -0
- gfunk-0.1.0/src/gfunk/errors.py +55 -0
- gfunk-0.1.0/src/gfunk/gmail.py +286 -0
- gfunk-0.1.0/src/gfunk/grind_tui.py +208 -0
- gfunk-0.1.0/src/gfunk/holla_tui.py +679 -0
- gfunk-0.1.0/src/gfunk/mcp_config.py +98 -0
- gfunk-0.1.0/src/gfunk/mothership.py +129 -0
- gfunk-0.1.0/src/gfunk/mothership_config.py +64 -0
- gfunk-0.1.0/src/gfunk/regulate.py +113 -0
- gfunk-0.1.0/src/gfunk/regulate_tui.py +331 -0
- gfunk-0.1.0/src/gfunk/snoop_tui.py +559 -0
- gfunk-0.1.0/src/gfunk/vibe.py +114 -0
- gfunk-0.1.0/src/gfunk/workspace.py +753 -0
- gfunk-0.1.0/tests/conftest.py +117 -0
- gfunk-0.1.0/tests/test_auth.py +181 -0
- gfunk-0.1.0/tests/test_bootstrap.py +157 -0
- gfunk-0.1.0/tests/test_browser.py +86 -0
- gfunk-0.1.0/tests/test_cache.py +95 -0
- gfunk-0.1.0/tests/test_cli.py +316 -0
- gfunk-0.1.0/tests/test_cli_bounce.py +123 -0
- gfunk-0.1.0/tests/test_cli_bounce_json.py +56 -0
- gfunk-0.1.0/tests/test_cli_dj.py +222 -0
- gfunk-0.1.0/tests/test_cli_dj_list.py +69 -0
- gfunk-0.1.0/tests/test_cli_drop.py +130 -0
- gfunk-0.1.0/tests/test_cli_dubs.py +126 -0
- gfunk-0.1.0/tests/test_cli_grind.py +89 -0
- gfunk-0.1.0/tests/test_cli_holla.py +79 -0
- gfunk-0.1.0/tests/test_cli_mothership.py +113 -0
- gfunk-0.1.0/tests/test_cli_mount_up.py +460 -0
- gfunk-0.1.0/tests/test_cli_peep.py +169 -0
- gfunk-0.1.0/tests/test_cli_peep_open.py +117 -0
- gfunk-0.1.0/tests/test_cli_regulate.py +103 -0
- gfunk-0.1.0/tests/test_cli_sample.py +116 -0
- gfunk-0.1.0/tests/test_cli_snoop_delete.py +51 -0
- gfunk-0.1.0/tests/test_cli_snoop_move.py +120 -0
- gfunk-0.1.0/tests/test_cli_snoop_peek.py +64 -0
- gfunk-0.1.0/tests/test_cli_snoop_sheet.py +97 -0
- gfunk-0.1.0/tests/test_cli_snoop_walk.py +58 -0
- gfunk-0.1.0/tests/test_cli_utils.py +267 -0
- gfunk-0.1.0/tests/test_cli_vibe.py +113 -0
- gfunk-0.1.0/tests/test_cli_vibe_command.py +76 -0
- gfunk-0.1.0/tests/test_demo_data.py +125 -0
- gfunk-0.1.0/tests/test_dubs.py +185 -0
- gfunk-0.1.0/tests/test_dubs_tui.py +222 -0
- gfunk-0.1.0/tests/test_errors.py +55 -0
- gfunk-0.1.0/tests/test_gmail.py +364 -0
- gfunk-0.1.0/tests/test_grind_render.py +159 -0
- gfunk-0.1.0/tests/test_grind_tui.py +247 -0
- gfunk-0.1.0/tests/test_holla_tui.py +683 -0
- gfunk-0.1.0/tests/test_mcp_config.py +144 -0
- gfunk-0.1.0/tests/test_mothership.py +93 -0
- gfunk-0.1.0/tests/test_mothership_config.py +71 -0
- gfunk-0.1.0/tests/test_regulate.py +222 -0
- gfunk-0.1.0/tests/test_regulate_tui.py +263 -0
- gfunk-0.1.0/tests/test_snoop_tui.py +427 -0
- gfunk-0.1.0/tests/test_vibe_tui.py +110 -0
- gfunk-0.1.0/tests/test_workspace.py +425 -0
- gfunk-0.1.0/tests/test_workspace_gmail.py +323 -0
- gfunk-0.1.0/tests/test_workspace_script_files.py +51 -0
- gfunk-0.1.0/tests/test_workspace_scripts.py +109 -0
- gfunk-0.1.0/uv.lock +1895 -0
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
# gfunk
|
|
2
|
+
|
|
3
|
+
CLI and MCP server for Google Workspace. Python 3.13+, uv, strict mypy.
|
|
4
|
+
|
|
5
|
+
## Quick reference
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
uv sync # install
|
|
9
|
+
uv run pytest tests/ -q # run tests (uses -n 6 via xdist)
|
|
10
|
+
uv run pre-commit run --all-files # lint, format, type-check, test
|
|
11
|
+
uv run gfunk --version # smoke test
|
|
12
|
+
```
|
|
13
|
+
|
|
14
|
+
## Stack
|
|
15
|
+
|
|
16
|
+
- **Runtime:** Python 3.13+, uv
|
|
17
|
+
- **CLI framework:** argparse (src/gfunk/cli.py)
|
|
18
|
+
- **TUI:** Textual (src/gfunk/vibe.py)
|
|
19
|
+
- **MCP server:** mcp library, stdio transport (src/gfunk/mothership.py)
|
|
20
|
+
- **Auth:** Google OAuth2 Desktop flow, token cached at ~/.config/gfunk/
|
|
21
|
+
- **Cache:** SQLite at ~/.local/share/gfunk/cache.db
|
|
22
|
+
- **Linting:** ruff (strict rule set in .ruff.toml), mypy strict
|
|
23
|
+
- **Tests:** pytest + pytest-xdist (`-n 6`)
|
|
24
|
+
|
|
25
|
+
## Layout
|
|
26
|
+
|
|
27
|
+
- `src/gfunk/` — all source
|
|
28
|
+
- `tests/` — one test file per module, named `test_<module>.py` or `test_cli_<command>.py`
|
|
29
|
+
- `pyproject.toml` — deps, mypy config, commitizen config
|
|
30
|
+
- `.ruff.toml` — linter/formatter rules
|
|
31
|
+
- `.pre-commit-config.yaml` — full hook suite
|
|
32
|
+
|
|
33
|
+
## Conventions
|
|
34
|
+
|
|
35
|
+
- Every subcommand has exactly two names: one flavorful, one literal (e.g. `vibe` / `sheet`, `mothership` / `mcp`). No more, no less.
|
|
36
|
+
- Commit messages follow Conventional Commits (commitizen enforced).
|
|
37
|
+
- No comments unless the "why" is non-obvious.
|
|
38
|
+
- Type hints on all function signatures (mypy strict).
|
|
39
|
+
- Tests use synthetic fixtures only — never real customer data, names, or IDs.
|
|
40
|
+
- `block-private-terms` pre-commit hook catches PII in fixtures.
|
|
41
|
+
|
|
42
|
+
## Auth and scopes
|
|
43
|
+
|
|
44
|
+
Scopes are declared in `src/gfunk/auth.py`. A test asserts the exact scope list — any
|
|
45
|
+
change to scopes must update the test. Start read-only, widen deliberately.
|
|
46
|
+
|
|
47
|
+
## Adding a subcommand
|
|
48
|
+
|
|
49
|
+
1. Add the parser in `src/gfunk/cli.py` (with aliases).
|
|
50
|
+
2. Add the handler function in cli.py or a new module.
|
|
51
|
+
3. If it's an MCP tool too, register it in `src/gfunk/mothership.py`.
|
|
52
|
+
4. Write tests: `tests/test_cli_<name>.py`.
|
|
53
|
+
5. Update the vocabulary table in `README.md`.
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
version: 2
|
|
2
|
+
|
|
3
|
+
updates:
|
|
4
|
+
# Runtime dependencies. Google's client libraries ship security fixes on
|
|
5
|
+
# their own cadence, so this is the update stream that actually matters.
|
|
6
|
+
- package-ecosystem: uv
|
|
7
|
+
directory: /
|
|
8
|
+
schedule:
|
|
9
|
+
interval: weekly
|
|
10
|
+
open-pull-requests-limit: 5
|
|
11
|
+
groups:
|
|
12
|
+
# One PR for the routine churn; anything major still arrives alone and
|
|
13
|
+
# gets read properly.
|
|
14
|
+
python-minor-and-patch:
|
|
15
|
+
patterns: ['*']
|
|
16
|
+
update-types: [minor, patch]
|
|
17
|
+
commit-message:
|
|
18
|
+
prefix: 'fix'
|
|
19
|
+
prefix-development: 'chore'
|
|
20
|
+
# Wait a week before proposing a newly-published version. The supply-chain
|
|
21
|
+
# attack this blocks is a malicious release that gets auto-merged within
|
|
22
|
+
# minutes of publication; a week is long enough that a compromised version
|
|
23
|
+
# is usually yanked before Dependabot ever opens the PR. Dependabot's
|
|
24
|
+
# *security* updates are a separate stream and are not delayed by this.
|
|
25
|
+
cooldown:
|
|
26
|
+
default-days: 7
|
|
27
|
+
|
|
28
|
+
# A compromised or abandoned action runs with the workflow's token. Keep
|
|
29
|
+
# these current and pinned to a major.
|
|
30
|
+
- package-ecosystem: github-actions
|
|
31
|
+
directory: /
|
|
32
|
+
schedule:
|
|
33
|
+
interval: weekly
|
|
34
|
+
groups:
|
|
35
|
+
actions:
|
|
36
|
+
patterns: ['*']
|
|
37
|
+
commit-message:
|
|
38
|
+
prefix: 'ci'
|
|
39
|
+
cooldown:
|
|
40
|
+
default-days: 7
|
|
41
|
+
|
|
42
|
+
# pre-commit hook revs are pinned by rev: and go stale silently otherwise.
|
|
43
|
+
- package-ecosystem: pre-commit
|
|
44
|
+
directory: /
|
|
45
|
+
schedule:
|
|
46
|
+
interval: weekly
|
|
47
|
+
groups:
|
|
48
|
+
hooks:
|
|
49
|
+
patterns: ['*']
|
|
50
|
+
commit-message:
|
|
51
|
+
prefix: 'ci'
|
|
52
|
+
cooldown:
|
|
53
|
+
default-days: 7
|
|
@@ -0,0 +1,159 @@
|
|
|
1
|
+
# Lint, type-check, and test every push and PR.
|
|
2
|
+
#
|
|
3
|
+
# gfunk is public and holds a user's Workspace credentials at runtime, so the
|
|
4
|
+
# security jobs are part of CI rather than a separate optional workflow.
|
|
5
|
+
name: ci
|
|
6
|
+
|
|
7
|
+
on:
|
|
8
|
+
push:
|
|
9
|
+
branches: [main]
|
|
10
|
+
pull_request:
|
|
11
|
+
|
|
12
|
+
# Least privilege by default; a job that needs more asks for it itself.
|
|
13
|
+
permissions:
|
|
14
|
+
contents: read
|
|
15
|
+
|
|
16
|
+
concurrency:
|
|
17
|
+
group: ${{ github.workflow }}-${{ github.ref }}
|
|
18
|
+
cancel-in-progress: true
|
|
19
|
+
|
|
20
|
+
jobs:
|
|
21
|
+
check:
|
|
22
|
+
runs-on: ubuntu-latest
|
|
23
|
+
steps:
|
|
24
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
25
|
+
with:
|
|
26
|
+
persist-credentials: false
|
|
27
|
+
|
|
28
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
29
|
+
with:
|
|
30
|
+
python-version: '3.14'
|
|
31
|
+
enable-cache: true
|
|
32
|
+
|
|
33
|
+
- run: uv sync
|
|
34
|
+
|
|
35
|
+
- name: Lint
|
|
36
|
+
run: uv run --with ruff ruff check --config .ruff.toml src tests
|
|
37
|
+
|
|
38
|
+
- name: Format
|
|
39
|
+
run: uv run --with ruff ruff format --check --config .ruff.toml src tests
|
|
40
|
+
|
|
41
|
+
- name: Type-check
|
|
42
|
+
run: uv run --with mypy mypy
|
|
43
|
+
|
|
44
|
+
- name: Test
|
|
45
|
+
run: uv run pytest -q --cov --cov-report json
|
|
46
|
+
|
|
47
|
+
- name: Upload coverage
|
|
48
|
+
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
|
49
|
+
uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
50
|
+
with:
|
|
51
|
+
name: coverage
|
|
52
|
+
path: coverage.json
|
|
53
|
+
|
|
54
|
+
- name: Build
|
|
55
|
+
run: uv build
|
|
56
|
+
|
|
57
|
+
# `--version` must answer on a bare wheel with no dev group, no config,
|
|
58
|
+
# and no credentials -- that is the state a bug reporter is in.
|
|
59
|
+
- name: --version works from the built wheel alone
|
|
60
|
+
run: |
|
|
61
|
+
cd "$(mktemp -d)"
|
|
62
|
+
uv run --isolated --no-project \
|
|
63
|
+
--with "$GITHUB_WORKSPACE"/dist/*.whl gfunk --version
|
|
64
|
+
|
|
65
|
+
hooks:
|
|
66
|
+
runs-on: ubuntu-latest
|
|
67
|
+
steps:
|
|
68
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
69
|
+
with:
|
|
70
|
+
# gitleaks needs history to scan; a shallow clone scans one commit.
|
|
71
|
+
fetch-depth: 0
|
|
72
|
+
# No job here pushes, so leaving the token in .git/config only
|
|
73
|
+
# widens what a compromised step could walk off with.
|
|
74
|
+
persist-credentials: false
|
|
75
|
+
|
|
76
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
77
|
+
with:
|
|
78
|
+
python-version: '3.14'
|
|
79
|
+
enable-cache: true
|
|
80
|
+
|
|
81
|
+
- run: uv sync
|
|
82
|
+
|
|
83
|
+
# The same suite the contributor's commit ran, so a bypassed local hook
|
|
84
|
+
# is caught here rather than in review.
|
|
85
|
+
- name: pre-commit
|
|
86
|
+
run: uv run --with pre-commit pre-commit run --all-files --show-diff-on-failure
|
|
87
|
+
|
|
88
|
+
screenshots:
|
|
89
|
+
runs-on: ubuntu-latest
|
|
90
|
+
steps:
|
|
91
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
92
|
+
with:
|
|
93
|
+
persist-credentials: false
|
|
94
|
+
|
|
95
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
96
|
+
with:
|
|
97
|
+
python-version: '3.14'
|
|
98
|
+
enable-cache: true
|
|
99
|
+
|
|
100
|
+
- run: uv sync
|
|
101
|
+
|
|
102
|
+
- name: Regenerate screenshots
|
|
103
|
+
run: uv run python scripts/capture_screenshots.py
|
|
104
|
+
|
|
105
|
+
- name: Check for drift
|
|
106
|
+
run: |
|
|
107
|
+
if ! git diff --quiet docs/screenshots/; then
|
|
108
|
+
echo "::error::Screenshots are stale. Run: uv run python scripts/capture_screenshots.py"
|
|
109
|
+
git diff --stat docs/screenshots/
|
|
110
|
+
exit 1
|
|
111
|
+
fi
|
|
112
|
+
|
|
113
|
+
codeql:
|
|
114
|
+
runs-on: ubuntu-latest
|
|
115
|
+
permissions:
|
|
116
|
+
# Read the code to analyze it, and write the results to the Security tab.
|
|
117
|
+
contents: read
|
|
118
|
+
security-events: write
|
|
119
|
+
steps:
|
|
120
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
121
|
+
with:
|
|
122
|
+
persist-credentials: false
|
|
123
|
+
|
|
124
|
+
- uses: github/codeql-action/init@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4
|
|
125
|
+
with:
|
|
126
|
+
languages: python
|
|
127
|
+
queries: security-extended
|
|
128
|
+
|
|
129
|
+
- uses: github/codeql-action/analyze@ff2f1c621b7f889edc0d3c761ac2e6a3f8cdb0dd # v4
|
|
130
|
+
|
|
131
|
+
coverage-badge:
|
|
132
|
+
if: github.ref == format('refs/heads/{0}', github.event.repository.default_branch)
|
|
133
|
+
needs: check
|
|
134
|
+
runs-on: ubuntu-latest
|
|
135
|
+
permissions:
|
|
136
|
+
contents: write
|
|
137
|
+
steps:
|
|
138
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1 # zizmor: ignore[artipacked]
|
|
139
|
+
|
|
140
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
141
|
+
with:
|
|
142
|
+
name: coverage
|
|
143
|
+
|
|
144
|
+
- name: Generate badge
|
|
145
|
+
run: |
|
|
146
|
+
COV=$(python3 -c "import json; print(int(json.load(open('coverage.json'))['totals']['percent_covered']))")
|
|
147
|
+
if [ "$COV" -ge 90 ]; then COLOR="brightgreen";
|
|
148
|
+
elif [ "$COV" -ge 75 ]; then COLOR="green";
|
|
149
|
+
elif [ "$COV" -ge 60 ]; then COLOR="yellow";
|
|
150
|
+
else COLOR="red"; fi
|
|
151
|
+
mkdir -p badges
|
|
152
|
+
curl -sS "https://img.shields.io/badge/coverage-${COV}%25-${COLOR}" -o badges/coverage.svg
|
|
153
|
+
|
|
154
|
+
- uses: peaceiris/actions-gh-pages@1ef5a1b1df4c63fe21a2242edbee6cac921ece01 # v4.1.0
|
|
155
|
+
with:
|
|
156
|
+
github_token: ${{ secrets.GITHUB_TOKEN }}
|
|
157
|
+
publish_dir: ./badges
|
|
158
|
+
destination_dir: badges
|
|
159
|
+
keep_files: true
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
# Publish to PyPI when a version tag is pushed (trusted publisher / OIDC).
|
|
2
|
+
# Use: scripts/release.py to bump, review, tag, and push.
|
|
3
|
+
name: publish
|
|
4
|
+
|
|
5
|
+
on:
|
|
6
|
+
push:
|
|
7
|
+
tags:
|
|
8
|
+
- "v*"
|
|
9
|
+
|
|
10
|
+
permissions:
|
|
11
|
+
contents: read
|
|
12
|
+
|
|
13
|
+
jobs:
|
|
14
|
+
build:
|
|
15
|
+
runs-on: ubuntu-latest
|
|
16
|
+
steps:
|
|
17
|
+
- uses: actions/checkout@3d3c42e5aac5ba805825da76410c181273ba90b1 # v7.0.1
|
|
18
|
+
with:
|
|
19
|
+
persist-credentials: false
|
|
20
|
+
|
|
21
|
+
- uses: astral-sh/setup-uv@20cfd1bf945f4377ade1205e4dbc17946fc9a30d # v10.0.1
|
|
22
|
+
with:
|
|
23
|
+
python-version: '3.14'
|
|
24
|
+
enable-cache: false
|
|
25
|
+
|
|
26
|
+
- run: uv build
|
|
27
|
+
|
|
28
|
+
- uses: actions/upload-artifact@043fb46d1a93c77aae656e7c1c64a875d1fc6a0a # v7.0.1
|
|
29
|
+
with:
|
|
30
|
+
name: dist
|
|
31
|
+
path: dist/
|
|
32
|
+
|
|
33
|
+
publish:
|
|
34
|
+
needs: build
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
environment: pypi
|
|
37
|
+
permissions:
|
|
38
|
+
id-token: write
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/download-artifact@3e5f45b2cfb9172054b4087a40e8e0b5a5461e7c # v8.0.1
|
|
41
|
+
with:
|
|
42
|
+
name: dist
|
|
43
|
+
path: dist/
|
|
44
|
+
|
|
45
|
+
- uses: pypa/gh-action-pypi-publish@dc37677b2e1c63e2034f94d8a5b11f265b73ba33 # v1.14.2
|
|
46
|
+
with:
|
|
47
|
+
print-hash: true
|
gfunk-0.1.0/.gitignore
ADDED
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
default_install_hook_types: [pre-commit, commit-msg]
|
|
2
|
+
default_stages: [pre-commit]
|
|
3
|
+
|
|
4
|
+
repos:
|
|
5
|
+
# Fail a bad commit message in ~0.3s rather than after the whole suite.
|
|
6
|
+
- repo: https://github.com/dannybrown37/git-a-grip
|
|
7
|
+
rev: v0.12.0
|
|
8
|
+
hooks:
|
|
9
|
+
- id: commitizen-early
|
|
10
|
+
fail_fast: true
|
|
11
|
+
|
|
12
|
+
- repo: https://github.com/commitizen-tools/commitizen
|
|
13
|
+
rev: v4.17.1
|
|
14
|
+
hooks:
|
|
15
|
+
- id: commitizen
|
|
16
|
+
stages: [commit-msg]
|
|
17
|
+
|
|
18
|
+
# Anything below that reaches `git commit` is in a public repo forever.
|
|
19
|
+
- repo: https://github.com/gitleaks/gitleaks
|
|
20
|
+
rev: v8.30.1
|
|
21
|
+
hooks:
|
|
22
|
+
- id: gitleaks
|
|
23
|
+
|
|
24
|
+
- repo: https://github.com/pre-commit/pre-commit-hooks
|
|
25
|
+
rev: v6.0.0
|
|
26
|
+
hooks:
|
|
27
|
+
- id: detect-private-key
|
|
28
|
+
- id: check-added-large-files
|
|
29
|
+
args: [--maxkb=512]
|
|
30
|
+
- id: end-of-file-fixer
|
|
31
|
+
- id: trailing-whitespace
|
|
32
|
+
- id: check-merge-conflict
|
|
33
|
+
- id: check-toml
|
|
34
|
+
- id: check-yaml
|
|
35
|
+
- id: mixed-line-ending
|
|
36
|
+
# A stray credentials.json or token.json is gitignored, but -f defeats
|
|
37
|
+
# that; this refuses the file itself.
|
|
38
|
+
- id: forbid-new-submodules
|
|
39
|
+
|
|
40
|
+
# A workflow runs with a token; audit it like code.
|
|
41
|
+
- repo: https://github.com/rhysd/actionlint
|
|
42
|
+
rev: v1.7.12
|
|
43
|
+
hooks:
|
|
44
|
+
- id: actionlint
|
|
45
|
+
|
|
46
|
+
- repo: https://github.com/zizmorcore/zizmor-pre-commit
|
|
47
|
+
rev: v1.29.0
|
|
48
|
+
hooks:
|
|
49
|
+
- id: zizmor
|
|
50
|
+
|
|
51
|
+
- repo: https://github.com/dannybrown37/git-a-grip
|
|
52
|
+
rev: v0.12.0
|
|
53
|
+
hooks:
|
|
54
|
+
- id: ruff-check
|
|
55
|
+
args: [--config, .ruff.toml]
|
|
56
|
+
- id: ruff-format
|
|
57
|
+
args: [--config, .ruff.toml]
|
|
58
|
+
- id: mypy
|
|
59
|
+
# A secret scanner cannot find a real customer name or sheet ID. This
|
|
60
|
+
# can. Public repo + Workspace data makes it the load-bearing one.
|
|
61
|
+
- id: block-private-terms
|
|
62
|
+
- id: embed-tree
|
|
63
|
+
- id: pytest
|
|
64
|
+
args: [tests/, -q]
|
|
65
|
+
|
|
66
|
+
- repo: local
|
|
67
|
+
hooks:
|
|
68
|
+
- id: screenshots
|
|
69
|
+
name: screenshots
|
|
70
|
+
language: system
|
|
71
|
+
entry: bash -c 'uv run python scripts/capture_screenshots.py && git add docs/screenshots/*.svg'
|
|
72
|
+
files: '(src/gfunk/(holla_tui|grind_tui|snoop_tui|dubs_tui|regulate_tui|vibe|demo_data)\.py|scripts/capture_screenshots\.py)$'
|
|
73
|
+
pass_filenames: false
|
gfunk-0.1.0/.ruff.toml
ADDED
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
line-length = 88
|
|
2
|
+
show-fixes = true
|
|
3
|
+
target-version = "py313"
|
|
4
|
+
|
|
5
|
+
[lint]
|
|
6
|
+
select = [
|
|
7
|
+
"A",
|
|
8
|
+
"ANN",
|
|
9
|
+
"ARG",
|
|
10
|
+
"ASYNC",
|
|
11
|
+
"B",
|
|
12
|
+
"BLE",
|
|
13
|
+
"C4",
|
|
14
|
+
"C90",
|
|
15
|
+
"COM",
|
|
16
|
+
"D200",
|
|
17
|
+
"D201",
|
|
18
|
+
"D202",
|
|
19
|
+
"D205",
|
|
20
|
+
"D212",
|
|
21
|
+
"E",
|
|
22
|
+
"EM",
|
|
23
|
+
"ERA",
|
|
24
|
+
"EXE",
|
|
25
|
+
"F",
|
|
26
|
+
"FBT",
|
|
27
|
+
"G",
|
|
28
|
+
"I",
|
|
29
|
+
"ICN",
|
|
30
|
+
"ISC",
|
|
31
|
+
"N",
|
|
32
|
+
"PGH",
|
|
33
|
+
"PIE",
|
|
34
|
+
"PL",
|
|
35
|
+
"PT",
|
|
36
|
+
"PTH",
|
|
37
|
+
"PYI",
|
|
38
|
+
"Q",
|
|
39
|
+
"RET",
|
|
40
|
+
"RSE",
|
|
41
|
+
"RUF",
|
|
42
|
+
"S",
|
|
43
|
+
"SIM",
|
|
44
|
+
"SLF",
|
|
45
|
+
"T20",
|
|
46
|
+
"TCH",
|
|
47
|
+
"TID",
|
|
48
|
+
"TRY",
|
|
49
|
+
"UP",
|
|
50
|
+
"W",
|
|
51
|
+
"YTT",
|
|
52
|
+
]
|
|
53
|
+
ignore = [
|
|
54
|
+
"COM812",
|
|
55
|
+
"I001",
|
|
56
|
+
"ISC001",
|
|
57
|
+
"PLC0415",
|
|
58
|
+
"PLR0913",
|
|
59
|
+
"S101",
|
|
60
|
+
# Python 3.14 evaluates annotations lazily (PEP 649), so hiding imports in a
|
|
61
|
+
# TYPE_CHECKING block buys nothing and costs readability.
|
|
62
|
+
"TRY003",
|
|
63
|
+
]
|
|
64
|
+
|
|
65
|
+
[lint.per-file-ignores]
|
|
66
|
+
# The CLI is the one place printing to stdout is the product.
|
|
67
|
+
"src/gfunk/cli.py" = ["T20", "ANN401"]
|
|
68
|
+
# Browser-open fallback: only path when no browser is reachable, prints the link.
|
|
69
|
+
"src/gfunk/browser.py" = ["T20"]
|
|
70
|
+
# ANN401: JSON payloads and googleapiclient's `build()` results are genuinely
|
|
71
|
+
# untyped; narrowing them would be a lie rather than a type.
|
|
72
|
+
"src/gfunk/cache.py" = ["ANN401"]
|
|
73
|
+
"src/gfunk/workspace.py" = ["ANN401"]
|
|
74
|
+
"src/gfunk/mothership.py" = ["ANN401"]
|
|
75
|
+
# Workspace is duck-typed here too, same as cli.py's snoop/regulate helpers.
|
|
76
|
+
"src/gfunk/regulate_tui.py" = ["ANN401"]
|
|
77
|
+
# PLR2004/EM101: a literal in an assertion is the assertion.
|
|
78
|
+
# Screenshot capture script prints progress.
|
|
79
|
+
"scripts/*" = ["T20"]
|
|
80
|
+
"tests/*" = ["SLF001", "S105", "S106", "PLR2004", "EM101"]
|
gfunk-0.1.0/CHANGELOG.md
ADDED
|
@@ -0,0 +1,75 @@
|
|
|
1
|
+
## v0.1.0 (2026-08-28)
|
|
2
|
+
|
|
3
|
+
### Feat
|
|
4
|
+
|
|
5
|
+
- snoop should color code by file type now
|
|
6
|
+
- add recency and size sorting options to 'snoop' command
|
|
7
|
+
- add gmail integration and a 'grind' command that shows agenda for next X days
|
|
8
|
+
- expose gfunk vibe
|
|
9
|
+
- default snoop command to opening a TUI with doc viewer
|
|
10
|
+
- pdf attachments in holla archive works!
|
|
11
|
+
- add a modal during archive processing (which can take a while for labels)
|
|
12
|
+
- allow deletion of empty labels (warn for non-empty and reject)
|
|
13
|
+
- holla command gets preview pane with scroll plus delete shortcut
|
|
14
|
+
- continue expanding gfunk holla features
|
|
15
|
+
- set up holla / emails command
|
|
16
|
+
- gfunk dubs finds duplicates or suspected duplicates, TUI view
|
|
17
|
+
- gfunk snoop now offers multi-selection with tab
|
|
18
|
+
- gfunk regulate has a revoke option now
|
|
19
|
+
- regulate prints a nice tui view
|
|
20
|
+
- snoop gets limited live preview and a delete option (trash)
|
|
21
|
+
- add dj push/pull
|
|
22
|
+
- set up apps scripts api access, dj runs now prints recent runs
|
|
23
|
+
- reorganize and give flavor names for each topic in the help menu
|
|
24
|
+
- gfunk drop uploads a file
|
|
25
|
+
- gfunk vibe gets a simple TUI viewer and replaces sample
|
|
26
|
+
- add a (fairly weak) dj subcommand that opens google scripts pages
|
|
27
|
+
- gfunk slide for moving items around drive with fzf and browsing
|
|
28
|
+
- gfunk peep to view docs or sheets in the terminal, fzf selection
|
|
29
|
+
- add gfunk dig/open, gfunk bounce/export
|
|
30
|
+
- initial prototype ready
|
|
31
|
+
|
|
32
|
+
### Fix
|
|
33
|
+
|
|
34
|
+
- badge/readme update
|
|
35
|
+
- handle some test warnings
|
|
36
|
+
- trailing whitespace in readme
|
|
37
|
+
- show enter instructions on snoop command
|
|
38
|
+
- pre-commit + CI guard for regenerating screenshots when TUIs change
|
|
39
|
+
- improve 'gfunk vibe' menu
|
|
40
|
+
- blue->purple for clarity
|
|
41
|
+
- TUI menu was duplicative, now it's not
|
|
42
|
+
- pytext-xdist to cut test suite time in half, justfile with release script
|
|
43
|
+
- publish.yml for PyPI
|
|
44
|
+
- mothership/mcp should be tool-specific, details on cache in README
|
|
45
|
+
- add license, gitignore terms
|
|
46
|
+
- remove backlog
|
|
47
|
+
- add lookback on grind command
|
|
48
|
+
- improve grind tui
|
|
49
|
+
- TUI descriptions in help
|
|
50
|
+
- bug on vibe TUI, specifically label vibe TUI
|
|
51
|
+
- visual bug when deleting holla items down from >1 to 1
|
|
52
|
+
- when backing up attachments, create a directory and group renders + attachments in it
|
|
53
|
+
- lint
|
|
54
|
+
- cli options tweak; fix ruff toml issue driving me crazy in nvim
|
|
55
|
+
- header with flavor
|
|
56
|
+
- pre-commit passage
|
|
57
|
+
- y/n casing
|
|
58
|
+
- make revoke/delete easier in gfunk regulate
|
|
59
|
+
- Greatly improve layout of TUI on regulate command
|
|
60
|
+
- dj triggers label clarity
|
|
61
|
+
- add scripts.projects scope
|
|
62
|
+
- hanging tests, more flavor names
|
|
63
|
+
- merge everything into snoop, too many similar commands
|
|
64
|
+
- merged dig into peep, added a print action for snoop, update rules
|
|
65
|
+
- merge snoop and slide commands into one
|
|
66
|
+
- improve cli help
|
|
67
|
+
- remove get-down, it was confusing overlap, just a single setup/login/mount-up command
|
|
68
|
+
- downgrade to 3.13 for ruff lsp reasons
|
|
69
|
+
- slight update to alias and description for gfunk sample
|
|
70
|
+
- gfunk dig doesn't ask for ID if Ctrl+C out of fzf
|
|
71
|
+
- mothership has install and serve sub commands now
|
|
72
|
+
- narrow dig to just opening a file in your browser
|
|
73
|
+
- alias sample, remove mix, alias mothership
|
|
74
|
+
- pass linting
|
|
75
|
+
- make mount-up do better bootstrapping
|
gfunk-0.1.0/Justfile
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
# gfunk task runner
|
|
2
|
+
|
|
3
|
+
default:
|
|
4
|
+
@echo "Usage: just <recipe>"
|
|
5
|
+
@echo ""
|
|
6
|
+
@echo " release Interactive version bump, review, and publish"
|
|
7
|
+
@echo " install Install dependencies"
|
|
8
|
+
@echo " test Run the test suite with coverage"
|
|
9
|
+
@echo " lint Run all pre-commit hooks"
|
|
10
|
+
|
|
11
|
+
install:
|
|
12
|
+
uv sync
|
|
13
|
+
|
|
14
|
+
release:
|
|
15
|
+
uv run python scripts/release.py
|
|
16
|
+
|
|
17
|
+
test:
|
|
18
|
+
uv run pytest tests/ -q --cov=gfunk --cov-report=term-missing
|
|
19
|
+
|
|
20
|
+
lint:
|
|
21
|
+
uv run pre-commit run --all-files
|
gfunk-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Danny Brown
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
gfunk-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
Metadata-Version: 2.5
|
|
2
|
+
Name: gfunk
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: MCP server and CLI for programmatic Google Workspace access
|
|
5
|
+
License-File: LICENSE
|
|
6
|
+
Requires-Python: >=3.13
|
|
7
|
+
Requires-Dist: google-api-python-client>=2.198.0
|
|
8
|
+
Requires-Dist: google-auth-httplib2>=0.4.1
|
|
9
|
+
Requires-Dist: google-auth-oauthlib>=1.4.0
|
|
10
|
+
Requires-Dist: mcp>=2.0.0
|
|
11
|
+
Requires-Dist: tabulate>=0.10.0
|
|
12
|
+
Requires-Dist: textual>=3.0.0
|
|
13
|
+
Requires-Dist: xhtml2pdf>=0.2.17
|