owa-tools 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.
- owa_tools-0.1.0/LICENSE +21 -0
- owa_tools-0.1.0/PKG-INFO +114 -0
- owa_tools-0.1.0/README.md +97 -0
- owa_tools-0.1.0/owa/__init__.py +3 -0
- owa_tools-0.1.0/owa/__main__.py +7 -0
- owa_tools-0.1.0/owa/cli.py +193 -0
- owa_tools-0.1.0/owa_cal/__init__.py +10 -0
- owa_tools-0.1.0/owa_cal/__main__.py +6 -0
- owa_tools-0.1.0/owa_cal/api.py +55 -0
- owa_tools-0.1.0/owa_cal/auth.py +40 -0
- owa_tools-0.1.0/owa_cal/cli.py +985 -0
- owa_tools-0.1.0/owa_cal/config.py +47 -0
- owa_tools-0.1.0/owa_cal/dates.py +68 -0
- owa_tools-0.1.0/owa_cal/events.py +262 -0
- owa_tools-0.1.0/owa_cal/format.py +37 -0
- owa_tools-0.1.0/owa_cal/ics.py +302 -0
- owa_tools-0.1.0/owa_cal/profiles.py +94 -0
- owa_tools-0.1.0/owa_core/__init__.py +5 -0
- owa_tools-0.1.0/owa_core/auth.py +220 -0
- owa_tools-0.1.0/owa_core/config.py +101 -0
- owa_tools-0.1.0/owa_core/errors.py +121 -0
- owa_tools-0.1.0/owa_core/format.py +32 -0
- owa_tools-0.1.0/owa_core/http.py +213 -0
- owa_tools-0.1.0/owa_core/jwt.py +63 -0
- owa_tools-0.1.0/owa_core/modes.py +140 -0
- owa_tools-0.1.0/owa_core/schema.py +76 -0
- owa_tools-0.1.0/owa_core/secrets.py +67 -0
- owa_tools-0.1.0/owa_core/tty.py +30 -0
- owa_tools-0.1.0/owa_core/version.py +45 -0
- owa_tools-0.1.0/owa_doctor/__init__.py +9 -0
- owa_tools-0.1.0/owa_doctor/__main__.py +6 -0
- owa_tools-0.1.0/owa_doctor/cli.py +189 -0
- owa_tools-0.1.0/owa_doctor/format.py +65 -0
- owa_tools-0.1.0/owa_doctor/probe.py +135 -0
- owa_tools-0.1.0/owa_drive/__init__.py +9 -0
- owa_tools-0.1.0/owa_drive/__main__.py +6 -0
- owa_tools-0.1.0/owa_drive/api.py +88 -0
- owa_tools-0.1.0/owa_drive/auth.py +34 -0
- owa_tools-0.1.0/owa_drive/cli.py +449 -0
- owa_tools-0.1.0/owa_drive/config.py +41 -0
- owa_tools-0.1.0/owa_drive/format.py +61 -0
- owa_tools-0.1.0/owa_drive/items.py +39 -0
- owa_tools-0.1.0/owa_drive/paths.py +82 -0
- owa_tools-0.1.0/owa_graph/__init__.py +10 -0
- owa_tools-0.1.0/owa_graph/__main__.py +7 -0
- owa_tools-0.1.0/owa_graph/api.py +155 -0
- owa_tools-0.1.0/owa_graph/auth.py +80 -0
- owa_tools-0.1.0/owa_graph/cli.py +740 -0
- owa_tools-0.1.0/owa_graph/config.py +47 -0
- owa_tools-0.1.0/owa_graph/ctx.py +143 -0
- owa_tools-0.1.0/owa_graph/data/__init__.py +5 -0
- owa_tools-0.1.0/owa_graph/data/paths.json.gz +0 -0
- owa_tools-0.1.0/owa_graph/data/scopes.json +75 -0
- owa_tools-0.1.0/owa_graph/emit.py +134 -0
- owa_tools-0.1.0/owa_graph/format.py +419 -0
- owa_tools-0.1.0/owa_graph/paths.py +75 -0
- owa_tools-0.1.0/owa_graph/resources/__init__.py +67 -0
- owa_tools-0.1.0/owa_graph/resources/_argv.py +49 -0
- owa_tools-0.1.0/owa_graph/resources/calendar.py +118 -0
- owa_tools-0.1.0/owa_graph/resources/chats.py +34 -0
- owa_tools-0.1.0/owa_graph/resources/contacts.py +47 -0
- owa_tools-0.1.0/owa_graph/resources/directory.py +23 -0
- owa_tools-0.1.0/owa_graph/resources/files.py +101 -0
- owa_tools-0.1.0/owa_graph/resources/groups.py +45 -0
- owa_tools-0.1.0/owa_graph/resources/mail.py +133 -0
- owa_tools-0.1.0/owa_graph/resources/me.py +55 -0
- owa_tools-0.1.0/owa_graph/resources/planner.py +57 -0
- owa_tools-0.1.0/owa_graph/resources/presence.py +40 -0
- owa_tools-0.1.0/owa_graph/resources/sites.py +38 -0
- owa_tools-0.1.0/owa_graph/resources/teams.py +54 -0
- owa_tools-0.1.0/owa_graph/resources/todo.py +46 -0
- owa_tools-0.1.0/owa_graph/resources/users.py +58 -0
- owa_tools-0.1.0/owa_graph/scopes.py +94 -0
- owa_tools-0.1.0/owa_mail/__init__.py +10 -0
- owa_tools-0.1.0/owa_mail/__main__.py +6 -0
- owa_tools-0.1.0/owa_mail/api.py +55 -0
- owa_tools-0.1.0/owa_mail/auth.py +40 -0
- owa_tools-0.1.0/owa_mail/cli.py +747 -0
- owa_tools-0.1.0/owa_mail/config.py +42 -0
- owa_tools-0.1.0/owa_mail/dates.py +30 -0
- owa_tools-0.1.0/owa_mail/folders.py +63 -0
- owa_tools-0.1.0/owa_mail/format.py +93 -0
- owa_tools-0.1.0/owa_mail/messages.py +286 -0
- owa_tools-0.1.0/owa_mail/scheduled.py +50 -0
- owa_tools-0.1.0/owa_people/__init__.py +9 -0
- owa_tools-0.1.0/owa_people/__main__.py +6 -0
- owa_tools-0.1.0/owa_people/api.py +52 -0
- owa_tools-0.1.0/owa_people/auth.py +39 -0
- owa_tools-0.1.0/owa_people/cli.py +419 -0
- owa_tools-0.1.0/owa_people/config.py +42 -0
- owa_tools-0.1.0/owa_people/format.py +50 -0
- owa_tools-0.1.0/owa_people/people.py +51 -0
- owa_tools-0.1.0/owa_sched/__init__.py +9 -0
- owa_tools-0.1.0/owa_sched/__main__.py +6 -0
- owa_tools-0.1.0/owa_sched/api.py +37 -0
- owa_tools-0.1.0/owa_sched/auth.py +38 -0
- owa_tools-0.1.0/owa_sched/cli.py +436 -0
- owa_tools-0.1.0/owa_sched/config.py +53 -0
- owa_tools-0.1.0/owa_sched/dates.py +90 -0
- owa_tools-0.1.0/owa_sched/format.py +44 -0
- owa_tools-0.1.0/owa_sched/schedule.py +79 -0
- owa_tools-0.1.0/owa_tools.egg-info/PKG-INFO +114 -0
- owa_tools-0.1.0/owa_tools.egg-info/SOURCES.txt +109 -0
- owa_tools-0.1.0/owa_tools.egg-info/dependency_links.txt +1 -0
- owa_tools-0.1.0/owa_tools.egg-info/entry_points.txt +9 -0
- owa_tools-0.1.0/owa_tools.egg-info/requires.txt +7 -0
- owa_tools-0.1.0/owa_tools.egg-info/top_level.txt +9 -0
- owa_tools-0.1.0/pyproject.toml +88 -0
- owa_tools-0.1.0/setup.cfg +4 -0
- owa_tools-0.1.0/tests/test_agents_index.py +68 -0
- owa_tools-0.1.0/tests/test_architecture_contracts.py +91 -0
owa_tools-0.1.0/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Carl Joakim Damsleth
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
owa_tools-0.1.0/PKG-INFO
ADDED
|
@@ -0,0 +1,114 @@
|
|
|
1
|
+
Metadata-Version: 2.4
|
|
2
|
+
Name: owa-tools
|
|
3
|
+
Version: 0.1.0
|
|
4
|
+
Summary: Monorepo workspace for owa-piggy consumer CLIs
|
|
5
|
+
Author: Carl Joakim Damsleth
|
|
6
|
+
License-Expression: MIT
|
|
7
|
+
Requires-Python: >=3.10
|
|
8
|
+
Description-Content-Type: text/markdown
|
|
9
|
+
License-File: LICENSE
|
|
10
|
+
Provides-Extra: dev
|
|
11
|
+
Requires-Dist: build>=1; extra == "dev"
|
|
12
|
+
Requires-Dist: pytest>=7; extra == "dev"
|
|
13
|
+
Requires-Dist: pytest-cov>=4; extra == "dev"
|
|
14
|
+
Requires-Dist: ruff>=0.4; extra == "dev"
|
|
15
|
+
Requires-Dist: twine>=5; extra == "dev"
|
|
16
|
+
Dynamic: license-file
|
|
17
|
+
|
|
18
|
+
# owa-tools
|
|
19
|
+
|
|
20
|
+
Monorepo for the seven `owa-piggy` consumer CLIs: `owa-cal`, `owa-mail`, `owa-graph`, `owa-doctor`, `owa-people`, `owa-sched`, `owa-drive`. Plus the umbrella `owa` discovery binary.
|
|
21
|
+
|
|
22
|
+
The auth broker `owa-piggy` lives in its own repository. It is the only persistent-secret holder in the suite.
|
|
23
|
+
|
|
24
|
+
## Status
|
|
25
|
+
|
|
26
|
+
`owa-tools` is currently unreleased and uses one suite version for all console scripts.
|
|
27
|
+
|
|
28
|
+
| CLI | Status |
|
|
29
|
+
|---|---|
|
|
30
|
+
| `owa-cal` | beta |
|
|
31
|
+
| `owa-mail` | beta |
|
|
32
|
+
| `owa-graph` | beta |
|
|
33
|
+
| `owa-doctor` | alpha |
|
|
34
|
+
| `owa-people` | alpha |
|
|
35
|
+
| `owa-sched` | alpha |
|
|
36
|
+
| `owa-drive` | alpha |
|
|
37
|
+
| `owa` | umbrella discovery binary |
|
|
38
|
+
|
|
39
|
+
## Layout
|
|
40
|
+
|
|
41
|
+
```
|
|
42
|
+
owa-tools/
|
|
43
|
+
├── owa_cal/ calendar CRUD over Outlook REST
|
|
44
|
+
├── owa_mail/ mail CRUD over Outlook REST
|
|
45
|
+
├── owa_graph/ Microsoft Graph CLI (verb-first + 14 resource shortcut groups)
|
|
46
|
+
├── owa_doctor/ health check across the suite
|
|
47
|
+
├── owa_people/ people, directory, contacts (Graph)
|
|
48
|
+
├── owa_sched/ free/busy and slot finding (Graph)
|
|
49
|
+
├── owa_drive/ OneDrive CRUD (Graph)
|
|
50
|
+
├── owa/ umbrella `owa` binary (list, schema, doctor, version)
|
|
51
|
+
├── tools/ CI helpers (stdlib check)
|
|
52
|
+
├── tests/ per-tool test suites
|
|
53
|
+
├── completions/ bash, zsh, fish
|
|
54
|
+
└── docs/ per-tool docs
|
|
55
|
+
```
|
|
56
|
+
|
|
57
|
+
## Running
|
|
58
|
+
|
|
59
|
+
Local dev install:
|
|
60
|
+
|
|
61
|
+
```bash
|
|
62
|
+
python3 -m venv .venv
|
|
63
|
+
.venv/bin/pip install -e .[dev]
|
|
64
|
+
.venv/bin/owa list
|
|
65
|
+
```
|
|
66
|
+
|
|
67
|
+
Wheel build:
|
|
68
|
+
|
|
69
|
+
```bash
|
|
70
|
+
.venv/bin/python -m build --wheel
|
|
71
|
+
```
|
|
72
|
+
|
|
73
|
+
The wheel contains all eight console scripts (`owa`, `owa-cal`, `owa-mail`, `owa-graph`, `owa-doctor`, `owa-people`, `owa-sched`, `owa-drive`) and they all report the same `owa-tools` suite version.
|
|
74
|
+
|
|
75
|
+
Test suite:
|
|
76
|
+
|
|
77
|
+
```bash
|
|
78
|
+
.venv/bin/python -m pytest
|
|
79
|
+
.venv/bin/coverage run --source=owa_core -m pytest -q
|
|
80
|
+
.venv/bin/coverage report --fail-under=95
|
|
81
|
+
.venv/bin/python -m pytest --cov --cov-fail-under=90
|
|
82
|
+
.venv/bin/python tools/check_stdlib_only.py
|
|
83
|
+
.venv/bin/python tools/check_no_secrets.py
|
|
84
|
+
.venv/bin/python tools/check_docs_sync.py
|
|
85
|
+
.venv/bin/python tools/check_artifacts.py dist/* # after build
|
|
86
|
+
.venv/bin/python tools/check_console_smoke.py # after build
|
|
87
|
+
```
|
|
88
|
+
|
|
89
|
+
See `RELEASING.md` for the suite tag-and-publish flow.
|
|
90
|
+
|
|
91
|
+
## More Docs
|
|
92
|
+
|
|
93
|
+
- `docs/security.md` defines the token, config, redaction, and live-test
|
|
94
|
+
boundaries.
|
|
95
|
+
- `docs/agent-integration.md` documents schema discovery, `--agent`, and
|
|
96
|
+
`--err-json`.
|
|
97
|
+
- `docs/profile-model.md` explains how `owa-tools` profile aliases map to
|
|
98
|
+
`owa-piggy` profiles.
|
|
99
|
+
- `docs/migrating-from-individual-installs.md` walks existing users from
|
|
100
|
+
the legacy per-tool installs to the `owa-tools` suite.
|
|
101
|
+
|
|
102
|
+
## Conventions
|
|
103
|
+
|
|
104
|
+
- Stdlib only at runtime, except for the local suite packages and `owa-piggy`.
|
|
105
|
+
- JSON on stdout, logs on stderr, `--pretty` for humans.
|
|
106
|
+
- `--agent` wraps JSON-compatible output for automation; `--err-json` emits
|
|
107
|
+
structured stderr.
|
|
108
|
+
- Auth via `owa-piggy` (subprocess, JSON contract).
|
|
109
|
+
- For agents and contributors, start with `AGENTS.md`, then read the nearest
|
|
110
|
+
local `AGENTS.md` for the package or directory being edited.
|
|
111
|
+
|
|
112
|
+
## License
|
|
113
|
+
|
|
114
|
+
MIT.
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
# owa-tools
|
|
2
|
+
|
|
3
|
+
Monorepo for the seven `owa-piggy` consumer CLIs: `owa-cal`, `owa-mail`, `owa-graph`, `owa-doctor`, `owa-people`, `owa-sched`, `owa-drive`. Plus the umbrella `owa` discovery binary.
|
|
4
|
+
|
|
5
|
+
The auth broker `owa-piggy` lives in its own repository. It is the only persistent-secret holder in the suite.
|
|
6
|
+
|
|
7
|
+
## Status
|
|
8
|
+
|
|
9
|
+
`owa-tools` is currently unreleased and uses one suite version for all console scripts.
|
|
10
|
+
|
|
11
|
+
| CLI | Status |
|
|
12
|
+
|---|---|
|
|
13
|
+
| `owa-cal` | beta |
|
|
14
|
+
| `owa-mail` | beta |
|
|
15
|
+
| `owa-graph` | beta |
|
|
16
|
+
| `owa-doctor` | alpha |
|
|
17
|
+
| `owa-people` | alpha |
|
|
18
|
+
| `owa-sched` | alpha |
|
|
19
|
+
| `owa-drive` | alpha |
|
|
20
|
+
| `owa` | umbrella discovery binary |
|
|
21
|
+
|
|
22
|
+
## Layout
|
|
23
|
+
|
|
24
|
+
```
|
|
25
|
+
owa-tools/
|
|
26
|
+
├── owa_cal/ calendar CRUD over Outlook REST
|
|
27
|
+
├── owa_mail/ mail CRUD over Outlook REST
|
|
28
|
+
├── owa_graph/ Microsoft Graph CLI (verb-first + 14 resource shortcut groups)
|
|
29
|
+
├── owa_doctor/ health check across the suite
|
|
30
|
+
├── owa_people/ people, directory, contacts (Graph)
|
|
31
|
+
├── owa_sched/ free/busy and slot finding (Graph)
|
|
32
|
+
├── owa_drive/ OneDrive CRUD (Graph)
|
|
33
|
+
├── owa/ umbrella `owa` binary (list, schema, doctor, version)
|
|
34
|
+
├── tools/ CI helpers (stdlib check)
|
|
35
|
+
├── tests/ per-tool test suites
|
|
36
|
+
├── completions/ bash, zsh, fish
|
|
37
|
+
└── docs/ per-tool docs
|
|
38
|
+
```
|
|
39
|
+
|
|
40
|
+
## Running
|
|
41
|
+
|
|
42
|
+
Local dev install:
|
|
43
|
+
|
|
44
|
+
```bash
|
|
45
|
+
python3 -m venv .venv
|
|
46
|
+
.venv/bin/pip install -e .[dev]
|
|
47
|
+
.venv/bin/owa list
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
Wheel build:
|
|
51
|
+
|
|
52
|
+
```bash
|
|
53
|
+
.venv/bin/python -m build --wheel
|
|
54
|
+
```
|
|
55
|
+
|
|
56
|
+
The wheel contains all eight console scripts (`owa`, `owa-cal`, `owa-mail`, `owa-graph`, `owa-doctor`, `owa-people`, `owa-sched`, `owa-drive`) and they all report the same `owa-tools` suite version.
|
|
57
|
+
|
|
58
|
+
Test suite:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
.venv/bin/python -m pytest
|
|
62
|
+
.venv/bin/coverage run --source=owa_core -m pytest -q
|
|
63
|
+
.venv/bin/coverage report --fail-under=95
|
|
64
|
+
.venv/bin/python -m pytest --cov --cov-fail-under=90
|
|
65
|
+
.venv/bin/python tools/check_stdlib_only.py
|
|
66
|
+
.venv/bin/python tools/check_no_secrets.py
|
|
67
|
+
.venv/bin/python tools/check_docs_sync.py
|
|
68
|
+
.venv/bin/python tools/check_artifacts.py dist/* # after build
|
|
69
|
+
.venv/bin/python tools/check_console_smoke.py # after build
|
|
70
|
+
```
|
|
71
|
+
|
|
72
|
+
See `RELEASING.md` for the suite tag-and-publish flow.
|
|
73
|
+
|
|
74
|
+
## More Docs
|
|
75
|
+
|
|
76
|
+
- `docs/security.md` defines the token, config, redaction, and live-test
|
|
77
|
+
boundaries.
|
|
78
|
+
- `docs/agent-integration.md` documents schema discovery, `--agent`, and
|
|
79
|
+
`--err-json`.
|
|
80
|
+
- `docs/profile-model.md` explains how `owa-tools` profile aliases map to
|
|
81
|
+
`owa-piggy` profiles.
|
|
82
|
+
- `docs/migrating-from-individual-installs.md` walks existing users from
|
|
83
|
+
the legacy per-tool installs to the `owa-tools` suite.
|
|
84
|
+
|
|
85
|
+
## Conventions
|
|
86
|
+
|
|
87
|
+
- Stdlib only at runtime, except for the local suite packages and `owa-piggy`.
|
|
88
|
+
- JSON on stdout, logs on stderr, `--pretty` for humans.
|
|
89
|
+
- `--agent` wraps JSON-compatible output for automation; `--err-json` emits
|
|
90
|
+
structured stderr.
|
|
91
|
+
- Auth via `owa-piggy` (subprocess, JSON contract).
|
|
92
|
+
- For agents and contributors, start with `AGENTS.md`, then read the nearest
|
|
93
|
+
local `AGENTS.md` for the package or directory being edited.
|
|
94
|
+
|
|
95
|
+
## License
|
|
96
|
+
|
|
97
|
+
MIT.
|
|
@@ -0,0 +1,193 @@
|
|
|
1
|
+
"""Umbrella `owa` binary. Discovery only; not a full dispatcher.
|
|
2
|
+
|
|
3
|
+
Subcommands:
|
|
4
|
+
owa list List installed consumer CLIs and their versions.
|
|
5
|
+
owa schema Aggregate schemas from each consumer CLI.
|
|
6
|
+
owa doctor Forward to `owa-doctor probe`.
|
|
7
|
+
owa version Print the umbrella version.
|
|
8
|
+
|
|
9
|
+
Real work lives in the consumer CLIs (owa-cal, owa-mail, ...).
|
|
10
|
+
"""
|
|
11
|
+
from __future__ import annotations
|
|
12
|
+
|
|
13
|
+
import json
|
|
14
|
+
import shutil
|
|
15
|
+
import subprocess
|
|
16
|
+
import sys
|
|
17
|
+
from pathlib import Path
|
|
18
|
+
|
|
19
|
+
from . import __version__
|
|
20
|
+
|
|
21
|
+
CONSUMERS = (
|
|
22
|
+
"owa-cal",
|
|
23
|
+
"owa-mail",
|
|
24
|
+
"owa-graph",
|
|
25
|
+
"owa-doctor",
|
|
26
|
+
"owa-people",
|
|
27
|
+
"owa-sched",
|
|
28
|
+
"owa-drive",
|
|
29
|
+
)
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def _which(name: str) -> str | None:
|
|
33
|
+
for base in (
|
|
34
|
+
Path(sys.argv[0]).parent,
|
|
35
|
+
Path(sys.argv[0]).resolve().parent,
|
|
36
|
+
Path(sys.executable).parent,
|
|
37
|
+
Path(sys.executable).resolve().parent,
|
|
38
|
+
):
|
|
39
|
+
candidate = base / name
|
|
40
|
+
if candidate.exists():
|
|
41
|
+
return str(candidate)
|
|
42
|
+
found = shutil.which(name)
|
|
43
|
+
if found:
|
|
44
|
+
return found
|
|
45
|
+
return None
|
|
46
|
+
|
|
47
|
+
|
|
48
|
+
def _version_of(binary: str) -> str | None:
|
|
49
|
+
"""Best-effort version lookup. Tries `--version`, falls back to the
|
|
50
|
+
first line of `--help`. Returns None on failure.
|
|
51
|
+
|
|
52
|
+
Several owa-* CLIs do not implement --version yet; that's tracked as
|
|
53
|
+
Phase 6 work in the implementation plan. This function works against
|
|
54
|
+
today's state."""
|
|
55
|
+
path = _which(binary)
|
|
56
|
+
if path is None:
|
|
57
|
+
return None
|
|
58
|
+
|
|
59
|
+
def _run(args: list[str]) -> str | None:
|
|
60
|
+
try:
|
|
61
|
+
out = subprocess.run(
|
|
62
|
+
[path, *args],
|
|
63
|
+
capture_output=True,
|
|
64
|
+
text=True,
|
|
65
|
+
timeout=5,
|
|
66
|
+
)
|
|
67
|
+
except (subprocess.TimeoutExpired, OSError):
|
|
68
|
+
return None
|
|
69
|
+
return (out.stdout or out.stderr or "").strip() or None
|
|
70
|
+
|
|
71
|
+
text = _run(["--version"])
|
|
72
|
+
if text and not _looks_like_error(text):
|
|
73
|
+
return text.splitlines()[0]
|
|
74
|
+
text = _run(["--help"])
|
|
75
|
+
if text:
|
|
76
|
+
return text.splitlines()[0]
|
|
77
|
+
return None
|
|
78
|
+
|
|
79
|
+
|
|
80
|
+
def _looks_like_error(text: str) -> bool:
|
|
81
|
+
head = text.lower()
|
|
82
|
+
return head.startswith(("error", "usage:", "unknown")) or "unknown command" in head
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
def cmd_list(argv: list[str]) -> int:
|
|
86
|
+
del argv
|
|
87
|
+
rows = []
|
|
88
|
+
for name in CONSUMERS:
|
|
89
|
+
path = _which(name)
|
|
90
|
+
rows.append({
|
|
91
|
+
"tool": name,
|
|
92
|
+
"installed": path is not None,
|
|
93
|
+
"path": path,
|
|
94
|
+
"version": _version_of(name) if path else None,
|
|
95
|
+
})
|
|
96
|
+
json.dump(rows, sys.stdout, ensure_ascii=False, indent=2)
|
|
97
|
+
sys.stdout.write("\n")
|
|
98
|
+
return 0
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
def cmd_version(argv: list[str]) -> int:
|
|
102
|
+
del argv
|
|
103
|
+
sys.stdout.write(f"owa {__version__}\n")
|
|
104
|
+
return 0
|
|
105
|
+
|
|
106
|
+
|
|
107
|
+
def cmd_doctor(argv: list[str]) -> int:
|
|
108
|
+
path = _which("owa-doctor")
|
|
109
|
+
if path is None:
|
|
110
|
+
sys.stderr.write("owa-doctor not on PATH\n")
|
|
111
|
+
return 13
|
|
112
|
+
return subprocess.call([path, "probe", *argv])
|
|
113
|
+
|
|
114
|
+
|
|
115
|
+
def cmd_schema(argv: list[str]) -> int:
|
|
116
|
+
"""Aggregate `<tool> schema` output across installed consumers.
|
|
117
|
+
|
|
118
|
+
Each consumer either ships a JSON schema (Phase 3+) or doesn't.
|
|
119
|
+
Tools that don't yet support `schema` get a stub entry with
|
|
120
|
+
``"schema_supported": false``. With ``--tool <name>``, fetches a
|
|
121
|
+
single tool only.
|
|
122
|
+
"""
|
|
123
|
+
only = None
|
|
124
|
+
if argv and argv[0] in ("--tool", "-t"):
|
|
125
|
+
if len(argv) < 2:
|
|
126
|
+
sys.stderr.write("--tool requires a value\n")
|
|
127
|
+
return 2
|
|
128
|
+
only = argv[1]
|
|
129
|
+
aggregate = []
|
|
130
|
+
for name in CONSUMERS:
|
|
131
|
+
if only and name != only:
|
|
132
|
+
continue
|
|
133
|
+
path = _which(name)
|
|
134
|
+
if path is None:
|
|
135
|
+
aggregate.append({"tool": name, "installed": False})
|
|
136
|
+
continue
|
|
137
|
+
entry: dict[str, object] = {"tool": name, "installed": True, "path": path}
|
|
138
|
+
try:
|
|
139
|
+
proc = subprocess.run(
|
|
140
|
+
[path, "schema"], capture_output=True, text=True, timeout=5,
|
|
141
|
+
)
|
|
142
|
+
except (subprocess.TimeoutExpired, OSError) as e:
|
|
143
|
+
entry["schema_supported"] = False
|
|
144
|
+
entry["error"] = str(e)
|
|
145
|
+
aggregate.append(entry)
|
|
146
|
+
continue
|
|
147
|
+
if proc.returncode == 0 and proc.stdout.strip():
|
|
148
|
+
try:
|
|
149
|
+
entry["schema"] = json.loads(proc.stdout)
|
|
150
|
+
entry["schema_supported"] = True
|
|
151
|
+
except json.JSONDecodeError:
|
|
152
|
+
entry["schema_supported"] = False
|
|
153
|
+
entry["error"] = "non-JSON output from `schema`"
|
|
154
|
+
else:
|
|
155
|
+
entry["schema_supported"] = False
|
|
156
|
+
aggregate.append(entry)
|
|
157
|
+
json.dump(aggregate, sys.stdout, ensure_ascii=False, indent=2)
|
|
158
|
+
sys.stdout.write("\n")
|
|
159
|
+
return 0
|
|
160
|
+
|
|
161
|
+
|
|
162
|
+
def cmd_help(argv: list[str]) -> int:
|
|
163
|
+
del argv
|
|
164
|
+
sys.stdout.write(__doc__ or "")
|
|
165
|
+
return 0
|
|
166
|
+
|
|
167
|
+
|
|
168
|
+
COMMANDS = {
|
|
169
|
+
"list": cmd_list,
|
|
170
|
+
"version": cmd_version,
|
|
171
|
+
"doctor": cmd_doctor,
|
|
172
|
+
"schema": cmd_schema,
|
|
173
|
+
"help": cmd_help,
|
|
174
|
+
"--help": cmd_help,
|
|
175
|
+
"-h": cmd_help,
|
|
176
|
+
"--version": cmd_version,
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
|
|
180
|
+
def main(argv: list[str] | None = None) -> int:
|
|
181
|
+
argv = list(sys.argv[1:] if argv is None else argv)
|
|
182
|
+
if not argv:
|
|
183
|
+
return cmd_help([])
|
|
184
|
+
cmd = argv[0]
|
|
185
|
+
handler = COMMANDS.get(cmd)
|
|
186
|
+
if handler is None:
|
|
187
|
+
sys.stderr.write(f"unknown command: {cmd}\n")
|
|
188
|
+
return 2
|
|
189
|
+
return handler(argv[1:])
|
|
190
|
+
|
|
191
|
+
|
|
192
|
+
if __name__ == "__main__":
|
|
193
|
+
sys.exit(main())
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
"""owa-cal - calendar CLI for Outlook / Microsoft 365."""
|
|
2
|
+
|
|
3
|
+
from owa_core.version import suite_version
|
|
4
|
+
|
|
5
|
+
__version__ = suite_version()
|
|
6
|
+
|
|
7
|
+
# Defined after __version__ so cli.py can safely `from . import __version__`.
|
|
8
|
+
from .cli import main # noqa: E402
|
|
9
|
+
|
|
10
|
+
__all__ = ["main", "__version__"]
|
|
@@ -0,0 +1,55 @@
|
|
|
1
|
+
"""Outlook REST HTTP helper.
|
|
2
|
+
|
|
3
|
+
One function: api_request. Returns parsed JSON or None (for
|
|
4
|
+
return-to-caller failures). For auth/permission failures we exit the
|
|
5
|
+
process with a clear message - owa-cal is a CLI, not a library, and
|
|
6
|
+
there is no recovery path for a 401 except telling the user to re-run.
|
|
7
|
+
"""
|
|
8
|
+
import urllib.parse
|
|
9
|
+
|
|
10
|
+
from owa_core import http
|
|
11
|
+
from owa_core.errors import (
|
|
12
|
+
AuthExpiredError,
|
|
13
|
+
ConflictError,
|
|
14
|
+
InternalError,
|
|
15
|
+
NetworkError,
|
|
16
|
+
NotFoundError,
|
|
17
|
+
OwaError,
|
|
18
|
+
RateLimitedError,
|
|
19
|
+
ScopeInsufficientError,
|
|
20
|
+
emit_error,
|
|
21
|
+
)
|
|
22
|
+
|
|
23
|
+
|
|
24
|
+
def api_request(method, base, endpoint, access_token, body=None, debug=False):
|
|
25
|
+
"""Issue a request against Outlook REST.
|
|
26
|
+
|
|
27
|
+
- `base` and `endpoint` are joined with a single slash.
|
|
28
|
+
- `body` is dict-serialised to JSON when non-None.
|
|
29
|
+
- Returns parsed JSON on 2xx, None on 404/429 (caller decides),
|
|
30
|
+
and exits on 401/403 (unrecoverable without reconfig).
|
|
31
|
+
"""
|
|
32
|
+
url = f'{base}/{endpoint}'
|
|
33
|
+
try:
|
|
34
|
+
return http.request(method, url, token=access_token, body=body, debug=debug).json
|
|
35
|
+
except (AuthExpiredError, ScopeInsufficientError) as error:
|
|
36
|
+
raise error
|
|
37
|
+
except (ConflictError, InternalError, NetworkError, NotFoundError, RateLimitedError) as error:
|
|
38
|
+
emit_error(error)
|
|
39
|
+
return None
|
|
40
|
+
except OwaError as error:
|
|
41
|
+
emit_error(error)
|
|
42
|
+
return None
|
|
43
|
+
|
|
44
|
+
|
|
45
|
+
def api_get(base, endpoint, access_token, debug=False):
|
|
46
|
+
return api_request('GET', base, endpoint, access_token, debug=debug)
|
|
47
|
+
|
|
48
|
+
|
|
49
|
+
def build_query(params):
|
|
50
|
+
"""Build an OData query string. Values are URL-encoded, keys are
|
|
51
|
+
not (they are $-prefixed OData system params)."""
|
|
52
|
+
parts = []
|
|
53
|
+
for k, v in params.items():
|
|
54
|
+
parts.append(f'{k}={urllib.parse.quote(str(v), safe="")}')
|
|
55
|
+
return '&'.join(parts)
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
"""Token acquisition. Audience: outlook (Outlook REST endpoint).
|
|
2
|
+
|
|
3
|
+
owa-cal targets `outlook.office.com`, not Microsoft Graph: the OWA SPA
|
|
4
|
+
client owa-piggy borrows does NOT carry Calendars.ReadWrite on the
|
|
5
|
+
Graph audience - OWA itself calls Outlook REST for calendar.
|
|
6
|
+
|
|
7
|
+
Thin wrapper over owa_core.auth - see owa_drive/auth.py for the
|
|
8
|
+
"""
|
|
9
|
+
|
|
10
|
+
from owa_core import auth as _core
|
|
11
|
+
from owa_core.errors import OwaError, emit_error
|
|
12
|
+
|
|
13
|
+
TOOL_NAME = 'owa-cal'
|
|
14
|
+
AUDIENCE = 'outlook'
|
|
15
|
+
API_BASE = 'https://outlook.office.com/api/v2.0'
|
|
16
|
+
|
|
17
|
+
def _log_token_remaining(access, debug):
|
|
18
|
+
_core.log_token_remaining(access, debug)
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
def _refresh_via_owa_piggy(config, debug=False):
|
|
22
|
+
try:
|
|
23
|
+
token = _core.get_token_for_config(
|
|
24
|
+
config, tool_name=TOOL_NAME, audience=AUDIENCE, debug=debug,
|
|
25
|
+
)
|
|
26
|
+
except OwaError as error:
|
|
27
|
+
emit_error(error)
|
|
28
|
+
return None
|
|
29
|
+
return token.access_token
|
|
30
|
+
|
|
31
|
+
|
|
32
|
+
def do_token_refresh(config, debug=False):
|
|
33
|
+
return _refresh_via_owa_piggy(config, debug=debug)
|
|
34
|
+
|
|
35
|
+
|
|
36
|
+
def setup_auth(config, debug=False):
|
|
37
|
+
token = _core.get_token_for_config(
|
|
38
|
+
config, tool_name=TOOL_NAME, audience=AUDIENCE, debug=debug,
|
|
39
|
+
)
|
|
40
|
+
return token.access_token, API_BASE
|