yuki-cli 0.1.1__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (43) hide show
  1. yuki_cli-0.1.1/.github/workflows/ci.yml +50 -0
  2. yuki_cli-0.1.1/.github/workflows/release.yml +214 -0
  3. yuki_cli-0.1.1/.gitignore +4 -0
  4. yuki_cli-0.1.1/CLAUDE.md +62 -0
  5. yuki_cli-0.1.1/Cargo.lock +1987 -0
  6. yuki_cli-0.1.1/Cargo.toml +35 -0
  7. yuki_cli-0.1.1/LICENSE +21 -0
  8. yuki_cli-0.1.1/Makefile +75 -0
  9. yuki_cli-0.1.1/PKG-INFO +167 -0
  10. yuki_cli-0.1.1/README.md +146 -0
  11. yuki_cli-0.1.1/pyproject.toml +35 -0
  12. yuki_cli-0.1.1/src/cli/accounts.rs +171 -0
  13. yuki_cli-0.1.1/src/cli/admin.rs +35 -0
  14. yuki_cli-0.1.1/src/cli/check.rs +422 -0
  15. yuki_cli-0.1.1/src/cli/contacts.rs +78 -0
  16. yuki_cli-0.1.1/src/cli/documents.rs +332 -0
  17. yuki_cli-0.1.1/src/cli/init.rs +157 -0
  18. yuki_cli-0.1.1/src/cli/invoices.rs +136 -0
  19. yuki_cli-0.1.1/src/cli/mod.rs +352 -0
  20. yuki_cli-0.1.1/src/cli/projects.rs +79 -0
  21. yuki_cli-0.1.1/src/cli/upload.rs +180 -0
  22. yuki_cli-0.1.1/src/cli/vat.rs +73 -0
  23. yuki_cli-0.1.1/src/client/accounting.rs +612 -0
  24. yuki_cli-0.1.1/src/client/accounting_info.rs +581 -0
  25. yuki_cli-0.1.1/src/client/archive.rs +452 -0
  26. yuki_cli-0.1.1/src/client/contact.rs +164 -0
  27. yuki_cli-0.1.1/src/client/mod.rs +15 -0
  28. yuki_cli-0.1.1/src/client/sales.rs +125 -0
  29. yuki_cli-0.1.1/src/client/soap_client.rs +260 -0
  30. yuki_cli-0.1.1/src/client/vat.rs +236 -0
  31. yuki_cli-0.1.1/src/config.rs +68 -0
  32. yuki_cli-0.1.1/src/error.rs +39 -0
  33. yuki_cli-0.1.1/src/lib.rs +6 -0
  34. yuki_cli-0.1.1/src/main.rs +311 -0
  35. yuki_cli-0.1.1/src/output.rs +56 -0
  36. yuki_cli-0.1.1/src/period.rs +77 -0
  37. yuki_cli-0.1.1/tests/config.rs +192 -0
  38. yuki_cli-0.1.1/tests/error.rs +38 -0
  39. yuki_cli-0.1.1/tests/output.rs +68 -0
  40. yuki_cli-0.1.1/tests/parsers.rs +377 -0
  41. yuki_cli-0.1.1/tests/period.rs +91 -0
  42. yuki_cli-0.1.1/tests/soap_client.rs +132 -0
  43. yuki_cli-0.1.1/yuki_cli_py/__init__.py +10 -0
