@patimweb/pi-zep 1.0.0
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.
- package/.node-version +1 -0
- package/LICENSE +21 -0
- package/README.md +179 -0
- package/index.ts +49 -0
- package/package.json +53 -0
- package/src/clients/zep-client.ts +587 -0
- package/src/clients/zep-parse.ts +423 -0
- package/src/config.ts +155 -0
- package/src/formatting/formatters.ts +92 -0
- package/src/session.ts +86 -0
- package/src/tools/shared.ts +44 -0
- package/src/tools/zep-book.ts +323 -0
- package/src/tools/zep-delete.ts +42 -0
- package/src/tools/zep-login.ts +75 -0
- package/src/tools/zep-profile.ts +73 -0
- package/src/tools/zep-projects.ts +78 -0
- package/src/tools/zep-setup.ts +74 -0
- package/src/tools/zep-status.ts +103 -0
- package/src/tools/zep-week.ts +45 -0
- package/src/types.ts +151 -0
- package/tsconfig.json +18 -0
package/.node-version
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
26.5.0
|
package/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Patrick Weppelmann
|
|
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.
|
package/README.md
ADDED
|
@@ -0,0 +1,179 @@
|
|
|
1
|
+
# pi-zep
|
|
2
|
+
|
|
3
|
+
A [pi](https://github.com/earendil-works/pi) extension that books project times
|
|
4
|
+
(**Projektzeiten**) in ZEP over plain HTTP — no browser, and no paid ZEP REST API.
|
|
5
|
+
|
|
6
|
+
## Why
|
|
7
|
+
|
|
8
|
+
ZEP's official REST API is a paid add-on and, at least up to ZEP v7.13.88,
|
|
9
|
+
cannot create a Projektzeit booking at all. The web UI itself talks to a plain
|
|
10
|
+
PHP endpoint that can, and it needs no API key. This extension speaks that
|
|
11
|
+
endpoint directly.
|
|
12
|
+
|
|
13
|
+
## How it works
|
|
14
|
+
|
|
15
|
+
The whole protocol was reverse-engineered from the web client. Three parts:
|
|
16
|
+
|
|
17
|
+
| Step | Request |
|
|
18
|
+
|------|---------|
|
|
19
|
+
| Login | `POST {root}/view/login.php` with `userid`, `password`, `login` → `302` whose `Location` carries the new `CLIENTSESSID` |
|
|
20
|
+
| Page / token | `GET {root}/view/index.php?CLIENTSESSID=…&menu=ProjektzeitVerwaltungMgr&action=save` → HTML containing `var requesttoken = '…'` |
|
|
21
|
+
| Booking | `POST {root}/view/ajax.php?CLIENTSESSID=…&pageContextId=Projektzeiten&mgr=ProjektzeitMgr&mgrId=3&action=save&JS_ENV_VAR_isFast=true&JS_ENV_VAR_locale=de&json=true&ajax=1` |
|
|
22
|
+
|
|
23
|
+
`{root}` is `<baseUrl>/<mandant>`, e.g. `https://zep-online.de/zepneoimpulse`.
|
|
24
|
+
|
|
25
|
+
The booking request needs:
|
|
26
|
+
|
|
27
|
+
* the **`x-requesttoken`** header (mandatory), plus `requesttoken` in the body,
|
|
28
|
+
* the **`PHPSESSID`** cookie **and** the `CLIENTSESSID` query parameter — neither alone is accepted,
|
|
29
|
+
* a form-urlencoded body with `datum`, `von`, `bis`, `dauer`, `projektId`, `vorgangId`, `taetigkeit`, `bemerkung`, `ort`, …
|
|
30
|
+
|
|
31
|
+
### Three things that bite
|
|
32
|
+
|
|
33
|
+
1. **The token rotates.** Every response — including errors — hands out the next
|
|
34
|
+
`requesttoken`. This client adopts it automatically. Reusing an old token is
|
|
35
|
+
not merely rejected: it makes the server **log the session out**
|
|
36
|
+
(`msg=555001` / `555003` / `555009`) and you have to log in again.
|
|
37
|
+
2. **Never send an ajax call without a token.** The client fetches the page first
|
|
38
|
+
when it does not hold a fresh token, so it can never fall into (1).
|
|
39
|
+
3. **ZEP locks the account after 5 failed password attempts.** The client
|
|
40
|
+
therefore caches the session in `~/.pi/zep-session.json` and only logs in
|
|
41
|
+
again when the cached session stops working. A failed login reports the
|
|
42
|
+
remaining attempts (`noch=N`).
|
|
43
|
+
|
|
44
|
+
Reads and deletes use the same envelope:
|
|
45
|
+
|
|
46
|
+
```
|
|
47
|
+
GET …/ajax.php?…&mgr=ProjektzeitMgr&mgrId=0&action=setKw&kwDate=<monday> # week overview
|
|
48
|
+
GET …/ajax.php?…&mgr=ProjektzeitMgr&mgrId=0&action=delete&objectId=<id>&kwDate=<monday>&userId=<user>
|
|
49
|
+
POST …/ajax.php?…&mgrId=3&action=save&formrefresh=true&formrefreshtrigger=projektId # re-render the form for another project
|
|
50
|
+
```
|
|
51
|
+
|
|
52
|
+
The last one is the UI's own mechanism for changing the project: the Vorgang
|
|
53
|
+
list is rendered server-side, so it only contains the Vorgänge of the project
|
|
54
|
+
the page was rendered for. Posting `formrefresh=true` returns the new
|
|
55
|
+
`<select id="vorgangId">` without saving anything.
|
|
56
|
+
|
|
57
|
+
### Response shapes (verified against the live server)
|
|
58
|
+
|
|
59
|
+
| Outcome | `error` | `javascript` | content |
|
|
60
|
+
|---------|---------|--------------|---------|
|
|
61
|
+
| accepted | `null` | `dispatchZepEvent('zepProjektzeitMgrRefresh')` | calendar snippet |
|
|
62
|
+
| rejected | `"error"` | `null` | snippet containing `alert-message` with the reason |
|
|
63
|
+
|
|
64
|
+
**A rejection does not arrive in `javascript`.** It arrives as a `schnipsel`
|
|
65
|
+
whose target is a form message slot:
|
|
66
|
+
|
|
67
|
+
```html
|
|
68
|
+
<div class="card-alert alert alert-danger">
|
|
69
|
+
<div class="alert-message">Projektzeit Do, 17.09.2026, 08:00 - 08:15 Speichern
|
|
70
|
+
fehlgeschlagen. Diese Zeit kann nicht gebucht werden, da dadurch die Plan
|
|
71
|
+
Stunden des Projekts/Vorgangs überschritten würden. Projektleiter wurden ggf.
|
|
72
|
+
per E-Mail informiert.</div>
|
|
73
|
+
</div>
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
A client that only inspects `javascript` would report a useless "unspecified
|
|
77
|
+
error" here. `extractSnippetAlert()` reads the real text.
|
|
78
|
+
|
|
79
|
+
Other message texts observed:
|
|
80
|
+
|
|
81
|
+
* `… Tätigkeit co bei Vorgang 90-02 nicht erlaubt.` — the activity is not allowed for that Vorgang (90-02 only accepts `co_nb`).
|
|
82
|
+
* `… Plan Stunden des Projekts/Vorgangs überschritten…` — the Vorgang's planned hours are used up.
|
|
83
|
+
|
|
84
|
+
### Week table quirks
|
|
85
|
+
|
|
86
|
+
* The `setKw` snippet carries **no `target`** — it has to be recognised by content (`pz_kwnav`, `id="objectId…"`, `class="tag_…"`), never by target.
|
|
87
|
+
* The save response ships a *calendar* snippet targeting `managerdiv_ProjektzeitMgr`, which must not be mistaken for the week table.
|
|
88
|
+
* The ISO date of a day row is in `id="day_YYYY-MM-DD"` (fallback: the `openMahlzeiten(this, 'YYYY-MM-DD')` onclick, then the weekday + `DD.MM.` label).
|
|
89
|
+
* A day row's label is split across `<div>Do</div><div>17.09.</div>`, so plain `innerText` yields `Do17.09.` while tag-stripping yields `Do 17.09.` — parse the latter.
|
|
90
|
+
* Booking rows have 10 cells (`action, von, bis, Dauer, action, Projekt, Vorgang, Tät., fakturierbar, Bemerkung`); the `class="luecke"` spacer rows in between have 1 and must be skipped; the sum row has 3.
|
|
91
|
+
|
|
92
|
+
## Tools
|
|
93
|
+
|
|
94
|
+
| Tool | Purpose |
|
|
95
|
+
|------|---------|
|
|
96
|
+
| `zep_setup` | Store `userid`, `password`, `mandant` (default `zepneoimpulse`) |
|
|
97
|
+
| `zep_login` | Log in with the stored credentials, cache the session (`force` to ignore the cache) |
|
|
98
|
+
| `zep_status` | Show profiles and verify the live session (`probe` loads the booking form) |
|
|
99
|
+
| `zep_profile` | List / switch / delete profiles, `clear-session` |
|
|
100
|
+
| `zep_projects` | List projects and, per project, its Vorgänge and Tätigkeiten |
|
|
101
|
+
| `zep_week` | Week overview: bookings with row id, times, duration, sum, under-booked days |
|
|
102
|
+
| `zep_book` | Create bookings — one block or an `entries` array; `dryRun` supported |
|
|
103
|
+
| `zep_delete` | Delete a booking by row id |
|
|
104
|
+
|
|
105
|
+
## Usage
|
|
106
|
+
|
|
107
|
+
```
|
|
108
|
+
zep_setup name=work userid=patrick.weppelmann password=… mandant=zepneoimpulse
|
|
109
|
+
zep_status probe=true
|
|
110
|
+
zep_projects projekt=Julabo
|
|
111
|
+
zep_week
|
|
112
|
+
zep_book datum=2026-09-17 von=08:00 bis=12:00 projekt=413 vorgang=90-01 taetigkeit=co bemerkung="3447 Ladeanzeige nach Pick"
|
|
113
|
+
zep_book datum=2026-09-17 entries=[{von:"08:00",bis:"12:00",bemerkung:"…"},{von:"13:00",bis:"17:00",bemerkung:"…"}]
|
|
114
|
+
```
|
|
115
|
+
|
|
116
|
+
`projekt` and `vorgang` accept either an id (`413`, `3284`) or part of the label
|
|
117
|
+
(`Julabo`, `90-01`). A full example, `dryRun` first:
|
|
118
|
+
|
|
119
|
+
```
|
|
120
|
+
zep_book projekt=Julabo vorgang=90-01 datum=2026-09-17 von=08:00 bis=12:00 dryRun=true
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
## Important behaviour
|
|
124
|
+
|
|
125
|
+
* **ZEP rejects rather than overwrites.** A booking that would exceed the
|
|
126
|
+
Vorgang's planned hours comes back as *"Diese Zeit kann nicht gebucht werden,
|
|
127
|
+
da dadurch die Plan Stunden des Projekts/Vorgangs überschritten würden.
|
|
128
|
+
Projektleiter wurden ggf. per E-Mail informiert."* This extension reports that
|
|
129
|
+
text verbatim and never silently retries on another Vorgang.
|
|
130
|
+
* **`fakturierbar` is derived server-side** from project + Vorgang + Tätigkeit.
|
|
131
|
+
The field is `disabled` in the UI and is deliberately not sent.
|
|
132
|
+
* **`jpeg`-free, dependency-light:** the only runtime dependency is `typebox`.
|
|
133
|
+
|
|
134
|
+
## Verified live
|
|
135
|
+
|
|
136
|
+
The full write cycle was exercised against the real system on 2026-09-17:
|
|
137
|
+
form re-render for another project, a booking created and confirmed in the week
|
|
138
|
+
table, then deleted and confirmed gone. A rejected booking (plan hours
|
|
139
|
+
exhausted) and a rejected Tätigkeit were reproduced as well.
|
|
140
|
+
|
|
141
|
+
What is *not* covered by that test run: a successful password login, because
|
|
142
|
+
that requires the account password. The endpoint, the field names and the
|
|
143
|
+
failure contract (`302 → login.php?…&action=login&msg=1&noch=N`) were verified
|
|
144
|
+
with throwaway credentials; everything after the redirect was verified with a
|
|
145
|
+
real session.
|
|
146
|
+
|
|
147
|
+
## Files
|
|
148
|
+
|
|
149
|
+
```
|
|
150
|
+
index.ts extension entry point (tool registration)
|
|
151
|
+
src/types.ts plain data shapes
|
|
152
|
+
src/config.ts profile + session persistence (~/.pi, mode 0600)
|
|
153
|
+
src/clients/zep-client.ts HTTP session handling, booking, week, delete
|
|
154
|
+
src/clients/zep-parse.ts tolerant parsers for ZEP's HTML / ajax JSON
|
|
155
|
+
src/formatting/ domain data -> text
|
|
156
|
+
src/tools/ one tool per file
|
|
157
|
+
tests/ parser + wire-contract tests (stubbed fetch)
|
|
158
|
+
```
|
|
159
|
+
|
|
160
|
+
## Development
|
|
161
|
+
|
|
162
|
+
```bash
|
|
163
|
+
npm install
|
|
164
|
+
npm test # node --test
|
|
165
|
+
npx tsc --noEmit
|
|
166
|
+
```
|
|
167
|
+
|
|
168
|
+
The wire contract is pinned by `tests/zep-client.test.ts`, which asserts the
|
|
169
|
+
exact query parameters, headers and body fields against the live system's
|
|
170
|
+
observed behaviour.
|
|
171
|
+
|
|
172
|
+
## Configuration files
|
|
173
|
+
|
|
174
|
+
| File | Content |
|
|
175
|
+
|------|---------|
|
|
176
|
+
| `~/.pi/zep-config.json` | profiles with `userid` / `password` / `mandant` |
|
|
177
|
+
| `~/.pi/zep-session.json` | cached `CLIENTSESSID`, cookies and current `requesttoken` |
|
|
178
|
+
|
|
179
|
+
Both are written with mode `0600`.
|
package/index.ts
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* pi ZEP Extension
|
|
3
|
+
*
|
|
4
|
+
* A ZEP time-tracking client that talks to the zep-online.de web UI's own
|
|
5
|
+
* PHP endpoint instead of the paid ZEP REST API.
|
|
6
|
+
*
|
|
7
|
+
* Tools:
|
|
8
|
+
* - zep_setup: store userid/password/Mandant
|
|
9
|
+
* - zep_login: log in (password, or adopt a browser tab session)
|
|
10
|
+
* - zep_status: show config, log in, prove the session works
|
|
11
|
+
* - zep_profile: list/switch/delete profiles, clear cached sessions
|
|
12
|
+
* - zep_projects: list projects and their Vorgänge/Tätigkeiten
|
|
13
|
+
* - zep_week: read the Projektzeiten week overview
|
|
14
|
+
* - zep_book: create bookings (one or several blocks)
|
|
15
|
+
* - zep_delete: delete a booking
|
|
16
|
+
*
|
|
17
|
+
* Design:
|
|
18
|
+
* - types.ts plain data shapes
|
|
19
|
+
* - config.ts profile + session persistence (~/.pi)
|
|
20
|
+
* - clients/zep-client.ts HTTP session handling and the booking call
|
|
21
|
+
* - clients/zep-parse.ts tolerant parsers for ZEP's HTML/JSON
|
|
22
|
+
* - formatting/formatters.ts domain data -> text
|
|
23
|
+
* - tools/*.ts one tool per file
|
|
24
|
+
*/
|
|
25
|
+
|
|
26
|
+
import type { ExtensionAPI } from "@earendil-works/pi-coding-agent";
|
|
27
|
+
|
|
28
|
+
import { loadConfig } from "./src/config.ts";
|
|
29
|
+
import { ZepSetupTool } from "./src/tools/zep-setup.ts";
|
|
30
|
+
import { ZepLoginTool } from "./src/tools/zep-login.ts";
|
|
31
|
+
import { ZepStatusTool } from "./src/tools/zep-status.ts";
|
|
32
|
+
import { ZepProfileTool } from "./src/tools/zep-profile.ts";
|
|
33
|
+
import { ZepProjectsTool } from "./src/tools/zep-projects.ts";
|
|
34
|
+
import { ZepWeekTool } from "./src/tools/zep-week.ts";
|
|
35
|
+
import { ZepBookTool } from "./src/tools/zep-book.ts";
|
|
36
|
+
import { ZepDeleteTool } from "./src/tools/zep-delete.ts";
|
|
37
|
+
|
|
38
|
+
export default function (pi: ExtensionAPI) {
|
|
39
|
+
loadConfig();
|
|
40
|
+
|
|
41
|
+
pi.registerTool(ZepSetupTool);
|
|
42
|
+
pi.registerTool(ZepLoginTool);
|
|
43
|
+
pi.registerTool(ZepStatusTool);
|
|
44
|
+
pi.registerTool(ZepProfileTool);
|
|
45
|
+
pi.registerTool(ZepProjectsTool);
|
|
46
|
+
pi.registerTool(ZepWeekTool);
|
|
47
|
+
pi.registerTool(ZepBookTool);
|
|
48
|
+
pi.registerTool(ZepDeleteTool);
|
|
49
|
+
}
|
package/package.json
ADDED
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@patimweb/pi-zep",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"description": "ZEP time-tracking for pi coding agent — book project times (Projektzeiten) on zep-online.de over plain HTTP, without the paid ZEP API",
|
|
5
|
+
"keywords": [
|
|
6
|
+
"pi-package",
|
|
7
|
+
"zep",
|
|
8
|
+
"zep-online",
|
|
9
|
+
"time-tracking",
|
|
10
|
+
"zeiterfassung",
|
|
11
|
+
"projektzeiten",
|
|
12
|
+
"pi-extension"
|
|
13
|
+
],
|
|
14
|
+
"author": "Patrick Weppelmann",
|
|
15
|
+
"license": "MIT",
|
|
16
|
+
"type": "module",
|
|
17
|
+
"engines": {
|
|
18
|
+
"node": ">=26.0.0"
|
|
19
|
+
},
|
|
20
|
+
"repository": {
|
|
21
|
+
"type": "git",
|
|
22
|
+
"url": "https://github.com/Smotherer007/pi-zep.git"
|
|
23
|
+
},
|
|
24
|
+
"bugs": {
|
|
25
|
+
"url": "https://github.com/Smotherer007/pi-zep/issues"
|
|
26
|
+
},
|
|
27
|
+
"scripts": {
|
|
28
|
+
"build": "echo 'No build step defined'",
|
|
29
|
+
"test": "node --test 'tests/**/*.test.ts'",
|
|
30
|
+
"test:watch": "node --test --watch 'tests/**/*.test.ts'",
|
|
31
|
+
"typecheck": "tsc --noEmit",
|
|
32
|
+
"live-check": "node scripts/live-check.ts"
|
|
33
|
+
},
|
|
34
|
+
"dependencies": {
|
|
35
|
+
"typebox": "^1.2.8"
|
|
36
|
+
},
|
|
37
|
+
"devDependencies": {
|
|
38
|
+
"@earendil-works/pi-coding-agent": "0.85.1",
|
|
39
|
+
"@semantic-release/git": "^10.0.0",
|
|
40
|
+
"@types/node": "^26.5.1",
|
|
41
|
+
"semantic-release": "^25.0.0",
|
|
42
|
+
"typescript": "7.0.2"
|
|
43
|
+
},
|
|
44
|
+
"publishConfig": {
|
|
45
|
+
"access": "public"
|
|
46
|
+
},
|
|
47
|
+
"pi": {
|
|
48
|
+
"extensions": [
|
|
49
|
+
"./index.ts"
|
|
50
|
+
],
|
|
51
|
+
"image": "https://raw.githubusercontent.com/Smotherer007/pi-zep/main/screenshot.png"
|
|
52
|
+
}
|
|
53
|
+
}
|