grit-cli 0.1.0a0__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.
- grit_cli-0.1.0a0/.github/workflows/ci.yml +85 -0
- grit_cli-0.1.0a0/.github/workflows/release.yml +101 -0
- grit_cli-0.1.0a0/.gitignore +101 -0
- grit_cli-0.1.0a0/CHANGELOG.md +37 -0
- grit_cli-0.1.0a0/CLAUDE.md +195 -0
- grit_cli-0.1.0a0/Docs/BRD.md +806 -0
- grit_cli-0.1.0a0/Docs/DEVELOPER.md +445 -0
- grit_cli-0.1.0a0/Docs/HELP_STANDARD.md +319 -0
- grit_cli-0.1.0a0/Docs/HOW_TO.md +299 -0
- grit_cli-0.1.0a0/Docs/PRD.md +228 -0
- grit_cli-0.1.0a0/Docs/SETUP.md +351 -0
- grit_cli-0.1.0a0/LICENSE +21 -0
- grit_cli-0.1.0a0/PKG-INFO +301 -0
- grit_cli-0.1.0a0/README.md +241 -0
- grit_cli-0.1.0a0/admin-ui/index.html +12 -0
- grit_cli-0.1.0a0/admin-ui/package-lock.json +3913 -0
- grit_cli-0.1.0a0/admin-ui/package.json +31 -0
- grit_cli-0.1.0a0/admin-ui/src/App.tsx +55 -0
- grit_cli-0.1.0a0/admin-ui/src/api/auth.ts +48 -0
- grit_cli-0.1.0a0/admin-ui/src/api/client.ts +27 -0
- grit_cli-0.1.0a0/admin-ui/src/api/enterprise.ts +85 -0
- grit_cli-0.1.0a0/admin-ui/src/components/LoadingSpinner.tsx +3 -0
- grit_cli-0.1.0a0/admin-ui/src/main.tsx +21 -0
- grit_cli-0.1.0a0/admin-ui/src/pages/AuditLogPage.tsx +118 -0
- grit_cli-0.1.0a0/admin-ui/src/pages/HelpPage.tsx +448 -0
- grit_cli-0.1.0a0/admin-ui/src/pages/LoginPage.tsx +102 -0
- grit_cli-0.1.0a0/admin-ui/src/pages/OrgPage.tsx +131 -0
- grit_cli-0.1.0a0/admin-ui/src/pages/SubscriptionPage.tsx +56 -0
- grit_cli-0.1.0a0/admin-ui/src/pages/TeamProfilesPage.tsx +128 -0
- grit_cli-0.1.0a0/admin-ui/tsconfig.json +25 -0
- grit_cli-0.1.0a0/admin-ui/tsconfig.node.json +10 -0
- grit_cli-0.1.0a0/admin-ui/vite.config.ts +20 -0
- grit_cli-0.1.0a0/assets/grit_logo_hi_res.png +0 -0
- grit_cli-0.1.0a0/backend/.env.example +11 -0
- grit_cli-0.1.0a0/backend/Dockerfile +18 -0
- grit_cli-0.1.0a0/backend/app/__init__.py +0 -0
- grit_cli-0.1.0a0/backend/app/api/__init__.py +0 -0
- grit_cli-0.1.0a0/backend/app/api/account.py +29 -0
- grit_cli-0.1.0a0/backend/app/api/auth.py +139 -0
- grit_cli-0.1.0a0/backend/app/api/deps.py +38 -0
- grit_cli-0.1.0a0/backend/app/api/enterprise.py +397 -0
- grit_cli-0.1.0a0/backend/app/api/license.py +39 -0
- grit_cli-0.1.0a0/backend/app/api/stripe_webhook.py +122 -0
- grit_cli-0.1.0a0/backend/app/api/sync.py +165 -0
- grit_cli-0.1.0a0/backend/app/config.py +47 -0
- grit_cli-0.1.0a0/backend/app/database.py +25 -0
- grit_cli-0.1.0a0/backend/app/main.py +48 -0
- grit_cli-0.1.0a0/backend/app/models/__init__.py +0 -0
- grit_cli-0.1.0a0/backend/app/models/user.py +111 -0
- grit_cli-0.1.0a0/backend/app/services/__init__.py +0 -0
- grit_cli-0.1.0a0/backend/app/services/license_service.py +62 -0
- grit_cli-0.1.0a0/backend/docker-compose.yml +52 -0
- grit_cli-0.1.0a0/backend/pyproject.toml +36 -0
- grit_cli-0.1.0a0/pyproject.toml +146 -0
- grit_cli-0.1.0a0/scripts/generate_license_keys.py +47 -0
- grit_cli-0.1.0a0/src/grit/__init__.py +4 -0
- grit_cli-0.1.0a0/src/grit/__main__.py +6 -0
- grit_cli-0.1.0a0/src/grit/cli/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_audit.py +124 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_auth.py +114 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_compliance.py +109 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_config.py +76 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_credential.py +142 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_daemon.py +106 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_enterprise.py +177 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_hook.py +60 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_profile.py +305 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_service.py +108 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_session.py +144 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_setup.py +92 -0
- grit_cli-0.1.0a0/src/grit/cli/cmd_sync.py +133 -0
- grit_cli-0.1.0a0/src/grit/cli/main.py +130 -0
- grit_cli-0.1.0a0/src/grit/cloud/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/cloud/auth.py +24 -0
- grit_cli-0.1.0a0/src/grit/cloud/client.py +15 -0
- grit_cli-0.1.0a0/src/grit/cloud/sync.py +12 -0
- grit_cli-0.1.0a0/src/grit/config/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/config/app_config.py +44 -0
- grit_cli-0.1.0a0/src/grit/config/paths.py +108 -0
- grit_cli-0.1.0a0/src/grit/config/subscription.py +293 -0
- grit_cli-0.1.0a0/src/grit/constants.py +38 -0
- grit_cli-0.1.0a0/src/grit/daemon/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/daemon/hook_manager.py +67 -0
- grit_cli-0.1.0a0/src/grit/daemon/pid.py +55 -0
- grit_cli-0.1.0a0/src/grit/daemon/recovery.py +41 -0
- grit_cli-0.1.0a0/src/grit/daemon/server.py +187 -0
- grit_cli-0.1.0a0/src/grit/daemon/watchdog.py +78 -0
- grit_cli-0.1.0a0/src/grit/enterprise/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/enterprise/audit.py +18 -0
- grit_cli-0.1.0a0/src/grit/enterprise/compliance.py +24 -0
- grit_cli-0.1.0a0/src/grit/enterprise/sso.py +45 -0
- grit_cli-0.1.0a0/src/grit/exceptions.py +81 -0
- grit_cli-0.1.0a0/src/grit/git/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/git/config.py +138 -0
- grit_cli-0.1.0a0/src/grit/git/credentials.py +277 -0
- grit_cli-0.1.0a0/src/grit/git/gpg.py +52 -0
- grit_cli-0.1.0a0/src/grit/git/hook.py +94 -0
- grit_cli-0.1.0a0/src/grit/git/repo.py +50 -0
- grit_cli-0.1.0a0/src/grit/git/ssh.py +20 -0
- grit_cli-0.1.0a0/src/grit/ipc/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/ipc/client.py +80 -0
- grit_cli-0.1.0a0/src/grit/ipc/protocol.py +80 -0
- grit_cli-0.1.0a0/src/grit/ipc/server.py +114 -0
- grit_cli-0.1.0a0/src/grit/models/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/models/profile.py +46 -0
- grit_cli-0.1.0a0/src/grit/models/session.py +61 -0
- grit_cli-0.1.0a0/src/grit/platform/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/platform/base.py +35 -0
- grit_cli-0.1.0a0/src/grit/platform/linux.py +93 -0
- grit_cli-0.1.0a0/src/grit/platform/macos.py +76 -0
- grit_cli-0.1.0a0/src/grit/platform/windows.py +53 -0
- grit_cli-0.1.0a0/src/grit/platform/windows_service.py +200 -0
- grit_cli-0.1.0a0/src/grit/session/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/session/detector.py +104 -0
- grit_cli-0.1.0a0/src/grit/session/engine.py +163 -0
- grit_cli-0.1.0a0/src/grit/storage/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/storage/_lock.py +45 -0
- grit_cli-0.1.0a0/src/grit/storage/profile_store.py +106 -0
- grit_cli-0.1.0a0/src/grit/storage/session_store.py +103 -0
- grit_cli-0.1.0a0/src/grit/ui/__init__.py +0 -0
- grit_cli-0.1.0a0/src/grit/ui/notifications.py +32 -0
- grit_cli-0.1.0a0/src/grit/ui/popup.py +124 -0
- grit_cli-0.1.0a0/src/grit/ui/tray.py +148 -0
- grit_cli-0.1.0a0/tests/__init__.py +0 -0
- grit_cli-0.1.0a0/tests/conftest.py +60 -0
- grit_cli-0.1.0a0/tests/e2e/__init__.py +0 -0
- grit_cli-0.1.0a0/tests/e2e/test_commit_flow.py +79 -0
- grit_cli-0.1.0a0/tests/integration/__init__.py +0 -0
- grit_cli-0.1.0a0/tests/integration/phase2/__init__.py +0 -0
- grit_cli-0.1.0a0/tests/integration/phase2/test_sync_flow.py +128 -0
- grit_cli-0.1.0a0/tests/integration/phase3/__init__.py +0 -0
- grit_cli-0.1.0a0/tests/integration/phase3/test_audit_wiring.py +58 -0
- grit_cli-0.1.0a0/tests/integration/test_daemon_lifecycle.py +47 -0
- grit_cli-0.1.0a0/tests/unit/__init__.py +0 -0
- grit_cli-0.1.0a0/tests/unit/phase2/__init__.py +0 -0
- grit_cli-0.1.0a0/tests/unit/phase2/test_cloud_client.py +74 -0
- grit_cli-0.1.0a0/tests/unit/phase2/test_profile_limit_enforcement.py +47 -0
- grit_cli-0.1.0a0/tests/unit/phase2/test_subscription.py +169 -0
- grit_cli-0.1.0a0/tests/unit/phase2/test_sync.py +75 -0
- grit_cli-0.1.0a0/tests/unit/phase3/__init__.py +0 -0
- grit_cli-0.1.0a0/tests/unit/phase3/test_audit.py +75 -0
- grit_cli-0.1.0a0/tests/unit/phase3/test_compliance.py +126 -0
- grit_cli-0.1.0a0/tests/unit/phase3/test_sso.py +158 -0
- grit_cli-0.1.0a0/tests/unit/test_cli.py +170 -0
- grit_cli-0.1.0a0/tests/unit/test_cli_credential.py +139 -0
- grit_cli-0.1.0a0/tests/unit/test_credentials.py +234 -0
- grit_cli-0.1.0a0/tests/unit/test_detector.py +95 -0
- grit_cli-0.1.0a0/tests/unit/test_git_config.py +93 -0
- grit_cli-0.1.0a0/tests/unit/test_git_hook.py +66 -0
- grit_cli-0.1.0a0/tests/unit/test_ipc_protocol.py +58 -0
- grit_cli-0.1.0a0/tests/unit/test_profile_store.py +92 -0
- grit_cli-0.1.0a0/tests/unit/test_session_engine.py +89 -0
- grit_cli-0.1.0a0/tests/unit/test_session_store.py +105 -0
- grit_cli-0.1.0a0/vscode-extension/package.json +42 -0
- grit_cli-0.1.0a0/vscode-extension/src/extension.ts +107 -0
- grit_cli-0.1.0a0/vscode-extension/src/ipcClient.ts +74 -0
- grit_cli-0.1.0a0/vscode-extension/src/statusBar.ts +66 -0
- grit_cli-0.1.0a0/vscode-extension/tsconfig.json +14 -0
|
@@ -0,0 +1,85 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: ["main", "develop"]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: ["main"]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
name: Lint & type-check
|
|
12
|
+
runs-on: ubuntu-latest
|
|
13
|
+
steps:
|
|
14
|
+
- uses: actions/checkout@v4
|
|
15
|
+
- uses: actions/setup-python@v5
|
|
16
|
+
with:
|
|
17
|
+
python-version: "3.12"
|
|
18
|
+
- run: pip install -e ".[dev]"
|
|
19
|
+
- run: ruff check src/ tests/
|
|
20
|
+
- run: mypy src/
|
|
21
|
+
|
|
22
|
+
test:
|
|
23
|
+
name: Unit tests — Python ${{ matrix.python }} on ${{ matrix.os }}
|
|
24
|
+
runs-on: ${{ matrix.os }}
|
|
25
|
+
strategy:
|
|
26
|
+
fail-fast: false
|
|
27
|
+
matrix:
|
|
28
|
+
os: [ubuntu-22.04, macos-14, windows-2022]
|
|
29
|
+
python: ["3.8", "3.11", "3.12"]
|
|
30
|
+
exclude:
|
|
31
|
+
# macos-14 is Apple Silicon (ARM64); setup-python has no 3.8 build for it.
|
|
32
|
+
# Python 3.8 is still covered on Ubuntu and Windows.
|
|
33
|
+
- os: macos-14
|
|
34
|
+
python: "3.8"
|
|
35
|
+
steps:
|
|
36
|
+
- uses: actions/checkout@v4
|
|
37
|
+
- uses: actions/setup-python@v5
|
|
38
|
+
with:
|
|
39
|
+
python-version: ${{ matrix.python }}
|
|
40
|
+
- name: Install dependencies
|
|
41
|
+
run: pip install -e ".[dev]"
|
|
42
|
+
- name: Run unit tests
|
|
43
|
+
run: pytest tests/unit/ --cov=grit --cov-report=xml -v
|
|
44
|
+
- name: Upload coverage
|
|
45
|
+
if: matrix.os == 'ubuntu-22.04' && matrix.python == '3.12'
|
|
46
|
+
uses: codecov/codecov-action@v4
|
|
47
|
+
with:
|
|
48
|
+
files: coverage.xml
|
|
49
|
+
|
|
50
|
+
integration:
|
|
51
|
+
name: Integration tests — ${{ matrix.os }}
|
|
52
|
+
runs-on: ${{ matrix.os }}
|
|
53
|
+
strategy:
|
|
54
|
+
fail-fast: false
|
|
55
|
+
matrix:
|
|
56
|
+
os: [ubuntu-22.04, macos-14, windows-2022]
|
|
57
|
+
steps:
|
|
58
|
+
- uses: actions/checkout@v4
|
|
59
|
+
- uses: actions/setup-python@v5
|
|
60
|
+
with:
|
|
61
|
+
python-version: "3.12"
|
|
62
|
+
- run: pip install -e ".[dev]"
|
|
63
|
+
- name: Configure git identity for tests
|
|
64
|
+
run: |
|
|
65
|
+
git config --global user.email "ci@grit.test"
|
|
66
|
+
git config --global user.name "Grit CI"
|
|
67
|
+
- run: pytest tests/integration/ -m integration -v
|
|
68
|
+
|
|
69
|
+
e2e:
|
|
70
|
+
name: End-to-end tests — ${{ matrix.os }}
|
|
71
|
+
runs-on: ${{ matrix.os }}
|
|
72
|
+
strategy:
|
|
73
|
+
matrix:
|
|
74
|
+
os: [ubuntu-22.04, macos-14, windows-2022]
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
- uses: actions/setup-python@v5
|
|
78
|
+
with:
|
|
79
|
+
python-version: "3.12"
|
|
80
|
+
- run: pip install -e ".[dev]"
|
|
81
|
+
- name: Configure git identity for tests
|
|
82
|
+
run: |
|
|
83
|
+
git config --global user.email "ci@grit.test"
|
|
84
|
+
git config --global user.name "Grit CI"
|
|
85
|
+
- run: pytest tests/e2e/ -m e2e -v --timeout=60
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
name: Release
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags: ["v*"]
|
|
6
|
+
workflow_dispatch:
|
|
7
|
+
inputs:
|
|
8
|
+
target:
|
|
9
|
+
description: "Publish target"
|
|
10
|
+
type: choice
|
|
11
|
+
options: [testpypi, pypi]
|
|
12
|
+
default: testpypi
|
|
13
|
+
|
|
14
|
+
jobs:
|
|
15
|
+
gate:
|
|
16
|
+
name: Gate — lint, type-check, unit tests
|
|
17
|
+
runs-on: ubuntu-latest
|
|
18
|
+
steps:
|
|
19
|
+
- uses: actions/checkout@v4
|
|
20
|
+
- uses: actions/setup-python@v5
|
|
21
|
+
with:
|
|
22
|
+
python-version: "3.12"
|
|
23
|
+
- run: pip install -e ".[dev]"
|
|
24
|
+
- run: ruff check src/ tests/
|
|
25
|
+
- run: mypy src/
|
|
26
|
+
- run: pytest tests/unit/ -q
|
|
27
|
+
|
|
28
|
+
build:
|
|
29
|
+
name: Build distributions
|
|
30
|
+
needs: gate
|
|
31
|
+
runs-on: ubuntu-latest
|
|
32
|
+
steps:
|
|
33
|
+
- uses: actions/checkout@v4
|
|
34
|
+
- uses: actions/setup-python@v5
|
|
35
|
+
with:
|
|
36
|
+
python-version: "3.12"
|
|
37
|
+
- run: pip install hatch
|
|
38
|
+
- run: hatch build
|
|
39
|
+
- uses: actions/upload-artifact@v4
|
|
40
|
+
with:
|
|
41
|
+
name: dist
|
|
42
|
+
path: dist/
|
|
43
|
+
|
|
44
|
+
publish-testpypi:
|
|
45
|
+
name: Publish to TestPyPI
|
|
46
|
+
needs: build
|
|
47
|
+
if: github.event_name == 'workflow_dispatch' && inputs.target == 'testpypi'
|
|
48
|
+
runs-on: ubuntu-latest
|
|
49
|
+
environment: testpypi
|
|
50
|
+
permissions:
|
|
51
|
+
id-token: write # OIDC trusted publishing
|
|
52
|
+
steps:
|
|
53
|
+
- uses: actions/download-artifact@v4
|
|
54
|
+
with:
|
|
55
|
+
name: dist
|
|
56
|
+
path: dist/
|
|
57
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
58
|
+
with:
|
|
59
|
+
repository-url: https://test.pypi.org/legacy/
|
|
60
|
+
|
|
61
|
+
publish-pypi:
|
|
62
|
+
name: Publish to PyPI
|
|
63
|
+
needs: build
|
|
64
|
+
if: github.event_name == 'push' || (github.event_name == 'workflow_dispatch' && inputs.target == 'pypi')
|
|
65
|
+
runs-on: ubuntu-latest
|
|
66
|
+
environment: pypi
|
|
67
|
+
permissions:
|
|
68
|
+
id-token: write # OIDC trusted publishing
|
|
69
|
+
steps:
|
|
70
|
+
- uses: actions/download-artifact@v4
|
|
71
|
+
with:
|
|
72
|
+
name: dist
|
|
73
|
+
path: dist/
|
|
74
|
+
- uses: pypa/gh-action-pypi-publish@release/v1
|
|
75
|
+
|
|
76
|
+
github-release:
|
|
77
|
+
name: GitHub Release
|
|
78
|
+
needs: publish-pypi
|
|
79
|
+
if: github.event_name == 'push'
|
|
80
|
+
runs-on: ubuntu-latest
|
|
81
|
+
permissions:
|
|
82
|
+
contents: write
|
|
83
|
+
steps:
|
|
84
|
+
- uses: actions/checkout@v4
|
|
85
|
+
- uses: actions/download-artifact@v4
|
|
86
|
+
with:
|
|
87
|
+
name: dist
|
|
88
|
+
path: dist/
|
|
89
|
+
- name: Create GitHub Release
|
|
90
|
+
env:
|
|
91
|
+
GH_TOKEN: ${{ github.token }}
|
|
92
|
+
run: |
|
|
93
|
+
notes="See [CHANGELOG.md](https://github.com/${GITHUB_REPOSITORY}/blob/main/CHANGELOG.md)."
|
|
94
|
+
prerelease=""
|
|
95
|
+
if [[ "${GITHUB_REF_NAME}" =~ (a|b|rc)[0-9]*$ ]]; then
|
|
96
|
+
prerelease="--prerelease"
|
|
97
|
+
fi
|
|
98
|
+
gh release create "${GITHUB_REF_NAME}" dist/* \
|
|
99
|
+
--title "${GITHUB_REF_NAME}" \
|
|
100
|
+
--notes "${notes}" \
|
|
101
|
+
${prerelease}
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
# ── Python ────────────────────────────────────────────────────────────────────
|
|
2
|
+
__pycache__/
|
|
3
|
+
*.py[cod]
|
|
4
|
+
*.pyo
|
|
5
|
+
*.pyd
|
|
6
|
+
*.so
|
|
7
|
+
*.egg
|
|
8
|
+
*.egg-info/
|
|
9
|
+
dist/
|
|
10
|
+
build/
|
|
11
|
+
.eggs/
|
|
12
|
+
pip-wheel-metadata/
|
|
13
|
+
*.whl
|
|
14
|
+
|
|
15
|
+
# Virtual environments
|
|
16
|
+
.venv/
|
|
17
|
+
venv/
|
|
18
|
+
env/
|
|
19
|
+
ENV/
|
|
20
|
+
|
|
21
|
+
# Editable install metadata
|
|
22
|
+
*.dist-info/
|
|
23
|
+
|
|
24
|
+
# ── Testing & coverage ────────────────────────────────────────────────────────
|
|
25
|
+
.pytest_cache/
|
|
26
|
+
.benchmarks/
|
|
27
|
+
.coverage
|
|
28
|
+
.coverage.*
|
|
29
|
+
coverage.xml
|
|
30
|
+
htmlcov/
|
|
31
|
+
*.lcov
|
|
32
|
+
|
|
33
|
+
# ── Type checking & linting ───────────────────────────────────────────────────
|
|
34
|
+
.mypy_cache/
|
|
35
|
+
.ruff_cache/
|
|
36
|
+
|
|
37
|
+
# ── Node / frontend (admin-ui, vscode-extension) ──────────────────────────────
|
|
38
|
+
node_modules/
|
|
39
|
+
dist/
|
|
40
|
+
out/
|
|
41
|
+
.vite/
|
|
42
|
+
*.vsix
|
|
43
|
+
|
|
44
|
+
# Build output
|
|
45
|
+
admin-ui/dist/
|
|
46
|
+
vscode-extension/out/
|
|
47
|
+
vscode-extension/dist/
|
|
48
|
+
|
|
49
|
+
# ── Secrets & credentials — NEVER commit these ────────────────────────────────
|
|
50
|
+
.env
|
|
51
|
+
.env.*
|
|
52
|
+
!.env.example
|
|
53
|
+
*.pem
|
|
54
|
+
*.key
|
|
55
|
+
*_private_key*
|
|
56
|
+
*_rsa*
|
|
57
|
+
license_private.pem
|
|
58
|
+
tokens.json
|
|
59
|
+
sso_session.json
|
|
60
|
+
enterprise.json
|
|
61
|
+
|
|
62
|
+
# RSA key pair generated by scripts/generate_license_keys.py
|
|
63
|
+
backend/.env
|
|
64
|
+
scripts/*.pem
|
|
65
|
+
scripts/*.key
|
|
66
|
+
|
|
67
|
+
# ── Grit runtime data (local daemon state) ────────────────────────────────────
|
|
68
|
+
grit.sock
|
|
69
|
+
grit.pid
|
|
70
|
+
grit.port
|
|
71
|
+
watched_repos.json
|
|
72
|
+
audit.log
|
|
73
|
+
sessions.json
|
|
74
|
+
|
|
75
|
+
# ── Docker ────────────────────────────────────────────────────────────────────
|
|
76
|
+
backend/.env
|
|
77
|
+
|
|
78
|
+
# ── IDEs & editors ────────────────────────────────────────────────────────────
|
|
79
|
+
.vscode/
|
|
80
|
+
!.vscode/extensions.json
|
|
81
|
+
!.vscode/launch.json
|
|
82
|
+
!.vscode/settings.json
|
|
83
|
+
.idea/
|
|
84
|
+
*.swp
|
|
85
|
+
*.swo
|
|
86
|
+
*~
|
|
87
|
+
.DS_Store
|
|
88
|
+
Thumbs.db
|
|
89
|
+
|
|
90
|
+
# ── VS Code extension packaging ───────────────────────────────────────────────
|
|
91
|
+
*.vsix
|
|
92
|
+
|
|
93
|
+
# ── Windows ───────────────────────────────────────────────────────────────────
|
|
94
|
+
desktop.ini
|
|
95
|
+
$RECYCLE.BIN/
|
|
96
|
+
|
|
97
|
+
# ── Misc ──────────────────────────────────────────────────────────────────────
|
|
98
|
+
.history/
|
|
99
|
+
*.log
|
|
100
|
+
*.tmp
|
|
101
|
+
*.bak
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented in this file.
|
|
4
|
+
|
|
5
|
+
The format is based on [Keep a Changelog](https://keepachangelog.com/en/1.1.0/),
|
|
6
|
+
and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
7
|
+
|
|
8
|
+
## [Unreleased]
|
|
9
|
+
|
|
10
|
+
## [0.1.0a0] - 2026-06-23
|
|
11
|
+
|
|
12
|
+
First public alpha of the open-source Grit core.
|
|
13
|
+
|
|
14
|
+
### Added
|
|
15
|
+
- **Background daemon** (`gritd`) with crash-safe atomic JSON storage and lazy
|
|
16
|
+
session TTL purging (default 8-hour sessions).
|
|
17
|
+
- **CLI** (`grit`) with commands for profiles, sessions, daemon control, config,
|
|
18
|
+
first-run setup, and per-profile HTTPS credential login.
|
|
19
|
+
- **Profile management** — name, email, GPG key, SSH key, and path/remote match
|
|
20
|
+
patterns, persisted to `profiles.json`.
|
|
21
|
+
- **Session engine** — per-repository profile resolution with auto-detection
|
|
22
|
+
(`.grit` file → path patterns → remote URL) and just-in-time prompting.
|
|
23
|
+
- **Git integration** — per-repo `pre-commit` hook injection, git config
|
|
24
|
+
apply/backup/restore, GPG signing configuration, and `core.sshCommand` setup.
|
|
25
|
+
- **IPC transport** — Unix domain socket on macOS/Linux, named pipe (TCP) on
|
|
26
|
+
Windows, with newline-delimited JSON messaging.
|
|
27
|
+
- **System tray** (pystray) with a profile picker and desktop notifications.
|
|
28
|
+
- **Cross-platform autostart** — XDG/systemd (Linux), LaunchAgent (macOS),
|
|
29
|
+
and registry Run key (Windows).
|
|
30
|
+
|
|
31
|
+
### Notes
|
|
32
|
+
- Pro/Enterprise features (cloud sync, team profiles, SSO, audit, compliance)
|
|
33
|
+
live in the separate optional `grit-pro` package; the core ships thin
|
|
34
|
+
re-export shims that degrade gracefully when it is not installed.
|
|
35
|
+
|
|
36
|
+
[Unreleased]: https://github.com/Kandeepasundaram/Grit/compare/v0.1.0a0...HEAD
|
|
37
|
+
[0.1.0a0]: https://github.com/Kandeepasundaram/Grit/releases/tag/v0.1.0a0
|
|
@@ -0,0 +1,195 @@
|
|
|
1
|
+
# CLAUDE.md
|
|
2
|
+
|
|
3
|
+
This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
|
|
4
|
+
|
|
5
|
+
## What This Project Is
|
|
6
|
+
|
|
7
|
+
**Grit** is a cross-platform background daemon with system tray integration that manages multiple Git identities (name, email, GPG key, SSH key) across repositories. It uses session-based profile memory (default 8-hour TTL) to automatically apply the right profile per repository, prompting the user just-in-time when a new repository is encountered.
|
|
8
|
+
|
|
9
|
+
## Development Commands
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
pip install -e ".[dev]" # install in editable mode with dev dependencies
|
|
13
|
+
|
|
14
|
+
# Run tests
|
|
15
|
+
pytest tests/unit/ # unit tests (fast, no git or daemon required)
|
|
16
|
+
pytest tests/unit/ --cov=grit --cov-report=term # with coverage
|
|
17
|
+
pytest -m integration # integration tests (requires real git binary)
|
|
18
|
+
pytest -m e2e --timeout=60 # end-to-end tests (CI only — start/stop daemon)
|
|
19
|
+
pytest tests/unit/test_profile_store.py::TestAdd::test_add_and_retrieve # single test
|
|
20
|
+
|
|
21
|
+
# Code quality
|
|
22
|
+
ruff check src/ tests/ # lint
|
|
23
|
+
ruff format src/ tests/ # format
|
|
24
|
+
mypy src/ # type check
|
|
25
|
+
|
|
26
|
+
# Run the daemon in foreground (for development)
|
|
27
|
+
python -m grit.daemon.server --verbose
|
|
28
|
+
# Or via CLI:
|
|
29
|
+
grit daemon start --foreground --verbose
|
|
30
|
+
```
|
|
31
|
+
|
|
32
|
+
## Architecture
|
|
33
|
+
|
|
34
|
+
### Key design decisions
|
|
35
|
+
- **`GRIT_CONFIG_DIR` env var**: overrides all config/data paths. Tests set this to `tmp_path` via the `tmp_config_dir` fixture in `tests/conftest.py` — no mocking of filesystem I/O needed.
|
|
36
|
+
- **Atomic writes**: all JSON storage writes go to `.tmp` then `os.replace()` — crash-safe.
|
|
37
|
+
- **IPC transport**: Unix domain socket (`~/.local/share/grit/grit.sock`) on macOS/Linux; Named Pipe on Windows (Phase 1C).
|
|
38
|
+
- **Git interception**: per-repo git hooks (not OS syscall trapping). `git/hook.py` injects a `pre-commit` hook that calls `grit hook pre-commit --repo <path>` → IPC → daemon.
|
|
39
|
+
- **Session TTL**: default 8 hours, configurable. Expired sessions purged lazily on every read.
|
|
40
|
+
|
|
41
|
+
### Module map
|
|
42
|
+
|
|
43
|
+
```
|
|
44
|
+
src/grit/
|
|
45
|
+
├── config/
|
|
46
|
+
│ ├── paths.py # all path resolution; reads GRIT_CONFIG_DIR env var
|
|
47
|
+
│ └── app_config.py # AppConfig dataclass, persisted to config.json
|
|
48
|
+
├── models/
|
|
49
|
+
│ ├── profile.py # Profile dataclass (id, name, email, gpg_key_id, ssh_key_path, patterns)
|
|
50
|
+
│ └── session.py # Session dataclass (repo_path, profile_id, expires_at, locked)
|
|
51
|
+
├── storage/
|
|
52
|
+
│ ├── _lock.py # cross-platform file locking (fcntl / msvcrt)
|
|
53
|
+
│ ├── profile_store.py # CRUD on profiles.json
|
|
54
|
+
│ └── session_store.py # CRUD on sessions.json; lazy TTL purge on every read
|
|
55
|
+
├── ipc/
|
|
56
|
+
│ ├── protocol.py # encode/decode newline-delimited JSON messages
|
|
57
|
+
│ ├── client.py # synchronous client (used by CLI + hooks)
|
|
58
|
+
│ └── server.py # asyncio server (runs inside daemon)
|
|
59
|
+
├── daemon/
|
|
60
|
+
│ ├── server.py # gritd entry point; registers IPC handlers; wires asyncio loop
|
|
61
|
+
│ ├── pid.py # PID file read/write/check via psutil
|
|
62
|
+
│ ├── watchdog.py # filesystem observer for .git/COMMIT_EDITMSG
|
|
63
|
+
│ ├── hook_manager.py # watched_repos.json registry + hook install
|
|
64
|
+
│ └── recovery.py # stale PID/socket detection and cleanup
|
|
65
|
+
├── git/
|
|
66
|
+
│ ├── repo.py # find_repo_root(), get_remote_url()
|
|
67
|
+
│ ├── config.py # read/write/unset git config; apply_profile()
|
|
68
|
+
│ ├── hook.py # install/uninstall pre-commit hook; sentinel: GRIT_HOOK_v1
|
|
69
|
+
│ ├── gpg.py # list GPG keys; configure_signing()
|
|
70
|
+
│ └── ssh.py # write core.sshCommand to git config
|
|
71
|
+
├── session/
|
|
72
|
+
│ ├── engine.py # SessionEngine: resolve → create → apply → invalidate
|
|
73
|
+
│ └── detector.py # auto-detect profile: .grit file > path patterns > remote URL
|
|
74
|
+
├── cli/
|
|
75
|
+
│ ├── main.py # root `grit` Click group
|
|
76
|
+
│ ├── cmd_profile.py # grit profile add/list/show/edit/delete
|
|
77
|
+
│ ├── cmd_session.py # grit session show/set/clear/list
|
|
78
|
+
│ ├── cmd_daemon.py # grit daemon start/stop/status/restart
|
|
79
|
+
│ ├── cmd_config.py # grit config get/set/list/reset
|
|
80
|
+
│ ├── cmd_hook.py # grit hook pre-commit (internal, called by git hook)
|
|
81
|
+
│ ├── cmd_setup.py # grit setup (first-run onboarding wizard)
|
|
82
|
+
│ ├── cmd_auth.py # grit auth login/logout/status (Phase 2 cloud auth)
|
|
83
|
+
│ ├── cmd_sync.py # grit sync push/pull/status (Phase 2 cloud sync)
|
|
84
|
+
│ ├── cmd_enterprise.py # grit enterprise config/sso-login/sso-status/sso-logout (Phase 3)
|
|
85
|
+
│ ├── cmd_audit.py # grit audit show/export/clear (Phase 3)
|
|
86
|
+
│ ├── cmd_compliance.py # grit compliance report/hooks/gpg (Phase 3)
|
|
87
|
+
│ └── cmd_service.py # grit service install/uninstall/start/stop/status (Phase 3, Windows)
|
|
88
|
+
├── ui/
|
|
89
|
+
│ ├── tray.py # pystray system tray icon + menu
|
|
90
|
+
│ ├── popup.py # profile picker dialog (PyQt6 → tkinter fallback)
|
|
91
|
+
│ └── notifications.py # plyer desktop notifications
|
|
92
|
+
├── platform/
|
|
93
|
+
│ ├── base.py # PlatformBase ABC + get_platform() factory
|
|
94
|
+
│ ├── linux.py # XDG autostart + systemd user unit
|
|
95
|
+
│ ├── macos.py # LaunchAgent plist
|
|
96
|
+
│ ├── windows.py # HKCU registry Run key
|
|
97
|
+
│ └── windows_service.py # Phase 3: pywin32 NT Service for enterprise multi-user; data in %PROGRAMDATA%\Grit
|
|
98
|
+
├── cloud/ # Phase 2: Freemium
|
|
99
|
+
│ ├── auth.py # device-flow OAuth2 (GitHub/Google); token storage in tokens.json
|
|
100
|
+
│ ├── client.py # REST client for api.grit.dev; raises OfflineError gracefully
|
|
101
|
+
│ ├── sync.py # SyncEngine: push/pull profiles+sessions; last-write-wins merge; 5s debounce auto-sync
|
|
102
|
+
│ └── license_public.pem # RSA public key for JWT verification (generated via scripts/generate_license_keys.py)
|
|
103
|
+
└── enterprise/
|
|
104
|
+
├── audit.py # append-only JSON-lines audit log; log_profile_switch/session_create/git_config_write
|
|
105
|
+
├── sso.py # EnterpriseConfig + SSOSession dataclasses; OIDC device flow; SAML 2.0 response parsing
|
|
106
|
+
└── compliance.py # check_hook_inventory / check_gpg_enforcement / generate_report → JSON compliance report
|
|
107
|
+
```
|
|
108
|
+
|
|
109
|
+
### Data flows
|
|
110
|
+
|
|
111
|
+
**Commit hook flow** (primary path):
|
|
112
|
+
```
|
|
113
|
+
git commit → .git/hooks/pre-commit →
|
|
114
|
+
grit hook pre-commit --repo <path> →
|
|
115
|
+
IPC: {"type": "pre-commit", "payload": {"repo_path": "..."}} →
|
|
116
|
+
daemon: SessionEngine.resolve() → apply profile to git config → {"needs_profile": false}
|
|
117
|
+
```
|
|
118
|
+
|
|
119
|
+
**Detection priority** (in `session/detector.py`):
|
|
120
|
+
1. `.grit` file in repo root (`profile = "Work"`)
|
|
121
|
+
2. `path_patterns` glob match (e.g. `~/work/*`)
|
|
122
|
+
3. `remote_patterns` match on `git remote get-url origin`
|
|
123
|
+
4. → `None` (prompt user)
|
|
124
|
+
|
|
125
|
+
### Phase 2 module responsibilities
|
|
126
|
+
- **`config/subscription.py`**: `load_license()` reads `license.json` (JWT verified with bundled RSA public key). Falls back to free tier if absent. `enforce_profile_limit(current_count)` raises `ValueError` — called by `ProfileStore.add()`. `require_pro(feature_name)` gates cloud sync / team profiles / SSO.
|
|
127
|
+
- **`cloud/auth.py`**: `start_device_flow(provider)` + `poll_device_flow(...)` — tokens stored in `tokens.json` (chmod 600 on POSIX). `get_access_token()` refreshes transparently.
|
|
128
|
+
- **`cloud/client.py`**: All methods raise `OfflineError` (not a crash) when unreachable — callers continue local operation. Reads `GRIT_API_URL` env var for on-premise overrides.
|
|
129
|
+
- **`cloud/sync.py`**: `SyncEngine.sync()` does full bidirectional merge. Team profiles stored read-only in `team_profiles.json`. `schedule_sync()` debounces 5s before triggering background upload.
|
|
130
|
+
- **`backend/`**: FastAPI + PostgreSQL + Redis + Stripe. Run with `docker-compose up` in `backend/`. Generate license keys first with `python scripts/generate_license_keys.py`.
|
|
131
|
+
|
|
132
|
+
### Phase 3 module responsibilities
|
|
133
|
+
- **`enterprise/sso.py`**: `EnterpriseConfig` (idp_type/idp_url/client_id/org_id/enforce_sso) persisted to `enterprise.json`. `SSOSession` with `is_expired()`. OIDC: `start_oidc_login()` + `poll_oidc_token()` (device flow via `_oidc_discover()` metadata endpoint). SAML: `get_saml_login_url()` + `process_saml_response()` (requires `python3-saml`). `resolve_profile_for_sso(session, profiles)` matches profiles by SSO identity.
|
|
134
|
+
- **`enterprise/audit.py`**: `_append()` writes JSON lines to `audit.log` atomically (open mode `"a"`). `export_entries(since=)` reads and filters. Wired into `SessionEngine.create()` (session_create), `SessionEngine.apply()` (profile_switch), and `git.config.apply_profile()` (git_config_write). All calls wrapped in try/except — audit failures never break the commit flow.
|
|
135
|
+
- **`enterprise/compliance.py`**: `generate_report()` calls `check_hook_inventory()`, `check_gpg_enforcement()`, `check_sso_compliance()`, `audit_summary()`. Returns `{"compliant": bool, "sections": {...}}`. `write_report(path)` serialises to JSON file.
|
|
136
|
+
- **`platform/windows_service.py`**: `_build_service_class()` factory (deferred import so pywin32 is optional). `install_service()`, `uninstall_service()`, `start_service()`, `stop_service()`, `query_service_status()`. Service sets `GRIT_CONFIG_DIR=%PROGRAMDATA%\Grit` before starting the asyncio daemon.
|
|
137
|
+
- **`backend/app/models/user.py`**: Added `Organization`, `OrgMember`, `AuditEvent` SQLAlchemy models.
|
|
138
|
+
- **`backend/app/api/enterprise.py`**: org CRUD (`/v1/enterprise/orgs/*`), member management, team profile CRUD, audit log query/ingest (`/v1/enterprise/audit`), SSO config endpoint (`/v1/enterprise/sso/{org_id}/config`). All org endpoints check `_require_org_role()` (member/admin/owner hierarchy).
|
|
139
|
+
- **`admin-ui/`**: Vite + React 18 + TypeScript + TanStack Query. Auth via device flow stored in localStorage. Pages: `OrgPage` (SSO config + member management), `TeamProfilesPage` (team profile CRUD), `AuditLogPage` (filterable table + CSV export), `SubscriptionPage`. Dev proxy at `/v1` → `localhost:8000`. Build: `cd admin-ui && npm run build`.
|
|
140
|
+
|
|
141
|
+
### Session resolution order (updated for Phase 3)
|
|
142
|
+
When `SessionEngine.resolve(repo_path)` is called:
|
|
143
|
+
1. Session cache hit (not expired) → return immediately
|
|
144
|
+
2. Enterprise SSO: if `enforce_sso=True` and valid SSO session exists → match profile via `resolve_profile_for_sso()` → create session
|
|
145
|
+
3. Auto-detect if `app_config.auto_detect=True`: `.grit` file > `path_patterns` > `remote_patterns`
|
|
146
|
+
4. Return `None` → daemon prompts user via popup
|
|
147
|
+
|
|
148
|
+
### IPC message types
|
|
149
|
+
`ping`, `pre-commit`, `get-session`, `set-session`, `delete-session`, `list-sessions`, `list-profiles`, `switch-profile`, `daemon-status`
|
|
150
|
+
|
|
151
|
+
### Testing patterns
|
|
152
|
+
- **Unit tests**: set `GRIT_CONFIG_DIR=tmp_path` via `tmp_config_dir` fixture — no mocking of file I/O
|
|
153
|
+
- **Git tests**: use `git_repo` fixture which runs `git init` in `tmp_path`
|
|
154
|
+
- **CLI tests**: use Click's `CliRunner` + `--config-dir` option; mock `grit.ipc.client.send_request` for daemon-dependent commands
|
|
155
|
+
- **Daemon tests**: start daemon as subprocess with `GRIT_CONFIG_DIR` set; wait for `ping()`
|
|
156
|
+
- **Time-dependent (TTL)**: use `freezegun` or monkeypatch `datetime.now`
|
|
157
|
+
- Marks: `@pytest.mark.integration` (needs git), `@pytest.mark.e2e` (needs daemon), `@pytest.mark.slow`
|
|
158
|
+
|
|
159
|
+
## Business Phases
|
|
160
|
+
|
|
161
|
+
- **Phase 1 (complete):** Open source, free — daemon + CLI + tray + VS Code extension (Months 1–12)
|
|
162
|
+
- **Phase 2 (complete):** Freemium — Pro tier ($5/mo), cloud sync, team profiles. `src/grit/cloud/` + `backend/` (Months 13–24)
|
|
163
|
+
- **Phase 3 (complete):** Enterprise — SSO (OIDC/SAML), audit logs, compliance reporting, Windows Service, backend enterprise org API, React admin UI. `src/grit/enterprise/`, `admin-ui/` (Months 25–36)
|
|
164
|
+
|
|
165
|
+
## Backend Development (Phase 2+3)
|
|
166
|
+
|
|
167
|
+
```bash
|
|
168
|
+
cd backend
|
|
169
|
+
cp .env.example .env # fill in credentials
|
|
170
|
+
python ../scripts/generate_license_keys.py # generate RSA key pair (once)
|
|
171
|
+
docker-compose up # start postgres + redis + api
|
|
172
|
+
|
|
173
|
+
# Without Docker:
|
|
174
|
+
pip install -e ".[dev]"
|
|
175
|
+
uvicorn app.main:app --reload
|
|
176
|
+
```
|
|
177
|
+
|
|
178
|
+
API docs available at `http://localhost:8000/docs` when running.
|
|
179
|
+
|
|
180
|
+
## Admin UI Development (Phase 3)
|
|
181
|
+
|
|
182
|
+
```bash
|
|
183
|
+
cd admin-ui
|
|
184
|
+
npm install
|
|
185
|
+
npm run dev # dev server at http://localhost:3000 (proxies /v1 → :8000)
|
|
186
|
+
npm run build # production build to admin-ui/dist/
|
|
187
|
+
npm run type-check # TypeScript validation
|
|
188
|
+
```
|
|
189
|
+
|
|
190
|
+
Set `grit_org_id` in browser localStorage to the organisation UUID to use the admin UI.
|
|
191
|
+
|
|
192
|
+
## Docs
|
|
193
|
+
|
|
194
|
+
- `Docs/PRD.md` — all functional/non-functional requirements, user stories, UI spec, data flows
|
|
195
|
+
- `Docs/BRD.md` — market analysis, business model, phase timelines, revenue targets
|