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/cli.js ADDED
@@ -0,0 +1,2639 @@
1
+ #!/usr/bin/env node
2
+
3
+ // src/cli/index.ts
4
+ import { defineCommand as defineCommand14, renderUsage, runMain } from "citty";
5
+
6
+ // src/cli/commands/auth.ts
7
+ import * as p from "@clack/prompts";
8
+ import { defineCommand } from "citty";
9
+
10
+ // src/sdk/auth.ts
11
+ import { execSync } from "child_process";
12
+ import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
13
+ import { homedir } from "os";
14
+ import { join } from "path";
15
+ import { ofetch } from "ofetch";
16
+ var AUTH0_DOMAIN = "auth0.tiime.fr";
17
+ var AUTH0_CLIENT_ID = "iEbsbe3o66gcTBfGRa012kj1Rb6vjAND";
18
+ var AUTH0_AUDIENCE = "https://chronos/";
19
+ var CONFIG_DIR = join(homedir(), ".config", "tiime");
20
+ var AUTH_FILE = join(CONFIG_DIR, "auth.json");
21
+ var KEYCHAIN_ACCOUNT = "tiime-cli";
22
+ var KEYCHAIN_SERVICE = "tiime-credentials";
23
+ var saveCredentialsToKeychain = (email, password2) => {
24
+ try {
25
+ const payload = JSON.stringify({ email, password: password2 });
26
+ execSync(
27
+ `security add-generic-password -a "${KEYCHAIN_ACCOUNT}" -s "${KEYCHAIN_SERVICE}" -w '${payload.replace(/'/g, "'\\''")}' -U`,
28
+ { stdio: "ignore" }
29
+ );
30
+ return true;
31
+ } catch {
32
+ return false;
33
+ }
34
+ };
35
+ var loadCredentialsFromKeychain = () => {
36
+ try {
37
+ const raw = execSync(
38
+ `security find-generic-password -a "${KEYCHAIN_ACCOUNT}" -s "${KEYCHAIN_SERVICE}" -w`,
39
+ { encoding: "utf-8", stdio: ["ignore", "pipe", "ignore"] }
40
+ ).trim();
41
+ const data = JSON.parse(raw);
42
+ if (typeof data === "object" && data !== null && "email" in data && "password" in data && typeof data.email === "string" && typeof data.password === "string") {
43
+ return data;
44
+ }
45
+ return null;
46
+ } catch {
47
+ return null;
48
+ }
49
+ };
50
+ var saveCredentialsToFile = (email, password2) => {
51
+ if (!existsSync(CONFIG_DIR)) {
52
+ mkdirSync(CONFIG_DIR, { recursive: true });
53
+ }
54
+ const filePath = join(CONFIG_DIR, "credentials.json");
55
+ writeFileSync(filePath, JSON.stringify({ email, password: password2 }, null, 2), {
56
+ mode: 384
57
+ });
58
+ };
59
+ var loadCredentialsFromFile = () => {
60
+ try {
61
+ const filePath = join(CONFIG_DIR, "credentials.json");
62
+ if (existsSync(filePath)) {
63
+ const data = JSON.parse(readFileSync(filePath, "utf-8"));
64
+ if (typeof data === "object" && data !== null && "email" in data && "password" in data && typeof data.email === "string" && typeof data.password === "string") {
65
+ return data;
66
+ }
67
+ }
68
+ } catch {
69
+ }
70
+ return null;
71
+ };
72
+ var saveCredentials = (email, password2) => {
73
+ if (!saveCredentialsToKeychain(email, password2)) {
74
+ saveCredentialsToFile(email, password2);
75
+ }
76
+ };
77
+ var loadCredentials = () => {
78
+ return loadCredentialsFromKeychain() ?? loadCredentialsFromFile();
79
+ };
80
+ var TokenManager = class {
81
+ tokens = null;
82
+ constructor() {
83
+ this.loadFromDisk();
84
+ }
85
+ async login(email, password2) {
86
+ const response = await ofetch(`https://${AUTH0_DOMAIN}/oauth/token`, {
87
+ method: "POST",
88
+ body: {
89
+ grant_type: "password",
90
+ client_id: AUTH0_CLIENT_ID,
91
+ audience: AUTH0_AUDIENCE,
92
+ scope: "openid email",
93
+ username: email,
94
+ password: password2
95
+ }
96
+ });
97
+ this.tokens = {
98
+ access_token: response.access_token,
99
+ expires_at: Date.now() + response.expires_in * 1e3
100
+ };
101
+ this.saveToDisk();
102
+ saveCredentials(email, password2);
103
+ return this.tokens;
104
+ }
105
+ async getValidToken() {
106
+ if (!this.tokens || this.isExpired()) {
107
+ const creds = loadCredentials();
108
+ if (creds) {
109
+ const tokens = await this.login(creds.email, creds.password);
110
+ return tokens.access_token;
111
+ }
112
+ throw new Error(
113
+ this.tokens ? "Token expired. Run `tiime auth login` to re-authenticate." : "Not authenticated. Run `tiime auth login` first."
114
+ );
115
+ }
116
+ return this.tokens.access_token;
117
+ }
118
+ isAuthenticated() {
119
+ return this.tokens !== null && !this.isExpired();
120
+ }
121
+ logout() {
122
+ this.tokens = null;
123
+ if (existsSync(AUTH_FILE)) {
124
+ writeFileSync(AUTH_FILE, "{}");
125
+ }
126
+ }
127
+ getTokenInfo() {
128
+ if (!this.tokens) {
129
+ return { email: null, expiresAt: null };
130
+ }
131
+ try {
132
+ const payload = JSON.parse(
133
+ Buffer.from(
134
+ this.tokens.access_token.split(".")[1],
135
+ "base64"
136
+ ).toString()
137
+ );
138
+ return {
139
+ email: payload["tiime/userEmail"] || null,
140
+ expiresAt: new Date(this.tokens.expires_at)
141
+ };
142
+ } catch {
143
+ return { email: null, expiresAt: new Date(this.tokens.expires_at) };
144
+ }
145
+ }
146
+ isExpired() {
147
+ if (!this.tokens) return true;
148
+ return Date.now() >= this.tokens.expires_at - 6e4;
149
+ }
150
+ loadFromDisk() {
151
+ try {
152
+ if (existsSync(AUTH_FILE)) {
153
+ const data = JSON.parse(readFileSync(AUTH_FILE, "utf-8"));
154
+ if (data.access_token && data.expires_at) {
155
+ this.tokens = data;
156
+ }
157
+ }
158
+ } catch {
159
+ }
160
+ }
161
+ saveToDisk() {
162
+ if (!existsSync(CONFIG_DIR)) {
163
+ mkdirSync(CONFIG_DIR, { recursive: true });
164
+ }
165
+ writeFileSync(AUTH_FILE, JSON.stringify(this.tokens, null, 2));
166
+ }
167
+ };
168
+
169
+ // src/cli/output.ts
170
+ import Table from "cli-table3";
171
+ import { consola } from "consola";
172
+
173
+ // src/sdk/errors.ts
174
+ var TiimeError = class extends Error {
175
+ constructor(message, status, endpoint, details) {
176
+ super(message);
177
+ this.status = status;
178
+ this.endpoint = endpoint;
179
+ this.details = details;
180
+ this.name = "TiimeError";
181
+ }
182
+ toJSON() {
183
+ return {
184
+ error: this.name,
185
+ message: this.message,
186
+ status: this.status,
187
+ endpoint: this.endpoint,
188
+ details: this.details
189
+ };
190
+ }
191
+ };
192
+
193
+ // src/cli/output.ts
194
+ var formatArg = {
195
+ format: {
196
+ type: "string",
197
+ description: "Format de sortie (json, table, csv)",
198
+ default: "json"
199
+ }
200
+ };
201
+ var stringifyValue = (value) => {
202
+ if (value === null || value === void 0) return "";
203
+ if (typeof value === "object") return JSON.stringify(value);
204
+ return String(value);
205
+ };
206
+ var outputJson = (data) => {
207
+ process.stdout.write(`${JSON.stringify(data, null, 2)}
208
+ `);
209
+ };
210
+ var outputTable = (data) => {
211
+ if (Array.isArray(data) && data.length > 0 && typeof data[0] === "object" && data[0] !== null) {
212
+ const keys = Object.keys(data[0]);
213
+ const table = new Table({ head: keys });
214
+ for (const row of data) {
215
+ const record = row;
216
+ table.push(keys.map((key) => stringifyValue(record[key])));
217
+ }
218
+ process.stdout.write(`${table.toString()}
219
+ `);
220
+ } else if (typeof data === "object" && data !== null && !Array.isArray(data)) {
221
+ const table = new Table();
222
+ for (const [key, value] of Object.entries(
223
+ data
224
+ )) {
225
+ table.push({ [key]: stringifyValue(value) });
226
+ }
227
+ process.stdout.write(`${table.toString()}
228
+ `);
229
+ } else {
230
+ outputJson(data);
231
+ }
232
+ };
233
+ var escapeCsvField = (value) => {
234
+ if (value.includes(",") || value.includes('"') || value.includes("\n")) {
235
+ return `"${value.replace(/"/g, '""')}"`;
236
+ }
237
+ return value;
238
+ };
239
+ var outputCsv = (data) => {
240
+ if (Array.isArray(data) && data.length > 0 && typeof data[0] === "object" && data[0] !== null) {
241
+ const keys = Object.keys(data[0]);
242
+ const header = keys.map(escapeCsvField).join(",");
243
+ const lines = data.map((row) => {
244
+ const record = row;
245
+ return keys.map((key) => escapeCsvField(stringifyValue(record[key]))).join(",");
246
+ });
247
+ process.stdout.write(`${header}
248
+ ${lines.join("\n")}
249
+ `);
250
+ } else if (typeof data === "object" && data !== null && !Array.isArray(data)) {
251
+ const entries = Object.entries(data);
252
+ const header = "key,value";
253
+ const lines = entries.map(
254
+ ([key, value]) => `${escapeCsvField(key)},${escapeCsvField(stringifyValue(value))}`
255
+ );
256
+ process.stdout.write(`${header}
257
+ ${lines.join("\n")}
258
+ `);
259
+ } else {
260
+ outputJson(data);
261
+ }
262
+ };
263
+ var output = (data, options) => {
264
+ const format = options?.format ?? "json";
265
+ if (!["json", "table", "csv"].includes(format)) {
266
+ process.stderr.write(
267
+ `${JSON.stringify({ error: `Format invalide : "${format}". Utilisez json, table ou csv.` })}
268
+ `
269
+ );
270
+ process.exit(1);
271
+ }
272
+ switch (format) {
273
+ case "table":
274
+ outputTable(data);
275
+ break;
276
+ case "csv":
277
+ outputCsv(data);
278
+ break;
279
+ default:
280
+ outputJson(data);
281
+ break;
282
+ }
283
+ };
284
+ var outputColoredStatus = (data) => {
285
+ const {
286
+ company_id,
287
+ bank_accounts,
288
+ invoices,
289
+ pending_quotations,
290
+ total_clients,
291
+ unimputed_transactions
292
+ } = data;
293
+ console.error("");
294
+ console.error(` \u{1F4CA} R\xE9sum\xE9 \u2014 Entreprise #${company_id}`);
295
+ for (const a of bank_accounts) {
296
+ console.error(
297
+ ` \u{1F4B0} Soldes : ${a.name} ${a.balance.toFixed(2)}${a.currency === "EUR" ? "\u20AC" : a.currency}`
298
+ );
299
+ }
300
+ console.error(
301
+ ` \u{1F4C4} Factures : ${invoices.drafts} brouillon(s), ${invoices.unpaid} impay\xE9e(s)`
302
+ );
303
+ console.error(` \u{1F4CB} Devis en cours : ${pending_quotations}`);
304
+ console.error(` \u{1F465} Clients : ${total_clients}`);
305
+ if (unimputed_transactions > 0) {
306
+ console.error(` \u26A0\uFE0F Transactions non imput\xE9es : ${unimputed_transactions}`);
307
+ } else {
308
+ console.error(` \u2705 Toutes les transactions sont imput\xE9es`);
309
+ }
310
+ console.error("");
311
+ };
312
+ var outputError = (error) => {
313
+ if (error instanceof TiimeError) {
314
+ process.stderr.write(`${JSON.stringify(error.toJSON())}
315
+ `);
316
+ } else {
317
+ const message = error instanceof Error ? error.message : String(error);
318
+ process.stderr.write(`${JSON.stringify({ error: message })}
319
+ `);
320
+ }
321
+ process.exit(1);
322
+ };
323
+
324
+ // src/cli/commands/auth.ts
325
+ var authCommand = defineCommand({
326
+ meta: { name: "auth", description: "Gestion de l'authentification" },
327
+ subCommands: {
328
+ login: defineCommand({
329
+ meta: { name: "login", description: "Se connecter \xE0 Tiime" },
330
+ args: {
331
+ email: {
332
+ type: "string",
333
+ description: "Adresse email"
334
+ },
335
+ password: {
336
+ type: "string",
337
+ description: "Mot de passe"
338
+ }
339
+ },
340
+ async run({ args }) {
341
+ const hasArgs = args.email && args.password;
342
+ if (hasArgs) {
343
+ try {
344
+ const tm = new TokenManager();
345
+ await tm.login(args.email, args.password);
346
+ const info = tm.getTokenInfo();
347
+ output({
348
+ status: "authenticated",
349
+ email: info.email,
350
+ expires_at: info.expiresAt?.toISOString()
351
+ });
352
+ } catch (e) {
353
+ outputError(e);
354
+ }
355
+ return;
356
+ }
357
+ p.intro("Connexion \xE0 Tiime");
358
+ const email = await p.text({
359
+ message: "Adresse email",
360
+ placeholder: "vous@example.com",
361
+ validate: (value) => {
362
+ if (!value || !value.includes("@")) return "Adresse email invalide";
363
+ }
364
+ });
365
+ if (p.isCancel(email)) {
366
+ p.cancel("Connexion annul\xE9e.");
367
+ return;
368
+ }
369
+ const password2 = await p.password({
370
+ message: "Mot de passe",
371
+ validate: (value) => {
372
+ if (!value) return "Le mot de passe est requis";
373
+ }
374
+ });
375
+ if (p.isCancel(password2)) {
376
+ p.cancel("Connexion annul\xE9e.");
377
+ return;
378
+ }
379
+ const s = p.spinner();
380
+ s.start("Authentification en cours...");
381
+ try {
382
+ const tm = new TokenManager();
383
+ await tm.login(email, password2);
384
+ const info = tm.getTokenInfo();
385
+ s.stop("Authentification r\xE9ussie");
386
+ p.outro(`Connect\xE9 en tant que ${info.email ?? email}`);
387
+ } catch (e) {
388
+ s.stop("Authentification \xE9chou\xE9e");
389
+ const message = e instanceof Error ? e.message : "Erreur inconnue";
390
+ p.cancel(message);
391
+ }
392
+ }
393
+ }),
394
+ logout: defineCommand({
395
+ meta: { name: "logout", description: "Se d\xE9connecter de Tiime" },
396
+ run() {
397
+ const tm = new TokenManager();
398
+ tm.logout();
399
+ output({ status: "logged_out" });
400
+ }
401
+ }),
402
+ status: defineCommand({
403
+ meta: {
404
+ name: "status",
405
+ description: "Afficher le statut d'authentification"
406
+ },
407
+ run() {
408
+ const tm = new TokenManager();
409
+ const info = tm.getTokenInfo();
410
+ output({
411
+ authenticated: tm.isAuthenticated(),
412
+ email: info.email,
413
+ expires_at: info.expiresAt?.toISOString() ?? null
414
+ });
415
+ }
416
+ })
417
+ }
418
+ });
419
+
420
+ // src/cli/commands/bank.ts
421
+ import { defineCommand as defineCommand2 } from "citty";
422
+
423
+ // src/sdk/client.ts
424
+ import { ofetch as ofetch2 } from "ofetch";
425
+
426
+ // src/sdk/resources/bank-accounts.ts
427
+ var BankAccountsResource = class {
428
+ constructor(fetch, companyId) {
429
+ this.fetch = fetch;
430
+ this.companyId = companyId;
431
+ }
432
+ list(enabled) {
433
+ return this.fetch(
434
+ `/companies/${this.companyId}/bank_accounts`,
435
+ { query: enabled !== void 0 ? { enabled } : void 0 }
436
+ );
437
+ }
438
+ get(bankAccountId) {
439
+ return this.fetch(
440
+ `/companies/${this.companyId}/bank_accounts/${bankAccountId}`
441
+ );
442
+ }
443
+ async balance() {
444
+ const accounts = await this.list(true);
445
+ return accounts.map((a) => ({
446
+ name: a.name,
447
+ balance_amount: a.balance_amount,
448
+ currency: a.balance_currency
449
+ }));
450
+ }
451
+ };
452
+
453
+ // src/sdk/resources/bank-transactions.ts
454
+ var BankTransactionsResource = class {
455
+ constructor(fetch, companyId) {
456
+ this.fetch = fetch;
457
+ this.companyId = companyId;
458
+ }
459
+ list(params) {
460
+ const start = ((params?.page ?? 1) - 1) * (params?.pageSize ?? 100);
461
+ const end = start + (params?.pageSize ?? 100);
462
+ const { page: _, pageSize: __, from, to, search, ...query } = params ?? {};
463
+ if (from) query.transaction_date_start = from;
464
+ if (to) query.transaction_date_end = to;
465
+ if (search) query.wording = search;
466
+ return this.fetch(
467
+ `/companies/${this.companyId}/bank_transactions`,
468
+ {
469
+ query: { hide_refused: false, ...query },
470
+ headers: {
471
+ Accept: "application/vnd.tiime.bank_transactions.v2+json,application/vnd.tiime.bank_transactions.without_documents+json",
472
+ Range: `items=${start}-${end}`
473
+ }
474
+ }
475
+ );
476
+ }
477
+ async listAll(params) {
478
+ const pageSize = params?.pageSize ?? 200;
479
+ const all = [];
480
+ let page = 1;
481
+ let hasMore = true;
482
+ while (hasMore) {
483
+ const response = await this.list({ ...params, page, pageSize });
484
+ all.push(...response.transactions);
485
+ hasMore = response.transactions.length === pageSize;
486
+ page++;
487
+ }
488
+ return all;
489
+ }
490
+ unimputed() {
491
+ return this.fetch(
492
+ `/companies/${this.companyId}/bank_transactions/unimputed`
493
+ );
494
+ }
495
+ };
496
+
497
+ // src/sdk/resources/clients.ts
498
+ var ClientsResource = class {
499
+ constructor(fetch, companyId) {
500
+ this.fetch = fetch;
501
+ this.companyId = companyId;
502
+ }
503
+ list(params) {
504
+ return this.fetch(`/companies/${this.companyId}/clients`, {
505
+ query: params,
506
+ headers: {
507
+ Accept: "application/vnd.tiime.timeline.v2+json",
508
+ Range: "items=0-*"
509
+ }
510
+ });
511
+ }
512
+ get(clientId) {
513
+ return this.fetch(
514
+ `/companies/${this.companyId}/clients/${clientId}`
515
+ );
516
+ }
517
+ create(params) {
518
+ return this.fetch(`/companies/${this.companyId}/clients`, {
519
+ method: "POST",
520
+ body: params
521
+ });
522
+ }
523
+ search(query) {
524
+ return this.fetch(`/companies/${this.companyId}/clients`, {
525
+ query: { search: query },
526
+ headers: {
527
+ Accept: "application/vnd.tiime.timeline.v2+json",
528
+ Range: "items=0-*"
529
+ }
530
+ });
531
+ }
532
+ };
533
+
534
+ // src/sdk/resources/company.ts
535
+ var CompanyResource = class {
536
+ constructor(fetch, companyId) {
537
+ this.fetch = fetch;
538
+ this.companyId = companyId;
539
+ }
540
+ get() {
541
+ return this.fetch(`/companies/${this.companyId}`);
542
+ }
543
+ users() {
544
+ return this.fetch(`/companies/${this.companyId}/users`);
545
+ }
546
+ appConfig() {
547
+ return this.fetch(`/companies/${this.companyId}/app_config`);
548
+ }
549
+ accountingPeriod(rangeYear = 1) {
550
+ return this.fetch(
551
+ `/companies/${this.companyId}/accounting_period/current`,
552
+ { query: { range_year: rangeYear } }
553
+ );
554
+ }
555
+ tiles(keys) {
556
+ return this.fetch(`/companies/${this.companyId}/tiles`, {
557
+ query: { keys: keys.join(",") }
558
+ });
559
+ }
560
+ dashboardBlocks(displayGroup = "monitoring") {
561
+ return this.fetch(`/companies/${this.companyId}/dashboard_blocks`, {
562
+ query: { sorts: "rank:asc", display_group: displayGroup }
563
+ });
564
+ }
565
+ };
566
+
567
+ // src/sdk/resources/documents.ts
568
+ var DocumentsResource = class {
569
+ constructor(fetch, companyId) {
570
+ this.fetch = fetch;
571
+ this.companyId = companyId;
572
+ }
573
+ list(params) {
574
+ const start = ((params?.page ?? 1) - 1) * (params?.pageSize ?? 25);
575
+ const end = start + (params?.pageSize ?? 25);
576
+ const { page: _, pageSize: __, ...query } = params ?? {};
577
+ return this.fetch(`/companies/${this.companyId}/documents`, {
578
+ query: {
579
+ sorts: "created_at:desc",
580
+ expand: "file_family,preview_available",
581
+ ...query
582
+ },
583
+ headers: {
584
+ Accept: "application/vnd.tiime.documents.v2+json,application/vnd.tiime.docs.query+json,application/vnd.tiime.docs.imputation+json",
585
+ Range: `items=${start}-${end}`
586
+ }
587
+ });
588
+ }
589
+ categories() {
590
+ return this.fetch(
591
+ `/companies/${this.companyId}/document_categories`,
592
+ {
593
+ headers: {
594
+ Accept: "application/vnd.tiime.documents.v3+json"
595
+ }
596
+ }
597
+ );
598
+ }
599
+ preview(documentId) {
600
+ return this.fetch(
601
+ `/companies/${this.companyId}/documents/${documentId}/preview`
602
+ );
603
+ }
604
+ upload(file, filename, type) {
605
+ const formData = new FormData();
606
+ formData.append("file", new Blob([file]), filename);
607
+ if (type) {
608
+ formData.append("type", type);
609
+ }
610
+ return this.fetch(`/companies/${this.companyId}/documents`, {
611
+ method: "POST",
612
+ body: formData
613
+ });
614
+ }
615
+ async download(documentId) {
616
+ return this.fetch(
617
+ `/companies/${this.companyId}/documents/${documentId}/download`,
618
+ {
619
+ headers: { Accept: "application/octet-stream" }
620
+ }
621
+ );
622
+ }
623
+ };
624
+
625
+ // src/sdk/resources/expense-reports.ts
626
+ var ExpenseReportsResource = class {
627
+ constructor(fetch, companyId) {
628
+ this.fetch = fetch;
629
+ this.companyId = companyId;
630
+ }
631
+ list(sorts = "metadata.date:desc") {
632
+ return this.fetch(
633
+ `/companies/${this.companyId}/expense_reports`,
634
+ {
635
+ query: { expand: "total_amount", sorts },
636
+ headers: { Range: "items=0-25" }
637
+ }
638
+ );
639
+ }
640
+ get(expenseReportId) {
641
+ return this.fetch(
642
+ `/companies/${this.companyId}/expense_reports/${expenseReportId}`
643
+ );
644
+ }
645
+ create(params) {
646
+ return this.fetch(
647
+ `/companies/${this.companyId}/expense_reports`,
648
+ {
649
+ method: "POST",
650
+ body: params
651
+ }
652
+ );
653
+ }
654
+ };
655
+
656
+ // src/sdk/resources/invoices.ts
657
+ var DEFAULT_INVOICE_TEMPLATE = {
658
+ template: "advanced",
659
+ status: "draft",
660
+ due_date_mode: "thirty_days",
661
+ title_enabled: true,
662
+ free_field_enabled: false,
663
+ free_field: "",
664
+ discount_enabled: false,
665
+ bank_detail_enabled: true,
666
+ payment_condition_enabled: true,
667
+ 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.",
668
+ text_lines: []
669
+ };
670
+ var InvoicesResource = class {
671
+ constructor(fetch, companyId) {
672
+ this.fetch = fetch;
673
+ this.companyId = companyId;
674
+ }
675
+ list(params) {
676
+ const start = ((params?.page ?? 1) - 1) * (params?.pageSize ?? 25);
677
+ const end = start + (params?.pageSize ?? 25);
678
+ const query = {
679
+ sorts: params?.sorts ?? "invoice_number:desc"
680
+ };
681
+ if (params?.status) query.status = params.status;
682
+ return this.fetch(`/companies/${this.companyId}/invoices`, {
683
+ query,
684
+ headers: { Range: `items=${start}-${end}` }
685
+ });
686
+ }
687
+ async listAll(params) {
688
+ const pageSize = params?.pageSize ?? 100;
689
+ const all = [];
690
+ let page = 1;
691
+ let hasMore = true;
692
+ while (hasMore) {
693
+ const batch = await this.list({
694
+ sorts: params?.sorts,
695
+ status: params?.status,
696
+ page,
697
+ pageSize
698
+ });
699
+ all.push(...batch);
700
+ hasMore = batch.length === pageSize;
701
+ page++;
702
+ }
703
+ return all;
704
+ }
705
+ get(invoiceId) {
706
+ return this.fetch(
707
+ `/companies/${this.companyId}/invoices/${invoiceId}`
708
+ );
709
+ }
710
+ create(params) {
711
+ const body = { ...DEFAULT_INVOICE_TEMPLATE, ...params };
712
+ for (const line of body.lines ?? []) {
713
+ line.line_amount = line.quantity * line.unit_amount;
714
+ line.sequence ??= 1;
715
+ line.invoicing_category_type ??= "benefit";
716
+ line.discount_description ??= "";
717
+ line.discount_amount ??= null;
718
+ line.discount_percentage ??= null;
719
+ }
720
+ return this.fetch(`/companies/${this.companyId}/invoices`, {
721
+ method: "POST",
722
+ body
723
+ });
724
+ }
725
+ update(invoiceId, params) {
726
+ return this.fetch(
727
+ `/companies/${this.companyId}/invoices/${invoiceId}`,
728
+ {
729
+ method: "PUT",
730
+ body: params
731
+ }
732
+ );
733
+ }
734
+ send(invoiceId, params) {
735
+ return this.fetch(
736
+ `/companies/${this.companyId}/invoices/${invoiceId}/send`,
737
+ {
738
+ method: "POST",
739
+ body: params
740
+ }
741
+ );
742
+ }
743
+ async downloadPdf(invoiceId) {
744
+ return this.fetch(
745
+ `/companies/${this.companyId}/invoices/${invoiceId}/pdf`,
746
+ {
747
+ headers: { Accept: "application/pdf" }
748
+ }
749
+ );
750
+ }
751
+ delete(invoiceId) {
752
+ return this.fetch(
753
+ `/companies/${this.companyId}/invoices/${invoiceId}`,
754
+ { method: "DELETE" }
755
+ );
756
+ }
757
+ async duplicate(invoiceId, overrides) {
758
+ const source = await this.get(invoiceId);
759
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
760
+ const lines = source.lines.map((l) => ({
761
+ description: l.description,
762
+ quantity: overrides?.quantity ?? l.quantity,
763
+ unit_amount: l.unit_amount,
764
+ vat_type: l.vat_type,
765
+ invoicing_unit: l.invoicing_unit,
766
+ invoicing_category_type: l.invoicing_category_type,
767
+ article: l.article
768
+ }));
769
+ return this.create({
770
+ client: source.client_id ? { id: source.client_id } : null,
771
+ emission_date: overrides?.emission_date ?? today,
772
+ title: source.title,
773
+ title_enabled: !!source.title,
774
+ lines,
775
+ status: "draft"
776
+ });
777
+ }
778
+ };
779
+
780
+ // src/sdk/resources/labels.ts
781
+ var LabelsResource = class {
782
+ constructor(fetch, companyId) {
783
+ this.fetch = fetch;
784
+ this.companyId = companyId;
785
+ }
786
+ list() {
787
+ return this.fetch(`/companies/${this.companyId}/labels`, {
788
+ headers: {
789
+ Accept: "application/vnd.tiime.labels.v2+json"
790
+ }
791
+ });
792
+ }
793
+ standard() {
794
+ return this.fetch(`/companies/${this.companyId}/standard_labels`);
795
+ }
796
+ tags() {
797
+ return this.fetch(`/companies/${this.companyId}/tags`, {
798
+ query: { expand: "tag_detail" }
799
+ });
800
+ }
801
+ };
802
+
803
+ // src/sdk/resources/quotations.ts
804
+ var QuotationsResource = class {
805
+ constructor(fetch, companyId) {
806
+ this.fetch = fetch;
807
+ this.companyId = companyId;
808
+ }
809
+ list(expand = "invoices") {
810
+ return this.fetch(`/companies/${this.companyId}/quotations`, {
811
+ query: { expand },
812
+ headers: { Range: "items=0-25" }
813
+ });
814
+ }
815
+ get(quotationId) {
816
+ return this.fetch(
817
+ `/companies/${this.companyId}/quotations/${quotationId}`
818
+ );
819
+ }
820
+ create(params) {
821
+ return this.fetch(`/companies/${this.companyId}/quotations`, {
822
+ method: "POST",
823
+ body: params
824
+ });
825
+ }
826
+ async downloadPdf(quotationId) {
827
+ return this.fetch(
828
+ `/companies/${this.companyId}/quotations/${quotationId}/pdf`,
829
+ {
830
+ headers: { Accept: "application/pdf" }
831
+ }
832
+ );
833
+ }
834
+ send(quotationId, params) {
835
+ return this.fetch(
836
+ `/companies/${this.companyId}/quotations/${quotationId}/send`,
837
+ {
838
+ method: "POST",
839
+ body: params
840
+ }
841
+ );
842
+ }
843
+ };
844
+
845
+ // src/sdk/resources/users.ts
846
+ var UsersResource = class {
847
+ constructor(fetch) {
848
+ this.fetch = fetch;
849
+ }
850
+ me() {
851
+ return this.fetch("/users/me");
852
+ }
853
+ legalInformations() {
854
+ return this.fetch("/users/me/legal_informations");
855
+ }
856
+ settings(companyId) {
857
+ return this.fetch(`/users/me/companies/${companyId}/settings`);
858
+ }
859
+ };
860
+
861
+ // src/sdk/client.ts
862
+ var BASE_URL = "https://chronos-api.tiime-apps.com/v1";
863
+ var TiimeClient = class {
864
+ fetch;
865
+ tokenManager;
866
+ companyId;
867
+ constructor(options) {
868
+ this.companyId = options.companyId;
869
+ this.tokenManager = options.tokenManager ?? new TokenManager();
870
+ this.fetch = ofetch2.create({
871
+ baseURL: BASE_URL,
872
+ retry: 2,
873
+ retryDelay: 500,
874
+ retryStatusCodes: [408, 429, 500, 502, 503, 504],
875
+ headers: {
876
+ "tiime-app": "tiime",
877
+ "tiime-app-version": "4.30.3",
878
+ "tiime-app-platform": "cli"
879
+ },
880
+ onRequest: async ({ options: options2 }) => {
881
+ const token = await this.tokenManager.getValidToken();
882
+ options2.headers.set("Authorization", `Bearer ${token}`);
883
+ },
884
+ onResponseError: ({ request, response }) => {
885
+ throw new TiimeError(
886
+ response.statusText || `HTTP ${response.status}`,
887
+ response.status,
888
+ String(request),
889
+ response._data
890
+ );
891
+ }
892
+ });
893
+ }
894
+ listCompanies() {
895
+ return this.fetch("/companies", {
896
+ headers: {
897
+ Accept: "application/vnd.tiime.companies.v2+json",
898
+ Range: "items=0-101"
899
+ }
900
+ });
901
+ }
902
+ get users() {
903
+ return new UsersResource(this.fetch);
904
+ }
905
+ get company() {
906
+ return new CompanyResource(this.fetch, this.companyId);
907
+ }
908
+ get clients() {
909
+ return new ClientsResource(this.fetch, this.companyId);
910
+ }
911
+ get invoices() {
912
+ return new InvoicesResource(this.fetch, this.companyId);
913
+ }
914
+ get quotations() {
915
+ return new QuotationsResource(this.fetch, this.companyId);
916
+ }
917
+ get bankAccounts() {
918
+ return new BankAccountsResource(this.fetch, this.companyId);
919
+ }
920
+ get bankTransactions() {
921
+ return new BankTransactionsResource(this.fetch, this.companyId);
922
+ }
923
+ get documents() {
924
+ return new DocumentsResource(this.fetch, this.companyId);
925
+ }
926
+ get expenseReports() {
927
+ return new ExpenseReportsResource(this.fetch, this.companyId);
928
+ }
929
+ get labels() {
930
+ return new LabelsResource(this.fetch, this.companyId);
931
+ }
932
+ };
933
+
934
+ // src/cli/config.ts
935
+ import { existsSync as existsSync2, mkdirSync as mkdirSync2, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "fs";
936
+ import { homedir as homedir2 } from "os";
937
+ import { join as join2 } from "path";
938
+ var CONFIG_DIR2 = join2(homedir2(), ".config", "tiime");
939
+ var CONFIG_FILE = join2(CONFIG_DIR2, "config.json");
940
+ var loadConfig = () => {
941
+ try {
942
+ if (existsSync2(CONFIG_FILE)) {
943
+ return JSON.parse(readFileSync2(CONFIG_FILE, "utf-8"));
944
+ }
945
+ } catch {
946
+ }
947
+ return {};
948
+ };
949
+ var saveConfig = (config) => {
950
+ if (!existsSync2(CONFIG_DIR2)) {
951
+ mkdirSync2(CONFIG_DIR2, { recursive: true });
952
+ }
953
+ writeFileSync2(CONFIG_FILE, JSON.stringify(config, null, 2));
954
+ };
955
+ var getCompanyId = () => {
956
+ const config = loadConfig();
957
+ if (!config.companyId) {
958
+ throw new Error(
959
+ "Aucune entreprise configur\xE9e. Ex\xE9cutez `tiime company use <id>` d'abord."
960
+ );
961
+ }
962
+ return config.companyId;
963
+ };
964
+
965
+ // src/cli/commands/bank.ts
966
+ var bankCommand = defineCommand2({
967
+ meta: { name: "bank", description: "Comptes bancaires et transactions" },
968
+ subCommands: {
969
+ balance: defineCommand2({
970
+ meta: {
971
+ name: "balance",
972
+ description: "Afficher les soldes des comptes"
973
+ },
974
+ args: { ...formatArg },
975
+ async run({ args }) {
976
+ try {
977
+ const client = new TiimeClient({ companyId: getCompanyId() });
978
+ const balances = await client.bankAccounts.balance();
979
+ output(balances, { format: args.format });
980
+ } catch (e) {
981
+ outputError(e);
982
+ }
983
+ }
984
+ }),
985
+ accounts: defineCommand2({
986
+ meta: { name: "accounts", description: "Lister les comptes bancaires" },
987
+ args: {
988
+ ...formatArg,
989
+ enabled: {
990
+ type: "boolean",
991
+ description: "Uniquement les comptes actifs",
992
+ default: true
993
+ }
994
+ },
995
+ async run({ args }) {
996
+ try {
997
+ const client = new TiimeClient({ companyId: getCompanyId() });
998
+ const accounts = await client.bankAccounts.list(args.enabled);
999
+ output(accounts, { format: args.format });
1000
+ } catch (e) {
1001
+ outputError(e);
1002
+ }
1003
+ }
1004
+ }),
1005
+ transactions: defineCommand2({
1006
+ meta: {
1007
+ name: "transactions",
1008
+ description: "Lister les transactions bancaires"
1009
+ },
1010
+ args: {
1011
+ "bank-account": {
1012
+ type: "string",
1013
+ description: "Filtrer par ID de compte bancaire"
1014
+ },
1015
+ "hide-refused": {
1016
+ type: "boolean",
1017
+ description: "Masquer les transactions refus\xE9es",
1018
+ default: false
1019
+ },
1020
+ sort: {
1021
+ type: "string",
1022
+ description: "Tri champ:direction (ex: date:desc)"
1023
+ },
1024
+ page: { type: "string", description: "Num\xE9ro de page", default: "1" },
1025
+ "page-size": {
1026
+ type: "string",
1027
+ description: "\xC9l\xE9ments par page",
1028
+ default: "100"
1029
+ },
1030
+ from: {
1031
+ type: "string",
1032
+ description: "Date de d\xE9but (YYYY-MM-DD)"
1033
+ },
1034
+ to: {
1035
+ type: "string",
1036
+ description: "Date de fin (YYYY-MM-DD)"
1037
+ },
1038
+ search: {
1039
+ type: "string",
1040
+ description: "Rechercher par libell\xE9"
1041
+ },
1042
+ all: {
1043
+ type: "boolean",
1044
+ description: "R\xE9cup\xE9rer toutes les pages",
1045
+ default: false
1046
+ },
1047
+ ...formatArg
1048
+ },
1049
+ async run({ args }) {
1050
+ try {
1051
+ const fmt = { format: args.format };
1052
+ const client = new TiimeClient({ companyId: getCompanyId() });
1053
+ const params = {
1054
+ bank_account: args["bank-account"] ? Number(args["bank-account"]) : void 0,
1055
+ hide_refused: args["hide-refused"],
1056
+ sorts: args.sort,
1057
+ from: args.from,
1058
+ to: args.to,
1059
+ search: args.search
1060
+ };
1061
+ if (args.all) {
1062
+ const transactions = await client.bankTransactions.listAll(params);
1063
+ output(transactions, fmt);
1064
+ } else {
1065
+ const transactions = await client.bankTransactions.list({
1066
+ ...params,
1067
+ page: Number(args.page),
1068
+ pageSize: Number(args["page-size"])
1069
+ });
1070
+ output(transactions, fmt);
1071
+ }
1072
+ } catch (e) {
1073
+ outputError(e);
1074
+ }
1075
+ }
1076
+ }),
1077
+ unimputed: defineCommand2({
1078
+ meta: {
1079
+ name: "unimputed",
1080
+ description: "Transactions non imput\xE9es"
1081
+ },
1082
+ args: { ...formatArg },
1083
+ async run({ args }) {
1084
+ try {
1085
+ const client = new TiimeClient({ companyId: getCompanyId() });
1086
+ const transactions = await client.bankTransactions.unimputed();
1087
+ output(transactions, { format: args.format });
1088
+ } catch (e) {
1089
+ outputError(e);
1090
+ }
1091
+ }
1092
+ })
1093
+ }
1094
+ });
1095
+
1096
+ // src/cli/commands/clients.ts
1097
+ import { defineCommand as defineCommand3 } from "citty";
1098
+ var clientsCommand = defineCommand3({
1099
+ meta: { name: "clients", description: "Gestion des clients" },
1100
+ subCommands: {
1101
+ list: defineCommand3({
1102
+ meta: { name: "list", description: "Lister les clients" },
1103
+ args: {
1104
+ ...formatArg,
1105
+ archived: {
1106
+ type: "boolean",
1107
+ description: "Inclure les clients archiv\xE9s",
1108
+ default: false
1109
+ }
1110
+ },
1111
+ async run({ args }) {
1112
+ try {
1113
+ const client = new TiimeClient({ companyId: getCompanyId() });
1114
+ const clients = await client.clients.list({
1115
+ archived: args.archived
1116
+ });
1117
+ output(clients, { format: args.format });
1118
+ } catch (e) {
1119
+ outputError(e);
1120
+ }
1121
+ }
1122
+ }),
1123
+ get: defineCommand3({
1124
+ meta: { name: "get", description: "D\xE9tails d'un client" },
1125
+ args: {
1126
+ id: { type: "string", description: "ID du client", required: true }
1127
+ },
1128
+ async run({ args }) {
1129
+ try {
1130
+ const client = new TiimeClient({ companyId: getCompanyId() });
1131
+ const result = await client.clients.get(Number(args.id));
1132
+ output(result);
1133
+ } catch (e) {
1134
+ outputError(e);
1135
+ }
1136
+ }
1137
+ }),
1138
+ create: defineCommand3({
1139
+ meta: { name: "create", description: "Cr\xE9er un client" },
1140
+ args: {
1141
+ name: {
1142
+ type: "string",
1143
+ description: "Nom du client",
1144
+ required: true
1145
+ },
1146
+ address: {
1147
+ type: "string",
1148
+ description: "Adresse du client"
1149
+ },
1150
+ "postal-code": {
1151
+ type: "string",
1152
+ description: "Code postal"
1153
+ },
1154
+ city: {
1155
+ type: "string",
1156
+ description: "Ville"
1157
+ },
1158
+ email: {
1159
+ type: "string",
1160
+ description: "Adresse email"
1161
+ },
1162
+ phone: {
1163
+ type: "string",
1164
+ description: "Num\xE9ro de t\xE9l\xE9phone"
1165
+ },
1166
+ siret: {
1167
+ type: "string",
1168
+ description: "SIREN ou SIRET"
1169
+ },
1170
+ professional: {
1171
+ type: "boolean",
1172
+ description: "Client professionnel",
1173
+ default: true
1174
+ }
1175
+ },
1176
+ async run({ args }) {
1177
+ try {
1178
+ const client = new TiimeClient({ companyId: getCompanyId() });
1179
+ const result = await client.clients.create({
1180
+ name: args.name,
1181
+ address: args.address,
1182
+ postal_code: args["postal-code"],
1183
+ city: args.city,
1184
+ email: args.email,
1185
+ phone: args.phone,
1186
+ siren_or_siret: args.siret,
1187
+ professional: args.professional
1188
+ });
1189
+ output(result);
1190
+ } catch (e) {
1191
+ outputError(e);
1192
+ }
1193
+ }
1194
+ }),
1195
+ search: defineCommand3({
1196
+ meta: { name: "search", description: "Rechercher un client" },
1197
+ args: {
1198
+ ...formatArg,
1199
+ query: {
1200
+ type: "string",
1201
+ description: "Terme de recherche",
1202
+ required: true
1203
+ }
1204
+ },
1205
+ async run({ args }) {
1206
+ try {
1207
+ const client = new TiimeClient({ companyId: getCompanyId() });
1208
+ const results = await client.clients.search(args.query);
1209
+ output(results, { format: args.format });
1210
+ } catch (e) {
1211
+ outputError(e);
1212
+ }
1213
+ }
1214
+ })
1215
+ }
1216
+ });
1217
+
1218
+ // src/cli/commands/company.ts
1219
+ import { defineCommand as defineCommand4 } from "citty";
1220
+ var companyCommand = defineCommand4({
1221
+ meta: { name: "company", description: "Gestion de l'entreprise" },
1222
+ subCommands: {
1223
+ list: defineCommand4({
1224
+ meta: { name: "list", description: "Lister toutes les entreprises" },
1225
+ args: { ...formatArg },
1226
+ async run({ args }) {
1227
+ try {
1228
+ const client = new TiimeClient({ companyId: 0 });
1229
+ const companies = await client.listCompanies();
1230
+ output(
1231
+ companies.map((c) => ({
1232
+ id: c.id,
1233
+ name: c.name,
1234
+ legal_form: c.legal_form,
1235
+ siret: c.siret,
1236
+ city: c.city
1237
+ })),
1238
+ { format: args.format }
1239
+ );
1240
+ } catch (e) {
1241
+ outputError(e);
1242
+ }
1243
+ }
1244
+ }),
1245
+ get: defineCommand4({
1246
+ meta: { name: "get", description: "D\xE9tails de l'entreprise active" },
1247
+ async run() {
1248
+ try {
1249
+ const client = new TiimeClient({
1250
+ companyId: getCompanyId()
1251
+ });
1252
+ const company = await client.company.get();
1253
+ output(company);
1254
+ } catch (e) {
1255
+ outputError(e);
1256
+ }
1257
+ }
1258
+ }),
1259
+ use: defineCommand4({
1260
+ meta: { name: "use", description: "D\xE9finir l'entreprise active" },
1261
+ args: {
1262
+ id: {
1263
+ type: "string",
1264
+ description: "ID de l'entreprise",
1265
+ required: true
1266
+ }
1267
+ },
1268
+ run({ args }) {
1269
+ const config = loadConfig();
1270
+ config.companyId = Number(args.id);
1271
+ saveConfig(config);
1272
+ output({ status: "ok", companyId: config.companyId });
1273
+ }
1274
+ }),
1275
+ me: defineCommand4({
1276
+ meta: {
1277
+ name: "me",
1278
+ description: "Info utilisateur courant (inclut active_company)"
1279
+ },
1280
+ async run() {
1281
+ try {
1282
+ const client = new TiimeClient({ companyId: 0 });
1283
+ const user = await client.users.me();
1284
+ output(user);
1285
+ } catch (e) {
1286
+ outputError(e);
1287
+ }
1288
+ }
1289
+ })
1290
+ }
1291
+ });
1292
+
1293
+ // src/cli/commands/completion.ts
1294
+ import { defineCommand as defineCommand5 } from "citty";
1295
+ var commands = {
1296
+ auth: {
1297
+ description: "Gestion de l'authentification",
1298
+ subs: {
1299
+ login: "Se connecter",
1300
+ logout: "Se d\xE9connecter",
1301
+ status: "Statut de connexion"
1302
+ }
1303
+ },
1304
+ company: {
1305
+ description: "Gestion de l'entreprise",
1306
+ subs: {
1307
+ list: "Lister les entreprises",
1308
+ get: "D\xE9tails de l'entreprise active",
1309
+ use: "D\xE9finir l'entreprise active",
1310
+ me: "Info utilisateur"
1311
+ }
1312
+ },
1313
+ invoices: {
1314
+ description: "Gestion des factures",
1315
+ subs: {
1316
+ list: "Lister les factures",
1317
+ get: "D\xE9tails d'une facture",
1318
+ create: "Cr\xE9er une facture",
1319
+ duplicate: "Dupliquer une facture",
1320
+ update: "Modifier une facture",
1321
+ send: "Envoyer par email",
1322
+ pdf: "T\xE9l\xE9charger le PDF",
1323
+ delete: "Supprimer un brouillon"
1324
+ }
1325
+ },
1326
+ clients: {
1327
+ description: "Gestion des clients",
1328
+ subs: {
1329
+ list: "Lister les clients",
1330
+ get: "D\xE9tails d'un client",
1331
+ create: "Cr\xE9er un client",
1332
+ search: "Rechercher un client"
1333
+ }
1334
+ },
1335
+ bank: {
1336
+ description: "Comptes et transactions",
1337
+ subs: {
1338
+ accounts: "Comptes bancaires",
1339
+ balance: "Soldes des comptes",
1340
+ transactions: "Transactions bancaires",
1341
+ unimputed: "Transactions non imput\xE9es"
1342
+ }
1343
+ },
1344
+ quotations: {
1345
+ description: "Gestion des devis",
1346
+ subs: {
1347
+ list: "Lister les devis",
1348
+ get: "D\xE9tails d'un devis",
1349
+ create: "Cr\xE9er un devis",
1350
+ pdf: "T\xE9l\xE9charger le PDF",
1351
+ send: "Envoyer par email"
1352
+ }
1353
+ },
1354
+ expenses: {
1355
+ description: "Notes de frais",
1356
+ subs: {
1357
+ list: "Lister les notes de frais",
1358
+ get: "D\xE9tails d'une note de frais",
1359
+ create: "Cr\xE9er une note de frais"
1360
+ }
1361
+ },
1362
+ documents: {
1363
+ description: "Gestion des documents",
1364
+ subs: {
1365
+ list: "Lister les documents",
1366
+ categories: "Cat\xE9gories de documents",
1367
+ upload: "Uploader un justificatif",
1368
+ download: "T\xE9l\xE9charger un document"
1369
+ }
1370
+ },
1371
+ labels: {
1372
+ description: "Labels et tags",
1373
+ subs: {
1374
+ list: "Labels personnalis\xE9s",
1375
+ standard: "Labels standards",
1376
+ tags: "Tags"
1377
+ }
1378
+ },
1379
+ status: {
1380
+ description: "R\xE9sum\xE9 rapide de la situation",
1381
+ subs: {}
1382
+ },
1383
+ open: {
1384
+ description: "Ouvrir Tiime dans le navigateur",
1385
+ subs: {}
1386
+ },
1387
+ version: {
1388
+ description: "Afficher la version",
1389
+ subs: {}
1390
+ },
1391
+ completion: {
1392
+ description: "Script d'autocompl\xE9tion",
1393
+ subs: {}
1394
+ }
1395
+ };
1396
+ var topLevelNames = Object.keys(commands);
1397
+ var commonFlags = ["--format", "--id", "--all"];
1398
+ var generateZsh = () => {
1399
+ const esc = (s) => s.replace(/'/g, "'\\''");
1400
+ const subcases = topLevelNames.filter((cmd) => Object.keys(commands[cmd].subs).length > 0).map((cmd) => {
1401
+ const subs = Object.entries(commands[cmd].subs).map(([name, desc]) => `'${name}:${esc(desc)}'`).join(" ");
1402
+ return ` ${cmd})
1403
+ local -a subcmds
1404
+ subcmds=(${subs})
1405
+ _describe 'sous-commande' subcmds
1406
+ ;;`;
1407
+ }).join("\n");
1408
+ return `#compdef tiime
1409
+
1410
+ _tiime() {
1411
+ local -a top_commands
1412
+ top_commands=(
1413
+ ${topLevelNames.map((c) => ` '${c}:${esc(commands[c].description)}'`).join("\n")}
1414
+ )
1415
+
1416
+ if (( CURRENT == 2 )); then
1417
+ _describe 'commande' top_commands
1418
+ return
1419
+ fi
1420
+
1421
+ local cmd="\${words[2]}"
1422
+
1423
+ if (( CURRENT == 3 )); then
1424
+ case "$cmd" in
1425
+ ${subcases}
1426
+ esac
1427
+ return
1428
+ fi
1429
+
1430
+ # Position 4+ : proposer les flags courants
1431
+ _arguments \\
1432
+ '--format[Format de sortie (json, table, csv)]' \\
1433
+ '--id[Identifiant de la ressource]' \\
1434
+ '--all[R\xE9cup\xE9rer tous les r\xE9sultats]'
1435
+ }
1436
+
1437
+ compdef _tiime tiime
1438
+ `;
1439
+ };
1440
+ var generateBash = () => {
1441
+ const subcases = topLevelNames.filter((cmd) => Object.keys(commands[cmd].subs).length > 0).map((cmd) => {
1442
+ const subs = Object.keys(commands[cmd].subs).join(" ");
1443
+ return ` ${cmd})
1444
+ COMPREPLY=( $(compgen -W "${subs}" -- "$cur") )
1445
+ return
1446
+ ;;`;
1447
+ }).join("\n");
1448
+ return `_tiime() {
1449
+ local cur prev cmd
1450
+ cur="\${COMP_WORDS[COMP_CWORD]}"
1451
+ prev="\${COMP_WORDS[COMP_CWORD-1]}"
1452
+
1453
+ if [[ \${COMP_CWORD} -eq 1 ]]; then
1454
+ COMPREPLY=( $(compgen -W "${topLevelNames.join(" ")}" -- "$cur") )
1455
+ return
1456
+ fi
1457
+
1458
+ cmd="\${COMP_WORDS[1]}"
1459
+
1460
+ if [[ \${COMP_CWORD} -eq 2 ]]; then
1461
+ case "$cmd" in
1462
+ ${subcases}
1463
+ esac
1464
+ return
1465
+ fi
1466
+
1467
+ # Position 3+ : proposer les flags courants
1468
+ COMPREPLY=( $(compgen -W "${commonFlags.join(" ")}" -- "$cur") )
1469
+ }
1470
+
1471
+ complete -F _tiime tiime
1472
+ `;
1473
+ };
1474
+ var generateFish = () => {
1475
+ const esc = (s) => s.replace(/'/g, "\\'");
1476
+ const lines = [
1477
+ "# Disable file completions by default",
1478
+ "complete -c tiime -f",
1479
+ "",
1480
+ "# Top-level commands",
1481
+ ...topLevelNames.map(
1482
+ (cmd) => `complete -c tiime -n '__fish_use_subcommand' -a '${cmd}' -d '${esc(commands[cmd].description)}'`
1483
+ ),
1484
+ "",
1485
+ "# Subcommands"
1486
+ ];
1487
+ for (const cmd of topLevelNames) {
1488
+ const subs = commands[cmd].subs;
1489
+ const subNames = Object.keys(subs);
1490
+ if (subNames.length > 0) {
1491
+ const condition = `__fish_seen_subcommand_from ${cmd}; and not __fish_seen_subcommand_from ${subNames.join(" ")}`;
1492
+ for (const [sub, desc] of Object.entries(subs)) {
1493
+ lines.push(
1494
+ `complete -c tiime -n '${condition}' -a '${sub}' -d '${esc(desc)}'`
1495
+ );
1496
+ }
1497
+ lines.push("");
1498
+ }
1499
+ }
1500
+ lines.push("# Common flags");
1501
+ for (const cmd of topLevelNames) {
1502
+ const subNames = Object.keys(commands[cmd].subs);
1503
+ if (subNames.length > 0) {
1504
+ const condition = `__fish_seen_subcommand_from ${subNames.join(" ")}`;
1505
+ lines.push(
1506
+ `complete -c tiime -n '${condition}' -l format -d 'Format de sortie'`
1507
+ );
1508
+ lines.push(
1509
+ `complete -c tiime -n '${condition}' -l id -d 'Identifiant de la ressource'`
1510
+ );
1511
+ lines.push(
1512
+ `complete -c tiime -n '${condition}' -l all -d 'R\xE9cup\xE9rer tous les r\xE9sultats'`
1513
+ );
1514
+ }
1515
+ }
1516
+ return `${lines.join("\n")}
1517
+ `;
1518
+ };
1519
+ var completionCommand = defineCommand5({
1520
+ meta: {
1521
+ name: "completion",
1522
+ description: "G\xE9n\xE9rer le script d'autocompl\xE9tion shell"
1523
+ },
1524
+ args: {
1525
+ shell: {
1526
+ type: "string",
1527
+ description: "Shell cible (zsh, bash, fish)",
1528
+ default: "zsh"
1529
+ }
1530
+ },
1531
+ run({ args }) {
1532
+ const shell = args.shell;
1533
+ switch (shell) {
1534
+ case "zsh":
1535
+ process.stdout.write(generateZsh());
1536
+ break;
1537
+ case "bash":
1538
+ process.stdout.write(generateBash());
1539
+ break;
1540
+ case "fish":
1541
+ process.stdout.write(generateFish());
1542
+ break;
1543
+ default:
1544
+ process.stderr.write(
1545
+ `Shell non support\xE9 : ${shell}. Utilisez zsh, bash ou fish.
1546
+ `
1547
+ );
1548
+ process.exit(1);
1549
+ }
1550
+ }
1551
+ });
1552
+
1553
+ // src/cli/commands/documents.ts
1554
+ import { readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
1555
+ import { basename } from "path";
1556
+ import { defineCommand as defineCommand6 } from "citty";
1557
+ var documentsCommand = defineCommand6({
1558
+ meta: { name: "documents", description: "Gestion des documents" },
1559
+ subCommands: {
1560
+ list: defineCommand6({
1561
+ meta: { name: "list", description: "Lister les documents" },
1562
+ args: {
1563
+ ...formatArg,
1564
+ type: {
1565
+ type: "string",
1566
+ description: "Type de document (ex: receipt)"
1567
+ },
1568
+ source: {
1569
+ type: "string",
1570
+ description: "Source du document (ex: accountant)"
1571
+ },
1572
+ page: { type: "string", description: "Num\xE9ro de page", default: "1" }
1573
+ },
1574
+ async run({ args }) {
1575
+ try {
1576
+ const client = new TiimeClient({ companyId: getCompanyId() });
1577
+ const docs = await client.documents.list({
1578
+ types: args.type,
1579
+ source: args.source,
1580
+ page: Number(args.page)
1581
+ });
1582
+ output(docs, { format: args.format });
1583
+ } catch (e) {
1584
+ outputError(e);
1585
+ }
1586
+ }
1587
+ }),
1588
+ upload: defineCommand6({
1589
+ meta: { name: "upload", description: "Uploader un justificatif" },
1590
+ args: {
1591
+ file: {
1592
+ type: "string",
1593
+ description: "Chemin du fichier \xE0 uploader",
1594
+ required: true
1595
+ },
1596
+ type: {
1597
+ type: "string",
1598
+ description: "Type de document"
1599
+ }
1600
+ },
1601
+ async run({ args }) {
1602
+ try {
1603
+ const client = new TiimeClient({ companyId: getCompanyId() });
1604
+ const fileBuffer = readFileSync3(args.file);
1605
+ const filename = basename(args.file);
1606
+ const result = await client.documents.upload(
1607
+ fileBuffer,
1608
+ filename,
1609
+ args.type
1610
+ );
1611
+ output(result);
1612
+ } catch (e) {
1613
+ outputError(e);
1614
+ }
1615
+ }
1616
+ }),
1617
+ download: defineCommand6({
1618
+ meta: { name: "download", description: "T\xE9l\xE9charger un document" },
1619
+ args: {
1620
+ id: {
1621
+ type: "string",
1622
+ description: "ID du document",
1623
+ required: true
1624
+ },
1625
+ output: {
1626
+ type: "string",
1627
+ description: "Chemin de sortie du fichier"
1628
+ }
1629
+ },
1630
+ async run({ args }) {
1631
+ try {
1632
+ const client = new TiimeClient({ companyId: getCompanyId() });
1633
+ const documentId = Number(args.id);
1634
+ const data = await client.documents.download(documentId);
1635
+ const outputPath = args.output ?? `document-${documentId}`;
1636
+ writeFileSync3(outputPath, Buffer.from(data));
1637
+ output({ status: "downloaded", path: outputPath });
1638
+ } catch (e) {
1639
+ outputError(e);
1640
+ }
1641
+ }
1642
+ }),
1643
+ categories: defineCommand6({
1644
+ meta: {
1645
+ name: "categories",
1646
+ description: "Lister les cat\xE9gories de documents"
1647
+ },
1648
+ args: { ...formatArg },
1649
+ async run({ args }) {
1650
+ try {
1651
+ const client = new TiimeClient({ companyId: getCompanyId() });
1652
+ const categories = await client.documents.categories();
1653
+ output(categories, { format: args.format });
1654
+ } catch (e) {
1655
+ outputError(e);
1656
+ }
1657
+ }
1658
+ })
1659
+ }
1660
+ });
1661
+
1662
+ // src/cli/commands/expenses.ts
1663
+ import { defineCommand as defineCommand7 } from "citty";
1664
+ var expensesCommand = defineCommand7({
1665
+ meta: { name: "expenses", description: "Gestion des notes de frais" },
1666
+ subCommands: {
1667
+ list: defineCommand7({
1668
+ meta: { name: "list", description: "Lister les notes de frais" },
1669
+ args: {
1670
+ ...formatArg,
1671
+ sort: {
1672
+ type: "string",
1673
+ description: "Tri champ:direction",
1674
+ default: "metadata.date:desc"
1675
+ }
1676
+ },
1677
+ async run({ args }) {
1678
+ try {
1679
+ const client = new TiimeClient({ companyId: getCompanyId() });
1680
+ const expenses = await client.expenseReports.list(args.sort);
1681
+ output(expenses, { format: args.format });
1682
+ } catch (e) {
1683
+ outputError(e);
1684
+ }
1685
+ }
1686
+ }),
1687
+ get: defineCommand7({
1688
+ meta: { name: "get", description: "D\xE9tails d'une note de frais" },
1689
+ args: {
1690
+ id: {
1691
+ type: "string",
1692
+ description: "ID de la note de frais",
1693
+ required: true
1694
+ }
1695
+ },
1696
+ async run({ args }) {
1697
+ try {
1698
+ const client = new TiimeClient({ companyId: getCompanyId() });
1699
+ const expense = await client.expenseReports.get(Number(args.id));
1700
+ output(expense);
1701
+ } catch (e) {
1702
+ outputError(e);
1703
+ }
1704
+ }
1705
+ }),
1706
+ create: defineCommand7({
1707
+ meta: { name: "create", description: "Cr\xE9er une note de frais" },
1708
+ args: {
1709
+ name: {
1710
+ type: "string",
1711
+ description: "Nom de la note de frais",
1712
+ required: true
1713
+ },
1714
+ date: {
1715
+ type: "string",
1716
+ description: "Date (YYYY-MM-DD)"
1717
+ }
1718
+ },
1719
+ async run({ args }) {
1720
+ try {
1721
+ const client = new TiimeClient({ companyId: getCompanyId() });
1722
+ const expense = await client.expenseReports.create({
1723
+ name: args.name,
1724
+ metadata: args.date ? { date: args.date } : void 0
1725
+ });
1726
+ output(expense);
1727
+ } catch (e) {
1728
+ outputError(e);
1729
+ }
1730
+ }
1731
+ })
1732
+ }
1733
+ });
1734
+
1735
+ // src/cli/commands/invoices.ts
1736
+ import { writeFileSync as writeFileSync4 } from "fs";
1737
+ import { defineCommand as defineCommand8 } from "citty";
1738
+ var invoicesCommand = defineCommand8({
1739
+ meta: { name: "invoices", description: "Gestion des factures" },
1740
+ subCommands: {
1741
+ list: defineCommand8({
1742
+ meta: { name: "list", description: "Lister les factures" },
1743
+ args: {
1744
+ ...formatArg,
1745
+ sort: {
1746
+ type: "string",
1747
+ description: "Tri champ:direction (ex: invoice_number:desc)",
1748
+ default: "invoice_number:desc"
1749
+ },
1750
+ status: {
1751
+ type: "string",
1752
+ description: "Filtrer par statut (draft, saved, sent, paid)"
1753
+ },
1754
+ page: { type: "string", description: "Num\xE9ro de page", default: "1" },
1755
+ "page-size": {
1756
+ type: "string",
1757
+ description: "\xC9l\xE9ments par page",
1758
+ default: "25"
1759
+ },
1760
+ all: {
1761
+ type: "boolean",
1762
+ description: "R\xE9cup\xE9rer toutes les pages",
1763
+ default: false
1764
+ }
1765
+ },
1766
+ async run({ args }) {
1767
+ try {
1768
+ const fmt = { format: args.format };
1769
+ const client = new TiimeClient({ companyId: getCompanyId() });
1770
+ if (args.all) {
1771
+ const invoices = await client.invoices.listAll({
1772
+ sorts: args.sort,
1773
+ status: args.status
1774
+ });
1775
+ output(invoices, fmt);
1776
+ } else {
1777
+ const invoices = await client.invoices.list({
1778
+ sorts: args.sort,
1779
+ status: args.status,
1780
+ page: Number(args.page),
1781
+ pageSize: Number(args["page-size"])
1782
+ });
1783
+ output(invoices, fmt);
1784
+ }
1785
+ } catch (e) {
1786
+ outputError(e);
1787
+ }
1788
+ }
1789
+ }),
1790
+ get: defineCommand8({
1791
+ meta: { name: "get", description: "D\xE9tails d'une facture" },
1792
+ args: {
1793
+ id: { type: "string", description: "ID de la facture", required: true }
1794
+ },
1795
+ async run({ args }) {
1796
+ try {
1797
+ const client = new TiimeClient({ companyId: getCompanyId() });
1798
+ const invoice = await client.invoices.get(Number(args.id));
1799
+ output(invoice);
1800
+ } catch (e) {
1801
+ outputError(e);
1802
+ }
1803
+ }
1804
+ }),
1805
+ create: defineCommand8({
1806
+ meta: {
1807
+ name: "create",
1808
+ description: "Cr\xE9er une facture (brouillon par d\xE9faut)"
1809
+ },
1810
+ args: {
1811
+ "client-id": {
1812
+ type: "string",
1813
+ description: "ID du client"
1814
+ },
1815
+ "client-name": {
1816
+ type: "string",
1817
+ description: "Nom du client (si pas de client-id)"
1818
+ },
1819
+ date: {
1820
+ type: "string",
1821
+ description: "Date d'\xE9mission (YYYY-MM-DD, d\xE9faut : aujourd'hui)"
1822
+ },
1823
+ title: {
1824
+ type: "string",
1825
+ description: "Titre de la facture"
1826
+ },
1827
+ description: {
1828
+ type: "string",
1829
+ description: "Description de la ligne (ligne simple)"
1830
+ },
1831
+ quantity: {
1832
+ type: "string",
1833
+ description: "Quantit\xE9 (ligne simple)",
1834
+ default: "1"
1835
+ },
1836
+ "unit-price": {
1837
+ type: "string",
1838
+ description: "Prix unitaire HT (ligne simple)"
1839
+ },
1840
+ unit: {
1841
+ type: "string",
1842
+ description: "Code unit\xE9 (day, hour, unit, etc.)"
1843
+ },
1844
+ vat: {
1845
+ type: "string",
1846
+ description: "Code TVA (normal=20%, reduced=10%, super_reduced=5.5%, none=0%)",
1847
+ default: "normal"
1848
+ },
1849
+ lines: {
1850
+ type: "string",
1851
+ description: `Multi-lignes en JSON : '[{"description":"Dev","quantity":20,"unit_price":540,"unit":"day"}]'`
1852
+ },
1853
+ "free-field": {
1854
+ type: "string",
1855
+ description: "Champ libre (ex : r\xE9f\xE9rence contrat)"
1856
+ },
1857
+ status: {
1858
+ type: "string",
1859
+ description: "Statut : draft (d\xE9faut) ou saved (num\xE9rot\xE9e)",
1860
+ default: "draft"
1861
+ },
1862
+ "dry-run": {
1863
+ type: "boolean",
1864
+ description: "Pr\xE9visualiser le payload sans cr\xE9er la facture",
1865
+ default: false
1866
+ }
1867
+ },
1868
+ async run({ args }) {
1869
+ try {
1870
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
1871
+ const unitMap = {
1872
+ day: 3,
1873
+ hour: 2,
1874
+ unit: 1,
1875
+ package: 4,
1876
+ word: 5,
1877
+ character: 6,
1878
+ page: 7
1879
+ };
1880
+ let invoiceLines;
1881
+ if (args.lines) {
1882
+ const parsed = JSON.parse(args.lines);
1883
+ invoiceLines = parsed.map((l) => ({
1884
+ description: l.description,
1885
+ quantity: l.quantity,
1886
+ unit_amount: l.unit_price,
1887
+ vat_type: { code: l.vat ?? args.vat },
1888
+ invoicing_unit: l.unit ? { id: unitMap[l.unit] ?? 1, code: l.unit } : null
1889
+ }));
1890
+ } else {
1891
+ if (!args.description || !args["unit-price"]) {
1892
+ outputError(
1893
+ "--description et --unit-price sont requis pour une ligne simple (ou utilisez --lines pour du multi-lignes)"
1894
+ );
1895
+ return;
1896
+ }
1897
+ invoiceLines = [
1898
+ {
1899
+ description: args.description,
1900
+ quantity: Number(args.quantity),
1901
+ unit_amount: Number(args["unit-price"]),
1902
+ vat_type: { code: args.vat },
1903
+ invoicing_unit: args.unit ? { id: unitMap[args.unit] ?? 1, code: args.unit } : null
1904
+ }
1905
+ ];
1906
+ }
1907
+ const params = {
1908
+ emission_date: args.date ?? today,
1909
+ title: args.title ?? null,
1910
+ lines: invoiceLines,
1911
+ status: args.status
1912
+ };
1913
+ if (args["client-id"]) {
1914
+ params.client = { id: Number(args["client-id"]) };
1915
+ } else if (args["client-name"]) {
1916
+ params.client_name = args["client-name"];
1917
+ }
1918
+ if (args["free-field"]) {
1919
+ params.free_field = args["free-field"];
1920
+ params.free_field_enabled = true;
1921
+ }
1922
+ if (args["dry-run"]) {
1923
+ output({ dry_run: true, payload: params });
1924
+ return;
1925
+ }
1926
+ const client = new TiimeClient({ companyId: getCompanyId() });
1927
+ const invoice = await client.invoices.create(params);
1928
+ output(invoice);
1929
+ } catch (e) {
1930
+ outputError(e);
1931
+ }
1932
+ }
1933
+ }),
1934
+ duplicate: defineCommand8({
1935
+ meta: {
1936
+ name: "duplicate",
1937
+ description: "Dupliquer une facture existante en brouillon"
1938
+ },
1939
+ args: {
1940
+ id: {
1941
+ type: "string",
1942
+ description: "ID de la facture source",
1943
+ required: true
1944
+ },
1945
+ date: {
1946
+ type: "string",
1947
+ description: "Date d'\xE9mission de la copie (YYYY-MM-DD, d\xE9faut : aujourd'hui)"
1948
+ },
1949
+ quantity: {
1950
+ type: "string",
1951
+ description: "Remplacer la quantit\xE9 pour toutes les lignes"
1952
+ }
1953
+ },
1954
+ async run({ args }) {
1955
+ try {
1956
+ const client = new TiimeClient({ companyId: getCompanyId() });
1957
+ const invoice = await client.invoices.duplicate(Number(args.id), {
1958
+ emission_date: args.date,
1959
+ quantity: args.quantity ? Number(args.quantity) : void 0
1960
+ });
1961
+ output(invoice);
1962
+ } catch (e) {
1963
+ outputError(e);
1964
+ }
1965
+ }
1966
+ }),
1967
+ update: defineCommand8({
1968
+ meta: { name: "update", description: "Mettre \xE0 jour une facture" },
1969
+ args: {
1970
+ id: {
1971
+ type: "string",
1972
+ description: "ID de la facture",
1973
+ required: true
1974
+ },
1975
+ title: {
1976
+ type: "string",
1977
+ description: "Nouveau titre de la facture"
1978
+ },
1979
+ status: {
1980
+ type: "string",
1981
+ description: "Nouveau statut (draft, saved)"
1982
+ },
1983
+ date: {
1984
+ type: "string",
1985
+ description: "Nouvelle date d'\xE9mission (YYYY-MM-DD)"
1986
+ },
1987
+ "free-field": {
1988
+ type: "string",
1989
+ description: "Nouveau champ libre"
1990
+ }
1991
+ },
1992
+ async run({ args }) {
1993
+ try {
1994
+ const updates = {};
1995
+ if (args.title !== void 0) updates.title = args.title;
1996
+ if (args.status !== void 0) updates.status = args.status;
1997
+ if (args.date !== void 0) updates.emission_date = args.date;
1998
+ if (args["free-field"] !== void 0) {
1999
+ updates.free_field = args["free-field"];
2000
+ updates.free_field_enabled = true;
2001
+ }
2002
+ const client = new TiimeClient({ companyId: getCompanyId() });
2003
+ const invoice = await client.invoices.update(
2004
+ Number(args.id),
2005
+ updates
2006
+ );
2007
+ output(invoice);
2008
+ } catch (e) {
2009
+ outputError(e);
2010
+ }
2011
+ }
2012
+ }),
2013
+ send: defineCommand8({
2014
+ meta: { name: "send", description: "Envoyer une facture par email" },
2015
+ args: {
2016
+ id: {
2017
+ type: "string",
2018
+ description: "ID de la facture",
2019
+ required: true
2020
+ },
2021
+ email: {
2022
+ type: "string",
2023
+ description: "Adresse email du destinataire",
2024
+ required: true
2025
+ },
2026
+ subject: {
2027
+ type: "string",
2028
+ description: "Objet de l'email"
2029
+ },
2030
+ message: {
2031
+ type: "string",
2032
+ description: "Corps du message"
2033
+ }
2034
+ },
2035
+ async run({ args }) {
2036
+ try {
2037
+ const client = new TiimeClient({ companyId: getCompanyId() });
2038
+ await client.invoices.send(Number(args.id), {
2039
+ recipients: [{ email: args.email }],
2040
+ subject: args.subject,
2041
+ message: args.message
2042
+ });
2043
+ output({
2044
+ status: "sent",
2045
+ id: Number(args.id),
2046
+ email: args.email
2047
+ });
2048
+ } catch (e) {
2049
+ outputError(e);
2050
+ }
2051
+ }
2052
+ }),
2053
+ pdf: defineCommand8({
2054
+ meta: {
2055
+ name: "pdf",
2056
+ description: "T\xE9l\xE9charger le PDF d'une facture"
2057
+ },
2058
+ args: {
2059
+ id: {
2060
+ type: "string",
2061
+ description: "ID de la facture",
2062
+ required: true
2063
+ },
2064
+ output: {
2065
+ type: "string",
2066
+ description: "Chemin de sortie du fichier (d\xE9faut : facture-{id}.pdf)"
2067
+ }
2068
+ },
2069
+ async run({ args }) {
2070
+ try {
2071
+ const client = new TiimeClient({ companyId: getCompanyId() });
2072
+ const buffer = await client.invoices.downloadPdf(Number(args.id));
2073
+ const filePath = args.output ?? `facture-${args.id}.pdf`;
2074
+ writeFileSync4(filePath, Buffer.from(buffer));
2075
+ output({ status: "downloaded", path: filePath });
2076
+ } catch (e) {
2077
+ outputError(e);
2078
+ }
2079
+ }
2080
+ }),
2081
+ delete: defineCommand8({
2082
+ meta: { name: "delete", description: "Supprimer une facture brouillon" },
2083
+ args: {
2084
+ id: {
2085
+ type: "string",
2086
+ description: "ID de la facture \xE0 supprimer",
2087
+ required: true
2088
+ }
2089
+ },
2090
+ async run({ args }) {
2091
+ try {
2092
+ const client = new TiimeClient({ companyId: getCompanyId() });
2093
+ await client.invoices.delete(Number(args.id));
2094
+ output({ status: "deleted", id: Number(args.id) });
2095
+ } catch (e) {
2096
+ outputError(e);
2097
+ }
2098
+ }
2099
+ })
2100
+ }
2101
+ });
2102
+
2103
+ // src/cli/commands/labels.ts
2104
+ import { defineCommand as defineCommand9 } from "citty";
2105
+ var labelsCommand = defineCommand9({
2106
+ meta: { name: "labels", description: "Gestion des labels et tags" },
2107
+ subCommands: {
2108
+ list: defineCommand9({
2109
+ meta: { name: "list", description: "Lister les labels personnalis\xE9s" },
2110
+ args: { ...formatArg },
2111
+ async run({ args }) {
2112
+ try {
2113
+ const client = new TiimeClient({ companyId: getCompanyId() });
2114
+ const labels = await client.labels.list();
2115
+ output(labels, { format: args.format });
2116
+ } catch (e) {
2117
+ outputError(e);
2118
+ }
2119
+ }
2120
+ }),
2121
+ standard: defineCommand9({
2122
+ meta: { name: "standard", description: "Lister les labels standards" },
2123
+ args: { ...formatArg },
2124
+ async run({ args }) {
2125
+ try {
2126
+ const client = new TiimeClient({ companyId: getCompanyId() });
2127
+ const labels = await client.labels.standard();
2128
+ output(labels, { format: args.format });
2129
+ } catch (e) {
2130
+ outputError(e);
2131
+ }
2132
+ }
2133
+ }),
2134
+ tags: defineCommand9({
2135
+ meta: { name: "tags", description: "Lister les tags" },
2136
+ args: { ...formatArg },
2137
+ async run({ args }) {
2138
+ try {
2139
+ const client = new TiimeClient({ companyId: getCompanyId() });
2140
+ const tags = await client.labels.tags();
2141
+ output(tags, { format: args.format });
2142
+ } catch (e) {
2143
+ outputError(e);
2144
+ }
2145
+ }
2146
+ })
2147
+ }
2148
+ });
2149
+
2150
+ // src/cli/commands/open.ts
2151
+ import { exec } from "child_process";
2152
+ import { defineCommand as defineCommand10 } from "citty";
2153
+ var APP_BASE_URL = "https://apps.tiime.fr";
2154
+ var sections = {
2155
+ invoices: "/invoicing/invoices",
2156
+ quotations: "/invoicing/quotations",
2157
+ clients: "/invoicing/clients",
2158
+ bank: "/bank",
2159
+ documents: "/documents",
2160
+ expenses: "/expense-reports"
2161
+ };
2162
+ var buildUrl = (section) => {
2163
+ if (!section) {
2164
+ return APP_BASE_URL;
2165
+ }
2166
+ const path = sections[section];
2167
+ if (!path) {
2168
+ throw new Error(
2169
+ `Section inconnue : ${section}. Sections disponibles : ${Object.keys(sections).join(", ")}`
2170
+ );
2171
+ }
2172
+ const companyId = getCompanyId();
2173
+ return `${APP_BASE_URL}/companies/${companyId}${path}`;
2174
+ };
2175
+ var openCommand = defineCommand10({
2176
+ meta: {
2177
+ name: "open",
2178
+ description: "Ouvrir Tiime dans le navigateur"
2179
+ },
2180
+ args: {
2181
+ section: {
2182
+ type: "positional",
2183
+ description: `Section \xE0 ouvrir (${Object.keys(sections).join(", ")})`,
2184
+ required: false
2185
+ }
2186
+ },
2187
+ run({ args }) {
2188
+ try {
2189
+ const url = buildUrl(args.section);
2190
+ exec(`open "${url}"`);
2191
+ output({ opened: url });
2192
+ } catch (e) {
2193
+ outputError(e);
2194
+ }
2195
+ }
2196
+ });
2197
+
2198
+ // src/cli/commands/quotations.ts
2199
+ import { writeFileSync as writeFileSync5 } from "fs";
2200
+ import { defineCommand as defineCommand11 } from "citty";
2201
+ var quotationsCommand = defineCommand11({
2202
+ meta: { name: "quotations", description: "Gestion des devis" },
2203
+ subCommands: {
2204
+ list: defineCommand11({
2205
+ meta: { name: "list", description: "Lister les devis" },
2206
+ args: { ...formatArg },
2207
+ async run({ args }) {
2208
+ try {
2209
+ const client = new TiimeClient({ companyId: getCompanyId() });
2210
+ const quotations = await client.quotations.list();
2211
+ output(quotations, { format: args.format });
2212
+ } catch (e) {
2213
+ outputError(e);
2214
+ }
2215
+ }
2216
+ }),
2217
+ get: defineCommand11({
2218
+ meta: { name: "get", description: "D\xE9tails d'un devis" },
2219
+ args: {
2220
+ id: { type: "string", description: "ID du devis", required: true }
2221
+ },
2222
+ async run({ args }) {
2223
+ try {
2224
+ const client = new TiimeClient({ companyId: getCompanyId() });
2225
+ const quotation = await client.quotations.get(Number(args.id));
2226
+ output(quotation);
2227
+ } catch (e) {
2228
+ outputError(e);
2229
+ }
2230
+ }
2231
+ }),
2232
+ create: defineCommand11({
2233
+ meta: {
2234
+ name: "create",
2235
+ description: "Cr\xE9er un devis (brouillon par d\xE9faut)"
2236
+ },
2237
+ args: {
2238
+ "client-id": {
2239
+ type: "string",
2240
+ description: "ID du client"
2241
+ },
2242
+ date: {
2243
+ type: "string",
2244
+ description: "Date du devis (YYYY-MM-DD, d\xE9faut : aujourd'hui)"
2245
+ },
2246
+ title: {
2247
+ type: "string",
2248
+ description: "Titre du devis"
2249
+ },
2250
+ description: {
2251
+ type: "string",
2252
+ description: "Description de la ligne (ligne simple)"
2253
+ },
2254
+ quantity: {
2255
+ type: "string",
2256
+ description: "Quantit\xE9 (ligne simple)",
2257
+ default: "1"
2258
+ },
2259
+ "unit-price": {
2260
+ type: "string",
2261
+ description: "Prix unitaire HT (ligne simple)"
2262
+ },
2263
+ vat: {
2264
+ type: "string",
2265
+ description: "Code TVA (normal=20%, reduced=10%, super_reduced=5.5%, none=0%)",
2266
+ default: "normal"
2267
+ },
2268
+ status: {
2269
+ type: "string",
2270
+ description: "Statut : draft (d\xE9faut) ou saved",
2271
+ default: "draft"
2272
+ }
2273
+ },
2274
+ async run({ args }) {
2275
+ try {
2276
+ const today = (/* @__PURE__ */ new Date()).toISOString().split("T")[0];
2277
+ if (!args.description || !args["unit-price"]) {
2278
+ outputError(
2279
+ "--description et --unit-price sont requis pour cr\xE9er un devis"
2280
+ );
2281
+ return;
2282
+ }
2283
+ const lines = [
2284
+ {
2285
+ description: args.description,
2286
+ quantity: Number(args.quantity),
2287
+ unit_amount: Number(args["unit-price"]),
2288
+ vat_type: { code: args.vat }
2289
+ }
2290
+ ];
2291
+ const params = {
2292
+ date: args.date ?? today,
2293
+ title: args.title ?? null,
2294
+ lines,
2295
+ status: args.status
2296
+ };
2297
+ if (args["client-id"]) {
2298
+ params.client = { id: Number(args["client-id"]) };
2299
+ }
2300
+ const client = new TiimeClient({ companyId: getCompanyId() });
2301
+ const quotation = await client.quotations.create(params);
2302
+ output(quotation);
2303
+ } catch (e) {
2304
+ outputError(e);
2305
+ }
2306
+ }
2307
+ }),
2308
+ pdf: defineCommand11({
2309
+ meta: {
2310
+ name: "pdf",
2311
+ description: "T\xE9l\xE9charger le PDF d'un devis"
2312
+ },
2313
+ args: {
2314
+ id: {
2315
+ type: "string",
2316
+ description: "ID du devis",
2317
+ required: true
2318
+ },
2319
+ output: {
2320
+ type: "string",
2321
+ description: "Chemin de sortie du fichier (d\xE9faut : devis-{id}.pdf)"
2322
+ }
2323
+ },
2324
+ async run({ args }) {
2325
+ try {
2326
+ const client = new TiimeClient({ companyId: getCompanyId() });
2327
+ const buffer = await client.quotations.downloadPdf(Number(args.id));
2328
+ const filePath = args.output ?? `devis-${args.id}.pdf`;
2329
+ writeFileSync5(filePath, Buffer.from(buffer));
2330
+ output({ status: "downloaded", path: filePath });
2331
+ } catch (e) {
2332
+ outputError(e);
2333
+ }
2334
+ }
2335
+ }),
2336
+ send: defineCommand11({
2337
+ meta: { name: "send", description: "Envoyer un devis par email" },
2338
+ args: {
2339
+ id: {
2340
+ type: "string",
2341
+ description: "ID du devis",
2342
+ required: true
2343
+ },
2344
+ email: {
2345
+ type: "string",
2346
+ description: "Adresse email du destinataire",
2347
+ required: true
2348
+ },
2349
+ subject: {
2350
+ type: "string",
2351
+ description: "Objet de l'email"
2352
+ },
2353
+ message: {
2354
+ type: "string",
2355
+ description: "Corps du message"
2356
+ }
2357
+ },
2358
+ async run({ args }) {
2359
+ try {
2360
+ const client = new TiimeClient({ companyId: getCompanyId() });
2361
+ await client.quotations.send(Number(args.id), {
2362
+ recipients: [{ email: args.email }],
2363
+ subject: args.subject,
2364
+ message: args.message
2365
+ });
2366
+ output({
2367
+ status: "sent",
2368
+ id: Number(args.id),
2369
+ email: args.email
2370
+ });
2371
+ } catch (e) {
2372
+ outputError(e);
2373
+ }
2374
+ }
2375
+ })
2376
+ }
2377
+ });
2378
+
2379
+ // src/cli/commands/status.ts
2380
+ import { defineCommand as defineCommand12 } from "citty";
2381
+ var statusCommand = defineCommand12({
2382
+ meta: { name: "status", description: "R\xE9sum\xE9 rapide de la situation" },
2383
+ args: { ...formatArg },
2384
+ async run({ args }) {
2385
+ try {
2386
+ const client = new TiimeClient({ companyId: getCompanyId() });
2387
+ const [
2388
+ accounts,
2389
+ draftInvoices,
2390
+ unpaidInvoices,
2391
+ unimputed,
2392
+ clients,
2393
+ quotations
2394
+ ] = await Promise.all([
2395
+ client.bankAccounts.list(true),
2396
+ client.invoices.list({ status: "draft" }),
2397
+ client.invoices.list({ status: "sent" }),
2398
+ client.bankTransactions.unimputed(),
2399
+ client.clients.list(),
2400
+ client.quotations.list()
2401
+ ]);
2402
+ const pendingQuotations = quotations.filter(
2403
+ (q) => q.status !== "accepted" && q.status !== "declined"
2404
+ );
2405
+ const data = {
2406
+ company_id: client.companyId,
2407
+ bank_accounts: accounts.map((a) => ({
2408
+ name: a.name,
2409
+ balance: a.balance_amount,
2410
+ currency: a.balance_currency
2411
+ })),
2412
+ invoices: {
2413
+ drafts: draftInvoices.length,
2414
+ unpaid: unpaidInvoices.length
2415
+ },
2416
+ pending_quotations: pendingQuotations.length,
2417
+ total_clients: clients.length,
2418
+ unimputed_transactions: unimputed.length
2419
+ };
2420
+ const format = args.format;
2421
+ output(data, { format });
2422
+ if (format === "json") {
2423
+ outputColoredStatus(data);
2424
+ }
2425
+ } catch (e) {
2426
+ outputError(e);
2427
+ }
2428
+ }
2429
+ });
2430
+
2431
+ // src/cli/commands/version.ts
2432
+ import { defineCommand as defineCommand13 } from "citty";
2433
+ var versionCommand = defineCommand13({
2434
+ meta: { name: "version", description: "Afficher la version" },
2435
+ run() {
2436
+ output({ version: "1.0.0", node: process.version });
2437
+ }
2438
+ });
2439
+
2440
+ // src/cli/i18n.ts
2441
+ var descriptionTranslations = {
2442
+ // Main
2443
+ "CLI pour la comptabilit\xE9 Tiime \u2014 sortie JSON pour agents IA": "CLI for Tiime accounting \u2014 JSON output for AI agents",
2444
+ // Top-level commands
2445
+ "Gestion de l'authentification": "Authentication management",
2446
+ "Gestion de l'entreprise": "Company management",
2447
+ "Gestion des factures": "Invoice management",
2448
+ "Gestion des clients": "Client management",
2449
+ "Comptes bancaires et transactions": "Bank accounts and transactions",
2450
+ "Gestion des devis": "Quotation management",
2451
+ "Gestion des notes de frais": "Expense report management",
2452
+ "Gestion des documents": "Document management",
2453
+ "Gestion des labels et tags": "Label and tag management",
2454
+ "R\xE9sum\xE9 rapide de la situation": "Quick status summary",
2455
+ "Ouvrir Tiime dans le navigateur": "Open Tiime in browser",
2456
+ "Afficher la version": "Show version",
2457
+ "G\xE9n\xE9rer le script d'autocompl\xE9tion shell": "Generate shell completion script",
2458
+ // Common subcommands
2459
+ "Se connecter \xE0 Tiime": "Log in to Tiime",
2460
+ "Se d\xE9connecter de Tiime": "Log out from Tiime",
2461
+ "Afficher le statut d'authentification": "Show authentication status",
2462
+ "Lister les entreprises": "List companies",
2463
+ "Lister toutes les entreprises": "List all companies",
2464
+ "D\xE9tails de l'entreprise active": "Active company details",
2465
+ "D\xE9finir l'entreprise active": "Set active company",
2466
+ "Info utilisateur": "User info",
2467
+ "Info utilisateur courant (inclut active_company)": "Current user info (includes active_company)",
2468
+ "Lister les factures": "List invoices",
2469
+ "D\xE9tails d'une facture": "Invoice details",
2470
+ "Cr\xE9er une facture (brouillon par d\xE9faut)": "Create an invoice (draft by default)",
2471
+ "Dupliquer une facture existante en brouillon": "Duplicate an invoice as draft",
2472
+ "Mettre \xE0 jour une facture": "Update an invoice",
2473
+ "Envoyer une facture par email": "Send an invoice by email",
2474
+ "T\xE9l\xE9charger le PDF d'une facture": "Download invoice PDF",
2475
+ "Supprimer une facture brouillon": "Delete a draft invoice",
2476
+ "Lister les clients": "List clients",
2477
+ "D\xE9tails d'un client": "Client details",
2478
+ "Cr\xE9er un client": "Create a client",
2479
+ "Rechercher un client": "Search for a client",
2480
+ "Afficher les soldes des comptes": "Show account balances",
2481
+ "Lister les comptes bancaires": "List bank accounts",
2482
+ "Lister les transactions bancaires": "List bank transactions",
2483
+ "Transactions non imput\xE9es": "Unmatched transactions",
2484
+ "Lister les devis": "List quotations",
2485
+ "D\xE9tails d'un devis": "Quotation details",
2486
+ "Cr\xE9er un devis": "Create a quotation",
2487
+ "T\xE9l\xE9charger le PDF d'un devis": "Download quotation PDF",
2488
+ "Envoyer un devis par email": "Send a quotation by email",
2489
+ "Lister les notes de frais": "List expense reports",
2490
+ "D\xE9tails d'une note de frais": "Expense report details",
2491
+ "Cr\xE9er une note de frais": "Create an expense report",
2492
+ "Lister les documents": "List documents",
2493
+ "Lister les cat\xE9gories de documents": "List document categories",
2494
+ "Uploader un justificatif": "Upload a receipt",
2495
+ "T\xE9l\xE9charger un document": "Download a document",
2496
+ "Labels personnalis\xE9s": "Custom labels",
2497
+ "Lister les labels personnalis\xE9s": "List custom labels",
2498
+ "Labels standards": "Standard labels",
2499
+ "Lister les labels standards": "List standard labels",
2500
+ Tags: "Tags",
2501
+ "Lister les tags": "List tags",
2502
+ // Arg descriptions
2503
+ "Format de sortie (json, table, csv)": "Output format (json, table, csv)",
2504
+ "ID de la facture": "Invoice ID",
2505
+ "ID du client": "Client ID",
2506
+ "ID du devis": "Quotation ID",
2507
+ "ID de la facture source": "Source invoice ID",
2508
+ "ID de la facture \xE0 supprimer": "Invoice ID to delete",
2509
+ "ID du document": "Document ID",
2510
+ "Nom du client": "Client name",
2511
+ "Nom du client (si pas de client-id)": "Client name (if no client-id)",
2512
+ "Date d'\xE9mission (YYYY-MM-DD, d\xE9faut : aujourd'hui)": "Issue date (YYYY-MM-DD, default: today)",
2513
+ "Titre de la facture": "Invoice title",
2514
+ "Description de la ligne (ligne simple)": "Line description (single line)",
2515
+ "Quantit\xE9 (ligne simple)": "Quantity (single line)",
2516
+ "Prix unitaire HT (ligne simple)": "Unit price excl. tax (single line)",
2517
+ "Code unit\xE9 (day, hour, unit, etc.)": "Unit code (day, hour, unit, etc.)",
2518
+ "Code TVA (normal=20%, reduced=10%, super_reduced=5.5%, none=0%)": "VAT code (normal=20%, reduced=10%, super_reduced=5.5%, none=0%)",
2519
+ 'Multi-lignes en JSON : \'[{"description":"Dev","quantity":20,"unit_price":540,"unit":"day"}]\'': `Multi-line JSON: '[{"description":"Dev","quantity":20,"unit_price":540,"unit":"day"}]'`,
2520
+ "Champ libre (ex : r\xE9f\xE9rence contrat)": "Free field (e.g.: contract reference)",
2521
+ "Statut : draft (d\xE9faut) ou saved (num\xE9rot\xE9e)": "Status: draft (default) or saved (numbered)",
2522
+ "Pr\xE9visualiser le payload sans cr\xE9er la facture": "Preview payload without creating invoice",
2523
+ "Adresse email du destinataire": "Recipient email address",
2524
+ "Objet de l'email": "Email subject",
2525
+ "Corps du message": "Message body",
2526
+ "Chemin de sortie du fichier": "Output file path",
2527
+ "Adresse email": "Email address",
2528
+ "Mot de passe": "Password",
2529
+ "ID de l'entreprise": "Company ID",
2530
+ "Tri champ:direction (ex: invoice_number:desc)": "Sort field:direction (e.g.: invoice_number:desc)",
2531
+ "Filtrer par statut (draft, saved, sent, paid)": "Filter by status (draft, saved, sent, paid)",
2532
+ "Num\xE9ro de page": "Page number",
2533
+ "\xC9l\xE9ments par page": "Items per page",
2534
+ "R\xE9cup\xE9rer toutes les pages": "Fetch all pages",
2535
+ "Inclure les clients archiv\xE9s": "Include archived clients",
2536
+ "Adresse du client": "Client address",
2537
+ "Code postal": "Postal code",
2538
+ Ville: "City",
2539
+ "Num\xE9ro de t\xE9l\xE9phone": "Phone number",
2540
+ "SIREN ou SIRET": "SIREN or SIRET",
2541
+ "Client professionnel": "Professional client",
2542
+ "Terme de recherche": "Search term",
2543
+ "Uniquement les comptes actifs": "Active accounts only",
2544
+ "Filtrer par ID de compte bancaire": "Filter by bank account ID",
2545
+ "Masquer les transactions refus\xE9es": "Hide refused transactions",
2546
+ "Tri champ:direction (ex: date:desc)": "Sort field:direction (e.g.: date:desc)",
2547
+ "Date de d\xE9but (YYYY-MM-DD)": "Start date (YYYY-MM-DD)",
2548
+ "Date de fin (YYYY-MM-DD)": "End date (YYYY-MM-DD)",
2549
+ "Rechercher par libell\xE9": "Search by label",
2550
+ "Tri champ:direction": "Sort field:direction",
2551
+ "Type de document (ex: receipt)": "Document type (e.g.: receipt)",
2552
+ "Source du document (ex: accountant)": "Document source (e.g.: accountant)",
2553
+ "Chemin du fichier \xE0 uploader": "File path to upload",
2554
+ "Type de document": "Document type",
2555
+ "Shell cible (zsh, bash, fish)": "Target shell (zsh, bash, fish)",
2556
+ "Nouveau titre de la facture": "New invoice title",
2557
+ "Nouveau statut (draft, saved)": "New status (draft, saved)",
2558
+ "Nouvelle date d'\xE9mission (YYYY-MM-DD)": "New issue date (YYYY-MM-DD)",
2559
+ "Nouveau champ libre": "New free field",
2560
+ "Date d'\xE9mission de la copie (YYYY-MM-DD, d\xE9faut : aujourd'hui)": "Copy issue date (YYYY-MM-DD, default: today)",
2561
+ "Remplacer la quantit\xE9 pour toutes les lignes": "Replace quantity for all lines",
2562
+ "Section \xE0 ouvrir (invoices, quotations, clients, bank, documents, expenses)": "Section to open (invoices, quotations, clients, bank, documents, expenses)",
2563
+ "Nom de la note de frais": "Expense report name",
2564
+ "Date (YYYY-MM-DD)": "Date (YYYY-MM-DD)",
2565
+ "Titre du devis": "Quotation title"
2566
+ };
2567
+ var frameworkTranslations = {
2568
+ fr: {
2569
+ USAGE: "UTILISATION",
2570
+ COMMANDS: "COMMANDES",
2571
+ OPTIONS: "OPTIONS",
2572
+ ARGUMENTS: "ARGUMENTS",
2573
+ "(required)": "(requis)",
2574
+ "Use %cmd% for more information about a command.": "Utilisez %cmd% pour plus d'informations sur une commande."
2575
+ },
2576
+ en: {}
2577
+ };
2578
+ var getLang = () => {
2579
+ if (process.env.TIIME_LANG) return process.env.TIIME_LANG;
2580
+ const sysLang = process.env.LC_ALL || process.env.LC_MESSAGES || process.env.LANG || "";
2581
+ if (sysLang.startsWith("fr")) return "fr";
2582
+ if (sysLang.startsWith("en")) return "en";
2583
+ return "fr";
2584
+ };
2585
+ var translateHelp = (text2) => {
2586
+ const lang = getLang();
2587
+ let result = text2;
2588
+ if (lang === "fr") {
2589
+ const dict = frameworkTranslations.fr;
2590
+ for (const [en, fr] of Object.entries(dict)) {
2591
+ if (en.includes("%cmd%")) continue;
2592
+ result = result.replaceAll(en, fr);
2593
+ }
2594
+ const usePattern = /Use (.*) for more information about a command\./g;
2595
+ const useReplacement = dict["Use %cmd% for more information about a command."];
2596
+ if (useReplacement) {
2597
+ result = result.replace(
2598
+ usePattern,
2599
+ (_match, cmd) => useReplacement.replace("%cmd%", cmd)
2600
+ );
2601
+ }
2602
+ }
2603
+ if (lang === "en") {
2604
+ for (const [fr, en] of Object.entries(descriptionTranslations)) {
2605
+ result = result.replaceAll(fr, en);
2606
+ }
2607
+ }
2608
+ return result;
2609
+ };
2610
+
2611
+ // src/cli/index.ts
2612
+ var main = defineCommand14({
2613
+ meta: {
2614
+ name: "tiime",
2615
+ version: "1.0.0",
2616
+ description: "CLI pour la comptabilit\xE9 Tiime \u2014 sortie JSON pour agents IA"
2617
+ },
2618
+ subCommands: {
2619
+ auth: authCommand,
2620
+ company: companyCommand,
2621
+ invoices: invoicesCommand,
2622
+ clients: clientsCommand,
2623
+ bank: bankCommand,
2624
+ quotations: quotationsCommand,
2625
+ expenses: expensesCommand,
2626
+ documents: documentsCommand,
2627
+ labels: labelsCommand,
2628
+ status: statusCommand,
2629
+ open: openCommand,
2630
+ version: versionCommand,
2631
+ completion: completionCommand
2632
+ }
2633
+ });
2634
+ var showTranslatedUsage = async (cmd, parent) => {
2635
+ const usage = await renderUsage(cmd, parent);
2636
+ console.log(`${translateHelp(usage)}
2637
+ `);
2638
+ };
2639
+ runMain(main, { showUsage: showTranslatedUsage });