clockify-unofficial-cli 0.2.0__tar.gz → 1.0.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.
- clockify_unofficial_cli-1.0.0/.claude-plugin/marketplace.json +15 -0
- clockify_unofficial_cli-1.0.0/.claude-plugin/plugin.json +18 -0
- clockify_unofficial_cli-1.0.0/AGENTS.md +117 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/Makefile +1 -1
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/PKG-INFO +64 -21
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/README.md +61 -18
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/ARCHITECTURE.md +51 -26
- clockify_unofficial_cli-1.0.0/docs/ROADMAP.md +137 -0
- clockify_unofficial_cli-1.0.0/docs/agents/agent-skills-format.md +68 -0
- clockify_unofficial_cli-1.0.0/docs/agents/claude-code-install-scopes.md +50 -0
- clockify_unofficial_cli-1.0.0/docs/agents/claude-code-marketplace-install.md +67 -0
- clockify_unofficial_cli-1.0.0/docs/agents/claude-code-plugin-updates.md +71 -0
- clockify_unofficial_cli-1.0.0/docs/agents/claude-code-shell-install.md +58 -0
- clockify_unofficial_cli-1.0.0/docs/agents/claude-code-skill-invocation.md +52 -0
- clockify_unofficial_cli-1.0.0/docs/agents/index.md +50 -0
- clockify_unofficial_cli-1.0.0/docs/agents/install-channels.md +65 -0
- clockify_unofficial_cli-1.0.0/docs/agents/npx-skills-install.md +65 -0
- clockify_unofficial_cli-1.0.0/docs/agents/npx-skills-manage.md +58 -0
- clockify_unofficial_cli-1.0.0/docs/agents/opencode-mcp-config.md +83 -0
- clockify_unofficial_cli-1.0.0/docs/agents/opencode-skill-discovery.md +50 -0
- clockify_unofficial_cli-1.0.0/docs/agents/opencode-skill-permissions.md +48 -0
- clockify_unofficial_cli-1.0.0/docs/agents/plugin-identity.md +74 -0
- clockify_unofficial_cli-1.0.0/docs/commands/expenses.md +47 -0
- clockify_unofficial_cli-1.0.0/docs/commands/index.md +11 -0
- clockify_unofficial_cli-1.0.0/docs/commands/invoices.md +68 -0
- clockify_unofficial_cli-1.0.0/docs/commands/reports.md +38 -0
- clockify_unofficial_cli-1.0.0/docs/commands/time-off.md +52 -0
- clockify_unofficial_cli-1.0.0/docs/coverage.md +285 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/index.md +12 -1
- clockify_unofficial_cli-1.0.0/docs/log.md +81 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/python/typer-parameter-objects.md +4 -0
- clockify_unofficial_cli-1.0.0/docs/toolchain/claude-plugin.md +22 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/toolchain/index.md +2 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/toolchain/layering-rule.md +1 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/toolchain/releasing.md +4 -2
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/mk/python.mk +2 -2
- clockify_unofficial_cli-1.0.0/mk/skills.mk +10 -0
- clockify_unofficial_cli-1.0.0/package.json +8 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/pyproject.toml +12 -30
- clockify_unofficial_cli-1.0.0/skills/clockify-cli/SKILL.md +94 -0
- clockify_unofficial_cli-1.0.0/skills/clockify-cli/references/exit-codes.md +17 -0
- clockify_unofficial_cli-1.0.0/skills/clockify-time-tracking/SKILL.md +66 -0
- clockify_unofficial_cli-1.0.0/skills/clockify-time-tracking/references/exit-codes.md +17 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/approval.py +174 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/expense.py +197 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/expense_category.py +141 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/group.py +29 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/invoice.py +257 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/invoice_item.py +115 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/invoice_payment.py +87 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/invoice_settings.py +72 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/report.py +158 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/shared_report.py +50 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/time_off.py +12 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/time_off_balance.py +214 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/time_off_policy.py +239 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/time_off_request.py +172 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/commands/webhook.py +240 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/main.py +14 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/output/columns.py +281 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/runtime/params.py +17 -1
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/services/expenses.py +101 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/services/files.py +36 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/services/invoices.py +164 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/services/money.py +15 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/services/parsing.py +5 -1
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/services/reports.py +193 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/services/resolve.py +28 -0
- clockify_unofficial_cli-1.0.0/src/clockify_unofficial_cli/services/time_off.py +121 -0
- clockify_unofficial_cli-1.0.0/tests/unit/commands/test_approval.py +137 -0
- clockify_unofficial_cli-1.0.0/tests/unit/commands/test_expense.py +459 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_group.py +51 -0
- clockify_unofficial_cli-1.0.0/tests/unit/commands/test_invoice.py +536 -0
- clockify_unofficial_cli-1.0.0/tests/unit/commands/test_report.py +129 -0
- clockify_unofficial_cli-1.0.0/tests/unit/commands/test_time_off.py +469 -0
- clockify_unofficial_cli-1.0.0/tests/unit/commands/test_webhook.py +236 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/runtime/test_params.py +37 -0
- clockify_unofficial_cli-1.0.0/tests/unit/services/test_expenses.py +33 -0
- clockify_unofficial_cli-1.0.0/tests/unit/services/test_files.py +39 -0
- clockify_unofficial_cli-1.0.0/tests/unit/services/test_money.py +18 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/services/test_parsing.py +10 -0
- clockify_unofficial_cli-1.0.0/tests/unit/services/test_reports.py +136 -0
- clockify_unofficial_cli-1.0.0/tests/unit/services/test_time_off.py +100 -0
- clockify_unofficial_cli-1.0.0/tests/unit/test_plugin_manifest.py +56 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/uv.lock +73 -12
- clockify_unofficial_cli-0.2.0/AGENTS.md +0 -168
- clockify_unofficial_cli-0.2.0/docs/ROADMAP.md +0 -205
- clockify_unofficial_cli-0.2.0/docs/coverage.md +0 -280
- clockify_unofficial_cli-0.2.0/docs/log.md +0 -21
- clockify_unofficial_cli-0.2.0/package.json +0 -8
- clockify_unofficial_cli-0.2.0/src/clockify_unofficial_cli/output/columns.py +0 -88
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.editorconfig +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.github/dependabot.yml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.github/workflows/ci.yml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.github/workflows/publish.yml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.github/workflows/python.yml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.gitignore +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.markdownlint-cli2.jsonc +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.markdownlint.yaml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/.pre-commit-config.yaml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/CONTRIBUTING.md +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/LICENSE +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/checkmake.ini +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/cspell.config.yaml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/dictionary.txt +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/conventions/commits-check.md +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/conventions/index.md +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/python/index.md +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/python/interpreter-source.md +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/python/pyproject-defaults.md +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/docs/toolchain/rejected-install-backends.md +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/mise.toml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/mk/.gitkeep +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/mk/clockify.mk +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/pnpm-lock.yaml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/pnpm-workspace.yaml +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/scripts/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/scripts/coverage_report.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/__main__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/_version.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/auth/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/auth/credentials.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/auth/env_store.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/auth/file_store.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/auth/keyring_store.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/auth/resolver.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/auth.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/client.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/config.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/custom_field.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/entry.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/project.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/tag.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/task.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/timer_shortcuts.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/user.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/commands/workspace.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/config/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/config/paths.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/config/settings.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/config/store.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/output/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/output/formats.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/output/registry.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/output/renderer.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/runtime/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/runtime/client_factory.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/runtime/context.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/runtime/errors.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/runtime/exit_codes.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/runtime/prompts.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/services/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/services/listing.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/src/clockify_unofficial_cli/services/timer.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/conftest.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/live/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/live/test_smoke.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/auth/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/auth/test_credentials.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/auth/test_stores.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_auth.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_client.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_config.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_custom_field.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_entry.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_project.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_tag.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_task.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_user.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/commands/test_workspace.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/config/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/config/test_settings.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/config/test_store.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/output/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/output/test_formats.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/runtime/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/runtime/test_context.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/runtime/test_errors.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/runtime/test_prompts.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/scripts/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/scripts/test_coverage_report.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/services/__init__.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/services/test_listing.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/services/test_resolve.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/services/test_timer.py +0 -0
- {clockify_unofficial_cli-0.2.0 → clockify_unofficial_cli-1.0.0}/tests/unit/test_main.py +0 -0
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clockify-cli-skills",
|
|
3
|
+
"description": "Clockify time-tracking and workspace skills for Claude Code",
|
|
4
|
+
"owner": {
|
|
5
|
+
"name": "G.A.JAGUAR",
|
|
6
|
+
"email": "dev@gajaguar.com"
|
|
7
|
+
},
|
|
8
|
+
"plugins": [
|
|
9
|
+
{
|
|
10
|
+
"name": "clockify-cli",
|
|
11
|
+
"source": "./",
|
|
12
|
+
"description": "Skills that teach an agent to drive the unofficial Clockify CLI"
|
|
13
|
+
}
|
|
14
|
+
]
|
|
15
|
+
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "clockify-cli",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "Clockify time-tracking and workspace skills for Claude Code",
|
|
5
|
+
"author": {
|
|
6
|
+
"name": "G.A.JAGUAR",
|
|
7
|
+
"email": "dev@gajaguar.com"
|
|
8
|
+
},
|
|
9
|
+
"license": "MIT",
|
|
10
|
+
"repository": "https://github.com/gajaguar/clockify-cli",
|
|
11
|
+
"keywords": [
|
|
12
|
+
"clockify",
|
|
13
|
+
"time-tracking",
|
|
14
|
+
"timesheet",
|
|
15
|
+
"cli",
|
|
16
|
+
"skill"
|
|
17
|
+
]
|
|
18
|
+
}
|
|
@@ -0,0 +1,117 @@
|
|
|
1
|
+
# AGENTS.md
|
|
2
|
+
|
|
3
|
+
The key words "MUST", "MUST NOT", "REQUIRED", "SHALL", "SHALL NOT", "SHOULD",
|
|
4
|
+
"SHOULD NOT", "RECOMMENDED", "MAY", and "OPTIONAL" in this document are to be
|
|
5
|
+
interpreted as described in [RFC 2119](https://www.ietf.org/rfc/rfc2119.txt).
|
|
6
|
+
|
|
7
|
+
## Agent instructions
|
|
8
|
+
|
|
9
|
+
- `AGENTS.md` is the only agent instructions file; put project rules here. The
|
|
10
|
+
repository MUST NOT contain a `CLAUDE.md` or any other tool-specific copy,
|
|
11
|
+
because a second copy drifts from this one.
|
|
12
|
+
|
|
13
|
+
## Command surface
|
|
14
|
+
|
|
15
|
+
- Run the `Makefile` targets (`make check`, `make fix`, `make test`, ...)
|
|
16
|
+
instead of the underlying tools, so the agent and CI use the same options.
|
|
17
|
+
`make help` lists them.
|
|
18
|
+
- Give every new target a `##` help line; `make help` prints it.
|
|
19
|
+
- `make test` skips the `live` tests, which call the real Clockify API; run
|
|
20
|
+
them only with `CLOCKIFY_TEST_API_KEY` set.
|
|
21
|
+
|
|
22
|
+
## Gate
|
|
23
|
+
|
|
24
|
+
- `make check` and `make test` MUST pass before any commit.
|
|
25
|
+
- Run `make fix` first for findings it can repair, then edit by hand.
|
|
26
|
+
|
|
27
|
+
## Commits and branches
|
|
28
|
+
|
|
29
|
+
- Write commit messages as
|
|
30
|
+
[Conventional Commits](https://www.conventionalcommits.org/) and branch
|
|
31
|
+
names as [Conventional Branch](https://conventionalbranch.org/)
|
|
32
|
+
(`<type>/<description>`, e.g. `feat/add-login`). A pre-commit hook and
|
|
33
|
+
`make commits-check` enforce both; see
|
|
34
|
+
[`docs/conventions/commits-check.md`](docs/conventions/commits-check.md).
|
|
35
|
+
- Name a documentation or dependency branch `chore/...`: a branch type is not
|
|
36
|
+
a commit type, and `docs/` is not one.
|
|
37
|
+
|
|
38
|
+
## Pull requests
|
|
39
|
+
|
|
40
|
+
Once a pull request is open, the agent MUST:
|
|
41
|
+
|
|
42
|
+
1. Wait for CI; while it fails, fix the cause, push to the same branch and
|
|
43
|
+
wait again until it passes.
|
|
44
|
+
2. Squash-merge a pull request with exactly one commit and use a regular merge
|
|
45
|
+
commit otherwise (`gh pr view --json commits` gives the count).
|
|
46
|
+
3. Delete the branch on the remote and locally.
|
|
47
|
+
4. Switch back to the base branch, pull it and run `git fetch --prune`.
|
|
48
|
+
|
|
49
|
+
## Documentation
|
|
50
|
+
|
|
51
|
+
- Write documentation as an OKF bundle of atomic notes under `docs/`: one
|
|
52
|
+
Markdown concept per file, with YAML frontmatter (`type`, `title`,
|
|
53
|
+
`description`).
|
|
54
|
+
- Add a new note to its directory's `index.md` and, by file name, to
|
|
55
|
+
[`docs/log.md`](docs/log.md).
|
|
56
|
+
- Write a note only when it explains something a reader cannot already get
|
|
57
|
+
from `make help`, a linter's own message, or the configuration it comes
|
|
58
|
+
from.
|
|
59
|
+
|
|
60
|
+
## Dependencies
|
|
61
|
+
|
|
62
|
+
- Add a new tool to the ecosystem manager that owns it; use `mise.toml` only
|
|
63
|
+
for a tool that bootstraps an ecosystem or has no manager in this
|
|
64
|
+
repository. See
|
|
65
|
+
[`docs/toolchain/layering-rule.md`](docs/toolchain/layering-rule.md).
|
|
66
|
+
|
|
67
|
+
## Python
|
|
68
|
+
|
|
69
|
+
- Write no docstrings on functions, methods or classes; add a comment only
|
|
70
|
+
where the *why* is not obvious from the code. `pylint-gajaguar`'s
|
|
71
|
+
`gajaguar-no-docstrings` fails `make check` on any docstring.
|
|
72
|
+
- Enable the plugin with `enable = ["gajaguar"]` in `pyproject.toml`'s
|
|
73
|
+
`[tool.pylint."messages control"]`, not with a list of rules, so a rule
|
|
74
|
+
added by a `pylint-gajaguar` upgrade runs without a config change.
|
|
75
|
+
- Keep `pyproject.toml` to settings that differ from the tool's default, and
|
|
76
|
+
keep a `lint.per-file-ignores` entry only while it matches a current
|
|
77
|
+
violation; see
|
|
78
|
+
[`docs/python/pyproject-defaults.md`](docs/python/pyproject-defaults.md).
|
|
79
|
+
|
|
80
|
+
## Architecture
|
|
81
|
+
|
|
82
|
+
- Read [Layering](docs/ARCHITECTURE.md#layering) before adding code:
|
|
83
|
+
dependencies point downward only, from `commands/` through `services/` and
|
|
84
|
+
`runtime/` to the SDK.
|
|
85
|
+
- Read [Authentication](docs/ARCHITECTURE.md#authentication) before changing
|
|
86
|
+
`auth/`: Clockify offers header API keys only, no OAuth2.
|
|
87
|
+
- Add a command by following
|
|
88
|
+
[Adding a command](docs/ARCHITECTURE.md#adding-a-command), which ends with
|
|
89
|
+
`make check`, `make test` and `make coverage-report`.
|
|
90
|
+
|
|
91
|
+
## Clockify rules
|
|
92
|
+
|
|
93
|
+
- Reach Clockify only through `clockify-unofficial-sdk`, never with `httpx`
|
|
94
|
+
or a hand-built URL, so retries, pagination and models stay in one place.
|
|
95
|
+
Add a missing capability to the SDK first and raise the SDK floor in
|
|
96
|
+
`pyproject.toml` once it is released; see
|
|
97
|
+
[Cross-repo workflow](docs/ROADMAP.md#cross-repo-workflow).
|
|
98
|
+
- Expose each command module as `APP: Final = typer.Typer(...)`, decorate
|
|
99
|
+
every command with `@APP.command(help=...)` then `@handle_errors`, and
|
|
100
|
+
declare options inline as `Annotated[..., typer.Option(...)]`, because
|
|
101
|
+
Typer cannot resolve PEP 695 `type` aliases.
|
|
102
|
+
- Put logic beyond a single SDK call in `services/`.
|
|
103
|
+
- Render data only through `AppContext.render`, and write diagnostics to
|
|
104
|
+
stderr through `AppContext.notify` or `CliError`, so `-o json` output
|
|
105
|
+
stays parseable.
|
|
106
|
+
- Report a new failure with an existing `ExitCode`
|
|
107
|
+
(`runtime/exit_codes.py`). Its values are a public contract: add a value,
|
|
108
|
+
never renumber one.
|
|
109
|
+
- Read API keys from the credential stores only. A key MUST NOT be accepted
|
|
110
|
+
as a command-line argument or printed outside `auth token`, because shell
|
|
111
|
+
history and logs would keep it.
|
|
112
|
+
- Replace a long argument list with a Parameter Object (a frozen,
|
|
113
|
+
`slots=True` dataclass), as the SDK does, instead of silencing
|
|
114
|
+
`too-many-arguments`; see
|
|
115
|
+
[Typer parameter objects](docs/python/typer-parameter-objects.md).
|
|
116
|
+
- Update [`docs/coverage.md`](docs/coverage.md) with every endpoint change;
|
|
117
|
+
`make coverage-report` checks it against the OpenAPI document.
|
|
@@ -68,7 +68,7 @@ spell: ## Spell-check files with cspell — accepts FILES="..."
|
|
|
68
68
|
|
|
69
69
|
commits-check: ## Validate the commit range and branch name against Conventional Commits/Branch — see docs/conventions/commits-check.md
|
|
70
70
|
@git log --no-merges --format='%B%x00' $(BASE)..HEAD | while IFS= read -r -d '' message; do \
|
|
71
|
-
message="$${message#$$'\n'}";
|
|
71
|
+
message="$${message#$$'\n'}"; \
|
|
72
72
|
echo "$$message" | $(CONVENTIONAL_GIT) check commit || exit 1; \
|
|
73
73
|
done
|
|
74
74
|
@$(CONVENTIONAL_GIT) check branch --name "$(BRANCH)"
|
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
Metadata-Version: 2.5
|
|
2
2
|
Name: clockify-unofficial-cli
|
|
3
|
-
Version: 0.
|
|
3
|
+
Version: 1.0.0
|
|
4
4
|
Summary: Unofficial command-line interface for Clockify, built on clockify-unofficial-sdk.
|
|
5
5
|
Author-email: "G.A.JAGUAR" <dev@gajaguar.com>
|
|
6
6
|
License-Expression: MIT
|
|
7
7
|
License-File: LICENSE
|
|
8
8
|
Keywords: cli,clockify,time-tracking
|
|
9
|
-
Classifier: Development Status ::
|
|
9
|
+
Classifier: Development Status :: 5 - Production/Stable
|
|
10
10
|
Classifier: Environment :: Console
|
|
11
11
|
Classifier: License :: OSI Approved :: MIT License
|
|
12
12
|
Classifier: Programming Language :: Python :: 3.14
|
|
13
13
|
Classifier: Typing :: Typed
|
|
14
14
|
Requires-Python: >=3.14
|
|
15
|
-
Requires-Dist: clockify-unofficial-sdk<2,>=1.
|
|
15
|
+
Requires-Dist: clockify-unofficial-sdk<2,>=1.10
|
|
16
16
|
Requires-Dist: keyring>=25.0
|
|
17
17
|
Requires-Dist: platformdirs>=4.0
|
|
18
18
|
Requires-Dist: pydantic>=2.9
|
|
@@ -42,6 +42,7 @@ Unofficial command-line interface for Clockify, built on
|
|
|
42
42
|
- [Getting started](#getting-started)
|
|
43
43
|
- [Prerequisites](#prerequisites)
|
|
44
44
|
- [Installation](#installation)
|
|
45
|
+
- [Agent skills](#agent-skills)
|
|
45
46
|
- [Usage](#usage)
|
|
46
47
|
- [Commands](#commands)
|
|
47
48
|
- [Global options](#global-options)
|
|
@@ -62,8 +63,9 @@ handles profiles, credential storage, name-to-ID resolution, output formatting,
|
|
|
62
63
|
and stable exit codes. The SDK owns HTTP, retries, pagination, typed models,
|
|
63
64
|
and API error mapping.
|
|
64
65
|
|
|
65
|
-
|
|
66
|
-
|
|
66
|
+
`1.0.0` covers every Clockify operation that the SDK offers: 105 of the 166
|
|
67
|
+
non-deprecated operations. The authoritative endpoint mapping is in
|
|
68
|
+
[`docs/coverage.md`](docs/coverage.md), and what comes next is in
|
|
67
69
|
[`docs/ROADMAP.md`](docs/ROADMAP.md).
|
|
68
70
|
|
|
69
71
|
## Key features
|
|
@@ -77,6 +79,8 @@ The CLI is being delivered in phases. The authoritative endpoint mapping is in
|
|
|
77
79
|
- **Stable automation contract** - Keep data on stdout, diagnostics on stderr,
|
|
78
80
|
and use documented exit codes.
|
|
79
81
|
- **CI support** - Use `CLOCKIFY_API_KEY` without writing a credential to disk.
|
|
82
|
+
- **Broad coverage** - Manage time entries, projects, reports, time off,
|
|
83
|
+
approvals, expenses, invoices and webhooks from the terminal.
|
|
80
84
|
- **Typed API foundation** - Build on the SDK's typed models, retries, and
|
|
81
85
|
pagination instead of making HTTP requests in the CLI.
|
|
82
86
|
|
|
@@ -135,6 +139,34 @@ make install
|
|
|
135
139
|
`make install` installs the pinned toolchain, Python dependencies, Node-based
|
|
136
140
|
documentation tools, and the pre-commit hook.
|
|
137
141
|
|
|
142
|
+
## Agent skills
|
|
143
|
+
|
|
144
|
+
The repository is also a Claude Code plugin. Pick one channel:
|
|
145
|
+
|
|
146
|
+
```text
|
|
147
|
+
# Claude Code
|
|
148
|
+
/plugin marketplace add gajaguar/clockify-cli
|
|
149
|
+
/plugin install clockify-cli@clockify-cli-skills
|
|
150
|
+
```
|
|
151
|
+
|
|
152
|
+
```bash
|
|
153
|
+
# Any Agent Skills-compatible agent
|
|
154
|
+
npx skills add gajaguar/clockify-cli
|
|
155
|
+
|
|
156
|
+
# opencode
|
|
157
|
+
npx skills add gajaguar/clockify-cli -a opencode -y
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
- `clockify-time-tracking` covers `start`, `stop`, `status`, `log` and `entry`.
|
|
161
|
+
- `clockify-cli` covers authentication, profiles, workspaces and the
|
|
162
|
+
project, task, tag, client, group, custom-field, webhook, report, time-off,
|
|
163
|
+
approval, expense and invoice commands.
|
|
164
|
+
|
|
165
|
+
The skills follow the [Agent Skills](https://agentskills.io/specification)
|
|
166
|
+
format, so other agents can read `skills/` directly. They require the CLI to
|
|
167
|
+
be installed and a user to have run `clockify auth login`. See
|
|
168
|
+
[`docs/agents/`](docs/agents/index.md) for what each install channel does.
|
|
169
|
+
|
|
138
170
|
## Usage
|
|
139
171
|
|
|
140
172
|
Log in interactively. The key is read from a hidden prompt, validated with
|
|
@@ -165,9 +197,21 @@ printf '%s' "$KEY" | clockify -p work auth login --with-token --region EU_CENTRA
|
|
|
165
197
|
- `config list` lists configured profiles.
|
|
166
198
|
- `config use NAME` sets the default profile.
|
|
167
199
|
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
200
|
+
- `start`, `stop`, `status` and `log` track time.
|
|
201
|
+
- `workspace`, `user`, `client`, `project`, `task`, `tag`, `group`,
|
|
202
|
+
`custom-field`, `entry` and `webhook` manage resources.
|
|
203
|
+
- `report` and `shared-report` summarize tracked time.
|
|
204
|
+
- `time-off` and `approval` handle leave and timesheet approvals.
|
|
205
|
+
- `expense` and `invoice` handle expenses, receipts, invoices and payments.
|
|
206
|
+
|
|
207
|
+
Run `clockify COMMAND --help` for the verbs of each group. Clockify operations
|
|
208
|
+
that the SDK does not offer yet are listed as `planned` in the
|
|
209
|
+
[coverage matrix](docs/coverage.md) and in the [roadmap](docs/ROADMAP.md).
|
|
210
|
+
|
|
211
|
+
Commands that download a file (`expense receipt`, `invoice export`) write it to
|
|
212
|
+
`--save PATH`, or to a pipe with `--save -`, and never through `-o`. Amounts are
|
|
213
|
+
typed in major units (`120.50`) and shown in Clockify's minor units. Time off,
|
|
214
|
+
approvals, expenses and invoices need a paid Clockify plan.
|
|
171
215
|
|
|
172
216
|
### Global options
|
|
173
217
|
|
|
@@ -238,28 +282,27 @@ Live tests are excluded from `make test`. They require
|
|
|
238
282
|
|
|
239
283
|
- Headless Linux hosts may not provide a Secret Service keyring backend. Use
|
|
240
284
|
`CLOCKIFY_API_KEY` or the explicit `--insecure-storage` fallback.
|
|
241
|
-
- The SDK is a `uv` git dependency pinned to a tag, so installation requires
|
|
242
|
-
access to GitHub.
|
|
243
285
|
- Python 3.14 is the minimum version required by the CLI and SDK.
|
|
244
286
|
|
|
245
287
|
## Roadmap
|
|
246
288
|
|
|
247
|
-
- [x]
|
|
248
|
-
- [x]
|
|
249
|
-
- [
|
|
250
|
-
- [
|
|
251
|
-
- [
|
|
252
|
-
- [
|
|
253
|
-
- [
|
|
254
|
-
- [ ]
|
|
289
|
+
- [x] `0.2.0`: foundation, workspaces, users, projects, tasks, tags, entries
|
|
290
|
+
- [x] `0.3.0`: group membership and webhooks
|
|
291
|
+
- [x] `0.4.0`: reports
|
|
292
|
+
- [x] `0.5.0`: time off and approvals
|
|
293
|
+
- [x] `0.6.0`: expenses and receipts
|
|
294
|
+
- [x] `0.7.0`: invoices
|
|
295
|
+
- [x] `1.0.0`: exit codes, JSON output and command grammar are a stable contract
|
|
296
|
+
- [ ] `1.x`: the 61 operations the SDK does not offer yet, as it adds them
|
|
255
297
|
|
|
256
|
-
See [`docs/ROADMAP.md`](docs/ROADMAP.md) for operation counts
|
|
257
|
-
|
|
298
|
+
See [`docs/ROADMAP.md`](docs/ROADMAP.md) for operation counts and what is
|
|
299
|
+
frozen from `1.0.0`.
|
|
258
300
|
|
|
259
301
|
## Open items
|
|
260
302
|
|
|
261
303
|
- Only the `GLOBAL` region host is verified against a live account.
|
|
262
|
-
-
|
|
304
|
+
- Time off, approvals, expenses and invoices are tested against mocked
|
|
305
|
+
responses; they have not been run against a paid workspace.
|
|
263
306
|
- Clockify's per-plan rate limits are not fully documented upstream.
|
|
264
307
|
|
|
265
308
|
## Contributing
|
|
@@ -19,6 +19,7 @@ Unofficial command-line interface for Clockify, built on
|
|
|
19
19
|
- [Getting started](#getting-started)
|
|
20
20
|
- [Prerequisites](#prerequisites)
|
|
21
21
|
- [Installation](#installation)
|
|
22
|
+
- [Agent skills](#agent-skills)
|
|
22
23
|
- [Usage](#usage)
|
|
23
24
|
- [Commands](#commands)
|
|
24
25
|
- [Global options](#global-options)
|
|
@@ -39,8 +40,9 @@ handles profiles, credential storage, name-to-ID resolution, output formatting,
|
|
|
39
40
|
and stable exit codes. The SDK owns HTTP, retries, pagination, typed models,
|
|
40
41
|
and API error mapping.
|
|
41
42
|
|
|
42
|
-
|
|
43
|
-
|
|
43
|
+
`1.0.0` covers every Clockify operation that the SDK offers: 105 of the 166
|
|
44
|
+
non-deprecated operations. The authoritative endpoint mapping is in
|
|
45
|
+
[`docs/coverage.md`](docs/coverage.md), and what comes next is in
|
|
44
46
|
[`docs/ROADMAP.md`](docs/ROADMAP.md).
|
|
45
47
|
|
|
46
48
|
## Key features
|
|
@@ -54,6 +56,8 @@ The CLI is being delivered in phases. The authoritative endpoint mapping is in
|
|
|
54
56
|
- **Stable automation contract** - Keep data on stdout, diagnostics on stderr,
|
|
55
57
|
and use documented exit codes.
|
|
56
58
|
- **CI support** - Use `CLOCKIFY_API_KEY` without writing a credential to disk.
|
|
59
|
+
- **Broad coverage** - Manage time entries, projects, reports, time off,
|
|
60
|
+
approvals, expenses, invoices and webhooks from the terminal.
|
|
57
61
|
- **Typed API foundation** - Build on the SDK's typed models, retries, and
|
|
58
62
|
pagination instead of making HTTP requests in the CLI.
|
|
59
63
|
|
|
@@ -112,6 +116,34 @@ make install
|
|
|
112
116
|
`make install` installs the pinned toolchain, Python dependencies, Node-based
|
|
113
117
|
documentation tools, and the pre-commit hook.
|
|
114
118
|
|
|
119
|
+
## Agent skills
|
|
120
|
+
|
|
121
|
+
The repository is also a Claude Code plugin. Pick one channel:
|
|
122
|
+
|
|
123
|
+
```text
|
|
124
|
+
# Claude Code
|
|
125
|
+
/plugin marketplace add gajaguar/clockify-cli
|
|
126
|
+
/plugin install clockify-cli@clockify-cli-skills
|
|
127
|
+
```
|
|
128
|
+
|
|
129
|
+
```bash
|
|
130
|
+
# Any Agent Skills-compatible agent
|
|
131
|
+
npx skills add gajaguar/clockify-cli
|
|
132
|
+
|
|
133
|
+
# opencode
|
|
134
|
+
npx skills add gajaguar/clockify-cli -a opencode -y
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
- `clockify-time-tracking` covers `start`, `stop`, `status`, `log` and `entry`.
|
|
138
|
+
- `clockify-cli` covers authentication, profiles, workspaces and the
|
|
139
|
+
project, task, tag, client, group, custom-field, webhook, report, time-off,
|
|
140
|
+
approval, expense and invoice commands.
|
|
141
|
+
|
|
142
|
+
The skills follow the [Agent Skills](https://agentskills.io/specification)
|
|
143
|
+
format, so other agents can read `skills/` directly. They require the CLI to
|
|
144
|
+
be installed and a user to have run `clockify auth login`. See
|
|
145
|
+
[`docs/agents/`](docs/agents/index.md) for what each install channel does.
|
|
146
|
+
|
|
115
147
|
## Usage
|
|
116
148
|
|
|
117
149
|
Log in interactively. The key is read from a hidden prompt, validated with
|
|
@@ -142,9 +174,21 @@ printf '%s' "$KEY" | clockify -p work auth login --with-token --region EU_CENTRA
|
|
|
142
174
|
- `config list` lists configured profiles.
|
|
143
175
|
- `config use NAME` sets the default profile.
|
|
144
176
|
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
177
|
+
- `start`, `stop`, `status` and `log` track time.
|
|
178
|
+
- `workspace`, `user`, `client`, `project`, `task`, `tag`, `group`,
|
|
179
|
+
`custom-field`, `entry` and `webhook` manage resources.
|
|
180
|
+
- `report` and `shared-report` summarize tracked time.
|
|
181
|
+
- `time-off` and `approval` handle leave and timesheet approvals.
|
|
182
|
+
- `expense` and `invoice` handle expenses, receipts, invoices and payments.
|
|
183
|
+
|
|
184
|
+
Run `clockify COMMAND --help` for the verbs of each group. Clockify operations
|
|
185
|
+
that the SDK does not offer yet are listed as `planned` in the
|
|
186
|
+
[coverage matrix](docs/coverage.md) and in the [roadmap](docs/ROADMAP.md).
|
|
187
|
+
|
|
188
|
+
Commands that download a file (`expense receipt`, `invoice export`) write it to
|
|
189
|
+
`--save PATH`, or to a pipe with `--save -`, and never through `-o`. Amounts are
|
|
190
|
+
typed in major units (`120.50`) and shown in Clockify's minor units. Time off,
|
|
191
|
+
approvals, expenses and invoices need a paid Clockify plan.
|
|
148
192
|
|
|
149
193
|
### Global options
|
|
150
194
|
|
|
@@ -215,28 +259,27 @@ Live tests are excluded from `make test`. They require
|
|
|
215
259
|
|
|
216
260
|
- Headless Linux hosts may not provide a Secret Service keyring backend. Use
|
|
217
261
|
`CLOCKIFY_API_KEY` or the explicit `--insecure-storage` fallback.
|
|
218
|
-
- The SDK is a `uv` git dependency pinned to a tag, so installation requires
|
|
219
|
-
access to GitHub.
|
|
220
262
|
- Python 3.14 is the minimum version required by the CLI and SDK.
|
|
221
263
|
|
|
222
264
|
## Roadmap
|
|
223
265
|
|
|
224
|
-
- [x]
|
|
225
|
-
- [x]
|
|
226
|
-
- [
|
|
227
|
-
- [
|
|
228
|
-
- [
|
|
229
|
-
- [
|
|
230
|
-
- [
|
|
231
|
-
- [ ]
|
|
266
|
+
- [x] `0.2.0`: foundation, workspaces, users, projects, tasks, tags, entries
|
|
267
|
+
- [x] `0.3.0`: group membership and webhooks
|
|
268
|
+
- [x] `0.4.0`: reports
|
|
269
|
+
- [x] `0.5.0`: time off and approvals
|
|
270
|
+
- [x] `0.6.0`: expenses and receipts
|
|
271
|
+
- [x] `0.7.0`: invoices
|
|
272
|
+
- [x] `1.0.0`: exit codes, JSON output and command grammar are a stable contract
|
|
273
|
+
- [ ] `1.x`: the 61 operations the SDK does not offer yet, as it adds them
|
|
232
274
|
|
|
233
|
-
See [`docs/ROADMAP.md`](docs/ROADMAP.md) for operation counts
|
|
234
|
-
|
|
275
|
+
See [`docs/ROADMAP.md`](docs/ROADMAP.md) for operation counts and what is
|
|
276
|
+
frozen from `1.0.0`.
|
|
235
277
|
|
|
236
278
|
## Open items
|
|
237
279
|
|
|
238
280
|
- Only the `GLOBAL` region host is verified against a live account.
|
|
239
|
-
-
|
|
281
|
+
- Time off, approvals, expenses and invoices are tested against mocked
|
|
282
|
+
responses; they have not been run against a paid workspace.
|
|
240
283
|
- Clockify's per-plan rate limits are not fully documented upstream.
|
|
241
284
|
|
|
242
285
|
## Contributing
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
---
|
|
2
|
-
type:
|
|
2
|
+
type: reference
|
|
3
3
|
title: Architecture and technical specification
|
|
4
4
|
description: The stack, layering, design patterns, authentication and extension checklist of clockify-cli.
|
|
5
5
|
tags: [architecture]
|
|
@@ -10,7 +10,7 @@ status: stable
|
|
|
10
10
|
|
|
11
11
|
This document is the technical specification for `clockify-cli`: what it is,
|
|
12
12
|
which stack and design patterns it uses and why, how authentication works,
|
|
13
|
-
and how to extend it. The
|
|
13
|
+
and how to extend it. The release history and what comes next live in
|
|
14
14
|
[`ROADMAP.md`](ROADMAP.md); the per-endpoint status lives in
|
|
15
15
|
[`coverage.md`](coverage.md).
|
|
16
16
|
|
|
@@ -26,6 +26,7 @@ and how to extend it. The phased delivery plan lives in
|
|
|
26
26
|
- [Configuration](#configuration)
|
|
27
27
|
- [Errors and exit codes](#errors-and-exit-codes)
|
|
28
28
|
- [Testing strategy](#testing-strategy)
|
|
29
|
+
- [Stability](#stability)
|
|
29
30
|
- [Adding a command](#adding-a-command)
|
|
30
31
|
- [Security considerations](#security-considerations)
|
|
31
32
|
- [Open items](#open-items)
|
|
@@ -41,9 +42,9 @@ stable exit codes.
|
|
|
41
42
|
|
|
42
43
|
Goals:
|
|
43
44
|
|
|
44
|
-
- Expose **every non-deprecated Clockify API operation
|
|
45
|
-
|
|
46
|
-
[`coverage.md`](coverage.md).
|
|
45
|
+
- Expose **every non-deprecated Clockify API operation that the SDK offers**
|
|
46
|
+
as a typed CLI command: 105 of the 166 at `1.0.0`, tracked row by row in
|
|
47
|
+
[`coverage.md`](coverage.md). The rest wait for the SDK.
|
|
47
48
|
- Be pleasant interactively (tables, prompts, names instead of IDs) **and**
|
|
48
49
|
predictable in scripts (JSON/CSV/ID output, stderr for diagnostics, stable
|
|
49
50
|
exit codes).
|
|
@@ -214,7 +215,7 @@ clockify-cli/
|
|
|
214
215
|
├── Makefile, mk/*.mk # check/fix/test surface; mk/clockify.mk
|
|
215
216
|
├── docs/
|
|
216
217
|
│ ├── ARCHITECTURE.md # this document
|
|
217
|
-
│ ├── ROADMAP.md #
|
|
218
|
+
│ ├── ROADMAP.md # releases to 1.0.0 and what comes next
|
|
218
219
|
│ └── coverage.md # endpoint → SDK method → CLI command
|
|
219
220
|
├── scripts/
|
|
220
221
|
│ └── coverage_report.py # checks coverage.md against openapi.json
|
|
@@ -242,32 +243,32 @@ clockify-cli/
|
|
|
242
243
|
│ │ ├── formats.py # table/json/jsonl/csv/id renderers
|
|
243
244
|
│ │ ├── registry.py # OutputFormat → renderer factory
|
|
244
245
|
│ │ └── columns.py # per-resource column specs
|
|
245
|
-
│ ├── services/ #
|
|
246
|
-
│
|
|
247
|
-
│
|
|
248
|
-
│
|
|
246
|
+
│ ├── services/ # resolve, parsing, listing, timer, reports, time_off,
|
|
247
|
+
│ │ # expenses, invoices, files, money
|
|
248
|
+
│ └── commands/ # one module per Clockify area (auth, project, entry,
|
|
249
|
+
│ # report, time_off, expense, invoice, webhook, ...)
|
|
249
250
|
└── tests/
|
|
250
251
|
├── conftest.py # in-memory keyring, fake client, runner
|
|
251
252
|
├── unit/ # mirrors src/ layout, offline
|
|
252
253
|
└── live/ # -m live, needs CLOCKIFY_TEST_API_KEY
|
|
253
254
|
```
|
|
254
255
|
|
|
255
|
-
|
|
256
|
+
New areas follow the same shape: one `commands/<domain>.py` per row group in
|
|
257
|
+
[`coverage.md`](coverage.md), with its logic in `services/`. Notable services:
|
|
256
258
|
|
|
257
|
-
- `
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
- `services/
|
|
261
|
-
- `services/
|
|
262
|
-
`yesterday 09:00`).
|
|
259
|
+
- `services/resolve.py` resolves a name or ID for every resource;
|
|
260
|
+
- `services/parsing.py` reads durations like `1h30m` and instants like
|
|
261
|
+
`yesterday 09:00`;
|
|
262
|
+
- `services/files.py` writes binary downloads (receipts, exported invoices);
|
|
263
|
+
- `services/money.py` converts typed amounts to Clockify's minor units.
|
|
263
264
|
|
|
264
265
|
## Command surface
|
|
265
266
|
|
|
266
267
|
- **Grammar.** `clockify [GLOBAL OPTIONS] <noun> <verb> [ARGS]`. The standard
|
|
267
268
|
verbs are `list`, `get`, `create`, `update` and `delete`; domain verbs such
|
|
268
269
|
as `archive`, `approve` or `export` are added where the API has them.
|
|
269
|
-
Time tracking also gets root shortcuts: `start`, `stop`, `status
|
|
270
|
-
|
|
270
|
+
Time tracking also gets root shortcuts: `start`, `stop`, `status` and
|
|
271
|
+
`log`.
|
|
271
272
|
- **Global options** must come before the noun:
|
|
272
273
|
|
|
273
274
|
| Option | Env var | Default |
|
|
@@ -278,7 +279,7 @@ Planned additions follow the same shape:
|
|
|
278
279
|
| `--version` | | |
|
|
279
280
|
| `--install-completion` | | Added by Typer |
|
|
280
281
|
|
|
281
|
-
- **Names or IDs.**
|
|
282
|
+
- **Names or IDs.** Arguments such as `PROJECT` or `TAG`
|
|
282
283
|
accept either an ID or an exact, case-insensitive name. An ambiguous name
|
|
283
284
|
is a usage error that lists the candidates.
|
|
284
285
|
- **Output contract.**
|
|
@@ -286,7 +287,11 @@ Planned additions follow the same shape:
|
|
|
286
287
|
to stderr.
|
|
287
288
|
- `json` keeps Clockify's camelCase field names so it matches the API docs.
|
|
288
289
|
- `id` prints one identifier per line for `xargs`.
|
|
289
|
-
-
|
|
290
|
+
- A command that downloads a file (`expense receipt`, `invoice export`)
|
|
291
|
+
writes the bytes to `--save PATH`, or to a pipe with `--save -`, and never
|
|
292
|
+
through `-o`.
|
|
293
|
+
- Report commands print a totals line to stderr, so stdout holds only rows.
|
|
294
|
+
- **Destructive commands** (`delete`, bulk operations): ask
|
|
290
295
|
for confirmation on a TTY and require `--yes` otherwise.
|
|
291
296
|
|
|
292
297
|
## Configuration
|
|
@@ -348,16 +353,34 @@ by `hint: <next step>`.
|
|
|
348
353
|
`CLOCKIFY_TEST_API_KEY` is set. The default run excludes them.
|
|
349
354
|
- **Gates.**
|
|
350
355
|
- `make check`: ruff `ALL`, mypy strict, Pyright, pylint with
|
|
351
|
-
`pylint-
|
|
356
|
+
`pylint-gajaguar`, markdownlint and cspell.
|
|
352
357
|
- `make test`: 90% coverage floor.
|
|
353
358
|
- `make coverage-report`: keeps `coverage.md` in sync with the upstream
|
|
354
359
|
spec.
|
|
355
360
|
|
|
361
|
+
## Stability
|
|
362
|
+
|
|
363
|
+
From `1.0.0` the following are a public contract, and a breaking change to
|
|
364
|
+
any of them needs a major version:
|
|
365
|
+
|
|
366
|
+
- the exit codes in [Errors and exit codes](#errors-and-exit-codes);
|
|
367
|
+
- the field names and types of `-o json` and `-o jsonl`, which keep Clockify's
|
|
368
|
+
camelCase names (adding a field is not breaking);
|
|
369
|
+
- what goes to stdout (rendered data) and what goes to stderr (prompts, totals,
|
|
370
|
+
progress and errors);
|
|
371
|
+
- the `clockify <noun> <verb>` grammar, the commands and options listed as
|
|
372
|
+
`done` in [`coverage.md`](coverage.md), the global options, the environment
|
|
373
|
+
variables and the keys of `config.toml`.
|
|
374
|
+
|
|
375
|
+
Table layout and column headers, the wording of messages and `--help`, and the
|
|
376
|
+
order of rows Clockify does not sort are not part of the contract. See
|
|
377
|
+
[`ROADMAP.md`](ROADMAP.md#what-100-freezes) for how new operations arrive.
|
|
378
|
+
|
|
356
379
|
## Adding a command
|
|
357
380
|
|
|
358
381
|
1. Confirm the SDK exposes the endpoint (check the SDK's `docs/coverage.md`).
|
|
359
|
-
If not, add it there first and
|
|
360
|
-
`
|
|
382
|
+
If not, add it there first and publish a release. Then raise the SDK
|
|
383
|
+
floor in `pyproject.toml`.
|
|
361
384
|
2. Add or extend `commands/<domain>.py`:
|
|
362
385
|
- Declare options inline with `Annotated[..., typer.Option(...)]`. Typer
|
|
363
386
|
can't resolve PEP 695 `type` aliases.
|
|
@@ -394,5 +417,7 @@ by `hint: <next step>`.
|
|
|
394
417
|
- Clockify's per-plan rate limits aren't documented upstream. The CLI relies
|
|
395
418
|
on the SDK's retry policy and exits with code 8 once retries are
|
|
396
419
|
exhausted.
|
|
397
|
-
-
|
|
398
|
-
|
|
420
|
+
- Time off, approvals, expenses and invoices need paid plans. Their commands
|
|
421
|
+
are tested against mocked responses built from the OpenAPI schemas, and have
|
|
422
|
+
not been run against a real paid workspace.
|
|
423
|
+
- Report totals are not part of `-o json`, because they go to stderr.
|