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.
Files changed (158) hide show
  1. grit_cli-0.1.0a0/.github/workflows/ci.yml +85 -0
  2. grit_cli-0.1.0a0/.github/workflows/release.yml +101 -0
  3. grit_cli-0.1.0a0/.gitignore +101 -0
  4. grit_cli-0.1.0a0/CHANGELOG.md +37 -0
  5. grit_cli-0.1.0a0/CLAUDE.md +195 -0
  6. grit_cli-0.1.0a0/Docs/BRD.md +806 -0
  7. grit_cli-0.1.0a0/Docs/DEVELOPER.md +445 -0
  8. grit_cli-0.1.0a0/Docs/HELP_STANDARD.md +319 -0
  9. grit_cli-0.1.0a0/Docs/HOW_TO.md +299 -0
  10. grit_cli-0.1.0a0/Docs/PRD.md +228 -0
  11. grit_cli-0.1.0a0/Docs/SETUP.md +351 -0
  12. grit_cli-0.1.0a0/LICENSE +21 -0
  13. grit_cli-0.1.0a0/PKG-INFO +301 -0
  14. grit_cli-0.1.0a0/README.md +241 -0
  15. grit_cli-0.1.0a0/admin-ui/index.html +12 -0
  16. grit_cli-0.1.0a0/admin-ui/package-lock.json +3913 -0
  17. grit_cli-0.1.0a0/admin-ui/package.json +31 -0
  18. grit_cli-0.1.0a0/admin-ui/src/App.tsx +55 -0
  19. grit_cli-0.1.0a0/admin-ui/src/api/auth.ts +48 -0
  20. grit_cli-0.1.0a0/admin-ui/src/api/client.ts +27 -0
  21. grit_cli-0.1.0a0/admin-ui/src/api/enterprise.ts +85 -0
  22. grit_cli-0.1.0a0/admin-ui/src/components/LoadingSpinner.tsx +3 -0
  23. grit_cli-0.1.0a0/admin-ui/src/main.tsx +21 -0
  24. grit_cli-0.1.0a0/admin-ui/src/pages/AuditLogPage.tsx +118 -0
  25. grit_cli-0.1.0a0/admin-ui/src/pages/HelpPage.tsx +448 -0
  26. grit_cli-0.1.0a0/admin-ui/src/pages/LoginPage.tsx +102 -0
  27. grit_cli-0.1.0a0/admin-ui/src/pages/OrgPage.tsx +131 -0
  28. grit_cli-0.1.0a0/admin-ui/src/pages/SubscriptionPage.tsx +56 -0
  29. grit_cli-0.1.0a0/admin-ui/src/pages/TeamProfilesPage.tsx +128 -0
  30. grit_cli-0.1.0a0/admin-ui/tsconfig.json +25 -0
  31. grit_cli-0.1.0a0/admin-ui/tsconfig.node.json +10 -0
  32. grit_cli-0.1.0a0/admin-ui/vite.config.ts +20 -0
  33. grit_cli-0.1.0a0/assets/grit_logo_hi_res.png +0 -0
  34. grit_cli-0.1.0a0/backend/.env.example +11 -0
  35. grit_cli-0.1.0a0/backend/Dockerfile +18 -0
  36. grit_cli-0.1.0a0/backend/app/__init__.py +0 -0
  37. grit_cli-0.1.0a0/backend/app/api/__init__.py +0 -0
  38. grit_cli-0.1.0a0/backend/app/api/account.py +29 -0
  39. grit_cli-0.1.0a0/backend/app/api/auth.py +139 -0
  40. grit_cli-0.1.0a0/backend/app/api/deps.py +38 -0
  41. grit_cli-0.1.0a0/backend/app/api/enterprise.py +397 -0
  42. grit_cli-0.1.0a0/backend/app/api/license.py +39 -0
  43. grit_cli-0.1.0a0/backend/app/api/stripe_webhook.py +122 -0
  44. grit_cli-0.1.0a0/backend/app/api/sync.py +165 -0
  45. grit_cli-0.1.0a0/backend/app/config.py +47 -0
  46. grit_cli-0.1.0a0/backend/app/database.py +25 -0
  47. grit_cli-0.1.0a0/backend/app/main.py +48 -0
  48. grit_cli-0.1.0a0/backend/app/models/__init__.py +0 -0
  49. grit_cli-0.1.0a0/backend/app/models/user.py +111 -0
  50. grit_cli-0.1.0a0/backend/app/services/__init__.py +0 -0
  51. grit_cli-0.1.0a0/backend/app/services/license_service.py +62 -0
  52. grit_cli-0.1.0a0/backend/docker-compose.yml +52 -0
  53. grit_cli-0.1.0a0/backend/pyproject.toml +36 -0
  54. grit_cli-0.1.0a0/pyproject.toml +146 -0
  55. grit_cli-0.1.0a0/scripts/generate_license_keys.py +47 -0
  56. grit_cli-0.1.0a0/src/grit/__init__.py +4 -0
  57. grit_cli-0.1.0a0/src/grit/__main__.py +6 -0
  58. grit_cli-0.1.0a0/src/grit/cli/__init__.py +0 -0
  59. grit_cli-0.1.0a0/src/grit/cli/cmd_audit.py +124 -0
  60. grit_cli-0.1.0a0/src/grit/cli/cmd_auth.py +114 -0
  61. grit_cli-0.1.0a0/src/grit/cli/cmd_compliance.py +109 -0
  62. grit_cli-0.1.0a0/src/grit/cli/cmd_config.py +76 -0
  63. grit_cli-0.1.0a0/src/grit/cli/cmd_credential.py +142 -0
  64. grit_cli-0.1.0a0/src/grit/cli/cmd_daemon.py +106 -0
  65. grit_cli-0.1.0a0/src/grit/cli/cmd_enterprise.py +177 -0
  66. grit_cli-0.1.0a0/src/grit/cli/cmd_hook.py +60 -0
  67. grit_cli-0.1.0a0/src/grit/cli/cmd_profile.py +305 -0
  68. grit_cli-0.1.0a0/src/grit/cli/cmd_service.py +108 -0
  69. grit_cli-0.1.0a0/src/grit/cli/cmd_session.py +144 -0
  70. grit_cli-0.1.0a0/src/grit/cli/cmd_setup.py +92 -0
  71. grit_cli-0.1.0a0/src/grit/cli/cmd_sync.py +133 -0
  72. grit_cli-0.1.0a0/src/grit/cli/main.py +130 -0
  73. grit_cli-0.1.0a0/src/grit/cloud/__init__.py +0 -0
  74. grit_cli-0.1.0a0/src/grit/cloud/auth.py +24 -0
  75. grit_cli-0.1.0a0/src/grit/cloud/client.py +15 -0
  76. grit_cli-0.1.0a0/src/grit/cloud/sync.py +12 -0
  77. grit_cli-0.1.0a0/src/grit/config/__init__.py +0 -0
  78. grit_cli-0.1.0a0/src/grit/config/app_config.py +44 -0
  79. grit_cli-0.1.0a0/src/grit/config/paths.py +108 -0
  80. grit_cli-0.1.0a0/src/grit/config/subscription.py +293 -0
  81. grit_cli-0.1.0a0/src/grit/constants.py +38 -0
  82. grit_cli-0.1.0a0/src/grit/daemon/__init__.py +0 -0
  83. grit_cli-0.1.0a0/src/grit/daemon/hook_manager.py +67 -0
  84. grit_cli-0.1.0a0/src/grit/daemon/pid.py +55 -0
  85. grit_cli-0.1.0a0/src/grit/daemon/recovery.py +41 -0
  86. grit_cli-0.1.0a0/src/grit/daemon/server.py +187 -0
  87. grit_cli-0.1.0a0/src/grit/daemon/watchdog.py +78 -0
  88. grit_cli-0.1.0a0/src/grit/enterprise/__init__.py +0 -0
  89. grit_cli-0.1.0a0/src/grit/enterprise/audit.py +18 -0
  90. grit_cli-0.1.0a0/src/grit/enterprise/compliance.py +24 -0
  91. grit_cli-0.1.0a0/src/grit/enterprise/sso.py +45 -0
  92. grit_cli-0.1.0a0/src/grit/exceptions.py +81 -0
  93. grit_cli-0.1.0a0/src/grit/git/__init__.py +0 -0
  94. grit_cli-0.1.0a0/src/grit/git/config.py +138 -0
  95. grit_cli-0.1.0a0/src/grit/git/credentials.py +277 -0
  96. grit_cli-0.1.0a0/src/grit/git/gpg.py +52 -0
  97. grit_cli-0.1.0a0/src/grit/git/hook.py +94 -0
  98. grit_cli-0.1.0a0/src/grit/git/repo.py +50 -0
  99. grit_cli-0.1.0a0/src/grit/git/ssh.py +20 -0
  100. grit_cli-0.1.0a0/src/grit/ipc/__init__.py +0 -0
  101. grit_cli-0.1.0a0/src/grit/ipc/client.py +80 -0
  102. grit_cli-0.1.0a0/src/grit/ipc/protocol.py +80 -0
  103. grit_cli-0.1.0a0/src/grit/ipc/server.py +114 -0
  104. grit_cli-0.1.0a0/src/grit/models/__init__.py +0 -0
  105. grit_cli-0.1.0a0/src/grit/models/profile.py +46 -0
  106. grit_cli-0.1.0a0/src/grit/models/session.py +61 -0
  107. grit_cli-0.1.0a0/src/grit/platform/__init__.py +0 -0
  108. grit_cli-0.1.0a0/src/grit/platform/base.py +35 -0
  109. grit_cli-0.1.0a0/src/grit/platform/linux.py +93 -0
  110. grit_cli-0.1.0a0/src/grit/platform/macos.py +76 -0
  111. grit_cli-0.1.0a0/src/grit/platform/windows.py +53 -0
  112. grit_cli-0.1.0a0/src/grit/platform/windows_service.py +200 -0
  113. grit_cli-0.1.0a0/src/grit/session/__init__.py +0 -0
  114. grit_cli-0.1.0a0/src/grit/session/detector.py +104 -0
  115. grit_cli-0.1.0a0/src/grit/session/engine.py +163 -0
  116. grit_cli-0.1.0a0/src/grit/storage/__init__.py +0 -0
  117. grit_cli-0.1.0a0/src/grit/storage/_lock.py +45 -0
  118. grit_cli-0.1.0a0/src/grit/storage/profile_store.py +106 -0
  119. grit_cli-0.1.0a0/src/grit/storage/session_store.py +103 -0
  120. grit_cli-0.1.0a0/src/grit/ui/__init__.py +0 -0
  121. grit_cli-0.1.0a0/src/grit/ui/notifications.py +32 -0
  122. grit_cli-0.1.0a0/src/grit/ui/popup.py +124 -0
  123. grit_cli-0.1.0a0/src/grit/ui/tray.py +148 -0
  124. grit_cli-0.1.0a0/tests/__init__.py +0 -0
  125. grit_cli-0.1.0a0/tests/conftest.py +60 -0
  126. grit_cli-0.1.0a0/tests/e2e/__init__.py +0 -0
  127. grit_cli-0.1.0a0/tests/e2e/test_commit_flow.py +79 -0
  128. grit_cli-0.1.0a0/tests/integration/__init__.py +0 -0
  129. grit_cli-0.1.0a0/tests/integration/phase2/__init__.py +0 -0
  130. grit_cli-0.1.0a0/tests/integration/phase2/test_sync_flow.py +128 -0
  131. grit_cli-0.1.0a0/tests/integration/phase3/__init__.py +0 -0
  132. grit_cli-0.1.0a0/tests/integration/phase3/test_audit_wiring.py +58 -0
  133. grit_cli-0.1.0a0/tests/integration/test_daemon_lifecycle.py +47 -0
  134. grit_cli-0.1.0a0/tests/unit/__init__.py +0 -0
  135. grit_cli-0.1.0a0/tests/unit/phase2/__init__.py +0 -0
  136. grit_cli-0.1.0a0/tests/unit/phase2/test_cloud_client.py +74 -0
  137. grit_cli-0.1.0a0/tests/unit/phase2/test_profile_limit_enforcement.py +47 -0
  138. grit_cli-0.1.0a0/tests/unit/phase2/test_subscription.py +169 -0
  139. grit_cli-0.1.0a0/tests/unit/phase2/test_sync.py +75 -0
  140. grit_cli-0.1.0a0/tests/unit/phase3/__init__.py +0 -0
  141. grit_cli-0.1.0a0/tests/unit/phase3/test_audit.py +75 -0
  142. grit_cli-0.1.0a0/tests/unit/phase3/test_compliance.py +126 -0
  143. grit_cli-0.1.0a0/tests/unit/phase3/test_sso.py +158 -0
  144. grit_cli-0.1.0a0/tests/unit/test_cli.py +170 -0
  145. grit_cli-0.1.0a0/tests/unit/test_cli_credential.py +139 -0
  146. grit_cli-0.1.0a0/tests/unit/test_credentials.py +234 -0
  147. grit_cli-0.1.0a0/tests/unit/test_detector.py +95 -0
  148. grit_cli-0.1.0a0/tests/unit/test_git_config.py +93 -0
  149. grit_cli-0.1.0a0/tests/unit/test_git_hook.py +66 -0
  150. grit_cli-0.1.0a0/tests/unit/test_ipc_protocol.py +58 -0
  151. grit_cli-0.1.0a0/tests/unit/test_profile_store.py +92 -0
  152. grit_cli-0.1.0a0/tests/unit/test_session_engine.py +89 -0
  153. grit_cli-0.1.0a0/tests/unit/test_session_store.py +105 -0
  154. grit_cli-0.1.0a0/vscode-extension/package.json +42 -0
  155. grit_cli-0.1.0a0/vscode-extension/src/extension.ts +107 -0
  156. grit_cli-0.1.0a0/vscode-extension/src/ipcClient.ts +74 -0
  157. grit_cli-0.1.0a0/vscode-extension/src/statusBar.ts +66 -0
  158. 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