@srafis/zsync 0.1.1 → 0.1.2
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/README.md +156 -132
- package/dist/zsync.js +12 -9
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -1,155 +1,179 @@
|
|
|
1
1
|
# zsync
|
|
2
2
|
|
|
3
|
-
|
|
4
|
-
entries, map projects to jobs, review the changes, and confirm. No scheduler.
|
|
3
|
+
Keep tracking time in Clockify and sync your entries to Zoho People when you're ready.
|
|
5
4
|
|
|
6
|
-
|
|
5
|
+
zsync is a terminal app for people who want to keep using their personal Clockify workspace while maintaining their work timesheet in Zoho. It offers another way to log time alongside the Zoho Chrome extension: use the Clockify interface you already know, then choose which completed entries to copy across.
|
|
6
|
+
|
|
7
|
+
Each run lets you select a period, pick entries, map Clockify projects to Zoho jobs, and confirm the sync. Nothing runs in the background.
|
|
8
|
+
|
|
9
|
+
## Get started
|
|
10
|
+
|
|
11
|
+
You need Bun, an interactive terminal, a Clockify account, and access to Zoho People's time tracker with an assigned job.
|
|
12
|
+
|
|
13
|
+
From this repository:
|
|
7
14
|
|
|
8
15
|
```sh
|
|
9
16
|
bun install
|
|
10
|
-
bun run dev --help
|
|
11
17
|
bun run dev --demo
|
|
18
|
+
```
|
|
19
|
+
|
|
20
|
+
The demo uses fictional entries and makes no network requests. It lets you try the selection and confirmation flow before connecting your accounts.
|
|
21
|
+
|
|
22
|
+
## Connect your accounts
|
|
23
|
+
|
|
24
|
+
### 1. Configure Clockify
|
|
25
|
+
|
|
26
|
+
Use the API key for your own Clockify account, along with your user ID and the workspace ID you want to sync. The configured user must match the account that owns the API key. zsync syncs that user's entries only.
|
|
27
|
+
|
|
28
|
+
### 2. Configure Zoho
|
|
29
|
+
|
|
30
|
+
Create a server-based OAuth client for your Zoho data center and register this redirect URI exactly:
|
|
31
|
+
|
|
32
|
+
```text
|
|
33
|
+
http://localhost:8765/callback
|
|
34
|
+
```
|
|
35
|
+
|
|
36
|
+
You need the client's ID and secret. Your Zoho role must allow Time Tracker API access, and you need an eligible, assigned job to receive entries.
|
|
37
|
+
|
|
38
|
+
### 3. Set your credentials
|
|
39
|
+
|
|
40
|
+
Export these five variables in your terminal, replacing the placeholders:
|
|
41
|
+
|
|
42
|
+
```sh
|
|
43
|
+
export CLOCKIFY_API_KEY='your-clockify-api-key'
|
|
44
|
+
export CLOCKIFY_USER_ID='your-clockify-user-id'
|
|
45
|
+
export CLOCKIFY_WORKSPACE_ID='your-clockify-workspace-id'
|
|
46
|
+
export ZOHO_CLIENT_ID='your-zoho-client-id'
|
|
47
|
+
export ZOHO_CLIENT_SECRET='your-zoho-client-secret'
|
|
48
|
+
```
|
|
49
|
+
|
|
50
|
+
For local Bun runs, you can also put these values in a project `.env` file. Keep credentials out of version control. The built Node executable expects exported environment variables.
|
|
51
|
+
|
|
52
|
+
If your Zoho account uses a data center other than `people.zoho.com`, set its region before connecting. For example, for `people.zoho.in`:
|
|
53
|
+
|
|
54
|
+
```sh
|
|
55
|
+
export ZOHO_REGION='in'
|
|
56
|
+
```
|
|
57
|
+
|
|
58
|
+
### 4. Authorize Zoho
|
|
59
|
+
|
|
60
|
+
```sh
|
|
12
61
|
bun run dev
|
|
13
62
|
```
|
|
14
63
|
|
|
15
|
-
|
|
16
|
-
|
|
17
|
-
|
|
64
|
+
On the first run, zsync opens your browser for Zoho authorization. It uses a temporary local listener on port 8765, which must be available. Authorization expires after five minutes if you don't finish it.
|
|
65
|
+
|
|
66
|
+
zsync requests these scopes:
|
|
67
|
+
|
|
68
|
+
- `ZOHOPEOPLE.timetracker.ALL`
|
|
69
|
+
- `ZOHOPEOPLE.forms.READ`
|
|
70
|
+
- `AaaServer.profile.READ`
|
|
18
71
|
|
|
19
|
-
After
|
|
20
|
-
`npm i -g @srafis/zsync` and run `zsync`.
|
|
72
|
+
After authorization, it looks up your People employee record using your email. If that lookup is unavailable, it asks for your numeric employee record ID, `ERECNO`. This is different from your displayed employee number.
|
|
21
73
|
|
|
22
|
-
|
|
74
|
+
The refresh token and employee record ID are saved locally for later runs. To authorize again:
|
|
23
75
|
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
Keep the token in GitHub Secrets, never in this repository.
|
|
76
|
+
```sh
|
|
77
|
+
bun run dev --connect
|
|
78
|
+
```
|
|
28
79
|
|
|
29
|
-
|
|
30
|
-
(starting at `0.1.1`). The version changes only in CI; no release commits are made.
|
|
31
|
-
Rerunning a published version skips publication. Bump the major/minor in
|
|
32
|
-
package.json when needed. The npm scope must belong to your account or organization.
|
|
80
|
+
Reconnecting preserves your saved job mappings.
|
|
33
81
|
|
|
34
|
-
|
|
82
|
+
## Sync your time
|
|
83
|
+
|
|
84
|
+
Track time in Clockify as usual, stop any timers you want to sync, then run:
|
|
35
85
|
|
|
36
86
|
```sh
|
|
87
|
+
bun run dev
|
|
88
|
+
```
|
|
89
|
+
|
|
90
|
+
1. Choose Today, Yesterday, This week, Last week, or This month.
|
|
91
|
+
2. Use the arrow keys to move through entries and Space to select or deselect them. Press Enter to continue.
|
|
92
|
+
3. Choose a Zoho job for each unmapped Clockify project. zsync remembers your choices. An exact, unique match with a Zoho project or job name is selected automatically.
|
|
93
|
+
4. Review your selection and submit the final Yes/No prompt. Yes is selected initially, but you still have to confirm it.
|
|
94
|
+
|
|
95
|
+
New entries start selected. Previously synced entries, including changed ones, start unselected. Select a changed entry to update its existing Zoho log. An unchanged entry is skipped.
|
|
96
|
+
|
|
97
|
+
Choosing No or cancelling before the sync makes no time-log changes in Zoho. Job mappings may already have been saved locally.
|
|
98
|
+
|
|
99
|
+
## What gets copied
|
|
100
|
+
|
|
101
|
+
| Clockify field | Zoho field or behavior |
|
|
102
|
+
| ------------------------------------------------------ | --------------------------------------------------------- |
|
|
103
|
+
| Description | Work Item |
|
|
104
|
+
| Project | The Zoho job you selected or matched |
|
|
105
|
+
| Duration | Hours, rounded to the nearest minute |
|
|
106
|
+
| Start date | Work date in your configured timezone |
|
|
107
|
+
| Billable flag | Billing status |
|
|
108
|
+
| Entry ID, project, tags, start/end times, billing flag | Readable YAML metadata in Description, with a sync marker |
|
|
109
|
+
|
|
110
|
+
Only completed entries are included. Weeks start on Monday, and current periods end at the time you start the CLI.
|
|
111
|
+
|
|
112
|
+
Entries belong to their local start date. A timer that crosses midnight is copied whole to that date. Entries longer than 24 hours or shorter than the duration that rounds to one minute must be corrected in Clockify first.
|
|
113
|
+
|
|
114
|
+
zsync creates duration-based logs. The original start and end timestamps are retained in metadata. It does not create Zoho projects or jobs.
|
|
115
|
+
|
|
116
|
+
## Running sync again
|
|
117
|
+
|
|
118
|
+
zsync writes readable YAML metadata in the Zoho log's Description and recognizes entries by a sync marker derived from their Clockify ID. Existing JSON descriptions are still recognized; selecting one updates it to YAML. Keep that metadata intact so later runs can find the existing log. Recognition works across machines and OAuth clients without a local sync ledger.
|
|
119
|
+
|
|
120
|
+
Selecting a changed entry overwrites differing Zoho fields with the Clockify values. Older marker-only logs are also recognized within their original account scope; selecting them updates their Work Item and metadata to the current format.
|
|
121
|
+
|
|
122
|
+
A manually entered Zoho log without sync metadata is not treated as a match, even if its title and duration are identical. Deleting a synced Zoho log makes its Clockify entry appear new again.
|
|
123
|
+
|
|
124
|
+
Before writing, zsync checks for changes in both services. It verifies each write afterward and attempts to reconcile an uncertain response without blindly repeating the write. If an entry remains `uncertain`, inspect it in Zoho before retrying. Failures are reported per entry, and failed or uncertain results produce a nonzero exit status. A run can partially succeed.
|
|
125
|
+
|
|
126
|
+
## Limits to know
|
|
127
|
+
|
|
128
|
+
- Sync runs one way, from Clockify to Zoho. It does not copy Zoho edits back, delete Zoho logs, or submit or approve timesheets.
|
|
129
|
+
- Locked or approved logs and multiple Zoho logs identifying the same Clockify entry are conflicts. Resolve them or deselect those entries before continuing.
|
|
130
|
+
- Lookup covers the selected entries' date span. If you move a previously synced entry to a date outside that span, reconcile its old Zoho log before syncing again.
|
|
131
|
+
- Run one sync at a time. There is no protection against simultaneous runs across terminals or machines.
|
|
132
|
+
- Clockify regional API endpoints are not supported. The app uses `api.clockify.me`.
|
|
133
|
+
- Zoho attendance, leave, date restrictions, and job permissions still apply and may cause a write to be rejected.
|
|
134
|
+
|
|
135
|
+
For your first real sync, select a few entries and check the resulting logs in Zoho.
|
|
136
|
+
|
|
137
|
+
## Optional configuration
|
|
138
|
+
|
|
139
|
+
| Variable | Purpose | Default |
|
|
140
|
+
| -------------------- | -------------------------------------------------------------------------- | --------------------------------- |
|
|
141
|
+
| `ZOHO_REGION` | Zoho data center: `com`, `eu`, `in`, `au`, `cn`, `jp`, `ca`, `sa`, or `uk` | Saved region, otherwise `com` |
|
|
142
|
+
| `ZSYNC_TIMEZONE` | IANA timezone, such as `Asia/Kolkata` | System timezone |
|
|
143
|
+
| `ZOHO_DATE_FORMAT` | Date format used by your Zoho organization | `yyyy-MM-dd` |
|
|
144
|
+
| `ZSYNC_STATE_DIR` | Directory for saved authentication and job mappings | Platform-specific directory below |
|
|
145
|
+
| `ZOHO_REFRESH_TOKEN` | Override the saved refresh token | Saved token |
|
|
146
|
+
| `ZOHO_EMPLOYEE_ID` | Override the saved employee record ID, `ERECNO` | Saved employee ID |
|
|
147
|
+
|
|
148
|
+
Supported date formats are `yyyy-MM-dd`, `dd-MM-yyyy`, `MM-dd-yyyy`, `yyyy/MM/dd`, `dd/MM/yyyy`, and `MM/dd/yyyy`.
|
|
149
|
+
|
|
150
|
+
Authentication and job preferences are stored per account. The authentication file contains a refresh token and is created with owner-only file permissions, `0600`. Client secrets are not saved by zsync.
|
|
151
|
+
|
|
152
|
+
Default storage locations:
|
|
153
|
+
|
|
154
|
+
- Linux: `$XDG_DATA_HOME/zsync`, or `~/.local/share/zsync`
|
|
155
|
+
- macOS: `~/Library/Application Support/zsync`
|
|
156
|
+
- Windows: `%APPDATA%/zsync`, with a local AppData fallback
|
|
157
|
+
|
|
158
|
+
## Development
|
|
159
|
+
|
|
160
|
+
Use Bun for dependency installation, development, and checks:
|
|
161
|
+
|
|
162
|
+
```sh
|
|
163
|
+
bun install
|
|
164
|
+
bun run dev --help
|
|
37
165
|
bun run typecheck
|
|
38
166
|
bun test
|
|
39
167
|
bun run build
|
|
40
168
|
bun pm pack
|
|
41
169
|
```
|
|
42
170
|
|
|
43
|
-
|
|
44
|
-
|
|
45
|
-
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
match the authenticated account. API keys are available in Clockify profile settings.
|
|
53
|
-
|
|
54
|
-
Only five shell exports are required: `CLOCKIFY_API_KEY`, `CLOCKIFY_USER_ID`,
|
|
55
|
-
`CLOCKIFY_WORKSPACE_ID`, `ZOHO_CLIENT_ID`, and `ZOHO_CLIENT_SECRET`.
|
|
56
|
-
|
|
57
|
-
On first run, browser authorization starts automatically. The default data center
|
|
58
|
-
is `people.zoho.com`; `ZOHO_REGION` remains an optional override.
|
|
59
|
-
Register `http://localhost:8765/callback` in your server-based OAuth client once.
|
|
60
|
-
The CLI starts a temporary loopback listener and opens the consent URL. After
|
|
61
|
-
authorization, the CLI continues automatically. The listener closes after the
|
|
62
|
-
callback, cancellation, or a five-minute timeout. Port 8765 must be available.
|
|
63
|
-
|
|
64
|
-
The CLI requests `ZOHOPEOPLE.timetracker.ALL`, `ZOHOPEOPLE.forms.READ`, and
|
|
65
|
-
`AaaServer.profile.READ`. It exchanges the code, looks up your People employee
|
|
66
|
-
record using your email, and saves the refresh token, region, and employee ID.
|
|
67
|
-
If employee lookup is unavailable, it asks for the numeric employee record ID
|
|
68
|
-
(ERECNO). Later runs reuse the saved authentication without additional exports.
|
|
69
|
-
Run `bun run dev --connect` to reconnect; saved job preferences are preserved.
|
|
70
|
-
|
|
71
|
-
Existing `ZOHO_REFRESH_TOKEN`, `ZOHO_REGION`, and `ZOHO_EMPLOYEE_ID` exports
|
|
72
|
-
remain optional overrides. Set `ZOHO_DATE_FORMAT` when your company returns
|
|
73
|
-
non-ISO dates, for example `dd-MM-yyyy` or `MM/dd/yyyy`.
|
|
74
|
-
|
|
75
|
-
- [Zoho People OAuth setup](https://www.zoho.com/people/api/oauth-steps.html)
|
|
76
|
-
- [Zoho People time-log API](https://www.zoho.com/people/api/timesheet/add-timelogs.html)
|
|
77
|
-
- [Clockify API](https://docs.clockify.me/)
|
|
78
|
-
|
|
79
|
-
The CLI refreshes access tokens in memory. Local preferences contain project/job mappings. A separate account-scoped authentication file stores the refresh token
|
|
80
|
-
with owner-only file permissions (0600); client secrets are never saved. Protect
|
|
81
|
-
the state directory because it now contains authentication and work information.
|
|
82
|
-
|
|
83
|
-
## Selection and mapping
|
|
84
|
-
|
|
85
|
-
The range menu contains Today, Yesterday, This week, Last week, and This month.
|
|
86
|
-
Weeks start on Monday. Current periods stop at the time the CLI starts; completed
|
|
87
|
-
periods use local calendar boundaries. Set `ZSYNC_TIMEZONE` to an IANA timezone if
|
|
88
|
-
the machine's timezone differs from yours.
|
|
89
|
-
|
|
90
|
-
Completed entries are assigned to their **local start date**. An entry beginning
|
|
91
|
-
before midnight and ending afterward is copied whole to its start date. Entries
|
|
92
|
-
longer than 24 hours or rounding to zero minutes are rejected for correction in
|
|
93
|
-
Clockify. Running timers are excluded. Durations round to the nearest minute, and
|
|
94
|
-
the preview shows the duration sent to Zoho. Clockify descriptions become Zoho
|
|
95
|
-
Work Items. Zoho Description stores JSON source metadata: the exact Clockify entry
|
|
96
|
-
ID, project name/ID, tags, original start/end timestamps, and billing flag, followed
|
|
97
|
-
by the existing sync marker. Metadata contains no credentials.
|
|
98
|
-
|
|
99
|
-
Older synced entries remain unchecked. Select one to move its title into Work Item
|
|
100
|
-
and replace its Description with metadata, updating the existing Zoho log. Selected entries overwrite differing destination fields with Clockify values.
|
|
101
|
-
|
|
102
|
-
Unsynced entries start checked. Synced entries, including changed ones, start
|
|
103
|
-
unchecked. Selecting an unchanged entry skips it; selecting a changed entry updates
|
|
104
|
-
its existing Zoho log when safe. If a Clockify project has exactly one matching
|
|
105
|
-
Zoho project/job name, that job is used. Otherwise, select the job explicitly.
|
|
106
|
-
Mappings are remembered per account. No Zoho jobs or projects are created.
|
|
107
|
-
|
|
108
|
-
The entry table fits the terminal width and truncates long cells with an ellipsis.
|
|
109
|
-
Date appears from 90 columns and Tags from 110 columns. The selected rows remain visible above the final confirmation.
|
|
110
|
-
|
|
111
|
-
Use Space to toggle entries and Enter to continue. The final Yes/No prompt requires
|
|
112
|
-
submission even though Yes is initially selected. No and cancellation make no Zoho
|
|
113
|
-
writes; local job mappings may already have been saved.
|
|
114
|
-
|
|
115
|
-
## Reruns and recovery
|
|
116
|
-
|
|
117
|
-
Sync status comes from metadata in Zoho Description, using the exact Clockify
|
|
118
|
-
entry ID. Matching logs start unchecked; selecting a changed entry updates its
|
|
119
|
-
existing log. Deleting a Zoho log makes the Clockify entry new and checked again.
|
|
120
|
-
Duplicate source IDs and locked logs are conflicts. Unmarked manual logs do not
|
|
121
|
-
count as synced, even if their titles and durations match.
|
|
122
|
-
|
|
123
|
-
Only authentication and project/job preferences are stored locally. Existing
|
|
124
|
-
legacy state files supply job preferences only; their ledger, pending writes, and
|
|
125
|
-
lock are ignored. New writes store preferences in `zsync-preferences-*.json`.
|
|
126
|
-
Legacy marker-only logs are recognized with their original account scope; logs
|
|
127
|
-
containing the exact entry ID work across machines and OAuth clients.
|
|
128
|
-
|
|
129
|
-
The CLI rechecks Zoho before committing and verifies each write. A timed-out write
|
|
130
|
-
is reconciled using remote metadata, never blindly retried in the same run. If
|
|
131
|
-
verification remains uncertain, inspect Zoho before rerunning. No persistent pending
|
|
132
|
-
queue or concurrent-execution protection is provided.
|
|
133
|
-
|
|
134
|
-
Lookup covers the selected entries' date span. If a previously synced entry moves
|
|
135
|
-
to a different date outside that span, reconcile the old Zoho log before syncing
|
|
136
|
-
again. Do not run syncs simultaneously on multiple machines.
|
|
137
|
-
|
|
138
|
-
Clockify remains the source of truth. This tool does not delete destination logs,
|
|
139
|
-
submit or approve timesheets, or synchronize Zoho edits back to Clockify. Zoho
|
|
140
|
-
workspace rules can reject time outside attendance, leave, allowed dates, or job
|
|
141
|
-
permissions. Failures are reported per entry and cause a nonzero exit status.
|
|
142
|
-
|
|
143
|
-
## Before first real sync
|
|
144
|
-
|
|
145
|
-
Run `--demo` to inspect the flow without credentials or network calls. Then use a
|
|
146
|
-
small authorized selection and verify its destination in Zoho People. Live API
|
|
147
|
-
behavior and organization policies must be checked in your own account; automated
|
|
148
|
-
tests use fictional responses and never create actual time logs.
|
|
149
|
-
|
|
150
|
-
Default state directories are `~/.local/share/zsync` on Linux (or under
|
|
151
|
-
`XDG_DATA_HOME`), `~/Library/Application Support/zsync` on macOS, and
|
|
152
|
-
`%APPDATA%/zsync` on Windows. Changing the OAuth client or employee changes the
|
|
153
|
-
account scope for local preferences. Exact entry IDs in remote metadata still
|
|
154
|
-
identify synced entries across OAuth clients. Supported Zoho region codes are `com`, `eu`, `in`, `au`, `cn`, `jp`,
|
|
155
|
-
`ca`, `sa`, and `uk`; availability and permissions depend on your People account.
|
|
171
|
+
The build produces `dist/zsync.js`, a Node 22+ executable exposed as `zsync` by the `@srafis/zsync` package. Bun is used for development; the built executable can run without it.
|
|
172
|
+
|
|
173
|
+
Tests use fictional API responses and do not create real time logs.
|
|
174
|
+
|
|
175
|
+
### Publishing
|
|
176
|
+
|
|
177
|
+
The GitHub Actions workflow in `.github/workflows/publish.yml` tests, builds, and publishes the package on pushes to `main`. Configure the repository's `NPM_TOKEN` Actions secret with permission to publish `@srafis/zsync` and bypass 2FA for CI.
|
|
178
|
+
|
|
179
|
+
CI adds the workflow run number to the patch version in `package.json`. It does not commit that version change. Republishing the same version is tolerated. Change the major or minor version in `package.json` when needed.
|
package/dist/zsync.js
CHANGED
|
@@ -5717,15 +5717,18 @@ function entryInput(entry, jobId, employeeId, zone) {
|
|
|
5717
5717
|
date: start.toZonedDateTimeISO(zone).toPlainDate().toString(),
|
|
5718
5718
|
minutes,
|
|
5719
5719
|
workItem: entry.description,
|
|
5720
|
-
description:
|
|
5721
|
-
source:
|
|
5722
|
-
entryId: entry.id
|
|
5723
|
-
project:
|
|
5724
|
-
|
|
5725
|
-
|
|
5726
|
-
|
|
5727
|
-
|
|
5728
|
-
|
|
5720
|
+
description: [
|
|
5721
|
+
"source: Clockify",
|
|
5722
|
+
`entryId: ${JSON.stringify(entry.id)}`,
|
|
5723
|
+
"project:",
|
|
5724
|
+
` id: ${JSON.stringify(entry.projectId)}`,
|
|
5725
|
+
` name: ${JSON.stringify(entry.projectName)}`,
|
|
5726
|
+
`tags: ${JSON.stringify(entry.tags)}`,
|
|
5727
|
+
`start: ${JSON.stringify(entry.start)}`,
|
|
5728
|
+
`end: ${JSON.stringify(entry.end)}`,
|
|
5729
|
+
`billable: ${entry.billable}`
|
|
5730
|
+
].join(`
|
|
5731
|
+
`),
|
|
5729
5732
|
billable: entry.billable
|
|
5730
5733
|
};
|
|
5731
5734
|
}
|
package/package.json
CHANGED