kolay-cli 0.1.0__tar.gz

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 Tunc Aucer
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.
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: kolay-cli
3
+ Version: 0.1.0
4
+ Summary: Command-line interface for Kolay IK (https://apidocs.kolayik.com)
5
+ Author: Tunc Aucer
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ezapmar/kolay-cli
8
+ Project-URL: Repository, https://github.com/ezapmar/kolay-cli
9
+ Project-URL: Issues, https://github.com/ezapmar/kolay-cli/issues
10
+ Keywords: kolay,hr,cli,kolayik,human-resources
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Office/Business
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: typer>=0.9.0
26
+ Requires-Dist: requests>=2.31.0
27
+ Requires-Dist: rich>=13.4.2
28
+ Dynamic: license-file
29
+
30
+ # Kolay CLI
31
+
32
+ ```
33
+ +************
34
+ +*+ **+
35
+ +*+ +*+ %% #%%
36
+ +*+ +*+ %% *#* #%%# #%% #%%#*#*+** **+
37
+ +**+ +*+ %% #%%* %%%##%%% #%% %%%#*%%%% %%# #%%
38
+ **+ +**+ %%%%# %%# %%##%%#%% %%% %%##%%#
39
+ +*+ +*++*+ %%#%%# %%% %%##%%*%% #%% %%%%#
40
+ +*+ ** +*+ %% #%% *%%%%%%# #%% #%%%%%%%% *%%%
41
+ +*+** **+ #%%
42
+ +************ #%%%
43
+ ```
44
+
45
+ A powerful, user-friendly command-line interface for the [Kolay IK API](https://apidocs.kolayik.com). Manage your HR operations directly from the terminal.
46
+
47
+ ## ✨ Features
48
+
49
+ | Module | Commands | Description |
50
+ |--------|----------|-------------|
51
+ | **Auth** | `login`, `status` | Securely store and manage your API token |
52
+ | **Person** | `list`, `view`, `leave-status`, `terminate` | List employees, view structured profiles, check leave balances, terminate with SGK codes |
53
+ | **Leave** | `list`, `view`, `create` | List leave records, view details, create leaves with interactive type picker |
54
+
55
+ ### Highlights
56
+
57
+ - 🎨 **Rich terminal output** — Structured tables, panels, and color-coded statuses
58
+ - 🔐 **Secure token storage** — Config file with restricted permissions (`0600`)
59
+ - 🇹🇷 **Turkish HR aware** — SGK termination codes, DD.MM.YYYY date format support
60
+ - ⚡ **Smart prompts** — Leave type picker, today's date defaults, active employee pre-checks
61
+
62
+ ## 📦 Installation
63
+
64
+ ### Using pipx (Recommended)
65
+ ```bash
66
+ pipx install kolay-cli
67
+ ```
68
+
69
+ ### From source
70
+ ```bash
71
+ git clone https://github.com/ezapmar/kolay-cli.git
72
+ cd kolay-cli
73
+ pip install -e .
74
+ ```
75
+
76
+ ## 🚀 Quick Start
77
+
78
+ ### 1. Authenticate
79
+ Generate an API token from [Kolay Developer Settings](https://app.kolayik.com/settings/developer-settings), then:
80
+ ```bash
81
+ kolay auth login
82
+ ```
83
+
84
+ ### 2. List your employees
85
+ ```bash
86
+ kolay person list
87
+ ```
88
+
89
+ ### 3. View someone's profile
90
+ ```bash
91
+ kolay person view <person-id>
92
+ ```
93
+
94
+ ### 4. Check leave balances
95
+ ```bash
96
+ kolay person leave-status <person-id>
97
+ ```
98
+
99
+ ### 5. Create a leave request
100
+ ```bash
101
+ kolay leave create
102
+ ```
103
+ The CLI will interactively show available leave types and let you pick by number.
104
+
105
+ ## 📖 Command Reference
106
+
107
+ ### Auth
108
+ ```bash
109
+ kolay auth login # Save your API token (prompted securely)
110
+ kolay auth status # Check if you're logged in
111
+ ```
112
+
113
+ ### Person
114
+ ```bash
115
+ kolay person list # List active employees
116
+ kolay person list --status inactive # List inactive employees
117
+ kolay person list --page 2 # Paginate results
118
+ kolay person view <id> # Structured profile view
119
+ kolay person leave-status <id> # Leave balances table
120
+ kolay person terminate <id> # Terminate with SGK reason codes
121
+ ```
122
+
123
+ ### Leave
124
+ ```bash
125
+ kolay leave list # List approved leaves (current year)
126
+ kolay leave list --status waiting # List pending leaves
127
+ kolay leave list --person-id <id> # Filter by person
128
+ kolay leave list --start-date "2025-01-01 00:00:00" --end-date "2025-12-31 23:59:59"
129
+ kolay leave view <leave-id> # View leave record details
130
+ kolay leave create # Interactive leave creation
131
+ ```
132
+
133
+ ## ⚙️ Configuration
134
+
135
+ The CLI stores its configuration at `~/.config/kolay/config.json` with restricted file permissions.
136
+
137
+ You can also use environment variables (they take precedence):
138
+ ```bash
139
+ export KOLAY_API_TOKEN="your-token-here"
140
+ export KOLAY_BASE_URL="https://api.kolayik.com" # optional
141
+ ```
142
+
143
+ ## 🛠 Development
144
+
145
+ ```bash
146
+ # Clone and setup
147
+ git clone https://github.com/ezapmar/kolay-cli.git
148
+ cd kolay-cli
149
+ python -m venv .venv
150
+ source .venv/bin/activate # or .venv\Scripts\activate on Windows
151
+ pip install -e .
152
+
153
+ # Run
154
+ kolay --help
155
+ ```
156
+
157
+ ## 📄 License
158
+
159
+ This project is licensed under the [MIT License](LICENSE) — use it freely, modify it, share it. See the `LICENSE` file for details.
@@ -0,0 +1,130 @@
1
+ # Kolay CLI
2
+
3
+ ```
4
+ +************
5
+ +*+ **+
6
+ +*+ +*+ %% #%%
7
+ +*+ +*+ %% *#* #%%# #%% #%%#*#*+** **+
8
+ +**+ +*+ %% #%%* %%%##%%% #%% %%%#*%%%% %%# #%%
9
+ **+ +**+ %%%%# %%# %%##%%#%% %%% %%##%%#
10
+ +*+ +*++*+ %%#%%# %%% %%##%%*%% #%% %%%%#
11
+ +*+ ** +*+ %% #%% *%%%%%%# #%% #%%%%%%%% *%%%
12
+ +*+** **+ #%%
13
+ +************ #%%%
14
+ ```
15
+
16
+ A powerful, user-friendly command-line interface for the [Kolay IK API](https://apidocs.kolayik.com). Manage your HR operations directly from the terminal.
17
+
18
+ ## ✨ Features
19
+
20
+ | Module | Commands | Description |
21
+ |--------|----------|-------------|
22
+ | **Auth** | `login`, `status` | Securely store and manage your API token |
23
+ | **Person** | `list`, `view`, `leave-status`, `terminate` | List employees, view structured profiles, check leave balances, terminate with SGK codes |
24
+ | **Leave** | `list`, `view`, `create` | List leave records, view details, create leaves with interactive type picker |
25
+
26
+ ### Highlights
27
+
28
+ - 🎨 **Rich terminal output** — Structured tables, panels, and color-coded statuses
29
+ - 🔐 **Secure token storage** — Config file with restricted permissions (`0600`)
30
+ - 🇹🇷 **Turkish HR aware** — SGK termination codes, DD.MM.YYYY date format support
31
+ - ⚡ **Smart prompts** — Leave type picker, today's date defaults, active employee pre-checks
32
+
33
+ ## 📦 Installation
34
+
35
+ ### Using pipx (Recommended)
36
+ ```bash
37
+ pipx install kolay-cli
38
+ ```
39
+
40
+ ### From source
41
+ ```bash
42
+ git clone https://github.com/ezapmar/kolay-cli.git
43
+ cd kolay-cli
44
+ pip install -e .
45
+ ```
46
+
47
+ ## 🚀 Quick Start
48
+
49
+ ### 1. Authenticate
50
+ Generate an API token from [Kolay Developer Settings](https://app.kolayik.com/settings/developer-settings), then:
51
+ ```bash
52
+ kolay auth login
53
+ ```
54
+
55
+ ### 2. List your employees
56
+ ```bash
57
+ kolay person list
58
+ ```
59
+
60
+ ### 3. View someone's profile
61
+ ```bash
62
+ kolay person view <person-id>
63
+ ```
64
+
65
+ ### 4. Check leave balances
66
+ ```bash
67
+ kolay person leave-status <person-id>
68
+ ```
69
+
70
+ ### 5. Create a leave request
71
+ ```bash
72
+ kolay leave create
73
+ ```
74
+ The CLI will interactively show available leave types and let you pick by number.
75
+
76
+ ## 📖 Command Reference
77
+
78
+ ### Auth
79
+ ```bash
80
+ kolay auth login # Save your API token (prompted securely)
81
+ kolay auth status # Check if you're logged in
82
+ ```
83
+
84
+ ### Person
85
+ ```bash
86
+ kolay person list # List active employees
87
+ kolay person list --status inactive # List inactive employees
88
+ kolay person list --page 2 # Paginate results
89
+ kolay person view <id> # Structured profile view
90
+ kolay person leave-status <id> # Leave balances table
91
+ kolay person terminate <id> # Terminate with SGK reason codes
92
+ ```
93
+
94
+ ### Leave
95
+ ```bash
96
+ kolay leave list # List approved leaves (current year)
97
+ kolay leave list --status waiting # List pending leaves
98
+ kolay leave list --person-id <id> # Filter by person
99
+ kolay leave list --start-date "2025-01-01 00:00:00" --end-date "2025-12-31 23:59:59"
100
+ kolay leave view <leave-id> # View leave record details
101
+ kolay leave create # Interactive leave creation
102
+ ```
103
+
104
+ ## ⚙️ Configuration
105
+
106
+ The CLI stores its configuration at `~/.config/kolay/config.json` with restricted file permissions.
107
+
108
+ You can also use environment variables (they take precedence):
109
+ ```bash
110
+ export KOLAY_API_TOKEN="your-token-here"
111
+ export KOLAY_BASE_URL="https://api.kolayik.com" # optional
112
+ ```
113
+
114
+ ## 🛠 Development
115
+
116
+ ```bash
117
+ # Clone and setup
118
+ git clone https://github.com/ezapmar/kolay-cli.git
119
+ cd kolay-cli
120
+ python -m venv .venv
121
+ source .venv/bin/activate # or .venv\Scripts\activate on Windows
122
+ pip install -e .
123
+
124
+ # Run
125
+ kolay --help
126
+ ```
127
+
128
+ ## 📄 License
129
+
130
+ This project is licensed under the [MIT License](LICENSE) — use it freely, modify it, share it. See the `LICENSE` file for details.
@@ -0,0 +1,47 @@
1
+ [build-system]
2
+ requires = ["setuptools>=61.0"]
3
+ build-backend = "setuptools.build_meta"
4
+
5
+ [project]
6
+ name = "kolay-cli"
7
+ version = "0.1.0"
8
+ description = "Command-line interface for Kolay IK (https://apidocs.kolayik.com)"
9
+ authors = [
10
+ {name = "Tunc Aucer"}
11
+ ]
12
+ readme = "README.md"
13
+ requires-python = ">=3.9"
14
+ dependencies = [
15
+ "typer>=0.9.0",
16
+ "requests>=2.31.0",
17
+ "rich>=13.4.2"
18
+ ]
19
+ license = {text = "MIT"}
20
+ keywords = ["kolay", "hr", "cli", "kolayik", "human-resources"]
21
+ classifiers = [
22
+ "Development Status :: 4 - Beta",
23
+ "Environment :: Console",
24
+ "Intended Audience :: Developers",
25
+ "Intended Audience :: System Administrators",
26
+ "License :: OSI Approved :: MIT License",
27
+ "Programming Language :: Python :: 3",
28
+ "Programming Language :: Python :: 3.9",
29
+ "Programming Language :: Python :: 3.10",
30
+ "Programming Language :: Python :: 3.11",
31
+ "Programming Language :: Python :: 3.12",
32
+ "Topic :: Office/Business",
33
+ ]
34
+
35
+ [project.urls]
36
+ Homepage = "https://github.com/ezapmar/kolay-cli"
37
+ Repository = "https://github.com/ezapmar/kolay-cli"
38
+ Issues = "https://github.com/ezapmar/kolay-cli/issues"
39
+
40
+ [project.scripts]
41
+ kolay = "kolay_cli.cli:app"
42
+
43
+ [tool.setuptools]
44
+ package-dir = {"" = "src"}
45
+
46
+ [tool.setuptools.packages.find]
47
+ where = ["src"]
@@ -0,0 +1,4 @@
1
+ [egg_info]
2
+ tag_build =
3
+ tag_date = 0
4
+
@@ -0,0 +1,4 @@
1
+ """
2
+ Kolay CLI
3
+ """
4
+ __version__ = "0.1.0"
@@ -0,0 +1,56 @@
1
+ import requests
2
+ from typing import Dict, Any, Optional
3
+
4
+ from . import config
5
+
6
+ class APIError(Exception):
7
+ pass
8
+
9
+ class KolayClient:
10
+ def __init__(self, token: Optional[str] = None, base_url: Optional[str] = None):
11
+ self.token = token or config.get_api_token()
12
+ self.base_url = (base_url or config.get_base_url()).rstrip("/")
13
+
14
+ if not self.token:
15
+ raise APIError("API token is required. Please set it using 'kolay auth login' or the KOLAY_API_TOKEN environment variable.")
16
+
17
+ self.session = requests.Session()
18
+ self.session.headers.update({
19
+ "Authorization": f"Bearer {self.token}",
20
+ "Accept": "application/json",
21
+ "Content-Type": "application/json"
22
+ })
23
+
24
+ def get(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
25
+ return self._request("GET", endpoint, params=params)
26
+
27
+ def post(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
28
+ return self._request("POST", endpoint, json=data)
29
+
30
+ def put(self, endpoint: str, data: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
31
+ return self._request("PUT", endpoint, json=data)
32
+
33
+ def delete(self, endpoint: str, params: Optional[Dict[str, Any]] = None) -> Dict[str, Any]:
34
+ return self._request("DELETE", endpoint, params=params)
35
+
36
+ def _request(self, method: str, endpoint: str, **kwargs) -> Dict[str, Any]:
37
+ url = f"{self.base_url}/{endpoint.lstrip('/')}"
38
+
39
+ try:
40
+ response = self.session.request(method, url, **kwargs)
41
+ response.raise_for_status()
42
+ # Most Kolay API responses return a json payload
43
+ if response.content:
44
+ return response.json()
45
+ return {}
46
+ except requests.exceptions.HTTPError as e:
47
+ msg = f"API Error: {e.response.status_code}"
48
+ try:
49
+ error_data = e.response.json()
50
+ if "message" in error_data:
51
+ msg += f" - {error_data['message']}"
52
+ except Exception:
53
+ msg += f" - {e.response.text}"
54
+ raise APIError(msg)
55
+ except Exception as e:
56
+ raise APIError(f"Request failed: {str(e)}")
@@ -0,0 +1,47 @@
1
+ import typer
2
+
3
+ from . import __version__
4
+ from .commands import auth, person, leave
5
+ from rich.console import Console
6
+
7
+ LOGO = """\b
8
+ [bold cyan] +************ [/bold cyan]
9
+ [bold cyan] +*+ **+ [/bold cyan]
10
+ [bold cyan] +*+ +*+ %% #%% [/bold cyan]
11
+ [bold cyan] +*+ +*+ %% *#* #%%# #%% #%%#*#*+** **+ [/bold cyan]
12
+ [bold cyan] +**+ +*+ %% #%%* %%%##%%% #%% %%%#*%%%% %%# #%% [/bold cyan]
13
+ [bold cyan] **+ +**+ %%%%# %%# %%##%%#%% %%% %%##%%# [/bold cyan]
14
+ [bold cyan] +*+ +*++*+ %%#%%# %%% %%##%%*%% #%% %%%%# [/bold cyan]
15
+ [bold cyan] +*+ ** +*+ %% #%% *%%%%%%# #%% #%%%%%%%% *%%% [/bold cyan]
16
+ [bold cyan] +*+** **+ #%% [/bold cyan]
17
+ [bold cyan] +************ #%%% [/bold cyan]
18
+
19
+ Kolay CLI - A command-line interface for the Kolay IK API.
20
+ """
21
+
22
+ app = typer.Typer(
23
+ no_args_is_help=True,
24
+ rich_markup_mode="rich"
25
+ )
26
+ console = Console()
27
+
28
+ app.add_typer(auth.app, name="auth")
29
+ app.add_typer(person.app, name="person")
30
+ app.add_typer(leave.app, name="leave")
31
+
32
+ @app.callback(invoke_without_command=True, help=LOGO)
33
+ def main(
34
+ version: bool = typer.Option(
35
+ False,
36
+ "--version",
37
+ "-v",
38
+ help="Print the CLI version.",
39
+ is_eager=True,
40
+ )
41
+ ):
42
+ if version:
43
+ console.print(f"Kolay CLI version: [bold cyan]{__version__}[/bold cyan]")
44
+ raise typer.Exit()
45
+
46
+ if __name__ == "__main__":
47
+ app()
@@ -0,0 +1,3 @@
1
+ from . import auth, person, leave
2
+
3
+ __all__ = ["auth", "person", "leave"]
@@ -0,0 +1,26 @@
1
+ import typer
2
+ from ..config import set_config_value, get_api_token
3
+ from rich.console import Console
4
+
5
+ app = typer.Typer(help="Auth commands for the Kolay CLI.")
6
+ console = Console()
7
+
8
+ @app.command()
9
+ def login(token: str = typer.Option(..., prompt="Please enter your Kolay API token", hide_input=True)):
10
+ """
11
+ Save your Kolay API token for future CLI use.
12
+ """
13
+ # Simple check if token works (maybe we can hit a profile endpoint later, for now we just save it)
14
+ set_config_value("api_token", token)
15
+ console.print(f"[green]Successfully saved API token to your configuration.[/green]")
16
+
17
+ @app.command()
18
+ def status():
19
+ """
20
+ Check if you are currently logged in.
21
+ """
22
+ token = get_api_token()
23
+ if token:
24
+ console.print(f"[green]You are logged in.[/green] (Token: ...{token[-4:] if len(token) > 4 else ''})")
25
+ else:
26
+ console.print("[red]You are NOT logged in.[/red] Please run [bold cyan]kolay auth login[/bold cyan].")
@@ -0,0 +1,204 @@
1
+ import typer
2
+ from typing import Optional
3
+ from rich.console import Console
4
+ from rich.table import Table
5
+ import json
6
+
7
+ from ..api import KolayClient, APIError
8
+
9
+ app = typer.Typer(help="Manage leave records in Kolay.")
10
+ console = Console()
11
+
12
+
13
+ @app.command(name="list")
14
+ def list_leaves(
15
+ status: str = typer.Option("approved", help="Filter by status: approved, waiting, rejected, cancelled"),
16
+ start_date: Optional[str] = typer.Option(None, help="Start date filter (YYYY-MM-DD HH:MM:SS). Defaults to Jan 1 of current year."),
17
+ end_date: Optional[str] = typer.Option(None, help="End date filter (YYYY-MM-DD HH:MM:SS). Defaults to Dec 31 of current year."),
18
+ person_id: Optional[str] = typer.Option(None, help="Filter by person ID"),
19
+ limit: int = typer.Option(100, help="Max number of records to return"),
20
+ include_inactive: bool = typer.Option(False, help="Include inactive employees"),
21
+ ):
22
+ """
23
+ List leave records. Filter by status, date range, or person.
24
+ """
25
+ from datetime import datetime
26
+
27
+ try:
28
+ client = KolayClient()
29
+
30
+ # API requires startDate and endDate — default to current year
31
+ now = datetime.now()
32
+ if not start_date:
33
+ start_date = f"{now.year}-01-01 00:00:00"
34
+ if not end_date:
35
+ end_date = f"{now.year}-12-31 23:59:59"
36
+
37
+ params: dict = {
38
+ "status": status,
39
+ "startDate": start_date,
40
+ "endDate": end_date,
41
+ "limit": limit,
42
+ }
43
+ if include_inactive:
44
+ params["include_inactive_employees"] = "true"
45
+ if person_id:
46
+ params["personId"] = person_id
47
+
48
+ console.print(f"Fetching leave records ({status}, {start_date[:10]} → {end_date[:10]})...")
49
+ response = client.get("v2/leave/list", params=params)
50
+
51
+ data = response.get("data", [])
52
+ if not data:
53
+ console.print("[yellow]No leave records found.[/yellow]")
54
+ return
55
+
56
+ table = Table(title="Leave Records")
57
+ table.add_column("ID", style="cyan", no_wrap=True)
58
+ table.add_column("Person", style="magenta")
59
+ table.add_column("Type", style="yellow")
60
+ table.add_column("Start", style="green")
61
+ table.add_column("End", style="green")
62
+ table.add_column("Status", style="bold")
63
+
64
+ for leave in data:
65
+ lid = str(leave.get("id", "N/A"))
66
+
67
+ person = leave.get("person", {})
68
+ if isinstance(person, dict):
69
+ person_name = person.get("name", "N/A")
70
+ else:
71
+ person_name = str(leave.get("personId", "N/A"))
72
+
73
+ leave_type = leave.get("type", leave.get("leaveType", {}))
74
+ type_name = leave_type.get("name", "N/A") if isinstance(leave_type, dict) else str(leave_type)
75
+
76
+ start = leave.get("startDate", "N/A")
77
+ end = leave.get("endDate", "N/A")
78
+ lstatus = leave.get("status", "N/A")
79
+
80
+ status_color = {
81
+ "approved": "[green]approved[/green]",
82
+ "waiting": "[yellow]waiting[/yellow]",
83
+ "rejected": "[red]rejected[/red]",
84
+ "cancelled": "[dim]cancelled[/dim]",
85
+ }.get(str(lstatus).lower(), lstatus)
86
+
87
+ table.add_row(lid, person_name, type_name, start, end, status_color)
88
+
89
+ console.print(table)
90
+ except APIError as e:
91
+ console.print(f"[bold red]API Error:[/bold red] {e}")
92
+ raise typer.Exit(1)
93
+
94
+
95
+ @app.command(name="view")
96
+ def view_leave(
97
+ leave_id: str = typer.Argument(..., help="ID of the leave record to view"),
98
+ ):
99
+ """
100
+ View the details of a specific leave record.
101
+ """
102
+ try:
103
+ client = KolayClient()
104
+ response = client.get(f"v2/leave/view/{leave_id}")
105
+
106
+ data = response.get("data", {})
107
+ if not data:
108
+ console.print(f"[yellow]Leave record '{leave_id}' not found or permission denied.[/yellow]")
109
+ return
110
+
111
+ console.print(f"\n[bold cyan]Leave Record ({leave_id})[/bold cyan]")
112
+ console.print(json.dumps(data, indent=2, ensure_ascii=False))
113
+ except APIError as e:
114
+ console.print(f"[bold red]API Error:[/bold red] {e}")
115
+ raise typer.Exit(1)
116
+
117
+
118
+ @app.command(name="create")
119
+ def create_leave(
120
+ person_id: Optional[str] = typer.Option(None, help="Person ID to create leave for"),
121
+ leave_type_id: Optional[str] = typer.Option(None, help="Leave type ID (auto-listed if omitted)"),
122
+ start_date: Optional[str] = typer.Option(None, help="Start datetime (YYYY-MM-DD HH:MM:SS)"),
123
+ end_date: Optional[str] = typer.Option(None, help="End datetime (YYYY-MM-DD HH:MM:SS)"),
124
+ comment: Optional[str] = typer.Option(None, help="Optional comment"),
125
+ replacement_person_id: Optional[str] = typer.Option(None, help="Optional replacement person ID"),
126
+ ):
127
+ """
128
+ Create a new leave record. Shows available leave types for the person.
129
+ """
130
+
131
+ try:
132
+ client = KolayClient()
133
+
134
+ # 1. Get person ID
135
+ if not person_id:
136
+ person_id = typer.prompt("Person ID")
137
+
138
+ # 2. Fetch available leave types for this person
139
+ if not leave_type_id:
140
+ console.print(f"\nFetching available leave types for person {person_id}...")
141
+ try:
142
+ status_resp = client.get(f"v2/person/leave-status/{person_id}")
143
+ leave_types = status_resp.get("data", [])
144
+ except APIError:
145
+ leave_types = []
146
+
147
+ if leave_types:
148
+ table = Table(title="Available Leave Types", header_style="bold cyan")
149
+ table.add_column("#", style="bold white", width=3)
150
+ table.add_column("Name")
151
+ table.add_column("Remaining", justify="right", style="green")
152
+ table.add_column("ID", style="dim")
153
+
154
+ for i, lt in enumerate(leave_types, 1):
155
+ name = lt.get("name", "N/A")
156
+ if lt.get("primary"):
157
+ name = f"⭐ {name}"
158
+ unused = lt.get("unused")
159
+ remaining = str(unused) if unused is not None else "∞"
160
+ table.add_row(str(i), name, remaining, lt.get("id", "N/A"))
161
+
162
+ console.print(table)
163
+
164
+ choice = typer.prompt("Pick a leave type (enter # or ID)")
165
+ # If user entered a number, map it
166
+ try:
167
+ idx = int(choice) - 1
168
+ if 0 <= idx < len(leave_types):
169
+ leave_type_id = leave_types[idx].get("id")
170
+ else:
171
+ leave_type_id = choice
172
+ except ValueError:
173
+ leave_type_id = choice
174
+ else:
175
+ leave_type_id = typer.prompt("Leave type ID")
176
+
177
+ # 3. Get dates
178
+ if not start_date:
179
+ start_date = typer.prompt("Start datetime (YYYY-MM-DD HH:MM:SS)")
180
+ if not end_date:
181
+ end_date = typer.prompt("End datetime (YYYY-MM-DD HH:MM:SS)")
182
+
183
+ # 4. Build payload and create
184
+ params: dict = {
185
+ "personId": person_id,
186
+ "leaveTypeId": leave_type_id,
187
+ "startDate": start_date,
188
+ "endDate": end_date,
189
+ }
190
+ if comment:
191
+ params["comment"] = comment
192
+ if replacement_person_id:
193
+ params["replacementPersonId"] = replacement_person_id
194
+
195
+ console.print("Creating leave record...")
196
+ response = client.post("v2/leave/create", data=params)
197
+
198
+ data = response.get("data", {})
199
+ leave_id = data.get("id", "N/A") if isinstance(data, dict) else "N/A"
200
+ console.print(f"[bold green]Leave record created![/bold green] ID: [cyan]{leave_id}[/cyan]")
201
+ except APIError as e:
202
+ console.print(f"[bold red]API Error:[/bold red] {e}")
203
+ raise typer.Exit(1)
204
+
@@ -0,0 +1,340 @@
1
+ import typer
2
+ from typing import Optional
3
+ from rich.console import Console
4
+ from rich.table import Table
5
+ import json
6
+
7
+ from ..api import KolayClient, APIError
8
+
9
+ app = typer.Typer(help="Manage person/employee records in Kolay.")
10
+ console = Console()
11
+
12
+ @app.command(name="list")
13
+ def list_people(page: int = 1, status: str = "active"):
14
+ """
15
+ List people from the Kolay API.
16
+ By default, it lists active employees.
17
+ """
18
+ try:
19
+ client = KolayClient()
20
+ console.print(f"Fetching people... (Page {page}, Status: {status})")
21
+ # Kolay uses POST for v2/person/list
22
+ payload = {"page": page, "status": status}
23
+ response = client.post("v2/person/list", data=payload)
24
+
25
+ data_resp = response.get("data", {})
26
+ if isinstance(data_resp, dict):
27
+ items = data_resp.get("items", [])
28
+ else:
29
+ items = data_resp
30
+
31
+ if not items:
32
+ console.print("[yellow]No people found.[/yellow]")
33
+ return
34
+
35
+ table = Table(title=f"Kolay Personnel ({status})")
36
+ table.add_column("ID", style="cyan", no_wrap=True)
37
+ table.add_column("Name", style="magenta")
38
+ table.add_column("Email", style="green")
39
+ table.add_column("Title", style="yellow")
40
+
41
+ for person in items:
42
+ # Person object depends on Kolay API response structure
43
+ # Usually has id, firstName, lastName, workEmail etc
44
+ # Fallback to get them gracefully
45
+ pid = str(person.get("id", "N/A"))
46
+ fname = person.get("firstName", person.get("first_name", ""))
47
+ lname = person.get("lastName", person.get("last_name", ""))
48
+ name = f"{fname} {lname}".strip() or person.get("name", "N/A")
49
+
50
+ email = person.get("workEmail", person.get("email", "N/A"))
51
+
52
+ # Extract title if present in unitList or another field
53
+ title = person.get("title", "N/A")
54
+ if title == "N/A" and "unitList" in person:
55
+ for unit in person.get("unitList", []):
56
+ if unit.get("default") or unit.get("active"):
57
+ for item in unit.get("items", []):
58
+ if item.get("unitName") == "Unvan":
59
+ title = item.get("unitItemName", title)
60
+
61
+ table.add_row(pid, name, email, title)
62
+
63
+ console.print(table)
64
+ except APIError as e:
65
+ console.print(f"[bold red]API Error:[/bold red] {e}")
66
+ raise typer.Exit(1)
67
+
68
+ @app.command(name="view")
69
+ def view_person(person_id: str = typer.Argument(..., help="ID of the person to view")):
70
+ """
71
+ View structured details of a specific person.
72
+ """
73
+ from rich.panel import Panel
74
+ from rich.columns import Columns
75
+
76
+ try:
77
+ client = KolayClient()
78
+ response = client.get(f"v2/person/view/{person_id}")
79
+
80
+ data = response.get("data", {})
81
+ person = data.get("person", data) if isinstance(data, dict) else data
82
+
83
+ if not person:
84
+ console.print(f"[yellow]Person {person_id} not found or permission denied.[/yellow]")
85
+ return
86
+
87
+ # Basic Info
88
+ full_name = f"{person.get('firstName', '')} {person.get('lastName', '')}".strip()
89
+ status_val = person.get('status', 'N/A')
90
+ status_color = "green" if status_val == "active" else "red"
91
+
92
+ console.print(f"\n[bold cyan]Person Profile:[/bold cyan] [bold white]{full_name}[/bold white] [[{status_color}]{status_val}[/{status_color}]]")
93
+
94
+ # 1. Personal Info Table
95
+ personal_table = Table(show_header=False, box=None)
96
+ personal_table.add_row("[bold]ID:[/bold]", person.get("id"))
97
+ personal_table.add_row("[bold]Email:[/bold]", person.get("workEmail") or person.get("email") or "N/A")
98
+ personal_table.add_row("[bold]Phone:[/bold]", person.get("mobilePhone") or "N/A")
99
+ personal_table.add_row("[bold]Birthday:[/bold]", person.get("birthday") or "N/A")
100
+ personal_table.add_row("[bold]Gender:[/bold]", person.get("gender") or "N/A")
101
+ personal_table.add_row("[bold]ID Number:[/bold]", person.get("idNumber") or "N/A")
102
+
103
+ # 2. Employment Table
104
+ employment_table = Table(show_header=False, box=None)
105
+ employment_table.add_row("[bold]Start Date:[/bold]", person.get("employmentStartDate") or "N/A")
106
+
107
+ # Extract unit info
108
+ units = person.get("unitList", [])
109
+ active_unit = next((u for u in units if u.get("active") or u.get("default")), {})
110
+ if active_unit:
111
+ employment_table.add_row("[bold]Type:[/bold]", active_unit.get("employmentType", "N/A"))
112
+ for item in active_unit.get("items", []):
113
+ employment_table.add_row(f"[bold]{item.get('unitName')}:[/bold]", item.get("unitItemName"))
114
+
115
+ console.print(Columns([
116
+ Panel(personal_table, title="Personal Information", border_style="blue", expand=True),
117
+ Panel(employment_table, title="Employment", border_style="magenta", expand=True)
118
+ ]))
119
+
120
+ # 3. Custom Fields (Data List)
121
+ custom_data = person.get("dataList", [])
122
+ # Filter only non-empty values
123
+ non_empty_custom = [f for f in custom_data if f.get("value")]
124
+
125
+ if non_empty_custom:
126
+ custom_table = Table(title="Custom Fields", box=None, show_header=True, header_style="bold cyan")
127
+ custom_table.add_column("Field")
128
+ custom_table.add_column("Value")
129
+ for field in non_empty_custom:
130
+ custom_table.add_row(field.get("fieldToken", "N/A"), str(field.get("value")))
131
+
132
+ console.print(Panel(custom_table, border_style="dim"))
133
+
134
+ except APIError as e:
135
+ console.print(f"[bold red]API Error:[/bold red] {e}")
136
+ raise typer.Exit(1)
137
+
138
+ @app.command(name="leave-status")
139
+ def view_leave_status(person_id: str = typer.Argument(..., help="ID of the person to view leave status for")):
140
+ """
141
+ Get structured leave status and balances for a specific person.
142
+ """
143
+ try:
144
+ client = KolayClient()
145
+ response = client.get(f"v2/person/leave-status/{person_id}")
146
+
147
+ data = response.get("data", [])
148
+ if not data:
149
+ console.print(f"[yellow]No leave status data for person {person_id}.[/yellow]")
150
+ return
151
+
152
+ console.print(f"\n[bold cyan]Leave Status & Balances:[/bold cyan] (ID: {person_id})")
153
+
154
+ table = Table(title=None, box=None, header_style="bold cyan")
155
+ table.add_column("Leave Type")
156
+ table.add_column("Paid?", justify="center")
157
+ table.add_column("Total", justify="right")
158
+ table.add_column("Used", justify="right")
159
+ table.add_column("Upcoming", justify="right")
160
+ table.add_column("Remaining", justify="right", style="bold green")
161
+ table.add_column("Next Accrual", justify="center")
162
+
163
+ def fmt(val):
164
+ if val is None: return "-"
165
+ try:
166
+ return str(int(val)) if float(val) == int(val) else str(val)
167
+ except (ValueError, TypeError):
168
+ return str(val)
169
+
170
+ for leave in data:
171
+ name = leave.get("name", "N/A")
172
+ if leave.get("primary"):
173
+ name = f"[bold white]⭐ {name}[/bold white]"
174
+
175
+ is_paid = "[green]Yes[/green]" if leave.get("isPaid") else "[red]No[/red]"
176
+
177
+ used = leave.get("used", 0)
178
+ upcoming = leave.get("totalUpcoming", 0)
179
+ total = leave.get("total", leave.get("dayLimit", "∞"))
180
+ unused = leave.get("unused")
181
+
182
+ # If unused is missing, try to calculate or show -
183
+ remaining = fmt(unused) if unused is not None else "∞"
184
+
185
+ next_date = leave.get("nextAccrualDate", "-")
186
+
187
+ table.add_row(
188
+ name,
189
+ is_paid,
190
+ fmt(total),
191
+ fmt(used),
192
+ fmt(upcoming),
193
+ remaining,
194
+ next_date
195
+ )
196
+
197
+ console.print(table)
198
+ console.print("\n[dim]Note: ⭐ indicates Primary Leave Type.[/dim]")
199
+
200
+ except APIError as e:
201
+ console.print(f"[bold red]API Error:[/bold red] {e}")
202
+ raise typer.Exit(1)
203
+
204
+ @app.command(name="terminate")
205
+ def terminate_person(
206
+ person_id: str = typer.Argument(..., help="ID of the person to terminate"),
207
+ termination_date: Optional[str] = typer.Option(
208
+ None,
209
+ help="Termination date (YYYY-MM-DD)",
210
+ show_default=False
211
+ ),
212
+ reason: Optional[str] = typer.Option(None, help="Termination reason")
213
+ ):
214
+ """
215
+ Terminate employment of a person.
216
+ """
217
+ from datetime import datetime
218
+
219
+ try:
220
+ client = KolayClient()
221
+ # Pre-check if person exists and is active
222
+ console.print(f"Checking status for person {person_id}...")
223
+ resp = client.get(f"v2/person/view/{person_id}")
224
+ person_data = resp.get("data", {})
225
+ person = person_data.get("person", person_data) if isinstance(person_data, dict) else person_data
226
+
227
+ if not person:
228
+ console.print(f"[red]Error: Person {person_id} not found.[/red]")
229
+ return
230
+
231
+ status = person.get("status")
232
+ if status != "active":
233
+ name = f"{person.get('firstName', '')} {person.get('lastName', '')}".strip()
234
+ console.print(f"[bold red]Stop![/bold red] {name or person_id} is currently [bold yellow]{status}[/bold yellow].")
235
+ console.print("Only active employees can be terminated.")
236
+ return
237
+
238
+ except APIError as e:
239
+ console.print(f"[bold red]Error checking person status:[/bold red] {e}")
240
+ raise typer.Exit(1)
241
+
242
+ # Get today's date as default
243
+ today = datetime.now().strftime("%Y-%m-%d")
244
+
245
+ if termination_date is None:
246
+ termination_date = typer.prompt("Termination date", default=today)
247
+
248
+ # Try to handle common formats like DD.MM.YYYY
249
+ if "." in termination_date:
250
+ try:
251
+ parts = termination_date.split(".")
252
+ if len(parts) == 3:
253
+ # Convert DD.MM.YYYY to YYYY-MM-DD
254
+ d, m, y = parts
255
+ termination_date = f"{y}-{m.zfill(2)}-{d.zfill(2)}"
256
+ except:
257
+ pass
258
+
259
+ REASON_CODES = {
260
+ "01": "Deneme süreli iş sözleşmesinin işverence feshi",
261
+ "02": "Deneme süreli iş sözleşmesinin işçi tarafından feshi",
262
+ "03": "Belirsiz süreli iş sözleşmesinin işçi tarafından feshi (İstifa)",
263
+ "04": "Belirsiz süreli iş sözleşmesinin işveren tarafından haklı sebep bildirilmeden feshi",
264
+ "05": "Belirli süreli iş sözleşmesinin sona ermesi",
265
+ "08": "Emeklilik (Yaşlılık) veya Toptan Ödeme Nedeniyle",
266
+ "09": "Maluliyet Nedeniyle",
267
+ "10": "Ölüm",
268
+ "11": "İş Kazası Sonucu Ölüm",
269
+ "12": "Askerlik",
270
+ "13": "Kadın İşçinin Evlenmesi",
271
+ "14": "Emeklilik İçin Yaş Dışında Diğer Şartların Tamamlanması",
272
+ "15": "Toplu İşçi Çıkarma",
273
+ "17": "İşyerinin Kapanması",
274
+ "18": "İşin Sona Ermesi",
275
+ "19": "Mevsimlik İşin Sona Ermesi",
276
+ "20": "Kampanya İşinin Sona Ermesi",
277
+ "21": "Statü Değişikliği",
278
+ "22": "Diğer Nedenler",
279
+ "23": "İşçi Kuruluşunda Görev Alınması",
280
+ "24": "Sözleşme Başlamadan Fesih",
281
+ "25": "İşçi Tarafından Zorunlu Nedenle Fesih",
282
+ "26": "İşçi Tarafından İşverenin Ahlak ve İyi Niyet Kurallarına Aykırı Davranışı Nedeniyle Fesih",
283
+ "27": "İşveren Tarafından İşçinin Ahlak ve İyi Niyet Kurallarına Aykırı Davranışı Nedeniyle Fesih",
284
+ "28": "Sağlık Nedeniyle İşçi Tarafından Fesih",
285
+ "29": "Sağlık Nedeniyle İşveren Tarafından Fesih",
286
+ "30": "Vize Süresinin Sona Ermesi",
287
+ "31": "Borçlar Kanunu, Sendikalar Kanunu, Grev ve Lokavt Kanunu Kapsamında Fesih",
288
+ "32": "4046 Sayılı Kanun Kapsamında Özelleştirme Nedeniyle Fesih",
289
+ "33": "Gazeteci Tarafından Sözleşmenin Feshi",
290
+ "34": "İşyerinin Devri, İşin veya İşyerinin Niteliğinin Değişmesi Nedeniyle Fesih",
291
+ "36": "KHK ile Kamu Görevinden Çıkarılma",
292
+ "37": "KHK ile Kamu Görevinden İade Edilme",
293
+ "40": "696 Sayılı KHK ile Kamu İşçiliğine Geçiş",
294
+ "41": "696 Sayılı KHK ile Kamu İşçiliğine Geçişte Başarısızlık",
295
+ }
296
+
297
+ if reason is None:
298
+ console.print("\n[bold cyan]Turkish Termination (SGK) Reason Codes:[/bold cyan]")
299
+ codes_table = Table(box=None, header_style="bold magenta", padding=(0, 2))
300
+ codes_table.add_column("Code", width=4)
301
+ codes_table.add_column("Description")
302
+
303
+ # Show top common codes first or just a selection
304
+ for c in ["01", "03", "04", "05", "12", "13", "17", "22", "27"]:
305
+ codes_table.add_row(c, REASON_CODES[c])
306
+ codes_table.add_row("..", "Enter 'all' to see all codes")
307
+
308
+ console.print(codes_table)
309
+
310
+ reason_code = typer.prompt("Reason Code", default="03")
311
+ if reason_code.lower() == "all":
312
+ full_table = Table(title="All Reason Codes", header_style="bold magenta")
313
+ full_table.add_column("Code")
314
+ full_table.add_column("Description")
315
+ for c, desc in sorted(REASON_CODES.items()):
316
+ full_table.add_row(c, desc)
317
+ console.print(full_table)
318
+ reason_code = typer.prompt("Reason Code", default="03")
319
+
320
+ details = typer.prompt("Details", default=REASON_CODES.get(reason_code, ""))
321
+ else:
322
+ reason_code = "03"
323
+ details = reason
324
+
325
+ try:
326
+ client = KolayClient()
327
+ payload = {
328
+ "personId": person_id,
329
+ "date": termination_date,
330
+ "reasonCode": reason_code,
331
+ "details": details
332
+ }
333
+
334
+ console.print(f"Terminating person {person_id} with date [bold cyan]{termination_date}[/bold cyan] and reason code [bold magenta]{reason_code}[/bold magenta]...")
335
+ client.post("v2/person/terminate", data=payload)
336
+ console.print("[bold green]Success![/bold green] Operation completed.")
337
+
338
+ except APIError as e:
339
+ console.print(f"[bold red]API Error:[/bold red] {e}")
340
+ raise typer.Exit(1)
@@ -0,0 +1,54 @@
1
+ import json
2
+ import os
3
+ from pathlib import Path
4
+ from typing import Optional
5
+
6
+ CONFIG_DIR = Path.home() / ".config" / "kolay"
7
+ CONFIG_FILE = CONFIG_DIR / "config.json"
8
+
9
+ def get_config_value(key: str) -> Optional[str]:
10
+ """Get a value from the config file or environment variables."""
11
+ # Environment variables have precedence
12
+ env_val = os.getenv(f"KOLAY_{key.upper()}")
13
+ if env_val:
14
+ return env_val
15
+
16
+ if not CONFIG_FILE.exists():
17
+ return None
18
+
19
+ try:
20
+ with open(CONFIG_FILE, "r") as f:
21
+ data = json.load(f)
22
+ return data.get(key)
23
+ except Exception:
24
+ return None
25
+
26
+ def set_config_value(key: str, value: str):
27
+ """Set a value in the config file and ensure restricted permissions."""
28
+ CONFIG_DIR.mkdir(parents=True, exist_ok=True)
29
+
30
+ data = {}
31
+ if CONFIG_FILE.exists():
32
+ try:
33
+ with open(CONFIG_FILE, "r") as f:
34
+ data = json.load(f)
35
+ except Exception:
36
+ pass
37
+
38
+ data[key] = value
39
+
40
+ # Create or overwrite file with restricted permissions
41
+ # We use a temp file or just write it then chmod
42
+ with open(CONFIG_FILE, "w") as f:
43
+ json.dump(data, f, indent=4)
44
+
45
+ try:
46
+ os.chmod(CONFIG_FILE, 0o600)
47
+ except Exception:
48
+ pass # Fallback for environments where chmod might fail
49
+
50
+ def get_api_token() -> Optional[str]:
51
+ return get_config_value("api_token")
52
+
53
+ def get_base_url() -> str:
54
+ return get_config_value("base_url") or "https://api.kolayik.com"
@@ -0,0 +1,159 @@
1
+ Metadata-Version: 2.4
2
+ Name: kolay-cli
3
+ Version: 0.1.0
4
+ Summary: Command-line interface for Kolay IK (https://apidocs.kolayik.com)
5
+ Author: Tunc Aucer
6
+ License: MIT
7
+ Project-URL: Homepage, https://github.com/ezapmar/kolay-cli
8
+ Project-URL: Repository, https://github.com/ezapmar/kolay-cli
9
+ Project-URL: Issues, https://github.com/ezapmar/kolay-cli/issues
10
+ Keywords: kolay,hr,cli,kolayik,human-resources
11
+ Classifier: Development Status :: 4 - Beta
12
+ Classifier: Environment :: Console
13
+ Classifier: Intended Audience :: Developers
14
+ Classifier: Intended Audience :: System Administrators
15
+ Classifier: License :: OSI Approved :: MIT License
16
+ Classifier: Programming Language :: Python :: 3
17
+ Classifier: Programming Language :: Python :: 3.9
18
+ Classifier: Programming Language :: Python :: 3.10
19
+ Classifier: Programming Language :: Python :: 3.11
20
+ Classifier: Programming Language :: Python :: 3.12
21
+ Classifier: Topic :: Office/Business
22
+ Requires-Python: >=3.9
23
+ Description-Content-Type: text/markdown
24
+ License-File: LICENSE
25
+ Requires-Dist: typer>=0.9.0
26
+ Requires-Dist: requests>=2.31.0
27
+ Requires-Dist: rich>=13.4.2
28
+ Dynamic: license-file
29
+
30
+ # Kolay CLI
31
+
32
+ ```
33
+ +************
34
+ +*+ **+
35
+ +*+ +*+ %% #%%
36
+ +*+ +*+ %% *#* #%%# #%% #%%#*#*+** **+
37
+ +**+ +*+ %% #%%* %%%##%%% #%% %%%#*%%%% %%# #%%
38
+ **+ +**+ %%%%# %%# %%##%%#%% %%% %%##%%#
39
+ +*+ +*++*+ %%#%%# %%% %%##%%*%% #%% %%%%#
40
+ +*+ ** +*+ %% #%% *%%%%%%# #%% #%%%%%%%% *%%%
41
+ +*+** **+ #%%
42
+ +************ #%%%
43
+ ```
44
+
45
+ A powerful, user-friendly command-line interface for the [Kolay IK API](https://apidocs.kolayik.com). Manage your HR operations directly from the terminal.
46
+
47
+ ## ✨ Features
48
+
49
+ | Module | Commands | Description |
50
+ |--------|----------|-------------|
51
+ | **Auth** | `login`, `status` | Securely store and manage your API token |
52
+ | **Person** | `list`, `view`, `leave-status`, `terminate` | List employees, view structured profiles, check leave balances, terminate with SGK codes |
53
+ | **Leave** | `list`, `view`, `create` | List leave records, view details, create leaves with interactive type picker |
54
+
55
+ ### Highlights
56
+
57
+ - 🎨 **Rich terminal output** — Structured tables, panels, and color-coded statuses
58
+ - 🔐 **Secure token storage** — Config file with restricted permissions (`0600`)
59
+ - 🇹🇷 **Turkish HR aware** — SGK termination codes, DD.MM.YYYY date format support
60
+ - ⚡ **Smart prompts** — Leave type picker, today's date defaults, active employee pre-checks
61
+
62
+ ## 📦 Installation
63
+
64
+ ### Using pipx (Recommended)
65
+ ```bash
66
+ pipx install kolay-cli
67
+ ```
68
+
69
+ ### From source
70
+ ```bash
71
+ git clone https://github.com/ezapmar/kolay-cli.git
72
+ cd kolay-cli
73
+ pip install -e .
74
+ ```
75
+
76
+ ## 🚀 Quick Start
77
+
78
+ ### 1. Authenticate
79
+ Generate an API token from [Kolay Developer Settings](https://app.kolayik.com/settings/developer-settings), then:
80
+ ```bash
81
+ kolay auth login
82
+ ```
83
+
84
+ ### 2. List your employees
85
+ ```bash
86
+ kolay person list
87
+ ```
88
+
89
+ ### 3. View someone's profile
90
+ ```bash
91
+ kolay person view <person-id>
92
+ ```
93
+
94
+ ### 4. Check leave balances
95
+ ```bash
96
+ kolay person leave-status <person-id>
97
+ ```
98
+
99
+ ### 5. Create a leave request
100
+ ```bash
101
+ kolay leave create
102
+ ```
103
+ The CLI will interactively show available leave types and let you pick by number.
104
+
105
+ ## 📖 Command Reference
106
+
107
+ ### Auth
108
+ ```bash
109
+ kolay auth login # Save your API token (prompted securely)
110
+ kolay auth status # Check if you're logged in
111
+ ```
112
+
113
+ ### Person
114
+ ```bash
115
+ kolay person list # List active employees
116
+ kolay person list --status inactive # List inactive employees
117
+ kolay person list --page 2 # Paginate results
118
+ kolay person view <id> # Structured profile view
119
+ kolay person leave-status <id> # Leave balances table
120
+ kolay person terminate <id> # Terminate with SGK reason codes
121
+ ```
122
+
123
+ ### Leave
124
+ ```bash
125
+ kolay leave list # List approved leaves (current year)
126
+ kolay leave list --status waiting # List pending leaves
127
+ kolay leave list --person-id <id> # Filter by person
128
+ kolay leave list --start-date "2025-01-01 00:00:00" --end-date "2025-12-31 23:59:59"
129
+ kolay leave view <leave-id> # View leave record details
130
+ kolay leave create # Interactive leave creation
131
+ ```
132
+
133
+ ## ⚙️ Configuration
134
+
135
+ The CLI stores its configuration at `~/.config/kolay/config.json` with restricted file permissions.
136
+
137
+ You can also use environment variables (they take precedence):
138
+ ```bash
139
+ export KOLAY_API_TOKEN="your-token-here"
140
+ export KOLAY_BASE_URL="https://api.kolayik.com" # optional
141
+ ```
142
+
143
+ ## 🛠 Development
144
+
145
+ ```bash
146
+ # Clone and setup
147
+ git clone https://github.com/ezapmar/kolay-cli.git
148
+ cd kolay-cli
149
+ python -m venv .venv
150
+ source .venv/bin/activate # or .venv\Scripts\activate on Windows
151
+ pip install -e .
152
+
153
+ # Run
154
+ kolay --help
155
+ ```
156
+
157
+ ## 📄 License
158
+
159
+ This project is licensed under the [MIT License](LICENSE) — use it freely, modify it, share it. See the `LICENSE` file for details.
@@ -0,0 +1,17 @@
1
+ LICENSE
2
+ README.md
3
+ pyproject.toml
4
+ src/kolay_cli/__init__.py
5
+ src/kolay_cli/api.py
6
+ src/kolay_cli/cli.py
7
+ src/kolay_cli/config.py
8
+ src/kolay_cli.egg-info/PKG-INFO
9
+ src/kolay_cli.egg-info/SOURCES.txt
10
+ src/kolay_cli.egg-info/dependency_links.txt
11
+ src/kolay_cli.egg-info/entry_points.txt
12
+ src/kolay_cli.egg-info/requires.txt
13
+ src/kolay_cli.egg-info/top_level.txt
14
+ src/kolay_cli/commands/__init__.py
15
+ src/kolay_cli/commands/auth.py
16
+ src/kolay_cli/commands/leave.py
17
+ src/kolay_cli/commands/person.py
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ kolay = kolay_cli.cli:app
@@ -0,0 +1,3 @@
1
+ typer>=0.9.0
2
+ requests>=2.31.0
3
+ rich>=13.4.2
@@ -0,0 +1 @@
1
+ kolay_cli