tiime-cli 1.1.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/dist/index.js ADDED
@@ -0,0 +1,694 @@
1
+ // src/sdk/auth.ts
2
+ import { execSync } from "child_process";
3
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
4
+ import { homedir } from "os";
5
+ import { join } from "path";
6
+ import { ofetch } from "ofetch";
7
+ var AUTH0_DOMAIN = "auth0.tiime.fr";
8
+ var AUTH0_CLIENT_ID = "iEbsbe3o66gcTBfGRa012kj1Rb6vjAND";
9
+ var AUTH0_AUDIENCE = "https://chronos/";
10
+ var CONFIG_DIR = join(homedir(), ".config", "tiime");
11
+ var AUTH_FILE = join(CONFIG_DIR, "auth.json");
12
+ var KEYCHAIN_ACCOUNT = "tiime-cli";
13
+ var KEYCHAIN_SERVICE = "tiime-credentials";
14
+ var saveCredentialsToKeychain = (email, password) => {
15
+ try {
16
+ const payload = JSON.stringify({ email, password });
17
+ execSync(
18
+ `security add-generic-password -a "${KEYCHAIN_ACCOUNT}" -s "${KEYCHAIN_SERVICE}" -w '${payload.replace(/'/g, "'\\''")}' -U`,
19
+ { stdio: "ignore" }
20
+ );
21
+ return true;
22
+ } catch {
23
+ return false;
24
+ }
25
+ };
26
+ var loadCredentialsFromKeychain = () => {
27
+ try {
28
+ const raw = execSync(
29
+ `security find-generic-password -a "${KEYCHAIN_ACCOUNT}" -s "${KEYCHAIN_SERVICE}" -w`,
30
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
31
+ ).trim();
32
+ const data = JSON.parse(raw);
33
+ if (typeof data === "object" && data !== null && "email" in data && "password" in data && typeof data.email === "string" && typeof data.password === "string") {
34
+ return data;
35
+ }
36
+ return null;
37
+ } catch {
38
+ return null;
39
+ }
40
+ };
41
+ var saveCredentialsToFile = (email, password) => {
42
+ if (!existsSync(CONFIG_DIR)) {
43
+ mkdirSync(CONFIG_DIR, { recursive: true });
44
+ }
45
+ const filePath = join(CONFIG_DIR, "credentials.json");
46
+ writeFileSync(filePath, JSON.stringify({ email, password }, null, 2), {
47
+ mode: 384
48
+ });
49
+ };
50
+ var loadCredentialsFromFile = () => {
51
+ try {
52
+ const filePath = join(CONFIG_DIR, "credentials.json");
53
+ if (existsSync(filePath)) {
54
+ const data = JSON.parse(readFileSync(filePath, "utf-8"));
55
+ if (typeof data === "object" && data !== null && "email" in data && "password" in data && typeof data.email === "string" && typeof data.password === "string") {
56
+ return data;
57
+ }
58
+ }
59
+ } catch {
60
+ }
61
+ return null;
62
+ };
63
+ var saveCredentials = (email, password) => {
64
+ if (!saveCredentialsToKeychain(email, password)) {
65
+ saveCredentialsToFile(email, password);
66
+ }
67
+ };
68
+ var loadCredentials = () => {
69
+ return loadCredentialsFromKeychain() ?? loadCredentialsFromFile();
70
+ };
71
+ var TokenManager = class {
72
+ tokens = null;
73
+ constructor() {
74
+ this.loadFromDisk();
75
+ }
76
+ async login(email, password) {
77
+ const response = await ofetch(`https://${AUTH0_DOMAIN}/oauth/token`, {
78
+ method: "POST",
79
+ body: {
80
+ grant_type: "password",
81
+ client_id: AUTH0_CLIENT_ID,
82
+ audience: AUTH0_AUDIENCE,
83
+ scope: "openid email",
84
+ username: email,
85
+ password
86
+ }
87
+ });
88
+ this.tokens = {
89
+ access_token: response.access_token,
90
+ expires_at: Date.now() + response.expires_in * 1e3
91
+ };
92
+ this.saveToDisk();
93
+ saveCredentials(email, password);
94
+ return this.tokens;
95
+ }
96
+ async getValidToken() {
97
+ if (!this.tokens || this.isExpired()) {
98
+ const creds = loadCredentials();
99
+ if (creds) {
100
+ const tokens = await this.login(creds.email, creds.password);
101
+ return tokens.access_token;
102
+ }
103
+ throw new Error(
104
+ this.tokens ? "Token expired. Run `tiime auth login` to re-authenticate." : "Not authenticated. Run `tiime auth login` first."
105
+ );
106
+ }
107
+ return this.tokens.access_token;
108
+ }
109
+ isAuthenticated() {
110
+ return this.tokens !== null && !this.isExpired();
111
+ }
112
+ logout() {
113
+ this.tokens = null;
114
+ if (existsSync(AUTH_FILE)) {
115
+ writeFileSync(AUTH_FILE, "{}");
116
+ }
117
+ }
118
+ getTokenInfo() {
119
+ if (!this.tokens) {
120
+ return { email: null, expiresAt: null };
121
+ }
122
+ try {
123
+ const payload = JSON.parse(
124
+ Buffer.from(
125
+ this.tokens.access_token.split(".")[1],
126
+ "base64"
127
+ ).toString()
128
+ );
129
+ return {
130
+ email: payload["tiime/userEmail"] || null,
131
+ expiresAt: new Date(this.tokens.expires_at)
132
+ };
133
+ } catch {
134
+ return { email: null, expiresAt: new Date(this.tokens.expires_at) };
135
+ }
136
+ }
137
+ isExpired() {
138
+ if (!this.tokens) return true;
139
+ return Date.now() >= this.tokens.expires_at - 6e4;
140
+ }
141
+ loadFromDisk() {
142
+ try {
143
+ if (existsSync(AUTH_FILE)) {
144
+ const data = JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
145
+ if (data.access_token && data.expires_at) {
146
+ this.tokens = data;
147
+ }
148
+ }
149
+ } catch {
150
+ }
151
+ }
152
+ saveToDisk() {
153
+ if (!existsSync(CONFIG_DIR)) {
154
+ mkdirSync(CONFIG_DIR, { recursive: true });
155
+ }
156
+ writeFileSync(AUTH_FILE, JSON.stringify(this.tokens, null, 2));
157
+ }
158
+ };
159
+
160
+ // src/sdk/client.ts
161
+ import { ofetch as ofetch2 } from "ofetch";
162
+
163
+ // src/sdk/errors.ts
164
+ var TiimeError = class extends Error {
165
+ constructor(message, status, endpoint, details) {
166
+ super(message);
167
+ this.status = status;
168
+ this.endpoint = endpoint;
169
+ this.details = details;
170
+ this.name = "TiimeError";
171
+ }
172
+ toJSON() {
173
+ return {
174
+ error: this.name,
175
+ message: this.message,
176
+ status: this.status,
177
+ endpoint: this.endpoint,
178
+ details: this.details
179
+ };
180
+ }
181
+ };
182
+
183
+ // src/sdk/resources/bank-accounts.ts
184
+ var BankAccountsResource = class {
185
+ constructor(fetch, companyId) {
186
+ this.fetch = fetch;
187
+ this.companyId = companyId;
188
+ }
189
+ list(enabled) {
190
+ return this.fetch(
191
+ `/companies/${this.companyId}/bank_accounts`,
192
+ { query: enabled !== void 0 ? { enabled } : void 0 }
193
+ );
194
+ }
195
+ get(bankAccountId) {
196
+ return this.fetch(
197
+ `/companies/${this.companyId}/bank_accounts/${bankAccountId}`
198
+ );
199
+ }
200
+ async balance() {
201
+ const accounts = await this.list(true);
202
+ return accounts.map((a) => ({
203
+ name: a.name,
204
+ balance_amount: a.balance_amount,
205
+ currency: a.balance_currency
206
+ }));
207
+ }
208
+ };
209
+
210
+ // src/sdk/resources/bank-transactions.ts
211
+ var BankTransactionsResource = class {
212
+ constructor(fetch, companyId) {
213
+ this.fetch = fetch;
214
+ this.companyId = companyId;
215
+ }
216
+ list(params) {
217
+ const start = ((params?.page ?? 1) - 1) * (params?.pageSize ?? 100);
218
+ const end = start + (params?.pageSize ?? 100);
219
+ const { page: _, pageSize: __, from, to, search, ...query } = params ?? {};
220
+ if (from) query.transaction_date_start = from;
221
+ if (to) query.transaction_date_end = to;
222
+ if (search) query.wording = search;
223
+ return this.fetch(
224
+ `/companies/${this.companyId}/bank_transactions`,
225
+ {
226
+ query: { hide_refused: false, ...query },
227
+ headers: {
228
+ Accept: "application/vnd.tiime.bank_transactions.v2+json,application/vnd.tiime.bank_transactions.without_documents+json",
229
+ Range: `items=${start}-${end}`
230
+ }
231
+ }
232
+ );
233
+ }
234
+ async listAll(params) {
235
+ const pageSize = params?.pageSize ?? 200;
236
+ const all = [];
237
+ let page = 1;
238
+ let hasMore = true;
239
+ while (hasMore) {
240
+ const response = await this.list({ ...params, page, pageSize });
241
+ all.push(...response.transactions);
242
+ hasMore = response.transactions.length === pageSize;
243
+ page++;
244
+ }
245
+ return all;
246
+ }
247
+ unimputed() {
248
+ return this.fetch(
249
+ `/companies/${this.companyId}/bank_transactions/unimputed`
250
+ );
251
+ }
252
+ };
253
+
254
+ // src/sdk/resources/clients.ts
255
+ var ClientsResource = class {
256
+ constructor(fetch, companyId) {
257
+ this.fetch = fetch;
258
+ this.companyId = companyId;
259
+ }
260
+ list(params) {
261
+ return this.fetch(`/companies/${this.companyId}/clients`, {
262
+ query: params,
263
+ headers: {
264
+ Accept: "application/vnd.tiime.timeline.v2+json",
265
+ Range: "items=0-*"
266
+ }
267
+ });
268
+ }
269
+ get(clientId) {
270
+ return this.fetch(
271
+ `/companies/${this.companyId}/clients/${clientId}`
272
+ );
273
+ }
274
+ create(params) {
275
+ return this.fetch(`/companies/${this.companyId}/clients`, {
276
+ method: "POST",
277
+ body: params
278
+ });
279
+ }
280
+ search(query) {
281
+ return this.fetch(`/companies/${this.companyId}/clients`, {
282
+ query: { search: query },
283
+ headers: {
284
+ Accept: "application/vnd.tiime.timeline.v2+json",
285
+ Range: "items=0-*"
286
+ }
287
+ });
288
+ }
289
+ };
290
+
291
+ // src/sdk/resources/company.ts
292
+ var CompanyResource = class {
293
+ constructor(fetch, companyId) {
294
+ this.fetch = fetch;
295
+ this.companyId = companyId;
296
+ }
297
+ get() {
298
+ return this.fetch(`/companies/${this.companyId}`);
299
+ }
300
+ users() {
301
+ return this.fetch(`/companies/${this.companyId}/users`);
302
+ }
303
+ appConfig() {
304
+ return this.fetch(`/companies/${this.companyId}/app_config`);
305
+ }
306
+ accountingPeriod(rangeYear = 1) {
307
+ return this.fetch(
308
+ `/companies/${this.companyId}/accounting_period/current`,
309
+ { query: { range_year: rangeYear } }
310
+ );
311
+ }
312
+ tiles(keys) {
313
+ return this.fetch(`/companies/${this.companyId}/tiles`, {
314
+ query: { keys: keys.join(",") }
315
+ });
316
+ }
317
+ dashboardBlocks(displayGroup = "monitoring") {
318
+ return this.fetch(`/companies/${this.companyId}/dashboard_blocks`, {
319
+ query: { sorts: "rank:asc", display_group: displayGroup }
320
+ });
321
+ }
322
+ };
323
+
324
+ // src/sdk/resources/documents.ts
325
+ var DocumentsResource = class {
326
+ constructor(fetch, companyId) {
327
+ this.fetch = fetch;
328
+ this.companyId = companyId;
329
+ }
330
+ list(params) {
331
+ const start = ((params?.page ?? 1) - 1) * (params?.pageSize ?? 25);
332
+ const end = start + (params?.pageSize ?? 25);
333
+ const { page: _, pageSize: __, ...query } = params ?? {};
334
+ return this.fetch(`/companies/${this.companyId}/documents`, {
335
+ query: {
336
+ sorts: "created_at:desc",
337
+ expand: "file_family,preview_available",
338
+ ...query
339
+ },
340
+ headers: {
341
+ Accept: "application/vnd.tiime.documents.v2+json,application/vnd.tiime.docs.query+json,application/vnd.tiime.docs.imputation+json",
342
+ Range: `items=${start}-${end}`
343
+ }
344
+ });
345
+ }
346
+ categories() {
347
+ return this.fetch(
348
+ `/companies/${this.companyId}/document_categories`,
349
+ {
350
+ headers: {
351
+ Accept: "application/vnd.tiime.documents.v3+json"
352
+ }
353
+ }
354
+ );
355
+ }
356
+ preview(documentId) {
357
+ return this.fetch(
358
+ `/companies/${this.companyId}/documents/${documentId}/preview`
359
+ );
360
+ }
361
+ upload(file, filename, type) {
362
+ const formData = new FormData();
363
+ formData.append("file", new Blob([file]), filename);
364
+ if (type) {
365
+ formData.append("type", type);
366
+ }
367
+ return this.fetch(`/companies/${this.companyId}/documents`, {
368
+ method: "POST",
369
+ body: formData
370
+ });
371
+ }
372
+ async download(documentId) {
373
+ return this.fetch(
374
+ `/companies/${this.companyId}/documents/${documentId}/download`,
375
+ {
376
+ headers: { Accept: "application/octet-stream" }
377
+ }
378
+ );
379
+ }
380
+ };
381
+
382
+ // src/sdk/resources/expense-reports.ts
383
+ var ExpenseReportsResource = class {
384
+ constructor(fetch, companyId) {
385
+ this.fetch = fetch;
386
+ this.companyId = companyId;
387
+ }
388
+ list(sorts = "metadata.date:desc") {
389
+ return this.fetch(
390
+ `/companies/${this.companyId}/expense_reports`,
391
+ {
392
+ query: { expand: "total_amount", sorts },
393
+ headers: { Range: "items=0-25" }
394
+ }
395
+ );
396
+ }
397
+ get(expenseReportId) {
398
+ return this.fetch(
399
+ `/companies/${this.companyId}/expense_reports/${expenseReportId}`
400
+ );
401
+ }
402
+ create(params) {
403
+ return this.fetch(
404
+ `/companies/${this.companyId}/expense_reports`,
405
+ {
406
+ method: "POST",
407
+ body: params
408
+ }
409
+ );
410
+ }
411
+ };
412
+
413
+ // src/sdk/resources/invoices.ts
414
+ var DEFAULT_INVOICE_TEMPLATE = {
415
+ template: "advanced",
416
+ status: "draft",
417
+ due_date_mode: "thirty_days",
418
+ title_enabled: true,
419
+ free_field_enabled: false,
420
+ free_field: "",
421
+ discount_enabled: false,
422
+ bank_detail_enabled: true,
423
+ payment_condition_enabled: true,
424
+ payment_condition: "En cas de retard de paiement, une p\xE9nalit\xE9 de 3 fois le taux d'int\xE9r\xEAt l\xE9gal sera appliqu\xE9e, \xE0 laquelle s'ajoutera une indemnit\xE9 forfaitaire pour frais de recouvrement de 40\u20AC.",
425
+ text_lines: []
426
+ };
427
+ var InvoicesResource = class {
428
+ constructor(fetch, companyId) {
429
+ this.fetch = fetch;
430
+ this.companyId = companyId;
431
+ }
432
+ list(params) {
433
+ const start = ((params?.page ?? 1) - 1) * (params?.pageSize ?? 25);
434
+ const end = start + (params?.pageSize ?? 25);
435
+ const query = {
436
+ sorts: params?.sorts ?? "invoice_number:desc"
437
+ };
438
+ if (params?.status) query.status = params.status;
439
+ return this.fetch(`/companies/${this.companyId}/invoices`, {
440
+ query,
441
+ headers: { Range: `items=${start}-${end}` }
442
+ });
443
+ }
444
+ async listAll(params) {
445
+ const pageSize = params?.pageSize ?? 100;
446
+ const all = [];
447
+ let page = 1;
448
+ let hasMore = true;
449
+ while (hasMore) {
450
+ const batch = await this.list({
451
+ sorts: params?.sorts,
452
+ status: params?.status,
453
+ page,
454
+ pageSize
455
+ });
456
+ all.push(...batch);
457
+ hasMore = batch.length === pageSize;
458
+ page++;
459
+ }
460
+ return all;
461
+ }
462
+ get(invoiceId) {
463
+ return this.fetch(
464
+ `/companies/${this.companyId}/invoices/${invoiceId}`
465
+ );
466
+ }
467
+ create(params) {
468
+ const body = { ...DEFAULT_INVOICE_TEMPLATE, ...params };
469
+ for (const line of body.lines ?? []) {
470
+ line.line_amount = line.quantity * line.unit_amount;
471
+ line.sequence ??= 1;
472
+ line.invoicing_category_type ??= "benefit";
473
+ line.discount_description ??= "";
474
+ line.discount_amount ??= null;
475
+ line.discount_percentage ??= null;
476
+ }
477
+ return this.fetch(`/companies/${this.companyId}/invoices`, {
478
+ method: "POST",
479
+ body
480
+ });
481
+ }
482
+ update(invoiceId, params) {
483
+ return this.fetch(
484
+ `/companies/${this.companyId}/invoices/${invoiceId}`,
485
+ {
486
+ method: "PUT",
487
+ body: params
488
+ }
489
+ );
490
+ }
491
+ send(invoiceId, params) {
492
+ return this.fetch(
493
+ `/companies/${this.companyId}/invoices/${invoiceId}/send`,
494
+ {
495
+ method: "POST",
496
+ body: params
497
+ }
498
+ );
499
+ }
500
+ async downloadPdf(invoiceId) {
501
+ return this.fetch(
502
+ `/companies/${this.companyId}/invoices/${invoiceId}/pdf`,
503
+ {
504
+ headers: { Accept: "application/pdf" }
505
+ }
506
+ );
507
+ }
508
+ delete(invoiceId) {
509
+ return this.fetch(
510
+ `/companies/${this.companyId}/invoices/${invoiceId}`,
511
+ { method: "DELETE" }
512
+ );
513
+ }
514
+ async duplicate(invoiceId, overrides) {
515
+ const source = await this.get(invoiceId);
516
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
517
+ const lines = source.lines.map((l) => ({
518
+ description: l.description,
519
+ quantity: overrides?.quantity ?? l.quantity,
520
+ unit_amount: l.unit_amount,
521
+ vat_type: l.vat_type,
522
+ invoicing_unit: l.invoicing_unit,
523
+ invoicing_category_type: l.invoicing_category_type,
524
+ article: l.article
525
+ }));
526
+ return this.create({
527
+ client: source.client_id ? { id: source.client_id } : null,
528
+ emission_date: overrides?.emission_date ?? today,
529
+ title: source.title,
530
+ title_enabled: !!source.title,
531
+ lines,
532
+ status: "draft"
533
+ });
534
+ }
535
+ };
536
+
537
+ // src/sdk/resources/labels.ts
538
+ var LabelsResource = class {
539
+ constructor(fetch, companyId) {
540
+ this.fetch = fetch;
541
+ this.companyId = companyId;
542
+ }
543
+ list() {
544
+ return this.fetch(`/companies/${this.companyId}/labels`, {
545
+ headers: {
546
+ Accept: "application/vnd.tiime.labels.v2+json"
547
+ }
548
+ });
549
+ }
550
+ standard() {
551
+ return this.fetch(`/companies/${this.companyId}/standard_labels`);
552
+ }
553
+ tags() {
554
+ return this.fetch(`/companies/${this.companyId}/tags`, {
555
+ query: { expand: "tag_detail" }
556
+ });
557
+ }
558
+ };
559
+
560
+ // src/sdk/resources/quotations.ts
561
+ var QuotationsResource = class {
562
+ constructor(fetch, companyId) {
563
+ this.fetch = fetch;
564
+ this.companyId = companyId;
565
+ }
566
+ list(expand = "invoices") {
567
+ return this.fetch(`/companies/${this.companyId}/quotations`, {
568
+ query: { expand },
569
+ headers: { Range: "items=0-25" }
570
+ });
571
+ }
572
+ get(quotationId) {
573
+ return this.fetch(
574
+ `/companies/${this.companyId}/quotations/${quotationId}`
575
+ );
576
+ }
577
+ create(params) {
578
+ return this.fetch(`/companies/${this.companyId}/quotations`, {
579
+ method: "POST",
580
+ body: params
581
+ });
582
+ }
583
+ async downloadPdf(quotationId) {
584
+ return this.fetch(
585
+ `/companies/${this.companyId}/quotations/${quotationId}/pdf`,
586
+ {
587
+ headers: { Accept: "application/pdf" }
588
+ }
589
+ );
590
+ }
591
+ send(quotationId, params) {
592
+ return this.fetch(
593
+ `/companies/${this.companyId}/quotations/${quotationId}/send`,
594
+ {
595
+ method: "POST",
596
+ body: params
597
+ }
598
+ );
599
+ }
600
+ };
601
+
602
+ // src/sdk/resources/users.ts
603
+ var UsersResource = class {
604
+ constructor(fetch) {
605
+ this.fetch = fetch;
606
+ }
607
+ me() {
608
+ return this.fetch("/users/me");
609
+ }
610
+ legalInformations() {
611
+ return this.fetch("/users/me/legal_informations");
612
+ }
613
+ settings(companyId) {
614
+ return this.fetch(`/users/me/companies/${companyId}/settings`);
615
+ }
616
+ };
617
+
618
+ // src/sdk/client.ts
619
+ var BASE_URL = "https://chronos-api.tiime-apps.com/v1";
620
+ var TiimeClient = class {
621
+ fetch;
622
+ tokenManager;
623
+ companyId;
624
+ constructor(options) {
625
+ this.companyId = options.companyId;
626
+ this.tokenManager = options.tokenManager ?? new TokenManager();
627
+ this.fetch = ofetch2.create({
628
+ baseURL: BASE_URL,
629
+ retry: 2,
630
+ retryDelay: 500,
631
+ retryStatusCodes: [408, 429, 500, 502, 503, 504],
632
+ headers: {
633
+ "tiime-app": "tiime",
634
+ "tiime-app-version": "4.30.3",
635
+ "tiime-app-platform": "cli"
636
+ },
637
+ onRequest: async ({ options: options2 }) => {
638
+ const token = await this.tokenManager.getValidToken();
639
+ options2.headers.set("Authorization", `Bearer ${token}`);
640
+ },
641
+ onResponseError: ({ request, response }) => {
642
+ throw new TiimeError(
643
+ response.statusText || `HTTP ${response.status}`,
644
+ response.status,
645
+ String(request),
646
+ response._data
647
+ );
648
+ }
649
+ });
650
+ }
651
+ listCompanies() {
652
+ return this.fetch("/companies", {
653
+ headers: {
654
+ Accept: "application/vnd.tiime.companies.v2+json",
655
+ Range: "items=0-101"
656
+ }
657
+ });
658
+ }
659
+ get users() {
660
+ return new UsersResource(this.fetch);
661
+ }
662
+ get company() {
663
+ return new CompanyResource(this.fetch, this.companyId);
664
+ }
665
+ get clients() {
666
+ return new ClientsResource(this.fetch, this.companyId);
667
+ }
668
+ get invoices() {
669
+ return new InvoicesResource(this.fetch, this.companyId);
670
+ }
671
+ get quotations() {
672
+ return new QuotationsResource(this.fetch, this.companyId);
673
+ }
674
+ get bankAccounts() {
675
+ return new BankAccountsResource(this.fetch, this.companyId);
676
+ }
677
+ get bankTransactions() {
678
+ return new BankTransactionsResource(this.fetch, this.companyId);
679
+ }
680
+ get documents() {
681
+ return new DocumentsResource(this.fetch, this.companyId);
682
+ }
683
+ get expenseReports() {
684
+ return new ExpenseReportsResource(this.fetch, this.companyId);
685
+ }
686
+ get labels() {
687
+ return new LabelsResource(this.fetch, this.companyId);
688
+ }
689
+ };
690
+ export {
691
+ TiimeClient,
692
+ TiimeError,
693
+ TokenManager
694
+ };