Skip to content

Commit 7f43ad8

Browse files
committed
Initial release: sb1 CLI for SpareBank 1 (accounts, transactions, transfers)
- BankID OAuth login with loopback callback; token refresh - Secret storage backends: file (default), keychain, 1Password (op) - Accounts, balances, transactions, classified, details, CSV export - Own-account transfers + credit card + pension, confirmation-gated - Norwegian-formatted tables, --json everywhere - Agent skills under skills/, CI + release workflows, install.sh - API terms honoured (no rate-limit circumvention, secrets redacted)
0 parents  commit 7f43ad8

43 files changed

Lines changed: 5574 additions & 0 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

.env.example

Lines changed: 19 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,19 @@
1+
# Bootstrap credentials for `sb1 login`. Copy to `.env` and fill in.
2+
# `.env` is git-ignored. After `sb1 login` persists these to your secret store
3+
# (keychain / file / 1Password), you can delete `.env`.
4+
5+
# From your app at https://developer.sparebank1.no
6+
CLIENT_ID=your-client-id
7+
CLIENT_SECRET=your-client-secret
8+
9+
# Must exactly match the redirect URI registered for your app.
10+
REDIRECT_URL=http://localhost:12345/callback
11+
12+
# Optional: where secrets are stored. One of: file (default) | keychain | op
13+
# SB1_STORE=keychain
14+
15+
# Optional (only for SB1_STORE=op): 1Password vault name (default: Private)
16+
# SB1_OP_VAULT=Private
17+
18+
# Optional (only for SB1_STORE=op): which 1Password account, if you have several
19+
# SB1_OP_ACCOUNT=my.1password.eu

.github/workflows/ci.yml

Lines changed: 27 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,27 @@
1+
name: CI
2+
3+
on:
4+
push:
5+
branches: [main]
6+
pull_request:
7+
8+
env:
9+
CARGO_TERM_COLOR: always
10+
11+
jobs:
12+
check:
13+
runs-on: ubuntu-latest
14+
steps:
15+
- uses: actions/checkout@v4
16+
- uses: dtolnay/rust-toolchain@stable
17+
with:
18+
components: rustfmt, clippy
19+
- uses: Swatinem/rust-cache@v2
20+
- name: Format
21+
run: cargo fmt --check
22+
- name: Clippy
23+
run: cargo clippy --all-targets -- -D warnings
24+
- name: Test
25+
run: cargo test
26+
- name: Build
27+
run: cargo build --release

.github/workflows/release.yml

Lines changed: 70 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,70 @@
1+
name: Release
2+
3+
# Tag a release to trigger: git tag v0.1.0 && git push origin v0.1.0
4+
on:
5+
push:
6+
tags: ["v*"]
7+
8+
permissions:
9+
contents: write
10+
11+
env:
12+
CARGO_TERM_COLOR: always
13+
BINARY: sb1
14+
15+
jobs:
16+
# Build a static-ish binary per platform and attach a tarball to the Release.
17+
binaries:
18+
strategy:
19+
fail-fast: false
20+
matrix:
21+
include:
22+
- target: x86_64-apple-darwin
23+
os: macos-latest
24+
- target: aarch64-apple-darwin
25+
os: macos-latest
26+
- target: x86_64-unknown-linux-gnu
27+
os: ubuntu-latest
28+
- target: aarch64-unknown-linux-gnu
29+
os: ubuntu-latest
30+
runs-on: ${{ matrix.os }}
31+
steps:
32+
- uses: actions/checkout@v4
33+
- uses: dtolnay/rust-toolchain@stable
34+
with:
35+
targets: ${{ matrix.target }}
36+
- uses: Swatinem/rust-cache@v2
37+
- name: Install cross-linker (Linux aarch64)
38+
if: matrix.target == 'aarch64-unknown-linux-gnu'
39+
run: |
40+
sudo apt-get update
41+
sudo apt-get install -y gcc-aarch64-linux-gnu
42+
echo "CARGO_TARGET_AARCH64_UNKNOWN_LINUX_GNU_LINKER=aarch64-linux-gnu-gcc" >> "$GITHUB_ENV"
43+
- name: Build
44+
run: cargo build --release --target ${{ matrix.target }}
45+
- name: Package
46+
run: |
47+
os=$(echo "${{ matrix.target }}" | grep -q apple && echo darwin || echo linux)
48+
arch=$(echo "${{ matrix.target }}" | cut -d- -f1)
49+
tar -C "target/${{ matrix.target }}/release" -czf "${BINARY}-${os}-${arch}.tar.gz" "${BINARY}"
50+
- uses: softprops/action-gh-release@v2
51+
with:
52+
files: "${{ env.BINARY }}-*.tar.gz"
53+
54+
# Publish to crates.io. Requires the CARGO_REGISTRY_TOKEN repo secret.
55+
# Idempotent: skips if this version is already published (so a manual
56+
# `cargo publish` and the tag-triggered run don't collide).
57+
crates:
58+
runs-on: ubuntu-latest
59+
steps:
60+
- uses: actions/checkout@v4
61+
- uses: dtolnay/rust-toolchain@stable
62+
- name: Publish if this version is not already on crates.io
63+
run: |
64+
name=$(cargo metadata --no-deps --format-version 1 | python3 -c "import sys,json;print(json.load(sys.stdin)['packages'][0]['name'])")
65+
version=$(cargo metadata --no-deps --format-version 1 | python3 -c "import sys,json;print(json.load(sys.stdin)['packages'][0]['version'])")
66+
if cargo search "$name" | grep -q "^$name = \"$version\""; then
67+
echo "$name v$version already on crates.io — skipping."
68+
else
69+
cargo publish --token ${{ secrets.CARGO_REGISTRY_TOKEN }}
70+
fi

.gitignore

Lines changed: 13 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,13 @@
1+
# Secrets — NEVER commit. Per SpareBank 1 API terms §4.1/§5.2 credentials are
2+
# personal and must stay confidential.
3+
.env
4+
.env.*
5+
!.env.example
6+
7+
# Rust
8+
/target
9+
**/*.rs.bk
10+
Cargo.lock.orig
11+
12+
# OS
13+
.DS_Store

0 commit comments

Comments
 (0)