@@ -0,0 +1,50 @@
1
+ name: CI
2
+
3
+ on:
4
+ push:
5
+ branches: [main]
6
+ pull_request:
7
+ workflow_dispatch:
8
+
9
+ concurrency:
10
+ group: ${{ github.workflow }}-${{ github.ref }}
11
+ cancel-in-progress: true
12
+
13
+ jobs:
14
+ lint:
15
+ name: Lint
16
+ runs-on: ubuntu-latest
17
+ steps:
18
+ - uses: actions/checkout@v4
19
+
20
+ - uses: dtolnay/rust-toolchain@stable
21
+ with:
22
+ components: rustfmt, clippy
23
+
24
+ - name: Run linting
25
+ run: make lint
26
+
27
+ test:
28
+ name: Test
29
+ runs-on: ubuntu-latest
30
+ steps:
31
+ - uses: actions/checkout@v4
32
+
33
+ - uses: dtolnay/rust-toolchain@stable
34
+
35
+ - name: Run tests
36
+ run: make test
37
+
38
+ all-checks-passed:
39
+ name: All checks passed
40
+ runs-on: ubuntu-latest
41
+ needs: [lint, test]
42
+ if: always()
43
+ steps:
44
+ - name: Verify all checks passed
45
+ run: |
46
+ if [ "${{ contains(needs.*.result, 'failure') || contains(needs.*.result, 'cancelled') }}" == "true" ]; then
47
+ echo "Some checks failed or were cancelled"
48
+ exit 1
49
+ fi
50
+ echo "All checks passed"
@@ -0,0 +1,214 @@
1
+ name: Release
2
+
3
+ on:
4
+ push:
5
+ tags:
6
+ - 'v[0-9]*.[0-9]*.[0-9]*'
7
+ workflow_dispatch:
8
+ inputs:
9
+ dry_run:
10
+ description: 'Dry run mode (skip actual publishing)'
11
+ required: false
12
+ default: true
13
+ type: boolean
14
+ skip_crates_io:
15
+ description: 'Skip publishing to crates.io (if already published)'
16
+ required: false
17
+ default: false
18
+ type: boolean
19
+ skip_pypi:
20
+ description: 'Skip publishing to PyPI (if already published)'
21
+ required: false
22
+ default: false
23
+ type: boolean
24
+
25
+ permissions:
26
+ contents: write
27
+
28
+ env:
29
+ GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
30
+
31
+ jobs:
32
+ test:
33
+ name: Run tests
34
+ runs-on: ubuntu-latest
35
+ steps:
36
+ - uses: actions/checkout@v4
37
+
38
+ - uses: dtolnay/rust-toolchain@stable
39
+
40
+ - name: Verify Cargo.lock is up to date
41
+ run: cargo check --locked
42
+
43
+ - name: Run tests
44
+ run: make test
45
+
46
+ build:
47
+ name: Build ${{ matrix.target }}
48
+ needs: test
49
+ strategy:
50
+ matrix:
51
+ include:
52
+ - os: ubuntu-latest
53
+ target: x86_64-unknown-linux-gnu
54
+ - os: ubuntu-latest
55
+ target: aarch64-unknown-linux-gnu
56
+ - os: macos-latest
57
+ target: x86_64-apple-darwin
58
+ - os: macos-latest
59
+ target: aarch64-apple-darwin
60
+ runs-on: ${{ matrix.os }}
61
+ steps:
62
+ - uses: actions/checkout@v4
63
+
64
+ - uses: dtolnay/rust-toolchain@stable
65
+ with:
66
+ targets: ${{ matrix.target }}
67
+
68
+ - name: Install cross-compilation tools
69
+ if: matrix.target == 'aarch64-unknown-linux-gnu'
70
+ run: sudo apt-get update && sudo apt-get install -y gcc-aarch64-linux-gnu
71
+
72
+ - name: Install maturin
73
+ run: pip install maturin
74
+
75
+ - name: Build wheel
76
+ shell: bash
77
+ run: |
78
+ case "${{ matrix.target }}" in
79
+ *-gnu)
80
+ pip install ziglang
81
+ maturin build --release --target ${{ matrix.target }} --compatibility manylinux_2_28 --zig
82
+ ;;
83
+ *)
84
+ maturin build --release --target ${{ matrix.target }}
85
+ ;;
86
+ esac
87
+
88
+ - name: Build binary
89
+ env:
90
+ CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER: aarch64-linux-gnu-gcc
91
+ run: cargo build --release --target ${{ matrix.target }}
92
+
93
+ - name: Verify binary
94
+ if: ${{ !contains(matrix.target, 'aarch64-unknown-linux') }}
95
+ run: ./target/${{ matrix.target }}/release/yuki --help
96
+
97
+ - name: Create release archive
98
+ shell: bash
99
+ run: |
100
+ VERSION=${GITHUB_REF#refs/tags/}
101
+ ARCHIVE_NAME="yuki-cli-${VERSION}-${{ matrix.target }}"
102
+
103
+ mkdir -p release-package
104
+ cp "target/${{ matrix.target }}/release/yuki" release-package/yuki
105
+ tar -czf "${ARCHIVE_NAME}.tar.gz" -C release-package yuki
106
+
107
+ if [[ "${{ runner.os }}" == "macOS" ]]; then
108
+ shasum -a 256 "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
109
+ else
110
+ sha256sum "${ARCHIVE_NAME}.tar.gz" > "${ARCHIVE_NAME}.tar.gz.sha256"
111
+ fi
112
+
113
+ rm -rf release-package
114
+
115
+ - name: Upload wheel
116
+ uses: actions/upload-artifact@v4
117
+ with:
118
+ path: target/wheels/*
119
+ name: wheel-${{ matrix.target }}
120
+
121
+ - name: Upload release archives
122
+ uses: actions/upload-artifact@v4
123
+ with:
124
+ path: |
125
+ yuki-cli-*-${{ matrix.target }}.tar.gz*
126
+ name: release-${{ matrix.target }}
127
+
128
+ sdist:
129
+ name: Build source distribution
130
+ runs-on: ubuntu-latest
131
+ needs: test
132
+ steps:
133
+ - uses: actions/checkout@v4
134
+
135
+ - name: Install maturin
136
+ run: pip install maturin
137
+
138
+ - name: Build sdist
139
+ run: maturin sdist
140
+
141
+ - uses: actions/upload-artifact@v4
142
+ with:
143
+ path: target/wheels/*.tar.gz
144
+ name: sdist
145
+
146
+ release:
147
+ runs-on: ubuntu-latest
148
+ needs: [build, sdist]
149
+ steps:
150
+ - uses: actions/checkout@v4
151
+
152
+ - uses: dtolnay/rust-toolchain@stable
153
+
154
+ - name: Install uv
155
+ uses: astral-sh/setup-uv@v5
156
+ with:
157
+ enable-cache: false
158
+
159
+ - name: Publish to crates.io
160
+ if: ${{ inputs.dry_run != true && inputs.skip_crates_io != true }}
161
+ env:
162
+ CARGO_REGISTRY_TOKEN: ${{ secrets.CARGO_REGISTRY_TOKEN }}
163
+ run: cargo publish --locked
164
+
165
+ - name: Test crates.io publish (dry run)
166
+ if: ${{ inputs.dry_run == true && inputs.skip_crates_io != true }}
167
+ run: |
168
+ echo "DRY RUN: Would publish to crates.io"
169
+ cargo publish --dry-run --locked
170
+
171
+ - name: Skip crates.io publishing
172
+ if: ${{ inputs.skip_crates_io == true }}
173
+ run: echo "Skipping crates.io publishing as requested"
174
+
175
+ - name: Download all artifacts
176
+ uses: actions/download-artifact@v4
177
+ with:
178
+ path: artifacts
179
+
180
+ - name: Publish to PyPI
181
+ if: ${{ inputs.dry_run != true && inputs.skip_pypi != true }}
182
+ env:
183
+ TWINE_USERNAME: __token__
184
+ TWINE_PASSWORD: ${{ secrets.PYPI_API_TOKEN }}
185
+ run: |
186
+ uv tool run twine upload artifacts/wheel-*/*.whl artifacts/sdist/*.tar.gz
187
+
188
+ - name: Test PyPI upload (dry run)
189
+ if: ${{ inputs.dry_run == true && inputs.skip_pypi != true }}
190
+ run: |
191
+ echo "DRY RUN: Would upload to PyPI:"
192
+ find artifacts/wheel-* -name "*.whl" -type f | sort
193
+ find artifacts/sdist -name "*.tar.gz" -type f | sort
194
+ uv tool run twine check artifacts/wheel-*/*.whl artifacts/sdist/*.tar.gz
195
+
196
+ - name: Skip PyPI publishing
197
+ if: ${{ inputs.skip_pypi == true }}
198
+ run: echo "Skipping PyPI publishing as requested"
199
+
200
+ - name: Create Release
201
+ if: ${{ inputs.dry_run != true }}
202
+ uses: softprops/action-gh-release@v2
203
+ with:
204
+ generate_release_notes: true
205
+ files: |
206
+ artifacts/release-*/yuki-cli-*.tar.gz
207
+ artifacts/release-*/yuki-cli-*.tar.gz.sha256
208
+
209
+ - name: Dry Run Summary
210
+ if: ${{ inputs.dry_run == true }}
211
+ run: |
212
+ echo "Dry run complete. Artifacts built but nothing published."
213
+ echo "Archives:"
214
+ find artifacts/release-* -type f | sort
@@ -0,0 +1,4 @@
1
+ /target
2
+ /docs/superpowers/
3
+ .env
4
+ config.toml
@@ -0,0 +1,62 @@
1
+ # CLAUDE.md
2
+
3
+ This file provides guidance to Claude Code (claude.ai/code) when working with code in this repository.
4
+
5
+ ## Build & Test Commands
6
+
7
+ ```sh
8
+ make check # Run clippy (warnings=errors) + fmt check + tests
9
+ make test # cargo test
10
+ make lint # clippy + fmt check
11
+ make fmt # auto-format
12
+ make install # cargo install --path . (installs to ~/.cargo/bin/yuki)
13
+ cargo test <name> # Run a single test by name
14
+ ```
15
+
16
+ After any code change that affects the binary, run `make install` to update `~/.cargo/bin/yuki`.
17
+
18
+ ## Architecture
19
+
20
+ Three-layer design: **CLI** (clap) → **command handlers** → **typed SOAP clients**.
21
+
22
+ ```
23
+ src/main.rs Entry point, clap dispatch, error formatting
24
+ src/cli/mod.rs Cli struct, Commands enum, setup_domain() helper
25
+ src/cli/*.rs Command handlers (one per subcommand group)
26
+ src/client/mod.rs local_name() helper, re-exports
27
+ src/client/soap_client.rs SoapEnvelope builder + SoapClient (HTTP transport, XML parsing)
28
+ src/client/*.rs Service-specific clients wrapping SoapClient
29
+ src/config.rs TOML config (~/.config/yuki/config.toml)
30
+ src/error.rs YukiError enum with exit codes (0/1/2/3/4)
31
+ src/output.rs TTY-aware table (comfy-table) / JSON output
32
+ src/period.rs Period string → (start_date, end_date) conversion
33
+ ```
34
+
35
+ ### SOAP Client Pattern
36
+
37
+ Each service client (accounting, archive, vat, contact, sales) wraps `SoapClient` with a specific base URL (`https://api.yukiworks.nl/ws/{Service}.asmx`). The flow is always: `authenticate()` → `set_current_domain()` → operation calls. The `setup_domain()` helper in `cli/mod.rs` handles the first two steps.
38
+
39
+ `SoapEnvelope` is a builder: `.new("Op").session(sid).param("key", "val").build()` produces the XML envelope. All operations require `administrationID` as a parameter.
40
+
41
+ ### Output Convention
42
+
43
+ Output is TTY-aware: tables for humans, JSON when piped. The `OutputFormat::from_flag()` method handles this. All command handlers follow the same pattern: build `headers` + `rows` vectors, then format with `format_table`/`format_json`.
44
+
45
+ ## Yuki API Quirks
46
+
47
+ These are hard-won lessons from the actual API behavior:
48
+
49
+ - SOAP namespace: `http://www.theyukicompany.com/`
50
+ - SOAP faults return as HTTP 500 — must parse XML body, not just status code
51
+ - `SetCurrentDomain` needs `domainID` (not `administrationID`)
52
+ - Almost all operations need `administrationID` as a parameter
53
+ - Administration `ID` is an XML **attribute**, not a child element
54
+ - VAT operations use exact casing: `VATReturnList`, `ActiveVATCodesList`
55
+ - Real XML field names often differ from what you'd guess (lowercase, different names)
56
+ - `paymentMethod` must be int (`0` = unspecified), not empty string
57
+ - `SearchDocuments`: `folderID=-1, tabID=-1` means "all folders"
58
+ - Amount uses dot decimal separator (`695.74`), not comma
59
+
60
+ ## Config
61
+
62
+ Stored at `~/.config/yuki/config.toml`. Each administration has both a `domain_id` (for `SetCurrentDomain`) and an `admin_id` (for operation parameters) — they are different UUIDs.