google-workspace-cli 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.
- google_workspace_cli-0.1.0/.claude/commands/quality.md +21 -0
- google_workspace_cli-0.1.0/.claude/commands/release.md +77 -0
- google_workspace_cli-0.1.0/.claude/commands/self-review.md +48 -0
- google_workspace_cli-0.1.0/.claude/commands/test.md +8 -0
- google_workspace_cli-0.1.0/.claude/rules/development-workflow.md +31 -0
- google_workspace_cli-0.1.0/.claude/rules/python.md +41 -0
- google_workspace_cli-0.1.0/.claude/rules/versioning.md +62 -0
- google_workspace_cli-0.1.0/.claude/settings.json +26 -0
- google_workspace_cli-0.1.0/.claude-plugin/marketplace.json +17 -0
- google_workspace_cli-0.1.0/.claude-plugin/plugin.json +22 -0
- google_workspace_cli-0.1.0/.github/FUNDING.yml +3 -0
- google_workspace_cli-0.1.0/.github/workflows/ci.yml +53 -0
- google_workspace_cli-0.1.0/.github/workflows/publish.yml +97 -0
- google_workspace_cli-0.1.0/.gitignore +20 -0
- google_workspace_cli-0.1.0/CHANGELOG.md +25 -0
- google_workspace_cli-0.1.0/CLAUDE.md +54 -0
- google_workspace_cli-0.1.0/CONTRIBUTING.md +78 -0
- google_workspace_cli-0.1.0/LICENSE +21 -0
- google_workspace_cli-0.1.0/PKG-INFO +270 -0
- google_workspace_cli-0.1.0/README.md +241 -0
- google_workspace_cli-0.1.0/SECURITY.md +44 -0
- google_workspace_cli-0.1.0/commands/gw-auth.md +23 -0
- google_workspace_cli-0.1.0/commands/gw-calendar.md +25 -0
- google_workspace_cli-0.1.0/commands/gw-drive.md +23 -0
- google_workspace_cli-0.1.0/commands/gw-loop.md +92 -0
- google_workspace_cli-0.1.0/commands/gw-mail.md +30 -0
- google_workspace_cli-0.1.0/commands/gw-setup.md +107 -0
- google_workspace_cli-0.1.0/commands/gw-workflow.md +33 -0
- google_workspace_cli-0.1.0/pyproject.toml +71 -0
- google_workspace_cli-0.1.0/src/gw/__init__.py +1 -0
- google_workspace_cli-0.1.0/src/gw/auth.py +199 -0
- google_workspace_cli-0.1.0/src/gw/calendar.py +239 -0
- google_workspace_cli-0.1.0/src/gw/cli.py +119 -0
- google_workspace_cli-0.1.0/src/gw/config.py +110 -0
- google_workspace_cli-0.1.0/src/gw/drive.py +189 -0
- google_workspace_cli-0.1.0/src/gw/gmail.py +366 -0
- google_workspace_cli-0.1.0/src/gw/output.py +35 -0
- google_workspace_cli-0.1.0/tests/conftest.py +53 -0
- google_workspace_cli-0.1.0/tests/test_auth.py +145 -0
- google_workspace_cli-0.1.0/tests/test_auth_service.py +38 -0
- google_workspace_cli-0.1.0/tests/test_bugfixes.py +322 -0
- google_workspace_cli-0.1.0/tests/test_calendar.py +180 -0
- google_workspace_cli-0.1.0/tests/test_cli.py +65 -0
- google_workspace_cli-0.1.0/tests/test_cli_config.py +32 -0
- google_workspace_cli-0.1.0/tests/test_config.py +94 -0
- google_workspace_cli-0.1.0/tests/test_coverage.py +388 -0
- google_workspace_cli-0.1.0/tests/test_drive.py +121 -0
- google_workspace_cli-0.1.0/tests/test_gmail.py +179 -0
- google_workspace_cli-0.1.0/tests/test_output.py +43 -0
- google_workspace_cli-0.1.0/uv.lock +895 -0
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
Run all quality checks.
|
|
2
|
+
|
|
3
|
+
Steps:
|
|
4
|
+
1. Lint:
|
|
5
|
+
```
|
|
6
|
+
uv run ruff check src/ tests/
|
|
7
|
+
```
|
|
8
|
+
2. Format check:
|
|
9
|
+
```
|
|
10
|
+
uv run ruff format --check src/ tests/
|
|
11
|
+
```
|
|
12
|
+
3. Type check:
|
|
13
|
+
```
|
|
14
|
+
uv run pyright src/
|
|
15
|
+
```
|
|
16
|
+
4. Tests:
|
|
17
|
+
```
|
|
18
|
+
uv run pytest tests/ -v
|
|
19
|
+
```
|
|
20
|
+
|
|
21
|
+
Report any issues found. If fixable, ask to auto-fix (`ruff check --fix`, `ruff format`).
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
---
|
|
2
|
+
description: Bump version, commit, tag, and push a release
|
|
3
|
+
arguments:
|
|
4
|
+
- name: level
|
|
5
|
+
description: "patch, minor, or major"
|
|
6
|
+
required: true
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
Release gw-cli with a version bump.
|
|
10
|
+
|
|
11
|
+
## Prerequisites
|
|
12
|
+
|
|
13
|
+
Before releasing, ensure:
|
|
14
|
+
1. `/quality` has been run and passes
|
|
15
|
+
2. Current branch is `master`
|
|
16
|
+
|
|
17
|
+
## Steps
|
|
18
|
+
|
|
19
|
+
### 1. Validate preconditions
|
|
20
|
+
|
|
21
|
+
- Argument: `$ARGUMENTS` (must be `patch`, `minor`, or `major`)
|
|
22
|
+
- Verify current branch is `master` (`git branch --show-current`). Abort if not.
|
|
23
|
+
|
|
24
|
+
### 2. Read current version
|
|
25
|
+
|
|
26
|
+
Read `src/gw/__init__.py` and extract `__version__`.
|
|
27
|
+
|
|
28
|
+
### 3. Calculate new version
|
|
29
|
+
|
|
30
|
+
Apply SemVer bump:
|
|
31
|
+
- `patch`: 0.1.0 → 0.1.1
|
|
32
|
+
- `minor`: 0.1.0 → 0.2.0
|
|
33
|
+
- `major`: 0.1.0 → 1.0.0
|
|
34
|
+
|
|
35
|
+
(While MAJOR is 0, treat breaking changes as `minor`.)
|
|
36
|
+
|
|
37
|
+
### 4. Update version in all three locations
|
|
38
|
+
|
|
39
|
+
Keep these in lockstep (see `.claude/rules/versioning.md`):
|
|
40
|
+
1. `src/gw/__init__.py` → `__version__`
|
|
41
|
+
2. `.claude-plugin/plugin.json` → `version`
|
|
42
|
+
3. `.claude-plugin/marketplace.json` → `plugins[0].version`
|
|
43
|
+
|
|
44
|
+
### 5. Lock and build
|
|
45
|
+
|
|
46
|
+
```bash
|
|
47
|
+
uv lock
|
|
48
|
+
uv build
|
|
49
|
+
```
|
|
50
|
+
|
|
51
|
+
Verify the build produces `dist/google_workspace_cli-X.Y.Z-*.whl` and `dist/google_workspace_cli-X.Y.Z.tar.gz`.
|
|
52
|
+
|
|
53
|
+
### 6. Commit and tag
|
|
54
|
+
|
|
55
|
+
```bash
|
|
56
|
+
git add src/gw/__init__.py .claude-plugin/plugin.json .claude-plugin/marketplace.json uv.lock
|
|
57
|
+
git commit -m "chore(release): vX.Y.Z"
|
|
58
|
+
git tag vX.Y.Z
|
|
59
|
+
```
|
|
60
|
+
|
|
61
|
+
### 7. Push
|
|
62
|
+
|
|
63
|
+
```bash
|
|
64
|
+
git push && git push --tags
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
This triggers the `Publish to PyPI` workflow → publishes `google-workspace-cli` to PyPI and creates a GitHub Release.
|
|
68
|
+
|
|
69
|
+
### 8. Update CHANGELOG and write release notes
|
|
70
|
+
|
|
71
|
+
1. `git log --oneline vPREVIOUS..vX.Y.Z` to review commits in this release.
|
|
72
|
+
2. Add a new section to the top of `CHANGELOG.md` for `vX.Y.Z`.
|
|
73
|
+
3. Write release notes grouped as **New features** / **Improvements** / **Bug fixes** (omit empty sections), referencing issue/PR numbers with `#N`. Add a `## Migration` section if any commit is a breaking change.
|
|
74
|
+
|
|
75
|
+
### 9. Report
|
|
76
|
+
|
|
77
|
+
Print the new version, the GitHub Release link, and the Actions run URL.
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
Perform a pre-PR self-review of all changes.
|
|
2
|
+
|
|
3
|
+
## Role
|
|
4
|
+
|
|
5
|
+
You are an AI code reviewer. Review only the modified code in the diff. Prioritize high-signal feedback over quantity.
|
|
6
|
+
|
|
7
|
+
## Review the diff
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
git diff master...HEAD
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
If no diff, review `git diff HEAD~1`.
|
|
14
|
+
|
|
15
|
+
## Review for (in priority order)
|
|
16
|
+
|
|
17
|
+
1. **Correctness** — bugs, edge cases, broken logic, unreachable code
|
|
18
|
+
2. **Security** — credential/token leaks, secrets in logs, unsafe defaults, committing `credentials.json`/tokens
|
|
19
|
+
3. **Error handling** — exceptions swallowed, auth errors not surfacing a recovery hint, missing input validation (`click.BadParameter`)
|
|
20
|
+
4. **Test coverage** — every changed/added code path must have a test. Check: new branches, error paths, edge cases
|
|
21
|
+
5. **CLI behavior** — `--json` / `--account` honored, output routed through `format_output`, help text accurate
|
|
22
|
+
6. **Consistency** — patterns used differently across files, naming inconsistencies with existing code
|
|
23
|
+
|
|
24
|
+
## Rules
|
|
25
|
+
|
|
26
|
+
- Only comment on lines that were changed in the diff
|
|
27
|
+
- Be specific and actionable — suggest concrete fixes
|
|
28
|
+
- Do not nitpick formatting (ruff handles it)
|
|
29
|
+
|
|
30
|
+
## Severity
|
|
31
|
+
|
|
32
|
+
| Marker | Meaning | Action |
|
|
33
|
+
|--------|---------|--------|
|
|
34
|
+
| `[C]` | Bug, security hole, data loss | Must fix before merge |
|
|
35
|
+
| `[W]` | Risk, missing guard, edge case | Should fix |
|
|
36
|
+
| `[I]` | Readability, minor improvement | Fix or justify |
|
|
37
|
+
|
|
38
|
+
## Output
|
|
39
|
+
|
|
40
|
+
```markdown
|
|
41
|
+
## Self-review: X files changed
|
|
42
|
+
|
|
43
|
+
`[C]` file:line — Description. **Fix:** concrete suggestion.
|
|
44
|
+
|
|
45
|
+
### Verdict: Ready / Needs fixes
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
If no significant issues: state "Changes look good" with a brief summary.
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "**"
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# Development Workflow
|
|
7
|
+
|
|
8
|
+
## Flow
|
|
9
|
+
|
|
10
|
+
1. **Start**: `gh issue view <number>` → understand the task
|
|
11
|
+
2. **Branch**: `git checkout -b {issue-number}-{type}/{description} master`
|
|
12
|
+
3. **Implement**: Write code keeping self-review criteria in mind from the start
|
|
13
|
+
4. **Quality**: Run `/quality` (lint, format, type-check, tests)
|
|
14
|
+
5. **Self-review**: Run `/self-review` — fix all `[C]` and `[W]` findings
|
|
15
|
+
6. **PR**: Create PR linking the issue. Only when the user explicitly asks.
|
|
16
|
+
7. **Merge**: Only after user approval. Never auto-merge.
|
|
17
|
+
|
|
18
|
+
## Commit Discipline
|
|
19
|
+
|
|
20
|
+
- Commit frequently with meaningful messages
|
|
21
|
+
- Follow Conventional Commits: `<type>(<scope>): <subject>`
|
|
22
|
+
- Each commit should be atomic — one logical change per commit
|
|
23
|
+
- Types: `feat`, `fix`, `refactor`, `test`, `docs`, `chore`, `ci`
|
|
24
|
+
|
|
25
|
+
## PR Rules
|
|
26
|
+
|
|
27
|
+
- Run `/quality` → `/self-review` before every PR
|
|
28
|
+
- PR title: Under 70 characters, Conventional Commits format
|
|
29
|
+
- Squash merge to `master`
|
|
30
|
+
- Delete branch after merge
|
|
31
|
+
- Never skip self-review before merge
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "src/**"
|
|
4
|
+
- "tests/**"
|
|
5
|
+
---
|
|
6
|
+
|
|
7
|
+
# Python Coding Rules
|
|
8
|
+
|
|
9
|
+
## Style
|
|
10
|
+
- snake_case for functions/variables, PascalCase for classes
|
|
11
|
+
- Type hints required on all function signatures
|
|
12
|
+
- `from __future__ import annotations` at the top of modules using new-style unions on 3.10
|
|
13
|
+
- Google-style docstrings on public functions and Click commands (the docstring is the `--help` text)
|
|
14
|
+
- Target Python 3.10+; keep syntax compatible with 3.10/3.11/3.12
|
|
15
|
+
|
|
16
|
+
## CLI (Click)
|
|
17
|
+
- Every command is a `@click.command` / `@click.group` with explicit `@click.option` / `@click.argument`
|
|
18
|
+
- User-facing output goes through `click.echo`, formatted via `gw.output.format_output` — never bare `print()`
|
|
19
|
+
- Respect the global `--json` / `--account` flags from `ctx.obj`; pass them into `format_output`
|
|
20
|
+
- Validate user input with `click.BadParameter`, not bare exceptions
|
|
21
|
+
|
|
22
|
+
## Error Handling
|
|
23
|
+
- Raise `RuntimeError` (or `click.BadParameter` for bad input) with an actionable message; the top-level CLI maps these to friendly errors
|
|
24
|
+
- Never swallow exceptions silently — re-raise or surface a clear message
|
|
25
|
+
- Auth/credential failures must tell the user how to recover (e.g. "Run 'gw auth login'")
|
|
26
|
+
|
|
27
|
+
## Google API
|
|
28
|
+
- Build services lazily; reuse credentials from the token store
|
|
29
|
+
- Never assume a field exists in an API response — use `.get(...)` with sensible defaults
|
|
30
|
+
- Request only the scopes a command needs
|
|
31
|
+
|
|
32
|
+
## Security
|
|
33
|
+
- Never commit `credentials.json`, tokens, or `.env` (see `.gitignore`)
|
|
34
|
+
- Never log access/refresh tokens or full credential payloads
|
|
35
|
+
- Store tokens under the config dir with restrictive permissions; never write secrets to `os.environ`
|
|
36
|
+
|
|
37
|
+
## Testing
|
|
38
|
+
- `pytest`; mock the Google API client (no live network calls in unit tests)
|
|
39
|
+
- Use Click's `CliRunner` for command tests; pass `--config-dir` to isolate state
|
|
40
|
+
- Test error paths (missing account, bad input, API failure), not just the happy path
|
|
41
|
+
- Every changed/added code path needs a test
|
|
@@ -0,0 +1,62 @@
|
|
|
1
|
+
---
|
|
2
|
+
paths:
|
|
3
|
+
- "src/gw/__init__.py"
|
|
4
|
+
- "pyproject.toml"
|
|
5
|
+
- ".claude-plugin/plugin.json"
|
|
6
|
+
- ".claude-plugin/marketplace.json"
|
|
7
|
+
---
|
|
8
|
+
|
|
9
|
+
# Version Management
|
|
10
|
+
|
|
11
|
+
## Single Source of Truth
|
|
12
|
+
|
|
13
|
+
- The canonical version is `__version__` in `src/gw/__init__.py`
|
|
14
|
+
- `pyproject.toml` uses `dynamic = ["version"]` with `[tool.hatch.version]` to read it — never set `version = "..."` in `pyproject.toml`
|
|
15
|
+
- The two Claude Code plugin manifests carry their own `version` and must be kept in sync:
|
|
16
|
+
- `.claude-plugin/plugin.json` → `version`
|
|
17
|
+
- `.claude-plugin/marketplace.json` → `plugins[0].version`
|
|
18
|
+
- `/release` bumps all three in lockstep
|
|
19
|
+
|
|
20
|
+
## SemVer Rules
|
|
21
|
+
|
|
22
|
+
Format: `MAJOR.MINOR.PATCH` (e.g. `0.2.3`)
|
|
23
|
+
|
|
24
|
+
| Change type | Bump | Example |
|
|
25
|
+
|-------------|------|---------|
|
|
26
|
+
| Breaking change | MAJOR | Removing/renaming a CLI command or flag |
|
|
27
|
+
| New feature (backward compatible) | MINOR | Adding a new `gw` subcommand |
|
|
28
|
+
| Bug fix, docs, refactor | PATCH | Fixing an error message |
|
|
29
|
+
|
|
30
|
+
While `MAJOR` is `0`, breaking changes bump MINOR instead.
|
|
31
|
+
|
|
32
|
+
## Conventional Commits → Version
|
|
33
|
+
|
|
34
|
+
| Commit prefix | Version bump |
|
|
35
|
+
|---------------|-------------|
|
|
36
|
+
| `feat` | MINOR (or PATCH if 0.x) |
|
|
37
|
+
| `fix`, `perf` | PATCH |
|
|
38
|
+
| `BREAKING CHANGE` footer or `!` suffix | MAJOR (or MINOR if 0.x) |
|
|
39
|
+
| `docs`, `chore`, `refactor`, `test`, `ci` | No bump |
|
|
40
|
+
|
|
41
|
+
## Release Flow
|
|
42
|
+
|
|
43
|
+
Use `/release patch|minor|major` to:
|
|
44
|
+
1. Bump `__version__` in `src/gw/__init__.py`
|
|
45
|
+
2. Sync `version` in `.claude-plugin/plugin.json` and `.claude-plugin/marketplace.json`
|
|
46
|
+
3. `uv lock` and `uv build` (verify)
|
|
47
|
+
4. Commit: `chore(release): vX.Y.Z`
|
|
48
|
+
5. Tag: `vX.Y.Z`
|
|
49
|
+
6. Push commit + tag → the `Publish to PyPI` workflow publishes to PyPI
|
|
50
|
+
|
|
51
|
+
### Test release
|
|
52
|
+
|
|
53
|
+
Manual trigger via GitHub Actions → `Publish to PyPI` → Run workflow → `testpypi`
|
|
54
|
+
|
|
55
|
+
## Release Infrastructure (Trusted Publishing)
|
|
56
|
+
|
|
57
|
+
Publishing uses PyPI's OIDC Trusted Publisher — **no API tokens**. Configure once:
|
|
58
|
+
|
|
59
|
+
- **PyPI project**: `google-workspace-cli`
|
|
60
|
+
- **Owner / repo**: `JFK/gw-cli`
|
|
61
|
+
- **Workflow**: `publish.yml`
|
|
62
|
+
- **Environments**: `pypi` (production) and `testpypi` — create both as GitHub Environments and register each as a Trusted Publisher on PyPI / TestPyPI
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
{
|
|
2
|
+
"hooks": {
|
|
3
|
+
"PostToolUse": [
|
|
4
|
+
{
|
|
5
|
+
"matcher": "Write|Edit",
|
|
6
|
+
"hooks": [
|
|
7
|
+
{
|
|
8
|
+
"type": "command",
|
|
9
|
+
"command": "file_path=$(cat | jq -r '.tool_input.file_path // empty'); [ -z \"$file_path\" ] && exit 0; case \"$file_path\" in *.py) uv run ruff format --quiet \"$file_path\" 2>/dev/null;; esac; exit 0"
|
|
10
|
+
}
|
|
11
|
+
]
|
|
12
|
+
}
|
|
13
|
+
],
|
|
14
|
+
"PreToolUse": [
|
|
15
|
+
{
|
|
16
|
+
"matcher": "Bash",
|
|
17
|
+
"hooks": [
|
|
18
|
+
{
|
|
19
|
+
"type": "command",
|
|
20
|
+
"command": "INPUT=$(cat); CMD=$(echo \"$INPUT\" | jq -r '.tool_input.command // empty'); if echo \"$CMD\" | grep -qE 'gh pr (merge|create)'; then echo 'Reminder: Before PR, ensure /quality and /self-review have been run.' >&2; fi; if echo \"$CMD\" | grep -qE 'git tag v'; then echo 'Reminder: Bump the version in src/gw/__init__.py and the .claude-plugin manifests before tagging (use /release).' >&2; fi; exit 0"
|
|
21
|
+
}
|
|
22
|
+
]
|
|
23
|
+
}
|
|
24
|
+
]
|
|
25
|
+
}
|
|
26
|
+
}
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://anthropic.com/claude-code/marketplace.schema.json",
|
|
3
|
+
"name": "gw-cli",
|
|
4
|
+
"description": "Google Workspace CLI for Claude Code — manage Calendar, Gmail, Drive with multi-account support",
|
|
5
|
+
"owner": {
|
|
6
|
+
"name": "Fumikazu Kiyota",
|
|
7
|
+
"email": "fumikazu.kiyota@gmail.com"
|
|
8
|
+
},
|
|
9
|
+
"plugins": [
|
|
10
|
+
{
|
|
11
|
+
"name": "gw-cli",
|
|
12
|
+
"source": "./",
|
|
13
|
+
"version": "0.1.0",
|
|
14
|
+
"category": "productivity"
|
|
15
|
+
}
|
|
16
|
+
]
|
|
17
|
+
}
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "gw-cli",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Google Workspace CLI for Claude Code — Calendar, Gmail, Drive with multi-account support",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "Fumikazu Kiyota",
|
|
7
|
+
"url": "https://github.com/JFK"
|
|
8
|
+
},
|
|
9
|
+
"repository": "https://github.com/JFK/gw-cli",
|
|
10
|
+
"license": "MIT",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"google-workspace",
|
|
13
|
+
"calendar",
|
|
14
|
+
"gmail",
|
|
15
|
+
"drive",
|
|
16
|
+
"multi-account",
|
|
17
|
+
"oauth",
|
|
18
|
+
"cli",
|
|
19
|
+
"productivity"
|
|
20
|
+
],
|
|
21
|
+
"commands": ["./commands/"]
|
|
22
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
name: CI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
branches: [master]
|
|
6
|
+
pull_request:
|
|
7
|
+
branches: [master]
|
|
8
|
+
|
|
9
|
+
jobs:
|
|
10
|
+
lint:
|
|
11
|
+
runs-on: ubuntu-latest
|
|
12
|
+
steps:
|
|
13
|
+
- uses: actions/checkout@v4
|
|
14
|
+
|
|
15
|
+
- uses: astral-sh/setup-uv@v6
|
|
16
|
+
with:
|
|
17
|
+
enable-cache: true
|
|
18
|
+
|
|
19
|
+
- name: Set up Python
|
|
20
|
+
run: uv python install 3.10
|
|
21
|
+
|
|
22
|
+
- name: Install dependencies
|
|
23
|
+
run: uv sync --dev
|
|
24
|
+
|
|
25
|
+
- name: Lint
|
|
26
|
+
run: uv run ruff check src/ tests/
|
|
27
|
+
|
|
28
|
+
- name: Format check
|
|
29
|
+
run: uv run ruff format --check src/ tests/
|
|
30
|
+
|
|
31
|
+
- name: Type check
|
|
32
|
+
run: uv run pyright src/
|
|
33
|
+
|
|
34
|
+
test:
|
|
35
|
+
runs-on: ubuntu-latest
|
|
36
|
+
strategy:
|
|
37
|
+
matrix:
|
|
38
|
+
python-version: ["3.10", "3.11", "3.12"]
|
|
39
|
+
steps:
|
|
40
|
+
- uses: actions/checkout@v4
|
|
41
|
+
|
|
42
|
+
- uses: astral-sh/setup-uv@v6
|
|
43
|
+
with:
|
|
44
|
+
enable-cache: true
|
|
45
|
+
|
|
46
|
+
- name: Set up Python ${{ matrix.python-version }}
|
|
47
|
+
run: uv python install ${{ matrix.python-version }}
|
|
48
|
+
|
|
49
|
+
- name: Install dependencies
|
|
50
|
+
run: uv sync --dev --python ${{ matrix.python-version }}
|
|
51
|
+
|
|
52
|
+
- name: Test with coverage
|
|
53
|
+
run: uv run pytest tests/ -v --cov=src/gw --cov-report=xml --cov-report=term
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
name: Publish to PyPI
|
|
2
|
+
|
|
3
|
+
on:
|
|
4
|
+
push:
|
|
5
|
+
tags:
|
|
6
|
+
- "v*"
|
|
7
|
+
workflow_dispatch:
|
|
8
|
+
inputs:
|
|
9
|
+
target:
|
|
10
|
+
description: "Publish target"
|
|
11
|
+
required: true
|
|
12
|
+
default: "testpypi"
|
|
13
|
+
type: choice
|
|
14
|
+
options:
|
|
15
|
+
- testpypi
|
|
16
|
+
|
|
17
|
+
jobs:
|
|
18
|
+
build:
|
|
19
|
+
runs-on: ubuntu-latest
|
|
20
|
+
steps:
|
|
21
|
+
- uses: actions/checkout@v4
|
|
22
|
+
|
|
23
|
+
- uses: astral-sh/setup-uv@v6
|
|
24
|
+
with:
|
|
25
|
+
enable-cache: true
|
|
26
|
+
|
|
27
|
+
- name: Build
|
|
28
|
+
run: uv build
|
|
29
|
+
|
|
30
|
+
- uses: actions/upload-artifact@v4
|
|
31
|
+
with:
|
|
32
|
+
name: dist
|
|
33
|
+
path: dist/
|
|
34
|
+
|
|
35
|
+
publish-testpypi:
|
|
36
|
+
if: github.event_name == 'workflow_dispatch'
|
|
37
|
+
needs: build
|
|
38
|
+
runs-on: ubuntu-latest
|
|
39
|
+
environment: testpypi
|
|
40
|
+
permissions:
|
|
41
|
+
id-token: write
|
|
42
|
+
steps:
|
|
43
|
+
- uses: actions/download-artifact@v4
|
|
44
|
+
with:
|
|
45
|
+
name: dist
|
|
46
|
+
path: dist/
|
|
47
|
+
|
|
48
|
+
- name: Publish to TestPyPI
|
|
49
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
50
|
+
with:
|
|
51
|
+
repository-url: https://test.pypi.org/legacy/
|
|
52
|
+
|
|
53
|
+
publish-pypi:
|
|
54
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
55
|
+
needs: build
|
|
56
|
+
runs-on: ubuntu-latest
|
|
57
|
+
environment: pypi
|
|
58
|
+
permissions:
|
|
59
|
+
id-token: write
|
|
60
|
+
steps:
|
|
61
|
+
- uses: actions/download-artifact@v4
|
|
62
|
+
with:
|
|
63
|
+
name: dist
|
|
64
|
+
path: dist/
|
|
65
|
+
|
|
66
|
+
- name: Publish to PyPI
|
|
67
|
+
uses: pypa/gh-action-pypi-publish@release/v1
|
|
68
|
+
|
|
69
|
+
github-release:
|
|
70
|
+
if: github.event_name == 'push' && startsWith(github.ref, 'refs/tags/v')
|
|
71
|
+
needs: publish-pypi
|
|
72
|
+
runs-on: ubuntu-latest
|
|
73
|
+
permissions:
|
|
74
|
+
contents: write
|
|
75
|
+
steps:
|
|
76
|
+
- uses: actions/checkout@v4
|
|
77
|
+
|
|
78
|
+
- name: Create GitHub Release
|
|
79
|
+
# Idempotent + race-safe: the /release skill may also create the release
|
|
80
|
+
# (with hand-written notes) right after pushing the tag, so a plain
|
|
81
|
+
# check-then-create here can lose the race and 422 with
|
|
82
|
+
# "Release.tag_name already exists". Re-check after a failed create and
|
|
83
|
+
# treat "already exists" as success.
|
|
84
|
+
run: |
|
|
85
|
+
if gh release view "$REF" >/dev/null 2>&1; then
|
|
86
|
+
echo "Release $REF already exists, skipping."
|
|
87
|
+
elif gh release create "$REF" --generate-notes; then
|
|
88
|
+
echo "Created release $REF."
|
|
89
|
+
elif gh release view "$REF" >/dev/null 2>&1; then
|
|
90
|
+
echo "Release $REF was created concurrently; treating as success."
|
|
91
|
+
else
|
|
92
|
+
echo "Failed to create release $REF." >&2
|
|
93
|
+
exit 1
|
|
94
|
+
fi
|
|
95
|
+
env:
|
|
96
|
+
GH_TOKEN: ${{ github.token }}
|
|
97
|
+
REF: ${{ github.ref_name }}
|
|
@@ -0,0 +1,20 @@
|
|
|
1
|
+
__pycache__/
|
|
2
|
+
*.pyc
|
|
3
|
+
*.egg-info/
|
|
4
|
+
dist/
|
|
5
|
+
build/
|
|
6
|
+
.venv/
|
|
7
|
+
credentials/
|
|
8
|
+
tokens/
|
|
9
|
+
.env
|
|
10
|
+
.env.*
|
|
11
|
+
*.json
|
|
12
|
+
!.claude-plugin/*.json
|
|
13
|
+
!.claude/settings.json
|
|
14
|
+
!pyproject.toml
|
|
15
|
+
.pytest_cache/
|
|
16
|
+
.ruff_cache/
|
|
17
|
+
.coverage
|
|
18
|
+
|
|
19
|
+
# local OAuth client secret (never commit)
|
|
20
|
+
credentials.json
|
|
@@ -0,0 +1,25 @@
|
|
|
1
|
+
# Changelog
|
|
2
|
+
|
|
3
|
+
All notable changes to this project are documented here. The format is based on
|
|
4
|
+
[Keep a Changelog](https://keepachangelog.com/en/1.1.0/), and this project adheres to
|
|
5
|
+
[Semantic Versioning](https://semver.org/spec/v2.0.0.html).
|
|
6
|
+
|
|
7
|
+
## [Unreleased]
|
|
8
|
+
|
|
9
|
+
### Added
|
|
10
|
+
- PyPI packaging: published as `google-workspace-cli` via hatchling with the version sourced from
|
|
11
|
+
`src/gw/__init__.py`.
|
|
12
|
+
- GitHub Actions: `ci.yml` (lint + type check + test on Python 3.10/3.11/3.12) and
|
|
13
|
+
`publish.yml` (OIDC Trusted Publishing to PyPI/TestPyPI + GitHub Release).
|
|
14
|
+
- Claude Code dev config: `CLAUDE.md`, `.claude/rules/`, and `.claude/commands/`
|
|
15
|
+
(`/quality`, `/test`, `/self-review`, `/release`).
|
|
16
|
+
- `CONTRIBUTING.md`.
|
|
17
|
+
|
|
18
|
+
## [0.1.0]
|
|
19
|
+
|
|
20
|
+
### Added
|
|
21
|
+
- Initial release: multi-account Google Workspace CLI (`gw`) for Calendar, Gmail, and Drive
|
|
22
|
+
with OAuth, plus a Claude Code plugin with 7 skills.
|
|
23
|
+
|
|
24
|
+
[Unreleased]: https://github.com/JFK/gw-cli/compare/v0.1.0...HEAD
|
|
25
|
+
[0.1.0]: https://github.com/JFK/gw-cli/releases/tag/v0.1.0
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
# gw-cli — Development Guide
|
|
2
|
+
|
|
3
|
+
## Overview
|
|
4
|
+
|
|
5
|
+
Google Workspace CLI for Claude Code. A Click-based, synchronous CLI (`gw`) over the Google
|
|
6
|
+
Calendar, Gmail, and Drive APIs with multi-account OAuth. Also ships as a Claude Code plugin.
|
|
7
|
+
|
|
8
|
+
- **PyPI package**: `google-workspace-cli` (the `gw-cli` name was taken) — import package and command are both `gw`
|
|
9
|
+
- **Plugin**: `.claude-plugin/` + `commands/gw-*.md` (7 skills)
|
|
10
|
+
|
|
11
|
+
Modules (`src/gw/`):
|
|
12
|
+
- `cli.py` — Click root group, global `--json` / `--account` flags
|
|
13
|
+
- `auth.py` — OAuth login/switch/list/remove, token store
|
|
14
|
+
- `calendar.py`, `gmail.py`, `drive.py` — per-service command groups
|
|
15
|
+
- `config.py` — config dir + account state
|
|
16
|
+
- `output.py` — `format_output` (text/JSON rendering)
|
|
17
|
+
|
|
18
|
+
## Development Workflow
|
|
19
|
+
|
|
20
|
+
See `.claude/rules/development-workflow.md` for the full flow (auto-loaded).
|
|
21
|
+
|
|
22
|
+
**Key sequence**: Issue → Branch → Implement → `/quality` → `/self-review` → PR → Merge
|
|
23
|
+
|
|
24
|
+
## Commands
|
|
25
|
+
|
|
26
|
+
```bash
|
|
27
|
+
uv sync --dev # Install deps (incl. dev)
|
|
28
|
+
uv run pytest tests/ -v # Tests
|
|
29
|
+
uv run ruff check src/ tests/ # Lint
|
|
30
|
+
uv run ruff format src/ tests/ # Format
|
|
31
|
+
uv run pyright src/ # Type check
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
**Slash commands**: `/quality`, `/test`, `/self-review`, `/release patch|minor|major`
|
|
35
|
+
|
|
36
|
+
## Conventions
|
|
37
|
+
|
|
38
|
+
- Click commands; user output via `click.echo(format_output(...))`, never bare `print()`
|
|
39
|
+
- Honor global `--json` / `--account` from `ctx.obj`
|
|
40
|
+
- Errors: `RuntimeError` / `click.BadParameter` with an actionable recovery hint
|
|
41
|
+
- Never commit `credentials.json`, tokens, or `.env`; never log tokens
|
|
42
|
+
- Full rules: `.claude/rules/python.md` (auto-loaded)
|
|
43
|
+
|
|
44
|
+
## Versioning & Release
|
|
45
|
+
|
|
46
|
+
Version lives in `src/gw/__init__.py` (`pyproject.toml` reads it via `dynamic`). The two
|
|
47
|
+
`.claude-plugin/*.json` manifests carry their own `version` and must stay in sync.
|
|
48
|
+
`/release` bumps all three, tags `vX.Y.Z`, and the `Publish to PyPI` workflow publishes `google-workspace-cli`
|
|
49
|
+
via OIDC Trusted Publishing. See `.claude/rules/versioning.md`.
|
|
50
|
+
|
|
51
|
+
## Branch Strategy
|
|
52
|
+
|
|
53
|
+
- Branch from `master` for every issue: `{issue-number}-{type}/{description}`
|
|
54
|
+
- Conventional Commits; squash merge to `master`
|