create-kvitton 0.4.1 → 0.4.5

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 CHANGED
@@ -1,14 +1,795 @@
1
1
  #!/usr/bin/env node
2
+ import { createRequire } from "node:module";
3
+ var __create = Object.create;
4
+ var __getProtoOf = Object.getPrototypeOf;
5
+ var __defProp = Object.defineProperty;
6
+ var __getOwnPropNames = Object.getOwnPropertyNames;
7
+ var __hasOwnProp = Object.prototype.hasOwnProperty;
8
+ var __toESM = (mod, isNodeMode, target) => {
9
+ target = mod != null ? __create(__getProtoOf(mod)) : {};
10
+ const to = isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target;
11
+ for (let key of __getOwnPropNames(mod))
12
+ if (!__hasOwnProp.call(to, key))
13
+ __defProp(to, key, {
14
+ get: () => mod[key],
15
+ enumerable: true
16
+ });
17
+ return to;
18
+ };
19
+ var __commonJS = (cb, mod) => () => (mod || cb((mod = { exports: {} }).exports, mod), mod.exports);
20
+ var __require = /* @__PURE__ */ createRequire(import.meta.url);
21
+
22
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/eta.js
23
+ var require_eta = __commonJS((exports, module) => {
24
+ class ETA {
25
+ constructor(length, initTime, initValue) {
26
+ this.etaBufferLength = length || 100;
27
+ this.valueBuffer = [initValue];
28
+ this.timeBuffer = [initTime];
29
+ this.eta = "0";
30
+ }
31
+ update(time, value, total) {
32
+ this.valueBuffer.push(value);
33
+ this.timeBuffer.push(time);
34
+ this.calculate(total - value);
35
+ }
36
+ getTime() {
37
+ return this.eta;
38
+ }
39
+ calculate(remaining) {
40
+ const currentBufferSize = this.valueBuffer.length;
41
+ const buffer = Math.min(this.etaBufferLength, currentBufferSize);
42
+ const v_diff = this.valueBuffer[currentBufferSize - 1] - this.valueBuffer[currentBufferSize - buffer];
43
+ const t_diff = this.timeBuffer[currentBufferSize - 1] - this.timeBuffer[currentBufferSize - buffer];
44
+ const vt_rate = v_diff / t_diff;
45
+ this.valueBuffer = this.valueBuffer.slice(-this.etaBufferLength);
46
+ this.timeBuffer = this.timeBuffer.slice(-this.etaBufferLength);
47
+ const eta = Math.ceil(remaining / vt_rate / 1000);
48
+ if (isNaN(eta)) {
49
+ this.eta = "NULL";
50
+ } else if (!isFinite(eta)) {
51
+ this.eta = "INF";
52
+ } else if (eta > 1e7) {
53
+ this.eta = "INF";
54
+ } else if (eta < 0) {
55
+ this.eta = 0;
56
+ } else {
57
+ this.eta = eta;
58
+ }
59
+ }
60
+ }
61
+ module.exports = ETA;
62
+ });
63
+
64
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/terminal.js
65
+ var require_terminal = __commonJS((exports, module) => {
66
+ var _readline = __require("readline");
67
+
68
+ class Terminal {
69
+ constructor(outputStream) {
70
+ this.stream = outputStream;
71
+ this.linewrap = true;
72
+ this.dy = 0;
73
+ }
74
+ cursorSave() {
75
+ if (!this.stream.isTTY) {
76
+ return;
77
+ }
78
+ this.stream.write("\x1B7");
79
+ }
80
+ cursorRestore() {
81
+ if (!this.stream.isTTY) {
82
+ return;
83
+ }
84
+ this.stream.write("\x1B8");
85
+ }
86
+ cursor(enabled) {
87
+ if (!this.stream.isTTY) {
88
+ return;
89
+ }
90
+ if (enabled) {
91
+ this.stream.write("\x1B[?25h");
92
+ } else {
93
+ this.stream.write("\x1B[?25l");
94
+ }
95
+ }
96
+ cursorTo(x = null, y = null) {
97
+ if (!this.stream.isTTY) {
98
+ return;
99
+ }
100
+ _readline.cursorTo(this.stream, x, y);
101
+ }
102
+ cursorRelative(dx = null, dy = null) {
103
+ if (!this.stream.isTTY) {
104
+ return;
105
+ }
106
+ this.dy = this.dy + dy;
107
+ _readline.moveCursor(this.stream, dx, dy);
108
+ }
109
+ cursorRelativeReset() {
110
+ if (!this.stream.isTTY) {
111
+ return;
112
+ }
113
+ _readline.moveCursor(this.stream, 0, -this.dy);
114
+ _readline.cursorTo(this.stream, 0, null);
115
+ this.dy = 0;
116
+ }
117
+ clearRight() {
118
+ if (!this.stream.isTTY) {
119
+ return;
120
+ }
121
+ _readline.clearLine(this.stream, 1);
122
+ }
123
+ clearLine() {
124
+ if (!this.stream.isTTY) {
125
+ return;
126
+ }
127
+ _readline.clearLine(this.stream, 0);
128
+ }
129
+ clearBottom() {
130
+ if (!this.stream.isTTY) {
131
+ return;
132
+ }
133
+ _readline.clearScreenDown(this.stream);
134
+ }
135
+ newline() {
136
+ this.stream.write(`
137
+ `);
138
+ this.dy++;
139
+ }
140
+ write(s, rawWrite = false) {
141
+ if (this.linewrap === true && rawWrite === false) {
142
+ this.stream.write(s.substr(0, this.getWidth()));
143
+ } else {
144
+ this.stream.write(s);
145
+ }
146
+ }
147
+ lineWrapping(enabled) {
148
+ if (!this.stream.isTTY) {
149
+ return;
150
+ }
151
+ this.linewrap = enabled;
152
+ if (enabled) {
153
+ this.stream.write("\x1B[?7h");
154
+ } else {
155
+ this.stream.write("\x1B[?7l");
156
+ }
157
+ }
158
+ isTTY() {
159
+ return this.stream.isTTY === true;
160
+ }
161
+ getWidth() {
162
+ return this.stream.columns || (this.stream.isTTY ? 80 : 200);
163
+ }
164
+ }
165
+ module.exports = Terminal;
166
+ });
167
+
168
+ // ../../node_modules/.bun/ansi-regex@5.0.1/node_modules/ansi-regex/index.js
169
+ var require_ansi_regex = __commonJS((exports, module) => {
170
+ module.exports = ({ onlyFirst = false } = {}) => {
171
+ const pattern = [
172
+ "[\\u001B\\u009B][[\\]()#;?]*(?:(?:(?:(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]+)*|[a-zA-Z\\d]+(?:;[-a-zA-Z\\d\\/#&.:=?%@~_]*)*)?\\u0007)",
173
+ "(?:(?:\\d{1,4}(?:;\\d{0,4})*)?[\\dA-PR-TZcf-ntqry=><~]))"
174
+ ].join("|");
175
+ return new RegExp(pattern, onlyFirst ? undefined : "g");
176
+ };
177
+ });
178
+
179
+ // ../../node_modules/.bun/strip-ansi@6.0.1/node_modules/strip-ansi/index.js
180
+ var require_strip_ansi = __commonJS((exports, module) => {
181
+ var ansiRegex = require_ansi_regex();
182
+ module.exports = (string) => typeof string === "string" ? string.replace(ansiRegex(), "") : string;
183
+ });
184
+
185
+ // ../../node_modules/.bun/is-fullwidth-code-point@3.0.0/node_modules/is-fullwidth-code-point/index.js
186
+ var require_is_fullwidth_code_point = __commonJS((exports, module) => {
187
+ var isFullwidthCodePoint = (codePoint) => {
188
+ if (Number.isNaN(codePoint)) {
189
+ return false;
190
+ }
191
+ if (codePoint >= 4352 && (codePoint <= 4447 || codePoint === 9001 || codePoint === 9002 || 11904 <= codePoint && codePoint <= 12871 && codePoint !== 12351 || 12880 <= codePoint && codePoint <= 19903 || 19968 <= codePoint && codePoint <= 42182 || 43360 <= codePoint && codePoint <= 43388 || 44032 <= codePoint && codePoint <= 55203 || 63744 <= codePoint && codePoint <= 64255 || 65040 <= codePoint && codePoint <= 65049 || 65072 <= codePoint && codePoint <= 65131 || 65281 <= codePoint && codePoint <= 65376 || 65504 <= codePoint && codePoint <= 65510 || 110592 <= codePoint && codePoint <= 110593 || 127488 <= codePoint && codePoint <= 127569 || 131072 <= codePoint && codePoint <= 262141)) {
192
+ return true;
193
+ }
194
+ return false;
195
+ };
196
+ module.exports = isFullwidthCodePoint;
197
+ module.exports.default = isFullwidthCodePoint;
198
+ });
199
+
200
+ // ../../node_modules/.bun/emoji-regex@8.0.0/node_modules/emoji-regex/index.js
201
+ var require_emoji_regex = __commonJS((exports, module) => {
202
+ module.exports = function() {
203
+ return /\uD83C\uDFF4\uDB40\uDC67\uDB40\uDC62(?:\uDB40\uDC65\uDB40\uDC6E\uDB40\uDC67|\uDB40\uDC73\uDB40\uDC63\uDB40\uDC74|\uDB40\uDC77\uDB40\uDC6C\uDB40\uDC73)\uDB40\uDC7F|\uD83D\uDC68(?:\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68\uD83C\uDFFB|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFE])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D)?\uD83D\uDC68|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D[\uDC68\uDC69])\u200D(?:\uD83D[\uDC66\uDC67])|[\u2695\u2696\u2708]\uFE0F|\uD83D[\uDC66\uDC67]|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|(?:\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708])\uFE0F|\uD83C\uDFFB\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C[\uDFFB-\uDFFF])|(?:\uD83E\uDDD1\uD83C\uDFFB\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)\uD83C\uDFFB|\uD83E\uDDD1(?:\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1)|(?:\uD83E\uDDD1\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFF\u200D\uD83E\uDD1D\u200D(?:\uD83D[\uDC68\uDC69]))(?:\uD83C[\uDFFB-\uDFFE])|(?:\uD83E\uDDD1\uD83C\uDFFC\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB\uDFFC])|\uD83D\uDC69(?:\uD83C\uDFFE\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB-\uDFFD\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFC\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFD-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFB\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFC-\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFD\u200D(?:\uD83E\uDD1D\u200D\uD83D\uDC68(?:\uD83C[\uDFFB\uDFFC\uDFFE\uDFFF])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\u200D(?:\u2764\uFE0F\u200D(?:\uD83D\uDC8B\u200D(?:\uD83D[\uDC68\uDC69])|\uD83D[\uDC68\uDC69])|\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD])|\uD83C\uDFFF\u200D(?:\uD83C[\uDF3E\uDF73\uDF93\uDFA4\uDFA8\uDFEB\uDFED]|\uD83D[\uDCBB\uDCBC\uDD27\uDD2C\uDE80\uDE92]|\uD83E[\uDDAF-\uDDB3\uDDBC\uDDBD]))|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67]))|(?:\uD83E\uDDD1\uD83C\uDFFD\u200D\uD83E\uDD1D\u200D\uD83E\uDDD1|\uD83D\uDC69\uD83C\uDFFE\u200D\uD83E\uDD1D\u200D\uD83D\uDC69)(?:\uD83C[\uDFFB-\uDFFD])|\uD83D\uDC69\u200D\uD83D\uDC66\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC69\u200D(?:\uD83D[\uDC66\uDC67])|(?:\uD83D\uDC41\uFE0F\u200D\uD83D\uDDE8|\uD83D\uDC69(?:\uD83C\uDFFF\u200D[\u2695\u2696\u2708]|\uD83C\uDFFE\u200D[\u2695\u2696\u2708]|\uD83C\uDFFC\u200D[\u2695\u2696\u2708]|\uD83C\uDFFB\u200D[\u2695\u2696\u2708]|\uD83C\uDFFD\u200D[\u2695\u2696\u2708]|\u200D[\u2695\u2696\u2708])|(?:(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)\uFE0F|\uD83D\uDC6F|\uD83E[\uDD3C\uDDDE\uDDDF])\u200D[\u2640\u2642]|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:(?:\uD83C[\uDFFB-\uDFFF])\u200D[\u2640\u2642]|\u200D[\u2640\u2642])|\uD83C\uDFF4\u200D\u2620)\uFE0F|\uD83D\uDC69\u200D\uD83D\uDC67\u200D(?:\uD83D[\uDC66\uDC67])|\uD83C\uDFF3\uFE0F\u200D\uD83C\uDF08|\uD83D\uDC15\u200D\uD83E\uDDBA|\uD83D\uDC69\u200D\uD83D\uDC66|\uD83D\uDC69\u200D\uD83D\uDC67|\uD83C\uDDFD\uD83C\uDDF0|\uD83C\uDDF4\uD83C\uDDF2|\uD83C\uDDF6\uD83C\uDDE6|[#\*0-9]\uFE0F\u20E3|\uD83C\uDDE7(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEF\uDDF1-\uDDF4\uDDF6-\uDDF9\uDDFB\uDDFC\uDDFE\uDDFF])|\uD83C\uDDF9(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDED\uDDEF-\uDDF4\uDDF7\uDDF9\uDDFB\uDDFC\uDDFF])|\uD83C\uDDEA(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDED\uDDF7-\uDDFA])|\uD83E\uDDD1(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF7(?:\uD83C[\uDDEA\uDDF4\uDDF8\uDDFA\uDDFC])|\uD83D\uDC69(?:\uD83C[\uDFFB-\uDFFF])|\uD83C\uDDF2(?:\uD83C[\uDDE6\uDDE8-\uDDED\uDDF0-\uDDFF])|\uD83C\uDDE6(?:\uD83C[\uDDE8-\uDDEC\uDDEE\uDDF1\uDDF2\uDDF4\uDDF6-\uDDFA\uDDFC\uDDFD\uDDFF])|\uD83C\uDDF0(?:\uD83C[\uDDEA\uDDEC-\uDDEE\uDDF2\uDDF3\uDDF5\uDDF7\uDDFC\uDDFE\uDDFF])|\uD83C\uDDED(?:\uD83C[\uDDF0\uDDF2\uDDF3\uDDF7\uDDF9\uDDFA])|\uD83C\uDDE9(?:\uD83C[\uDDEA\uDDEC\uDDEF\uDDF0\uDDF2\uDDF4\uDDFF])|\uD83C\uDDFE(?:\uD83C[\uDDEA\uDDF9])|\uD83C\uDDEC(?:\uD83C[\uDDE6\uDDE7\uDDE9-\uDDEE\uDDF1-\uDDF3\uDDF5-\uDDFA\uDDFC\uDDFE])|\uD83C\uDDF8(?:\uD83C[\uDDE6-\uDDEA\uDDEC-\uDDF4\uDDF7-\uDDF9\uDDFB\uDDFD-\uDDFF])|\uD83C\uDDEB(?:\uD83C[\uDDEE-\uDDF0\uDDF2\uDDF4\uDDF7])|\uD83C\uDDF5(?:\uD83C[\uDDE6\uDDEA-\uDDED\uDDF0-\uDDF3\uDDF7-\uDDF9\uDDFC\uDDFE])|\uD83C\uDDFB(?:\uD83C[\uDDE6\uDDE8\uDDEA\uDDEC\uDDEE\uDDF3\uDDFA])|\uD83C\uDDF3(?:\uD83C[\uDDE6\uDDE8\uDDEA-\uDDEC\uDDEE\uDDF1\uDDF4\uDDF5\uDDF7\uDDFA\uDDFF])|\uD83C\uDDE8(?:\uD83C[\uDDE6\uDDE8\uDDE9\uDDEB-\uDDEE\uDDF0-\uDDF5\uDDF7\uDDFA-\uDDFF])|\uD83C\uDDF1(?:\uD83C[\uDDE6-\uDDE8\uDDEE\uDDF0\uDDF7-\uDDFB\uDDFE])|\uD83C\uDDFF(?:\uD83C[\uDDE6\uDDF2\uDDFC])|\uD83C\uDDFC(?:\uD83C[\uDDEB\uDDF8])|\uD83C\uDDFA(?:\uD83C[\uDDE6\uDDEC\uDDF2\uDDF3\uDDF8\uDDFE\uDDFF])|\uD83C\uDDEE(?:\uD83C[\uDDE8-\uDDEA\uDDF1-\uDDF4\uDDF6-\uDDF9])|\uD83C\uDDEF(?:\uD83C[\uDDEA\uDDF2\uDDF4\uDDF5])|(?:\uD83C[\uDFC3\uDFC4\uDFCA]|\uD83D[\uDC6E\uDC71\uDC73\uDC77\uDC81\uDC82\uDC86\uDC87\uDE45-\uDE47\uDE4B\uDE4D\uDE4E\uDEA3\uDEB4-\uDEB6]|\uD83E[\uDD26\uDD37-\uDD39\uDD3D\uDD3E\uDDB8\uDDB9\uDDCD-\uDDCF\uDDD6-\uDDDD])(?:\uD83C[\uDFFB-\uDFFF])|(?:\u26F9|\uD83C[\uDFCB\uDFCC]|\uD83D\uDD75)(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u261D\u270A-\u270D]|\uD83C[\uDF85\uDFC2\uDFC7]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66\uDC67\uDC6B-\uDC6D\uDC70\uDC72\uDC74-\uDC76\uDC78\uDC7C\uDC83\uDC85\uDCAA\uDD74\uDD7A\uDD90\uDD95\uDD96\uDE4C\uDE4F\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1C\uDD1E\uDD1F\uDD30-\uDD36\uDDB5\uDDB6\uDDBB\uDDD2-\uDDD5])(?:\uD83C[\uDFFB-\uDFFF])|(?:[\u231A\u231B\u23E9-\u23EC\u23F0\u23F3\u25FD\u25FE\u2614\u2615\u2648-\u2653\u267F\u2693\u26A1\u26AA\u26AB\u26BD\u26BE\u26C4\u26C5\u26CE\u26D4\u26EA\u26F2\u26F3\u26F5\u26FA\u26FD\u2705\u270A\u270B\u2728\u274C\u274E\u2753-\u2755\u2757\u2795-\u2797\u27B0\u27BF\u2B1B\u2B1C\u2B50\u2B55]|\uD83C[\uDC04\uDCCF\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE1A\uDE2F\uDE32-\uDE36\uDE38-\uDE3A\uDE50\uDE51\uDF00-\uDF20\uDF2D-\uDF35\uDF37-\uDF7C\uDF7E-\uDF93\uDFA0-\uDFCA\uDFCF-\uDFD3\uDFE0-\uDFF0\uDFF4\uDFF8-\uDFFF]|\uD83D[\uDC00-\uDC3E\uDC40\uDC42-\uDCFC\uDCFF-\uDD3D\uDD4B-\uDD4E\uDD50-\uDD67\uDD7A\uDD95\uDD96\uDDA4\uDDFB-\uDE4F\uDE80-\uDEC5\uDECC\uDED0-\uDED2\uDED5\uDEEB\uDEEC\uDEF4-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])|(?:[#\*0-9\xA9\xAE\u203C\u2049\u2122\u2139\u2194-\u2199\u21A9\u21AA\u231A\u231B\u2328\u23CF\u23E9-\u23F3\u23F8-\u23FA\u24C2\u25AA\u25AB\u25B6\u25C0\u25FB-\u25FE\u2600-\u2604\u260E\u2611\u2614\u2615\u2618\u261D\u2620\u2622\u2623\u2626\u262A\u262E\u262F\u2638-\u263A\u2640\u2642\u2648-\u2653\u265F\u2660\u2663\u2665\u2666\u2668\u267B\u267E\u267F\u2692-\u2697\u2699\u269B\u269C\u26A0\u26A1\u26AA\u26AB\u26B0\u26B1\u26BD\u26BE\u26C4\u26C5\u26C8\u26CE\u26CF\u26D1\u26D3\u26D4\u26E9\u26EA\u26F0-\u26F5\u26F7-\u26FA\u26FD\u2702\u2705\u2708-\u270D\u270F\u2712\u2714\u2716\u271D\u2721\u2728\u2733\u2734\u2744\u2747\u274C\u274E\u2753-\u2755\u2757\u2763\u2764\u2795-\u2797\u27A1\u27B0\u27BF\u2934\u2935\u2B05-\u2B07\u2B1B\u2B1C\u2B50\u2B55\u3030\u303D\u3297\u3299]|\uD83C[\uDC04\uDCCF\uDD70\uDD71\uDD7E\uDD7F\uDD8E\uDD91-\uDD9A\uDDE6-\uDDFF\uDE01\uDE02\uDE1A\uDE2F\uDE32-\uDE3A\uDE50\uDE51\uDF00-\uDF21\uDF24-\uDF93\uDF96\uDF97\uDF99-\uDF9B\uDF9E-\uDFF0\uDFF3-\uDFF5\uDFF7-\uDFFF]|\uD83D[\uDC00-\uDCFD\uDCFF-\uDD3D\uDD49-\uDD4E\uDD50-\uDD67\uDD6F\uDD70\uDD73-\uDD7A\uDD87\uDD8A-\uDD8D\uDD90\uDD95\uDD96\uDDA4\uDDA5\uDDA8\uDDB1\uDDB2\uDDBC\uDDC2-\uDDC4\uDDD1-\uDDD3\uDDDC-\uDDDE\uDDE1\uDDE3\uDDE8\uDDEF\uDDF3\uDDFA-\uDE4F\uDE80-\uDEC5\uDECB-\uDED2\uDED5\uDEE0-\uDEE5\uDEE9\uDEEB\uDEEC\uDEF0\uDEF3-\uDEFA\uDFE0-\uDFEB]|\uD83E[\uDD0D-\uDD3A\uDD3C-\uDD45\uDD47-\uDD71\uDD73-\uDD76\uDD7A-\uDDA2\uDDA5-\uDDAA\uDDAE-\uDDCA\uDDCD-\uDDFF\uDE70-\uDE73\uDE78-\uDE7A\uDE80-\uDE82\uDE90-\uDE95])\uFE0F|(?:[\u261D\u26F9\u270A-\u270D]|\uD83C[\uDF85\uDFC2-\uDFC4\uDFC7\uDFCA-\uDFCC]|\uD83D[\uDC42\uDC43\uDC46-\uDC50\uDC66-\uDC78\uDC7C\uDC81-\uDC83\uDC85-\uDC87\uDC8F\uDC91\uDCAA\uDD74\uDD75\uDD7A\uDD90\uDD95\uDD96\uDE45-\uDE47\uDE4B-\uDE4F\uDEA3\uDEB4-\uDEB6\uDEC0\uDECC]|\uD83E[\uDD0F\uDD18-\uDD1F\uDD26\uDD30-\uDD39\uDD3C-\uDD3E\uDDB5\uDDB6\uDDB8\uDDB9\uDDBB\uDDCD-\uDDCF\uDDD1-\uDDDD])/g;
204
+ };
205
+ });
206
+
207
+ // ../../node_modules/.bun/string-width@4.2.3/node_modules/string-width/index.js
208
+ var require_string_width = __commonJS((exports, module) => {
209
+ var stripAnsi = require_strip_ansi();
210
+ var isFullwidthCodePoint = require_is_fullwidth_code_point();
211
+ var emojiRegex = require_emoji_regex();
212
+ var stringWidth = (string) => {
213
+ if (typeof string !== "string" || string.length === 0) {
214
+ return 0;
215
+ }
216
+ string = stripAnsi(string);
217
+ if (string.length === 0) {
218
+ return 0;
219
+ }
220
+ string = string.replace(emojiRegex(), " ");
221
+ let width = 0;
222
+ for (let i = 0;i < string.length; i++) {
223
+ const code = string.codePointAt(i);
224
+ if (code <= 31 || code >= 127 && code <= 159) {
225
+ continue;
226
+ }
227
+ if (code >= 768 && code <= 879) {
228
+ continue;
229
+ }
230
+ if (code > 65535) {
231
+ i++;
232
+ }
233
+ width += isFullwidthCodePoint(code) ? 2 : 1;
234
+ }
235
+ return width;
236
+ };
237
+ module.exports = stringWidth;
238
+ module.exports.default = stringWidth;
239
+ });
240
+
241
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/format-value.js
242
+ var require_format_value = __commonJS((exports, module) => {
243
+ module.exports = function formatValue(v, options, type) {
244
+ if (options.autopadding !== true) {
245
+ return v;
246
+ }
247
+ function autopadding(value, length) {
248
+ return (options.autopaddingChar + value).slice(-length);
249
+ }
250
+ switch (type) {
251
+ case "percentage":
252
+ return autopadding(v, 3);
253
+ default:
254
+ return v;
255
+ }
256
+ };
257
+ });
258
+
259
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/format-bar.js
260
+ var require_format_bar = __commonJS((exports, module) => {
261
+ module.exports = function formatBar(progress, options) {
262
+ const completeSize = Math.round(progress * options.barsize);
263
+ const incompleteSize = options.barsize - completeSize;
264
+ return options.barCompleteString.substr(0, completeSize) + options.barGlue + options.barIncompleteString.substr(0, incompleteSize);
265
+ };
266
+ });
267
+
268
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/format-time.js
269
+ var require_format_time = __commonJS((exports, module) => {
270
+ module.exports = function formatTime(t, options, roundToMultipleOf) {
271
+ function round(input) {
272
+ if (roundToMultipleOf) {
273
+ return roundToMultipleOf * Math.round(input / roundToMultipleOf);
274
+ } else {
275
+ return input;
276
+ }
277
+ }
278
+ function autopadding(v) {
279
+ return (options.autopaddingChar + v).slice(-2);
280
+ }
281
+ if (t > 3600) {
282
+ return autopadding(Math.floor(t / 3600)) + "h" + autopadding(round(t % 3600 / 60)) + "m";
283
+ } else if (t > 60) {
284
+ return autopadding(Math.floor(t / 60)) + "m" + autopadding(round(t % 60)) + "s";
285
+ } else if (t > 10) {
286
+ return autopadding(round(t)) + "s";
287
+ } else {
288
+ return autopadding(t) + "s";
289
+ }
290
+ };
291
+ });
292
+
293
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/formatter.js
294
+ var require_formatter = __commonJS((exports, module) => {
295
+ var _stringWidth = require_string_width();
296
+ var _defaultFormatValue = require_format_value();
297
+ var _defaultFormatBar = require_format_bar();
298
+ var _defaultFormatTime = require_format_time();
299
+ module.exports = function defaultFormatter(options, params, payload) {
300
+ let s = options.format;
301
+ const formatTime = options.formatTime || _defaultFormatTime;
302
+ const formatValue = options.formatValue || _defaultFormatValue;
303
+ const formatBar = options.formatBar || _defaultFormatBar;
304
+ const percentage = Math.floor(params.progress * 100) + "";
305
+ const stopTime = params.stopTime || Date.now();
306
+ const elapsedTime = Math.round((stopTime - params.startTime) / 1000);
307
+ const context = Object.assign({}, payload, {
308
+ bar: formatBar(params.progress, options),
309
+ percentage: formatValue(percentage, options, "percentage"),
310
+ total: formatValue(params.total, options, "total"),
311
+ value: formatValue(params.value, options, "value"),
312
+ eta: formatValue(params.eta, options, "eta"),
313
+ eta_formatted: formatTime(params.eta, options, 5),
314
+ duration: formatValue(elapsedTime, options, "duration"),
315
+ duration_formatted: formatTime(elapsedTime, options, 1)
316
+ });
317
+ s = s.replace(/\{(\w+)\}/g, function(match, key) {
318
+ if (typeof context[key] !== "undefined") {
319
+ return context[key];
320
+ }
321
+ return match;
322
+ });
323
+ const fullMargin = Math.max(0, params.maxWidth - _stringWidth(s) - 2);
324
+ const halfMargin = Math.floor(fullMargin / 2);
325
+ switch (options.align) {
326
+ case "right":
327
+ s = fullMargin > 0 ? " ".repeat(fullMargin) + s : s;
328
+ break;
329
+ case "center":
330
+ s = halfMargin > 0 ? " ".repeat(halfMargin) + s : s;
331
+ break;
332
+ case "left":
333
+ default:
334
+ break;
335
+ }
336
+ return s;
337
+ };
338
+ });
339
+
340
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/options.js
341
+ var require_options = __commonJS((exports, module) => {
342
+ function mergeOption(v, defaultValue) {
343
+ if (typeof v === "undefined" || v === null) {
344
+ return defaultValue;
345
+ } else {
346
+ return v;
347
+ }
348
+ }
349
+ module.exports = {
350
+ parse: function parse(rawOptions, preset) {
351
+ const options = {};
352
+ const opt = Object.assign({}, preset, rawOptions);
353
+ options.throttleTime = 1000 / mergeOption(opt.fps, 10);
354
+ options.stream = mergeOption(opt.stream, process.stderr);
355
+ options.terminal = mergeOption(opt.terminal, null);
356
+ options.clearOnComplete = mergeOption(opt.clearOnComplete, false);
357
+ options.stopOnComplete = mergeOption(opt.stopOnComplete, false);
358
+ options.barsize = mergeOption(opt.barsize, 40);
359
+ options.align = mergeOption(opt.align, "left");
360
+ options.hideCursor = mergeOption(opt.hideCursor, false);
361
+ options.linewrap = mergeOption(opt.linewrap, false);
362
+ options.barGlue = mergeOption(opt.barGlue, "");
363
+ options.barCompleteChar = mergeOption(opt.barCompleteChar, "=");
364
+ options.barIncompleteChar = mergeOption(opt.barIncompleteChar, "-");
365
+ options.format = mergeOption(opt.format, "progress [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}");
366
+ options.formatTime = mergeOption(opt.formatTime, null);
367
+ options.formatValue = mergeOption(opt.formatValue, null);
368
+ options.formatBar = mergeOption(opt.formatBar, null);
369
+ options.etaBufferLength = mergeOption(opt.etaBuffer, 10);
370
+ options.etaAsynchronousUpdate = mergeOption(opt.etaAsynchronousUpdate, false);
371
+ options.progressCalculationRelative = mergeOption(opt.progressCalculationRelative, false);
372
+ options.synchronousUpdate = mergeOption(opt.synchronousUpdate, true);
373
+ options.noTTYOutput = mergeOption(opt.noTTYOutput, false);
374
+ options.notTTYSchedule = mergeOption(opt.notTTYSchedule, 2000);
375
+ options.emptyOnZero = mergeOption(opt.emptyOnZero, false);
376
+ options.forceRedraw = mergeOption(opt.forceRedraw, false);
377
+ options.autopadding = mergeOption(opt.autopadding, false);
378
+ options.gracefulExit = mergeOption(opt.gracefulExit, false);
379
+ return options;
380
+ },
381
+ assignDerivedOptions: function assignDerivedOptions(options) {
382
+ options.barCompleteString = options.barCompleteChar.repeat(options.barsize + 1);
383
+ options.barIncompleteString = options.barIncompleteChar.repeat(options.barsize + 1);
384
+ options.autopaddingChar = options.autopadding ? mergeOption(options.autopaddingChar, " ") : "";
385
+ return options;
386
+ }
387
+ };
388
+ });
389
+
390
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/generic-bar.js
391
+ var require_generic_bar = __commonJS((exports, module) => {
392
+ var _ETA = require_eta();
393
+ var _Terminal = require_terminal();
394
+ var _formatter = require_formatter();
395
+ var _options = require_options();
396
+ var _EventEmitter = __require("events");
397
+ module.exports = class GenericBar extends _EventEmitter {
398
+ constructor(options) {
399
+ super();
400
+ this.options = _options.assignDerivedOptions(options);
401
+ this.terminal = this.options.terminal ? this.options.terminal : new _Terminal(this.options.stream);
402
+ this.value = 0;
403
+ this.startValue = 0;
404
+ this.total = 100;
405
+ this.lastDrawnString = null;
406
+ this.startTime = null;
407
+ this.stopTime = null;
408
+ this.lastRedraw = Date.now();
409
+ this.eta = new _ETA(this.options.etaBufferLength, 0, 0);
410
+ this.payload = {};
411
+ this.isActive = false;
412
+ this.formatter = typeof this.options.format === "function" ? this.options.format : _formatter;
413
+ }
414
+ render(forceRendering = false) {
415
+ const params = {
416
+ progress: this.getProgress(),
417
+ eta: this.eta.getTime(),
418
+ startTime: this.startTime,
419
+ stopTime: this.stopTime,
420
+ total: this.total,
421
+ value: this.value,
422
+ maxWidth: this.terminal.getWidth()
423
+ };
424
+ if (this.options.etaAsynchronousUpdate) {
425
+ this.updateETA();
426
+ }
427
+ const s = this.formatter(this.options, params, this.payload);
428
+ const forceRedraw = forceRendering || this.options.forceRedraw || this.options.noTTYOutput && !this.terminal.isTTY();
429
+ if (forceRedraw || this.lastDrawnString != s) {
430
+ this.emit("redraw-pre");
431
+ this.terminal.cursorTo(0, null);
432
+ this.terminal.write(s);
433
+ this.terminal.clearRight();
434
+ this.lastDrawnString = s;
435
+ this.lastRedraw = Date.now();
436
+ this.emit("redraw-post");
437
+ }
438
+ }
439
+ start(total, startValue, payload) {
440
+ this.value = startValue || 0;
441
+ this.total = typeof total !== "undefined" && total >= 0 ? total : 100;
442
+ this.startValue = startValue || 0;
443
+ this.payload = payload || {};
444
+ this.startTime = Date.now();
445
+ this.stopTime = null;
446
+ this.lastDrawnString = "";
447
+ this.eta = new _ETA(this.options.etaBufferLength, this.startTime, this.value);
448
+ this.isActive = true;
449
+ this.emit("start", total, startValue);
450
+ }
451
+ stop() {
452
+ this.isActive = false;
453
+ this.stopTime = Date.now();
454
+ this.emit("stop", this.total, this.value);
455
+ }
456
+ update(arg0, arg1 = {}) {
457
+ if (typeof arg0 === "number") {
458
+ this.value = arg0;
459
+ this.eta.update(Date.now(), arg0, this.total);
460
+ }
461
+ const payloadData = (typeof arg0 === "object" ? arg0 : arg1) || {};
462
+ this.emit("update", this.total, this.value);
463
+ for (const key in payloadData) {
464
+ this.payload[key] = payloadData[key];
465
+ }
466
+ if (this.value >= this.getTotal() && this.options.stopOnComplete) {
467
+ this.stop();
468
+ }
469
+ }
470
+ getProgress() {
471
+ let progress = this.value / this.total;
472
+ if (this.options.progressCalculationRelative) {
473
+ progress = (this.value - this.startValue) / (this.total - this.startValue);
474
+ }
475
+ if (isNaN(progress)) {
476
+ progress = this.options && this.options.emptyOnZero ? 0 : 1;
477
+ }
478
+ progress = Math.min(Math.max(progress, 0), 1);
479
+ return progress;
480
+ }
481
+ increment(arg0 = 1, arg1 = {}) {
482
+ if (typeof arg0 === "object") {
483
+ this.update(this.value + 1, arg0);
484
+ } else {
485
+ this.update(this.value + arg0, arg1);
486
+ }
487
+ }
488
+ getTotal() {
489
+ return this.total;
490
+ }
491
+ setTotal(total) {
492
+ if (typeof total !== "undefined" && total >= 0) {
493
+ this.total = total;
494
+ }
495
+ }
496
+ updateETA() {
497
+ this.eta.update(Date.now(), this.value, this.total);
498
+ }
499
+ };
500
+ });
501
+
502
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/single-bar.js
503
+ var require_single_bar = __commonJS((exports, module) => {
504
+ var _GenericBar = require_generic_bar();
505
+ var _options = require_options();
506
+ module.exports = class SingleBar extends _GenericBar {
507
+ constructor(options, preset) {
508
+ super(_options.parse(options, preset));
509
+ this.timer = null;
510
+ if (this.options.noTTYOutput && this.terminal.isTTY() === false) {
511
+ this.options.synchronousUpdate = false;
512
+ }
513
+ this.schedulingRate = this.terminal.isTTY() ? this.options.throttleTime : this.options.notTTYSchedule;
514
+ this.sigintCallback = null;
515
+ }
516
+ render() {
517
+ if (this.timer) {
518
+ clearTimeout(this.timer);
519
+ this.timer = null;
520
+ }
521
+ super.render();
522
+ if (this.options.noTTYOutput && this.terminal.isTTY() === false) {
523
+ this.terminal.newline();
524
+ }
525
+ this.timer = setTimeout(this.render.bind(this), this.schedulingRate);
526
+ }
527
+ update(current, payload) {
528
+ if (!this.timer) {
529
+ return;
530
+ }
531
+ super.update(current, payload);
532
+ if (this.options.synchronousUpdate && this.lastRedraw + this.options.throttleTime * 2 < Date.now()) {
533
+ this.render();
534
+ }
535
+ }
536
+ start(total, startValue, payload) {
537
+ if (this.options.noTTYOutput === false && this.terminal.isTTY() === false) {
538
+ return;
539
+ }
540
+ if (this.sigintCallback === null && this.options.gracefulExit) {
541
+ this.sigintCallback = this.stop.bind(this);
542
+ process.once("SIGINT", this.sigintCallback);
543
+ process.once("SIGTERM", this.sigintCallback);
544
+ }
545
+ this.terminal.cursorSave();
546
+ if (this.options.hideCursor === true) {
547
+ this.terminal.cursor(false);
548
+ }
549
+ if (this.options.linewrap === false) {
550
+ this.terminal.lineWrapping(false);
551
+ }
552
+ super.start(total, startValue, payload);
553
+ this.render();
554
+ }
555
+ stop() {
556
+ if (!this.timer) {
557
+ return;
558
+ }
559
+ if (this.sigintCallback) {
560
+ process.removeListener("SIGINT", this.sigintCallback);
561
+ process.removeListener("SIGTERM", this.sigintCallback);
562
+ this.sigintCallback = null;
563
+ }
564
+ this.render();
565
+ super.stop();
566
+ clearTimeout(this.timer);
567
+ this.timer = null;
568
+ if (this.options.hideCursor === true) {
569
+ this.terminal.cursor(true);
570
+ }
571
+ if (this.options.linewrap === false) {
572
+ this.terminal.lineWrapping(true);
573
+ }
574
+ this.terminal.cursorRestore();
575
+ if (this.options.clearOnComplete) {
576
+ this.terminal.cursorTo(0, null);
577
+ this.terminal.clearLine();
578
+ } else {
579
+ this.terminal.newline();
580
+ }
581
+ }
582
+ };
583
+ });
584
+
585
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/lib/multi-bar.js
586
+ var require_multi_bar = __commonJS((exports, module) => {
587
+ var _Terminal = require_terminal();
588
+ var _BarElement = require_generic_bar();
589
+ var _options = require_options();
590
+ var _EventEmitter = __require("events");
591
+ module.exports = class MultiBar extends _EventEmitter {
592
+ constructor(options, preset) {
593
+ super();
594
+ this.bars = [];
595
+ this.options = _options.parse(options, preset);
596
+ this.options.synchronousUpdate = false;
597
+ this.terminal = this.options.terminal ? this.options.terminal : new _Terminal(this.options.stream);
598
+ this.timer = null;
599
+ this.isActive = false;
600
+ this.schedulingRate = this.terminal.isTTY() ? this.options.throttleTime : this.options.notTTYSchedule;
601
+ this.loggingBuffer = [];
602
+ this.sigintCallback = null;
603
+ }
604
+ create(total, startValue, payload, barOptions = {}) {
605
+ const bar = new _BarElement(Object.assign({}, this.options, {
606
+ terminal: this.terminal
607
+ }, barOptions));
608
+ this.bars.push(bar);
609
+ if (this.options.noTTYOutput === false && this.terminal.isTTY() === false) {
610
+ return bar;
611
+ }
612
+ if (this.sigintCallback === null && this.options.gracefulExit) {
613
+ this.sigintCallback = this.stop.bind(this);
614
+ process.once("SIGINT", this.sigintCallback);
615
+ process.once("SIGTERM", this.sigintCallback);
616
+ }
617
+ if (!this.isActive) {
618
+ if (this.options.hideCursor === true) {
619
+ this.terminal.cursor(false);
620
+ }
621
+ if (this.options.linewrap === false) {
622
+ this.terminal.lineWrapping(false);
623
+ }
624
+ this.timer = setTimeout(this.update.bind(this), this.schedulingRate);
625
+ }
626
+ this.isActive = true;
627
+ bar.start(total, startValue, payload);
628
+ this.emit("start");
629
+ return bar;
630
+ }
631
+ remove(bar) {
632
+ const index = this.bars.indexOf(bar);
633
+ if (index < 0) {
634
+ return false;
635
+ }
636
+ this.bars.splice(index, 1);
637
+ this.update();
638
+ this.terminal.newline();
639
+ this.terminal.clearBottom();
640
+ return true;
641
+ }
642
+ update() {
643
+ if (this.timer) {
644
+ clearTimeout(this.timer);
645
+ this.timer = null;
646
+ }
647
+ this.emit("update-pre");
648
+ this.terminal.cursorRelativeReset();
649
+ this.emit("redraw-pre");
650
+ if (this.loggingBuffer.length > 0) {
651
+ this.terminal.clearLine();
652
+ while (this.loggingBuffer.length > 0) {
653
+ this.terminal.write(this.loggingBuffer.shift(), true);
654
+ }
655
+ }
656
+ for (let i = 0;i < this.bars.length; i++) {
657
+ if (i > 0) {
658
+ this.terminal.newline();
659
+ }
660
+ this.bars[i].render();
661
+ }
662
+ this.emit("redraw-post");
663
+ if (this.options.noTTYOutput && this.terminal.isTTY() === false) {
664
+ this.terminal.newline();
665
+ this.terminal.newline();
666
+ }
667
+ this.timer = setTimeout(this.update.bind(this), this.schedulingRate);
668
+ this.emit("update-post");
669
+ if (this.options.stopOnComplete && !this.bars.find((bar) => bar.isActive)) {
670
+ this.stop();
671
+ }
672
+ }
673
+ stop() {
674
+ clearTimeout(this.timer);
675
+ this.timer = null;
676
+ if (this.sigintCallback) {
677
+ process.removeListener("SIGINT", this.sigintCallback);
678
+ process.removeListener("SIGTERM", this.sigintCallback);
679
+ this.sigintCallback = null;
680
+ }
681
+ this.isActive = false;
682
+ if (this.options.hideCursor === true) {
683
+ this.terminal.cursor(true);
684
+ }
685
+ if (this.options.linewrap === false) {
686
+ this.terminal.lineWrapping(true);
687
+ }
688
+ this.terminal.cursorRelativeReset();
689
+ this.emit("stop-pre-clear");
690
+ if (this.options.clearOnComplete) {
691
+ this.terminal.clearBottom();
692
+ } else {
693
+ for (let i = 0;i < this.bars.length; i++) {
694
+ if (i > 0) {
695
+ this.terminal.newline();
696
+ }
697
+ this.bars[i].render();
698
+ this.bars[i].stop();
699
+ }
700
+ this.terminal.newline();
701
+ }
702
+ this.emit("stop");
703
+ }
704
+ log(s) {
705
+ this.loggingBuffer.push(s);
706
+ }
707
+ };
708
+ });
709
+
710
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/legacy.js
711
+ var require_legacy = __commonJS((exports, module) => {
712
+ module.exports = {
713
+ format: "progress [{bar}] {percentage}% | ETA: {eta}s | {value}/{total}",
714
+ barCompleteChar: "=",
715
+ barIncompleteChar: "-"
716
+ };
717
+ });
718
+
719
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/shades-classic.js
720
+ var require_shades_classic = __commonJS((exports, module) => {
721
+ module.exports = {
722
+ format: " {bar} {percentage}% | ETA: {eta}s | {value}/{total}",
723
+ barCompleteChar: "█",
724
+ barIncompleteChar: "░"
725
+ };
726
+ });
727
+
728
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/shades-grey.js
729
+ var require_shades_grey = __commonJS((exports, module) => {
730
+ module.exports = {
731
+ format: " \x1B[90m{bar}\x1B[0m {percentage}% | ETA: {eta}s | {value}/{total}",
732
+ barCompleteChar: "█",
733
+ barIncompleteChar: "░"
734
+ };
735
+ });
736
+
737
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/rect.js
738
+ var require_rect = __commonJS((exports, module) => {
739
+ module.exports = {
740
+ format: " {bar}■ {percentage}% | ETA: {eta}s | {value}/{total}",
741
+ barCompleteChar: "■",
742
+ barIncompleteChar: " "
743
+ };
744
+ });
745
+
746
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/presets/index.js
747
+ var require_presets = __commonJS((exports, module) => {
748
+ var _legacy = require_legacy();
749
+ var _shades_classic = require_shades_classic();
750
+ var _shades_grey = require_shades_grey();
751
+ var _rect = require_rect();
752
+ module.exports = {
753
+ legacy: _legacy,
754
+ shades_classic: _shades_classic,
755
+ shades_grey: _shades_grey,
756
+ rect: _rect
757
+ };
758
+ });
759
+
760
+ // ../../node_modules/.bun/cli-progress@3.12.0/node_modules/cli-progress/cli-progress.js
761
+ var require_cli_progress = __commonJS((exports, module) => {
762
+ var _SingleBar = require_single_bar();
763
+ var _MultiBar = require_multi_bar();
764
+ var _Presets = require_presets();
765
+ var _Formatter = require_formatter();
766
+ var _defaultFormatValue = require_format_value();
767
+ var _defaultFormatBar = require_format_bar();
768
+ var _defaultFormatTime = require_format_time();
769
+ module.exports = {
770
+ Bar: _SingleBar,
771
+ SingleBar: _SingleBar,
772
+ MultiBar: _MultiBar,
773
+ Presets: _Presets,
774
+ Format: {
775
+ Formatter: _Formatter,
776
+ BarFormat: _defaultFormatBar,
777
+ ValueFormat: _defaultFormatValue,
778
+ TimeFormat: _defaultFormatTime
779
+ }
780
+ };
781
+ });
2
782
 
3
783
  // src/index.ts
4
784
  import { Command } from "commander";
5
785
 
6
786
  // src/commands/create.ts
7
- import { select, input, password, confirm } from "@inquirer/prompts";
787
+ import { select, input, password, confirm, checkbox } from "@inquirer/prompts";
8
788
  import ora from "ora";
9
- import * as path5 from "node:path";
10
- import * as fs5 from "node:fs/promises";
11
- import { fileURLToPath } from "node:url";
789
+ import * as path7 from "node:path";
790
+ import * as fs11 from "node:fs/promises";
791
+ import * as os2 from "node:os";
792
+ import { fileURLToPath as fileURLToPath2 } from "node:url";
12
793
 
13
794
  // ../../integrations/bokio/dist/config.js
14
795
  var BOKIO_CONFIG = {
@@ -123,8 +904,10 @@ class IntegrationError extends Error {
123
904
  }
124
905
 
125
906
  class AuthenticationError extends IntegrationError {
126
- constructor(message, cause) {
907
+ response;
908
+ constructor(message, response, cause) {
127
909
  super(message, "AUTHENTICATION_ERROR", 401, cause);
910
+ this.response = response;
128
911
  this.name = "AuthenticationError";
129
912
  }
130
913
  }
@@ -220,13 +1003,15 @@ class HttpClient {
220
1003
  return await response.json();
221
1004
  }
222
1005
  if (response.status === 401) {
223
- throw new AuthenticationError("Authentication failed");
1006
+ const errorBody2 = await response.text().catch(() => {
1007
+ return;
1008
+ });
1009
+ throw new AuthenticationError("Authentication failed", errorBody2);
224
1010
  }
225
1011
  if (response.status === 429) {
226
1012
  const retryAfter = response.headers.get("Retry-After");
227
1013
  const retryAfterMs = retryAfter ? parseInt(retryAfter, 10) * 1000 : 1000;
228
1014
  if (attempt < this.retryConfig.maxRetries) {
229
- console.log(`[HttpClient] Rate limited, waiting ${retryAfterMs}ms before retry ${attempt + 1}/${this.retryConfig.maxRetries}`);
230
1015
  await new Promise((resolve) => setTimeout(resolve, retryAfterMs));
231
1016
  continue;
232
1017
  }
@@ -308,7 +1093,10 @@ class HttpClient {
308
1093
  return await response.json();
309
1094
  }
310
1095
  if (response.status === 401) {
311
- throw new AuthenticationError("Authentication failed");
1096
+ const errorBody2 = await response.text().catch(() => {
1097
+ return;
1098
+ });
1099
+ throw new AuthenticationError("Authentication failed", errorBody2);
312
1100
  }
313
1101
  if (response.status === 429) {
314
1102
  const retryAfter = response.headers.get("Retry-After");
@@ -353,7 +1141,10 @@ class HttpClient {
353
1141
  clearTimeout(timeoutId);
354
1142
  if (!response.ok) {
355
1143
  if (response.status === 401) {
356
- throw new AuthenticationError("Authentication failed");
1144
+ const errorBody2 = await response.text().catch(() => {
1145
+ return;
1146
+ });
1147
+ throw new AuthenticationError("Authentication failed", errorBody2);
357
1148
  }
358
1149
  if (response.status === 429) {
359
1150
  const retryAfter = response.headers.get("Retry-After");
@@ -666,54 +1457,820 @@ class BokioClient {
666
1457
  return validate(UploadSchema, raw, "Upload");
667
1458
  }
668
1459
  }
669
- // ../sync/dist/transformers/bokio.js
670
- function mapBokioEntryToJournalEntry(entry) {
671
- const voucherNumberMatch = entry.journalEntryNumber.match(/\d+/);
672
- const voucherNumber = voucherNumberMatch ? parseInt(voucherNumberMatch[0], 10) : 0;
673
- const seriesCode = entry.journalEntryNumber.replace(/\d+/g, "") || undefined;
674
- const lines = entry.items.map((item, idx) => ({
675
- lineNumber: idx + 1,
676
- account: String(item.account),
677
- debit: { amount: item.debit, currency: "SEK" },
678
- credit: { amount: item.credit, currency: "SEK" }
679
- }));
680
- const totalDebit = entry.items.reduce((sum, item) => sum + item.debit, 0);
681
- const totalCredit = entry.items.reduce((sum, item) => sum + item.credit, 0);
682
- return {
683
- series: seriesCode,
684
- entryNumber: voucherNumber,
685
- entryDate: entry.date,
686
- description: entry.title,
687
- status: "POSTED",
688
- currency: "SEK",
689
- externalId: entry.id,
690
- totalDebit: { amount: totalDebit, currency: "SEK" },
691
- totalCredit: { amount: totalCredit, currency: "SEK" },
692
- lines,
693
- sourceIntegration: "bokio",
694
- sourceSyncedAt: new Date().toISOString()
695
- };
696
- }
697
- // ../sync/dist/storage/filesystem.js
698
- import * as fs from "node:fs/promises";
699
- import * as path from "node:path";
700
- import * as crypto from "node:crypto";
701
-
702
- class FilesystemStorageService {
703
- basePath;
704
- constructor(basePath) {
705
- this.basePath = basePath;
706
- }
707
- resolvePath(relativePath) {
708
- return path.join(this.basePath, relativePath);
709
- }
710
- contentHash(content) {
711
- return crypto.createHash("sha1").update(content).digest("hex");
712
- }
713
- async readFile(filePath) {
714
- const absolutePath = this.resolvePath(filePath);
715
- const content = await fs.readFile(absolutePath, "utf-8");
716
- return {
1460
+ // ../../integrations/fortnox/dist/config.js
1461
+ var FORTNOX_CONFIG = {
1462
+ API_BASE_URL: "https://api.fortnox.se",
1463
+ AUTH_BASE_URL: "https://apps.fortnox.se",
1464
+ TOKEN_ENDPOINT: "/oauth-v1/token",
1465
+ AUTHORIZE_ENDPOINT: "/oauth-v1/auth",
1466
+ API_VERSION: "3",
1467
+ RATE_LIMIT: {
1468
+ MAX_REQUESTS: 25,
1469
+ WINDOW_SECONDS: 5
1470
+ },
1471
+ TOKEN_EXPIRY_SECONDS: 3600,
1472
+ REFRESH_TOKEN_EXPIRY_DAYS: 45
1473
+ };
1474
+ var FORTNOX_SCOPES = {
1475
+ INVOICE: "invoice",
1476
+ CUSTOMER: "customer",
1477
+ ARTICLE: "article",
1478
+ ORDER: "order",
1479
+ OFFER: "offer",
1480
+ SUPPLIER: "supplier",
1481
+ SUPPLIERINVOICE: "supplierinvoice",
1482
+ ACCOUNT: "account",
1483
+ VOUCHER: "voucher",
1484
+ BOOKKEEPING: "bookkeeping",
1485
+ SETTINGS: "settings",
1486
+ SALARY: "salary",
1487
+ PRINT: "print",
1488
+ CURRENCY: "currency",
1489
+ PROJECT: "project",
1490
+ PRICE: "price",
1491
+ INBOX: "inbox",
1492
+ ARCHIVE: "archive",
1493
+ COMPANYINFORMATION: "companyinformation",
1494
+ CONNECTFILE: "connectfile",
1495
+ COSTCENTER: "costcenter",
1496
+ NOXFINANS: "noxfinans",
1497
+ PAYMENT: "payment",
1498
+ WAREHOUSE: "warehouse"
1499
+ };
1500
+ // ../../integrations/fortnox/dist/schemas.js
1501
+ import { z as z2 } from "zod";
1502
+ var FortnoxMetaInformationSchema = z2.object({
1503
+ "@TotalResources": z2.number(),
1504
+ "@TotalPages": z2.number(),
1505
+ "@CurrentPage": z2.number()
1506
+ }).passthrough();
1507
+ var FortnoxErrorResponseSchema = z2.object({
1508
+ ErrorInformation: z2.object({
1509
+ Error: z2.number(),
1510
+ Message: z2.string(),
1511
+ Code: z2.number()
1512
+ })
1513
+ }).passthrough();
1514
+ var FinancialYearSchema = z2.object({
1515
+ Id: z2.number(),
1516
+ FromDate: z2.string(),
1517
+ ToDate: z2.string()
1518
+ }).passthrough();
1519
+ var FinancialYearResponseSchema = z2.object({
1520
+ FinancialYear: FinancialYearSchema
1521
+ }).passthrough();
1522
+ var FinancialYearsResponseSchema = z2.object({
1523
+ MetaInformation: FortnoxMetaInformationSchema,
1524
+ FinancialYears: z2.array(FinancialYearSchema)
1525
+ }).passthrough();
1526
+ var AccountSchema2 = z2.object({
1527
+ Number: z2.union([z2.string(), z2.number()]).transform((v) => String(v)),
1528
+ Description: z2.string()
1529
+ }).passthrough();
1530
+ var AccountResponseSchema = z2.object({
1531
+ Account: AccountSchema2
1532
+ }).passthrough();
1533
+ var AccountsResponseSchema = z2.object({
1534
+ MetaInformation: FortnoxMetaInformationSchema,
1535
+ Accounts: z2.array(AccountSchema2)
1536
+ }).passthrough();
1537
+ var CreateAccountRequestSchema = z2.object({
1538
+ Account: z2.object({
1539
+ Number: z2.string(),
1540
+ Description: z2.string()
1541
+ })
1542
+ });
1543
+ var VoucherSeriesSchema = z2.object({
1544
+ Code: z2.string(),
1545
+ Description: z2.string()
1546
+ }).passthrough();
1547
+ var VoucherSeriesResponseSchema = z2.object({
1548
+ VoucherSeries: VoucherSeriesSchema
1549
+ }).passthrough();
1550
+ var VoucherSeriesListResponseSchema = z2.object({
1551
+ MetaInformation: FortnoxMetaInformationSchema
1552
+ }).and(z2.union([
1553
+ z2.object({ VoucherSeriesList: z2.array(VoucherSeriesSchema) }),
1554
+ z2.object({ VoucherSeriesCollection: z2.array(VoucherSeriesSchema) })
1555
+ ])).transform((data) => ({
1556
+ ...data,
1557
+ VoucherSeriesList: "VoucherSeriesList" in data ? data.VoucherSeriesList : data.VoucherSeriesCollection
1558
+ }));
1559
+ var CreateVoucherSeriesRequestSchema = z2.object({
1560
+ VoucherSeries: z2.object({
1561
+ Code: z2.string(),
1562
+ Description: z2.string()
1563
+ })
1564
+ });
1565
+ var CompanyInformationSchema = z2.object({
1566
+ CompanyName: z2.string(),
1567
+ Address: z2.string().nullish(),
1568
+ City: z2.string().nullish(),
1569
+ CountryCode: z2.string().nullish(),
1570
+ DatabaseNumber: z2.number().nullish(),
1571
+ OrganizationNumber: z2.string().nullish(),
1572
+ VisitAddress: z2.string().nullish(),
1573
+ VisitCity: z2.string().nullish(),
1574
+ VisitCountryCode: z2.string().nullish(),
1575
+ VisitZipCode: z2.string().nullish(),
1576
+ ZipCode: z2.string().nullish()
1577
+ }).passthrough();
1578
+ var CompanyInformationResponseSchema = z2.object({
1579
+ CompanyInformation: CompanyInformationSchema
1580
+ }).passthrough();
1581
+ var CostCenterSchema = z2.object({
1582
+ Code: z2.string(),
1583
+ Description: z2.string().optional()
1584
+ }).passthrough();
1585
+ var CostCenterResponseSchema = z2.object({
1586
+ CostCenter: CostCenterSchema
1587
+ }).passthrough();
1588
+ var CostCentersResponseSchema = z2.object({
1589
+ MetaInformation: FortnoxMetaInformationSchema.optional(),
1590
+ CostCenters: z2.array(CostCenterSchema)
1591
+ }).passthrough();
1592
+ var FortnoxFileSchema = z2.object({
1593
+ Id: z2.string(),
1594
+ Name: z2.string(),
1595
+ Path: z2.string().optional(),
1596
+ Size: z2.number().optional()
1597
+ }).passthrough();
1598
+ var FortnoxFolderSchema = z2.object({
1599
+ Id: z2.string().optional(),
1600
+ Name: z2.string(),
1601
+ Files: z2.array(FortnoxFileSchema).optional(),
1602
+ Folders: z2.array(z2.any()).optional()
1603
+ }).passthrough();
1604
+ var InboxFileResponseSchema = z2.object({
1605
+ File: FortnoxFileSchema
1606
+ }).passthrough();
1607
+ var InboxFilesResponseSchema = z2.object({
1608
+ Inbox: FortnoxFolderSchema.optional(),
1609
+ Folder: FortnoxFolderSchema.optional(),
1610
+ MetaInformation: FortnoxMetaInformationSchema.optional(),
1611
+ Files: z2.array(FortnoxFileSchema).optional()
1612
+ }).passthrough();
1613
+ var ArchiveFileResponseSchema = z2.object({
1614
+ File: FortnoxFileSchema
1615
+ }).passthrough();
1616
+ var ArchiveFilesResponseSchema = z2.object({
1617
+ Archive: FortnoxFolderSchema.optional(),
1618
+ Folder: FortnoxFolderSchema.optional(),
1619
+ MetaInformation: FortnoxMetaInformationSchema.optional(),
1620
+ Files: z2.array(FortnoxFileSchema).optional()
1621
+ }).passthrough();
1622
+ var VoucherRowSchema = z2.object({
1623
+ Account: z2.number(),
1624
+ Debit: z2.number().default(0),
1625
+ Credit: z2.number().default(0),
1626
+ CostCenter: z2.string().optional(),
1627
+ Project: z2.string().optional(),
1628
+ TransactionInformation: z2.string().optional(),
1629
+ Quantity: z2.number().optional(),
1630
+ Removed: z2.boolean().optional()
1631
+ }).passthrough();
1632
+ var VoucherSchema = z2.object({
1633
+ VoucherNumber: z2.number().optional(),
1634
+ VoucherSeries: z2.string(),
1635
+ VoucherDate: z2.string().optional(),
1636
+ Year: z2.number().optional(),
1637
+ Description: z2.string().nullish(),
1638
+ Comments: z2.string().nullish(),
1639
+ CostCenter: z2.string().nullish(),
1640
+ Project: z2.string().nullish(),
1641
+ VoucherRows: z2.array(VoucherRowSchema).optional(),
1642
+ ReferenceNumber: z2.string().nullish(),
1643
+ ReferenceType: z2.string().nullish(),
1644
+ TransactionDate: z2.string().nullish(),
1645
+ ApprovalState: z2.number().optional()
1646
+ }).passthrough();
1647
+ var VoucherResponseSchema = z2.object({
1648
+ Voucher: VoucherSchema
1649
+ }).passthrough();
1650
+ var VouchersResponseSchema = z2.object({
1651
+ MetaInformation: FortnoxMetaInformationSchema,
1652
+ Vouchers: z2.array(VoucherSchema)
1653
+ }).passthrough();
1654
+ var CreateVoucherRequestSchema = z2.object({
1655
+ Voucher: z2.object({
1656
+ VoucherSeries: z2.string(),
1657
+ TransactionDate: z2.string(),
1658
+ Description: z2.string().optional(),
1659
+ CostCenter: z2.string().optional(),
1660
+ Project: z2.string().optional(),
1661
+ Comments: z2.string().optional(),
1662
+ VoucherRows: z2.array(z2.object({
1663
+ Account: z2.number(),
1664
+ Debit: z2.number().optional(),
1665
+ Credit: z2.number().optional(),
1666
+ CostCenter: z2.string().optional(),
1667
+ Project: z2.string().optional(),
1668
+ TransactionInformation: z2.string().optional(),
1669
+ Quantity: z2.number().optional()
1670
+ }))
1671
+ })
1672
+ });
1673
+ var VoucherFileConnectionSchema = z2.object({
1674
+ FileId: z2.string(),
1675
+ VoucherNumber: z2.number().optional(),
1676
+ VoucherSeries: z2.string(),
1677
+ VoucherYear: z2.number().optional(),
1678
+ VoucherDescription: z2.string().optional()
1679
+ }).passthrough();
1680
+ var VoucherFileConnectionResponseSchema = z2.object({
1681
+ VoucherFileConnection: VoucherFileConnectionSchema
1682
+ }).passthrough();
1683
+ var CreateVoucherFileConnectionRequestSchema = z2.object({
1684
+ VoucherFileConnection: z2.object({
1685
+ FileId: z2.string(),
1686
+ VoucherNumber: z2.number(),
1687
+ VoucherSeries: z2.string(),
1688
+ VoucherYear: z2.number()
1689
+ })
1690
+ });
1691
+ var VoucherFileConnectionsResponseSchema = z2.object({
1692
+ VoucherFileConnections: z2.array(VoucherFileConnectionSchema)
1693
+ }).passthrough();
1694
+ // ../../integrations/fortnox/dist/auth.js
1695
+ function buildAuthorizationUrl(config, state) {
1696
+ const params = new URLSearchParams({
1697
+ client_id: config.clientId,
1698
+ redirect_uri: config.redirectUri,
1699
+ scope: config.scopes?.join(" ") ?? "",
1700
+ state,
1701
+ access_type: "offline",
1702
+ response_type: "code"
1703
+ });
1704
+ return `${FORTNOX_CONFIG.AUTH_BASE_URL}${FORTNOX_CONFIG.AUTHORIZE_ENDPOINT}?${params.toString()}`;
1705
+ }
1706
+ async function fetchToken(config, body) {
1707
+ const credentials = Buffer.from(`${config.clientId}:${config.clientSecret}`).toString("base64");
1708
+ const response = await fetch(`${FORTNOX_CONFIG.AUTH_BASE_URL}${FORTNOX_CONFIG.TOKEN_ENDPOINT}`, {
1709
+ method: "POST",
1710
+ headers: {
1711
+ "Content-Type": "application/x-www-form-urlencoded",
1712
+ Authorization: `Basic ${credentials}`
1713
+ },
1714
+ body: body.toString()
1715
+ });
1716
+ if (!response.ok) {
1717
+ const error = await response.json().catch(() => ({}));
1718
+ throw new Error(error.error_description ?? error.error ?? `Token request failed: ${response.status}`);
1719
+ }
1720
+ return response.json();
1721
+ }
1722
+ async function exchangeCodeForToken(config, code) {
1723
+ const body = new URLSearchParams({
1724
+ grant_type: "authorization_code",
1725
+ code,
1726
+ redirect_uri: config.redirectUri
1727
+ });
1728
+ return fetchToken(config, body);
1729
+ }
1730
+ // ../../integrations/fortnox/dist/pagination.js
1731
+ var DEFAULT_LIMIT = 100;
1732
+ var MAX_LIMIT = 500;
1733
+ function toFortnoxParams(params) {
1734
+ if (!params)
1735
+ return {};
1736
+ const pageSize = Math.min(params.pageSize ?? DEFAULT_LIMIT, MAX_LIMIT);
1737
+ const page = Math.max(params.page ?? 1, 1);
1738
+ return {
1739
+ limit: pageSize,
1740
+ offset: (page - 1) * pageSize
1741
+ };
1742
+ }
1743
+ function fromFortnoxMeta(meta, pageSize = DEFAULT_LIMIT) {
1744
+ const totalItems = meta["@TotalResources"];
1745
+ const totalPages = meta["@TotalPages"];
1746
+ const currentPage = meta["@CurrentPage"];
1747
+ return {
1748
+ totalItems,
1749
+ totalPages,
1750
+ currentPage,
1751
+ pageSize,
1752
+ hasNextPage: currentPage < totalPages,
1753
+ hasPreviousPage: currentPage > 1
1754
+ };
1755
+ }
1756
+ function buildQueryString(params) {
1757
+ const entries = Object.entries(params).filter(([_, v]) => v !== undefined);
1758
+ if (entries.length === 0)
1759
+ return "";
1760
+ return "?" + entries.map(([k, v]) => `${k}=${encodeURIComponent(String(v))}`).join("&");
1761
+ }
1762
+ // ../../integrations/fortnox/dist/client.js
1763
+ class FortnoxClient {
1764
+ httpClient;
1765
+ constructor(config) {
1766
+ const rateLimiter = TokenBucketRateLimiter.forFortnox();
1767
+ this.httpClient = new HttpClient({
1768
+ baseUrl: FORTNOX_CONFIG.API_BASE_URL,
1769
+ getAccessToken: config.getAccessToken,
1770
+ rateLimiter,
1771
+ timeout: 600000,
1772
+ defaultHeaders: {
1773
+ Accept: "application/json"
1774
+ },
1775
+ silent: config.silent
1776
+ });
1777
+ }
1778
+ async request(path, options) {
1779
+ return this.httpClient.request(path, {
1780
+ method: options?.method ?? "GET",
1781
+ body: options?.body
1782
+ });
1783
+ }
1784
+ async get(path) {
1785
+ return this.httpClient.get(path);
1786
+ }
1787
+ async post(path, body) {
1788
+ return this.httpClient.post(path, body);
1789
+ }
1790
+ async put(path, body) {
1791
+ return this.httpClient.put(path, body);
1792
+ }
1793
+ async delete(path) {
1794
+ return this.httpClient.delete(path);
1795
+ }
1796
+ async getFinancialYears(params) {
1797
+ const { limit, offset } = toFortnoxParams(params);
1798
+ const query = buildQueryString({ limit, offset });
1799
+ const raw = await this.get(`/3/financialyears${query}`);
1800
+ const response = validate(FinancialYearsResponseSchema, raw, "FinancialYearsResponse");
1801
+ return {
1802
+ data: response.FinancialYears,
1803
+ pagination: fromFortnoxMeta(response.MetaInformation, limit ?? 100)
1804
+ };
1805
+ }
1806
+ async getFinancialYearByDate(date) {
1807
+ const raw = await this.get(`/3/financialyears/?date=${date}`);
1808
+ return validate(FinancialYearsResponseSchema, raw, "FinancialYearsResponse");
1809
+ }
1810
+ async getFinancialYearById(id) {
1811
+ const raw = await this.get(`/3/financialyears/${id}`);
1812
+ return validate(FinancialYearResponseSchema, raw, "FinancialYearResponse");
1813
+ }
1814
+ async getAccounts(params) {
1815
+ const { limit, offset } = toFortnoxParams(params);
1816
+ const query = buildQueryString({ limit, offset });
1817
+ const raw = await this.get(`/3/accounts${query}`);
1818
+ const response = validate(AccountsResponseSchema, raw, "AccountsResponse");
1819
+ return {
1820
+ data: response.Accounts,
1821
+ pagination: fromFortnoxMeta(response.MetaInformation, limit ?? 100)
1822
+ };
1823
+ }
1824
+ async getAccount(accountNumber) {
1825
+ const raw = await this.get(`/3/accounts/${accountNumber}`);
1826
+ return validate(AccountResponseSchema, raw, "AccountResponse");
1827
+ }
1828
+ async createAccount(request) {
1829
+ const raw = await this.post("/3/accounts", request);
1830
+ return validate(AccountResponseSchema, raw, "AccountResponse");
1831
+ }
1832
+ async getVoucherSeriesList(params) {
1833
+ const { limit, offset } = toFortnoxParams(params);
1834
+ const query = buildQueryString({ limit, offset });
1835
+ const raw = await this.get(`/3/voucherseries${query}`);
1836
+ const response = validate(VoucherSeriesListResponseSchema, raw, "VoucherSeriesListResponse");
1837
+ return {
1838
+ data: response.VoucherSeriesList,
1839
+ pagination: fromFortnoxMeta(response.MetaInformation, limit ?? 100)
1840
+ };
1841
+ }
1842
+ async getVoucherSeries(code, financialYearDate) {
1843
+ const raw = await this.get(`/3/voucherseries/${code}?financialyeardate=${financialYearDate}`);
1844
+ return validate(VoucherSeriesResponseSchema, raw, "VoucherSeriesResponse");
1845
+ }
1846
+ async createVoucherSeries(request) {
1847
+ const raw = await this.post("/3/voucherseries", request);
1848
+ return validate(VoucherSeriesResponseSchema, raw, "VoucherSeriesResponse");
1849
+ }
1850
+ async getCompanyInformation() {
1851
+ const raw = await this.get("/3/companyinformation");
1852
+ return validate(CompanyInformationResponseSchema, raw, "CompanyInformationResponse");
1853
+ }
1854
+ async getCostCenters(params) {
1855
+ const { limit, offset } = toFortnoxParams(params);
1856
+ const query = buildQueryString({ limit, offset });
1857
+ const raw = await this.get(`/3/costcenters${query}`);
1858
+ const response = validate(CostCentersResponseSchema, raw, "CostCentersResponse");
1859
+ const meta = response.MetaInformation ?? {
1860
+ "@TotalResources": response.CostCenters.length,
1861
+ "@TotalPages": 1,
1862
+ "@CurrentPage": 1
1863
+ };
1864
+ return {
1865
+ data: response.CostCenters,
1866
+ pagination: fromFortnoxMeta(meta, limit ?? 100)
1867
+ };
1868
+ }
1869
+ async getCostCenter(code) {
1870
+ const raw = await this.get(`/3/costcenters/${code}`);
1871
+ return validate(CostCenterResponseSchema, raw, "CostCenterResponse");
1872
+ }
1873
+ async getInboxFiles() {
1874
+ const raw = await this.get("/3/inbox");
1875
+ return validate(InboxFilesResponseSchema, raw, "InboxFilesResponse");
1876
+ }
1877
+ async getInboxFolder(folderId) {
1878
+ const raw = await this.get(`/3/inbox/${folderId}`);
1879
+ const response = validate(InboxFilesResponseSchema, raw, "InboxFilesResponse");
1880
+ const files = response.Folder?.Files ?? response.Files ?? [];
1881
+ if (files.length > 0) {
1882
+ console.info(`[FortnoxClient] Inbox folder "${folderId}" contains ${files.length} file(s):`, files.map((f) => ({ id: f.Id, name: f.Name, size: f.Size })));
1883
+ }
1884
+ return response;
1885
+ }
1886
+ async getInboxFile(id) {
1887
+ const raw = await this.get(`/3/inbox/${id}`);
1888
+ return validate(InboxFileResponseSchema, raw, "InboxFileResponse");
1889
+ }
1890
+ async uploadToInbox(options) {
1891
+ const formData = new FormData;
1892
+ formData.append("file", options.file, options.filename);
1893
+ const path = options.folderId ? `/3/inbox?path=${options.folderId}` : "/3/inbox";
1894
+ const raw = await this.httpClient.uploadFormData(path, formData);
1895
+ return validate(InboxFileResponseSchema, raw, "InboxFileResponse");
1896
+ }
1897
+ async downloadFromInbox(id) {
1898
+ return this.httpClient.downloadBinary(`/3/inbox/${id}`);
1899
+ }
1900
+ async getArchiveFiles() {
1901
+ const raw = await this.get("/3/archive");
1902
+ return validate(ArchiveFilesResponseSchema, raw, "ArchiveFilesResponse");
1903
+ }
1904
+ async getArchiveFile(id) {
1905
+ const raw = await this.get(`/3/archive/${id}`);
1906
+ return validate(ArchiveFileResponseSchema, raw, "ArchiveFileResponse");
1907
+ }
1908
+ async uploadToArchive(options) {
1909
+ const formData = new FormData;
1910
+ formData.append("file", options.file, options.filename);
1911
+ const path = options.folderId ? `/3/archive?path=${options.folderId}` : "/3/archive";
1912
+ const raw = await this.httpClient.uploadFormData(path, formData);
1913
+ return validate(ArchiveFileResponseSchema, raw, "ArchiveFileResponse");
1914
+ }
1915
+ async downloadFromArchive(id) {
1916
+ return this.httpClient.downloadBinary(`/3/archive/${id}`);
1917
+ }
1918
+ async deleteFromArchive(id) {
1919
+ await this.delete(`/3/archive/${id}`);
1920
+ }
1921
+ async deleteFromInbox(id) {
1922
+ await this.delete(`/3/inbox/${id}`);
1923
+ }
1924
+ async getVouchers(financialYear, params) {
1925
+ const { limit, offset } = toFortnoxParams(params);
1926
+ const query = buildQueryString({
1927
+ financialyear: financialYear,
1928
+ limit,
1929
+ offset
1930
+ });
1931
+ const raw = await this.get(`/3/vouchers${query}`);
1932
+ const response = validate(VouchersResponseSchema, raw, "VouchersResponse");
1933
+ return {
1934
+ data: response.Vouchers,
1935
+ pagination: fromFortnoxMeta(response.MetaInformation, limit ?? 100)
1936
+ };
1937
+ }
1938
+ async getVoucher(series, number, financialYear) {
1939
+ const raw = await this.get(`/3/vouchers/${series}/${number}?financialyear=${financialYear}`);
1940
+ return validate(VoucherResponseSchema, raw, "VoucherResponse");
1941
+ }
1942
+ async createVoucher(request) {
1943
+ const raw = await this.post("/3/vouchers", request);
1944
+ return validate(VoucherResponseSchema, raw, "VoucherResponse");
1945
+ }
1946
+ async getVoucherFileConnection(fileId) {
1947
+ const raw = await this.get(`/3/voucherfileconnections/${fileId}`);
1948
+ return validate(VoucherFileConnectionResponseSchema, raw, "VoucherFileConnectionResponse");
1949
+ }
1950
+ async createVoucherFileConnection(request) {
1951
+ const raw = await this.post("/3/voucherfileconnections", request);
1952
+ return validate(VoucherFileConnectionResponseSchema, raw, "VoucherFileConnectionResponse");
1953
+ }
1954
+ async deleteVoucherFileConnection(fileId) {
1955
+ await this.delete(`/3/voucherfileconnections/${fileId}`);
1956
+ }
1957
+ async getVoucherFileConnections() {
1958
+ const raw = await this.get("/3/voucherfileconnections");
1959
+ return validate(VoucherFileConnectionsResponseSchema, raw, "VoucherFileConnectionsResponse");
1960
+ }
1961
+ async getVoucherFileConnectionsForVoucher(series, number, year) {
1962
+ const response = await this.getVoucherFileConnections();
1963
+ return response.VoucherFileConnections.filter((conn) => conn.VoucherSeries === series && conn.VoucherNumber === number && conn.VoucherYear === year);
1964
+ }
1965
+ }
1966
+ // ../accounting/dist/accounting/tax-codes.js
1967
+ var SE_TAX_CODES = [
1968
+ {
1969
+ code: "SE_VAT_25_PURCHASE_DOMESTIC",
1970
+ description: "Inköp i Sverige, moms 25 %",
1971
+ kind: "VAT",
1972
+ direction: "PURCHASE",
1973
+ territory: "DOMESTIC",
1974
+ ratePercent: 25,
1975
+ isReverseCharge: false,
1976
+ isZeroRated: false,
1977
+ isExempt: false,
1978
+ posting: {
1979
+ inputVatAccountCode: "2641"
1980
+ }
1981
+ },
1982
+ {
1983
+ code: "SE_VAT_12_PURCHASE_DOMESTIC",
1984
+ description: "Inköp i Sverige, moms 12 %",
1985
+ kind: "VAT",
1986
+ direction: "PURCHASE",
1987
+ territory: "DOMESTIC",
1988
+ ratePercent: 12,
1989
+ isReverseCharge: false,
1990
+ isZeroRated: false,
1991
+ isExempt: false,
1992
+ posting: {
1993
+ inputVatAccountCode: "2641"
1994
+ }
1995
+ },
1996
+ {
1997
+ code: "SE_VAT_6_PURCHASE_DOMESTIC",
1998
+ description: "Inköp i Sverige, moms 6 %",
1999
+ kind: "VAT",
2000
+ direction: "PURCHASE",
2001
+ territory: "DOMESTIC",
2002
+ ratePercent: 6,
2003
+ isReverseCharge: false,
2004
+ isZeroRated: false,
2005
+ isExempt: false,
2006
+ posting: {
2007
+ inputVatAccountCode: "2641"
2008
+ }
2009
+ },
2010
+ {
2011
+ code: "SE_VAT_0_PURCHASE_DOMESTIC",
2012
+ description: "Inköp i Sverige, 0 % moms (momspliktigt men 0 %)",
2013
+ kind: "VAT",
2014
+ direction: "PURCHASE",
2015
+ territory: "DOMESTIC",
2016
+ ratePercent: 0,
2017
+ isReverseCharge: false,
2018
+ isZeroRated: true,
2019
+ isExempt: false,
2020
+ posting: {}
2021
+ },
2022
+ {
2023
+ code: "SE_VAT_EXEMPT_PURCHASE",
2024
+ description: "Momsfria inköp (utanför momsens tillämpningsområde)",
2025
+ kind: "VAT",
2026
+ direction: "PURCHASE",
2027
+ territory: "DOMESTIC",
2028
+ ratePercent: 0,
2029
+ isReverseCharge: false,
2030
+ isZeroRated: false,
2031
+ isExempt: true,
2032
+ posting: {}
2033
+ },
2034
+ {
2035
+ code: "SE_VAT_25_SALE_DOMESTIC",
2036
+ description: "Försäljning i Sverige, moms 25 %",
2037
+ kind: "VAT",
2038
+ direction: "SALE",
2039
+ territory: "DOMESTIC",
2040
+ ratePercent: 25,
2041
+ isReverseCharge: false,
2042
+ isZeroRated: false,
2043
+ isExempt: false,
2044
+ posting: {
2045
+ outputVatAccountCode: "2611"
2046
+ }
2047
+ },
2048
+ {
2049
+ code: "SE_VAT_12_SALE_DOMESTIC",
2050
+ description: "Försäljning i Sverige, moms 12 %",
2051
+ kind: "VAT",
2052
+ direction: "SALE",
2053
+ territory: "DOMESTIC",
2054
+ ratePercent: 12,
2055
+ isReverseCharge: false,
2056
+ isZeroRated: false,
2057
+ isExempt: false,
2058
+ posting: {
2059
+ outputVatAccountCode: "2611"
2060
+ }
2061
+ },
2062
+ {
2063
+ code: "SE_VAT_6_SALE_DOMESTIC",
2064
+ description: "Försäljning i Sverige, moms 6 %",
2065
+ kind: "VAT",
2066
+ direction: "SALE",
2067
+ territory: "DOMESTIC",
2068
+ ratePercent: 6,
2069
+ isReverseCharge: false,
2070
+ isZeroRated: false,
2071
+ isExempt: false,
2072
+ posting: {
2073
+ outputVatAccountCode: "2611"
2074
+ }
2075
+ },
2076
+ {
2077
+ code: "SE_VAT_0_SALE_DOMESTIC",
2078
+ description: "Momsfri försäljning i Sverige (undantagen/0 %)",
2079
+ kind: "VAT",
2080
+ direction: "SALE",
2081
+ territory: "DOMESTIC",
2082
+ ratePercent: 0,
2083
+ isReverseCharge: false,
2084
+ isZeroRated: false,
2085
+ isExempt: true,
2086
+ posting: {}
2087
+ },
2088
+ {
2089
+ code: "SE_VAT_25_PURCHASE_EU_GOODS_RC",
2090
+ description: "Inköp av varor från annat EU-land, 25 %, omvänd skattskyldighet",
2091
+ kind: "VAT",
2092
+ direction: "PURCHASE",
2093
+ territory: "EU",
2094
+ ratePercent: 25,
2095
+ isReverseCharge: true,
2096
+ isZeroRated: false,
2097
+ isExempt: false,
2098
+ posting: {
2099
+ reverseChargeOutputAccountCode: "2614",
2100
+ reverseChargeInputAccountCode: "2645"
2101
+ }
2102
+ },
2103
+ {
2104
+ code: "SE_VAT_25_PURCHASE_EU_SERVICES_RC",
2105
+ description: "Inköp av tjänster från annat EU-land, 25 %, omvänd skattskyldighet",
2106
+ kind: "VAT",
2107
+ direction: "PURCHASE",
2108
+ territory: "EU",
2109
+ ratePercent: 25,
2110
+ isReverseCharge: true,
2111
+ isZeroRated: false,
2112
+ isExempt: false,
2113
+ posting: {
2114
+ reverseChargeOutputAccountCode: "2614",
2115
+ reverseChargeInputAccountCode: "2645"
2116
+ }
2117
+ },
2118
+ {
2119
+ code: "SE_VAT_25_PURCHASE_NON_EU_SERVICES_RC",
2120
+ description: "Inköp av tjänster från land utanför EU, 25 %, omvänd skattskyldighet",
2121
+ kind: "VAT",
2122
+ direction: "PURCHASE",
2123
+ territory: "OUTSIDE_EU",
2124
+ ratePercent: 25,
2125
+ isReverseCharge: true,
2126
+ isZeroRated: false,
2127
+ isExempt: false,
2128
+ posting: {
2129
+ reverseChargeOutputAccountCode: "2614",
2130
+ reverseChargeInputAccountCode: "2645"
2131
+ }
2132
+ },
2133
+ {
2134
+ code: "SE_VAT_0_SALE_EU_GOODS_B2B",
2135
+ description: "Varuförsäljning till momsregistrerad kund i annat EU-land (0 %)",
2136
+ kind: "VAT",
2137
+ direction: "SALE",
2138
+ territory: "EU",
2139
+ ratePercent: 0,
2140
+ isReverseCharge: false,
2141
+ isZeroRated: true,
2142
+ isExempt: false,
2143
+ posting: {}
2144
+ },
2145
+ {
2146
+ code: "SE_VAT_0_SALE_EU_SERVICES_B2B",
2147
+ description: "Tjänsteförsäljning till momsregistrerad kund i annat EU-land (0 %)",
2148
+ kind: "VAT",
2149
+ direction: "SALE",
2150
+ territory: "EU",
2151
+ ratePercent: 0,
2152
+ isReverseCharge: false,
2153
+ isZeroRated: true,
2154
+ isExempt: false,
2155
+ posting: {}
2156
+ },
2157
+ {
2158
+ code: "SE_VAT_RC_SALE_DOMESTIC_CONSTRUCTION",
2159
+ description: "Försäljning av byggtjänster med omvänd skattskyldighet (ingen moms på fakturan)",
2160
+ kind: "VAT",
2161
+ direction: "SALE",
2162
+ territory: "DOMESTIC",
2163
+ ratePercent: 0,
2164
+ isReverseCharge: true,
2165
+ isZeroRated: false,
2166
+ isExempt: false,
2167
+ posting: {}
2168
+ },
2169
+ {
2170
+ code: "SE_VAT_RC_PURCHASE_DOMESTIC_CONSTRUCTION",
2171
+ description: "Inköp av byggtjänster med omvänd skattskyldighet (du beräknar svensk moms)",
2172
+ kind: "VAT",
2173
+ direction: "PURCHASE",
2174
+ territory: "DOMESTIC",
2175
+ ratePercent: 25,
2176
+ isReverseCharge: true,
2177
+ isZeroRated: false,
2178
+ isExempt: false,
2179
+ posting: {
2180
+ reverseChargeOutputAccountCode: "2617",
2181
+ reverseChargeInputAccountCode: "2647"
2182
+ }
2183
+ }
2184
+ ];
2185
+ var TAX_CODE_MAP = new Map(SE_TAX_CODES.map((code) => [code.code, code]));
2186
+ // ../accounting/dist/yaml/yaml-serializer.js
2187
+ import YAML from "yaml";
2188
+ // ../accounting/dist/transformers/fortnox.js
2189
+ function mapFortnoxVoucherToJournalEntry(voucher, fiscalYear) {
2190
+ const series = voucher.VoucherSeries;
2191
+ const externalId = `${voucher.VoucherSeries}-${voucher.VoucherNumber}`;
2192
+ let totalDebit = 0;
2193
+ let totalCredit = 0;
2194
+ const lines = (voucher.VoucherRows ?? []).map((row, idx) => {
2195
+ totalDebit += row.Debit ?? 0;
2196
+ totalCredit += row.Credit ?? 0;
2197
+ const memo = row.TransactionInformation || row.Description || undefined;
2198
+ return {
2199
+ lineNumber: idx + 1,
2200
+ account: String(row.Account),
2201
+ debit: { amount: row.Debit ?? 0, currency: "SEK" },
2202
+ credit: { amount: row.Credit ?? 0, currency: "SEK" },
2203
+ memo: memo ? String(memo) : undefined,
2204
+ costCenter: row.CostCenter ?? undefined
2205
+ };
2206
+ });
2207
+ return {
2208
+ series,
2209
+ entryNumber: voucher.VoucherNumber ?? 0,
2210
+ entryDate: voucher.TransactionDate ?? voucher.VoucherDate ?? "",
2211
+ description: voucher.Description ?? "",
2212
+ status: "POSTED",
2213
+ currency: "SEK",
2214
+ externalId,
2215
+ totalDebit: { amount: totalDebit, currency: "SEK" },
2216
+ totalCredit: { amount: totalCredit, currency: "SEK" },
2217
+ lines,
2218
+ sourceIntegration: "fortnox",
2219
+ sourceSyncedAt: new Date().toISOString(),
2220
+ voucherNumber: voucher.VoucherNumber,
2221
+ voucherSeriesCode: voucher.VoucherSeries
2222
+ };
2223
+ }
2224
+ // ../accounting/dist/transformers/bokio.js
2225
+ function mapBokioEntryToJournalEntry(entry) {
2226
+ const voucherNumberMatch = entry.journalEntryNumber.match(/\d+/);
2227
+ const voucherNumber = voucherNumberMatch ? parseInt(voucherNumberMatch[0], 10) : 0;
2228
+ const seriesCode = entry.journalEntryNumber.replace(/\d+/g, "") || undefined;
2229
+ const lines = entry.items.map((item, idx) => ({
2230
+ lineNumber: idx + 1,
2231
+ account: String(item.account),
2232
+ debit: { amount: item.debit, currency: "SEK" },
2233
+ credit: { amount: item.credit, currency: "SEK" }
2234
+ }));
2235
+ const totalDebit = entry.items.reduce((sum, item) => sum + item.debit, 0);
2236
+ const totalCredit = entry.items.reduce((sum, item) => sum + item.credit, 0);
2237
+ return {
2238
+ series: seriesCode,
2239
+ entryNumber: voucherNumber,
2240
+ entryDate: entry.date,
2241
+ description: entry.title,
2242
+ status: "POSTED",
2243
+ currency: "SEK",
2244
+ externalId: entry.id,
2245
+ totalDebit: { amount: totalDebit, currency: "SEK" },
2246
+ totalCredit: { amount: totalCredit, currency: "SEK" },
2247
+ lines,
2248
+ sourceIntegration: "bokio",
2249
+ sourceSyncedAt: new Date().toISOString(),
2250
+ reversingEntryExternalId: entry.reversingJournalEntryId ?? undefined,
2251
+ reversedByEntryExternalId: entry.reversedByJournalEntryId ?? undefined
2252
+ };
2253
+ }
2254
+ // ../accounting/dist/storage/filesystem.js
2255
+ import * as fs from "node:fs/promises";
2256
+ import * as path from "node:path";
2257
+ import * as crypto2 from "node:crypto";
2258
+
2259
+ class FilesystemStorageService {
2260
+ basePath;
2261
+ constructor(basePath) {
2262
+ this.basePath = basePath;
2263
+ }
2264
+ resolvePath(relativePath) {
2265
+ return path.join(this.basePath, relativePath);
2266
+ }
2267
+ contentHash(content) {
2268
+ return crypto2.createHash("sha1").update(content).digest("hex");
2269
+ }
2270
+ async readFile(filePath) {
2271
+ const absolutePath = this.resolvePath(filePath);
2272
+ const content = await fs.readFile(absolutePath, "utf-8");
2273
+ return {
717
2274
  content,
718
2275
  sha: this.contentHash(content)
719
2276
  };
@@ -773,7 +2330,7 @@ class FilesystemStorageService {
773
2330
  await fs.rename(absoluteOldPath, absoluteNewPath);
774
2331
  }
775
2332
  }
776
- // ../sync/dist/utils/file-namer.js
2333
+ // ../accounting/dist/utils/file-namer.js
777
2334
  function journalEntryDirName(entry) {
778
2335
  const series = entry.voucher_series_code || "";
779
2336
  const voucher = String(entry.voucher_number || "000").padStart(3, "0");
@@ -807,14 +2364,14 @@ function slugify(text, maxLength = 30) {
807
2364
  function sanitizeOrgName(name) {
808
2365
  return slugify(name, 50);
809
2366
  }
810
- // ../sync/dist/utils/yaml.js
811
- import YAML from "yaml";
812
- function parseYaml(yaml) {
813
- return YAML.parse(yaml);
2367
+ // ../accounting/dist/utils/yaml.js
2368
+ import YAML2 from "yaml";
2369
+ function parseYaml2(yaml) {
2370
+ return YAML2.parse(yaml);
814
2371
  }
815
- function toYaml(data) {
2372
+ function toYaml2(data) {
816
2373
  const cleanData = removeNulls(data);
817
- return YAML.stringify(cleanData, {
2374
+ return YAML2.stringify(cleanData, {
818
2375
  indent: 2,
819
2376
  lineWidth: 120,
820
2377
  minContentWidth: 0,
@@ -840,9 +2397,257 @@ function removeNulls(obj) {
840
2397
  }
841
2398
  return obj;
842
2399
  }
843
- // ../sync/dist/services/document-download.js
2400
+ // ../accounting/dist/utils/git.js
2401
+ import simpleGit from "simple-git";
844
2402
  import * as fs2 from "node:fs/promises";
845
2403
  import * as path2 from "node:path";
2404
+ var DEFAULT_GITIGNORE = `# Environment files with secrets
2405
+ .env
2406
+ .env.*
2407
+
2408
+ # CLI cache
2409
+ .kvitton/
2410
+
2411
+ # OS files
2412
+ .DS_Store
2413
+ Thumbs.db
2414
+
2415
+ # Editor files
2416
+ .vscode/
2417
+ .idea/
2418
+ *.swp
2419
+ `;
2420
+ async function initGitRepo(repoPath) {
2421
+ const git = simpleGit(repoPath);
2422
+ await git.init();
2423
+ await fs2.writeFile(path2.join(repoPath, ".gitignore"), DEFAULT_GITIGNORE);
2424
+ }
2425
+ async function commitAll(repoPath, message) {
2426
+ const git = simpleGit(repoPath);
2427
+ await git.add(".");
2428
+ const status = await git.status();
2429
+ if (status.files.length > 0) {
2430
+ await git.commit(message);
2431
+ return true;
2432
+ }
2433
+ return false;
2434
+ }
2435
+ // ../accounting/dist/utils/templates.js
2436
+ var AGENTS_TEMPLATE = `# AGENTS.md
2437
+
2438
+ This is an accounting data repository for {{COMPANY_NAME}} (Swedish company), storing financial records, journal entries, and documents. It integrates with {{PROVIDER}} (Swedish accounting software) for document management.
2439
+
2440
+ ## Repository Structure
2441
+
2442
+ \`\`\`
2443
+ /
2444
+ ├── accounts.yaml # Swedish chart of accounts (account codes 1000-9999)
2445
+ ├── journal-entries/ # Main accounting data organized by fiscal year
2446
+ │ └── FY-YYYY/ # Fiscal year folders (FY-2014 through FY-2026)
2447
+ │ ├── _fiscal-year.yaml
2448
+ │ └── [SERIES]-[NUM]-[DATE]-[DESC]/
2449
+ │ ├── entry.yaml
2450
+ │ └── *.pdf # Supporting documents
2451
+
2452
+ ├── inbox/ # Raw incoming documents (no entry yet)
2453
+ │ └── [DATE]-[NAME]/
2454
+ │ ├── documents.yaml
2455
+ │ └── *.pdf
2456
+ └── drafts/ # Documents with entries, ready to post
2457
+ └── [DATE]-[NAME]/ # e.g., "2025-12-24-anthropic"
2458
+ ├── documents.yaml
2459
+ ├── entry.yaml
2460
+ └── *.pdf
2461
+ \`\`\`
2462
+
2463
+ ## CLI Commands
2464
+
2465
+ Use the \`npx kvitton-cli\` CLI to interact with this repository:
2466
+
2467
+ \`\`\`bash
2468
+ # Sync data from provider
2469
+ npx kvitton-cli sync-journal # Sync journal entries from accounting provider
2470
+ npx kvitton-cli sync-inbox # Download inbox files from accounting provider
2471
+ npx kvitton-cli company-info # Display company information
2472
+
2473
+ # Create journal entries
2474
+ npx kvitton-cli create-entry --path <inbox-dir-or-file> \\
2475
+ --tax-code <code> --base-account <acc> --balancing-account <acc> \\
2476
+ --series <A-K> --entry-number <num>
2477
+
2478
+ # For raw files (not from inbox), also provide:
2479
+ # --description "Vendor name" --document-date 2025-01-15 --amount 1234.50
2480
+
2481
+ # Manage inbox
2482
+ npx kvitton-cli discard <inbox-directory> # Remove item, mark for deletion from provider
2483
+ npx kvitton-cli parse-pdf -f <file.pdf> # Extract text from PDF (for analysis)
2484
+
2485
+ # Currency conversion
2486
+ npx kvitton-cli convert-currency --amount <num> --currency <code> [--date YYYY-MM-DD]
2487
+ # Converts foreign currency to SEK using Riksbank exchange rates
2488
+ # Caches rates locally for efficiency
2489
+
2490
+ # Generate journal entry lines (alternative to create-entry for adding lines)
2491
+ npx kvitton-cli generate-lines --inbox-dir <path> \\
2492
+ --tax-code <code> --base-amount <num> --base-account <acc> \\
2493
+ --balancing-account <acc> [--memo "description"] [--currency USD]
2494
+
2495
+ # List available tax codes
2496
+ npx kvitton-cli list-tax-codes
2497
+
2498
+ # Update instructions
2499
+ npx kvitton-cli update # Update AGENTS.md to latest version
2500
+ \`\`\`
2501
+
2502
+ **Currency Conversion:** Both \`create-entry\` and \`generate-lines\` automatically convert to SEK when \`--currency\` is different from SEK, using Riksbank exchange rates for the entry date.
2503
+
2504
+ ## Workflow Overview
2505
+
2506
+ \`\`\`
2507
+ inbox/ → (create-entry) → drafts/ → (user confirms) → journal-entries/ → (sync-journal) → Provider
2508
+ \`\`\`
2509
+
2510
+ ## Bookkeeping New Entries
2511
+
2512
+ **Important:** Always ask the user for clarification if anything is unclear about the transaction before creating an entry.
2513
+
2514
+ To create a journal entry from an inbox document:
2515
+
2516
+ 1. **Read the document** - Use \`npx kvitton-cli parse-pdf\` for PDFs to understand the content
2517
+ 2. **Update documents.yaml** - After parsing, populate missing metadata in \`documents.yaml\`:
2518
+ - \`documentDate\`: Invoice/receipt date (YYYY-MM-DD)
2519
+ - \`description\`: Vendor or document name
2520
+ - \`totalAmount\`: With \`amount\` (string) and \`currency\` (e.g., EUR, SEK)
2521
+ 3. **Look for similar entries** - Search \`journal-entries/\` and \`drafts/\` for entries from the same vendor or similar transaction types to learn which accounts and tax codes were used previously
2522
+ 4. **Check accounts** - Review \`accounts.yaml\` for appropriate expense/revenue accounts
2523
+ 5. **Determine tax code** - Based on vendor location and transaction type (see Tax Codes below). Explain your reasoning and describe the tax code meaning to the user
2524
+ 6. **Ask for confirmation** - Before creating, confirm with the user: vendor, amount, accounts (with names), and tax code (with description)
2525
+ 7. **Create entry** - Run \`npx kvitton-cli create-entry\` with the appropriate parameters
2526
+ 8. **Files moved to drafts** - The inbox item is automatically moved to \`drafts/\` with \`entry.yaml\`
2527
+
2528
+ To discard unwanted inbox items:
2529
+ \`\`\`bash
2530
+ npx kvitton-cli discard inbox/2025-01-15-spam-document
2531
+ \`\`\`
2532
+ This removes the directory and marks the item for deletion from the provider on next sync.
2533
+
2534
+ ## Posting Entries
2535
+
2536
+ **Important:** Moving entries to \`journal-entries/\` posts them to the real ledger. Always confirm with the user before posting.
2537
+
2538
+ When the user confirms draft entries are ready to post:
2539
+
2540
+ 1. **Determine entry number** - Check existing entries in \`journal-entries/FY-YYYY/\` for the series to find the next available number
2541
+ 2. **Move to journal-entries** - Rename the draft directory to the proper format:
2542
+ \`\`\`
2543
+ drafts/2025-01-15-anthropic/
2544
+ → journal-entries/FY-2025/A-042-2025-01-15-anthropic/
2545
+ \`\`\`
2546
+ Format: \`{SERIES}-{NUM}-{DATE}-{DESC}/\`
2547
+ 3. **Update entry.yaml** - Ensure \`entryNumber\` matches the directory and \`status\` is set appropriately
2548
+ 4. **Sync to provider** - Run \`npx kvitton-cli sync-journal\` to post the entries to {{PROVIDER}}
2549
+
2550
+ ## Tax Codes
2551
+
2552
+ ### Common Tax Code Patterns
2553
+
2554
+ | Scenario | Tax Code | Accounts |
2555
+ |----------|----------|----------|
2556
+ | Domestic purchase 25% | \`SE_VAT_25_PURCHASE_DOMESTIC\` | Expense + 2641 + 2440 |
2557
+ | Domestic purchase 12% | \`SE_VAT_12_PURCHASE_DOMESTIC\` | Expense + 2641 + 2440 |
2558
+ | Non-EU services (US SaaS) | \`SE_VAT_25_PURCHASE_NON_EU_SERVICES_RC\` | 5422 + 2645 + 2614 + 2820 |
2559
+ | EU services | \`SE_VAT_25_PURCHASE_EU_SERVICES_RC\` | Expense + 2645 + 2614 + 2440 |
2560
+ | Exempt purchase | \`SE_VAT_EXEMPT_PURCHASE\` | Expense + balancing |
2561
+
2562
+ **Notes:**
2563
+ - \`RC\` = Reverse Charge (buyer reports VAT instead of seller)
2564
+ - Use \`--list-tax-codes\` to see all available codes with descriptions
2565
+ - Account 2641 = Incoming VAT (domestic), 2645 = Incoming VAT (reverse charge)
2566
+ - Account 2614 = Outgoing VAT (reverse charge)
2567
+
2568
+ ## Entities
2569
+
2570
+ ### Journal Entry (entry.yaml)
2571
+
2572
+ \`\`\`yaml
2573
+ series: A # A=Admin, B=Customer invoices, C=Customer payments,
2574
+ # D=Supplier invoices, E=Supplier payments, K=Salary
2575
+ entryNumber: 1
2576
+ entryDate: 2024-01-03
2577
+ description: Transaction description
2578
+ status: POSTED # or DRAFT
2579
+ currency: SEK
2580
+ lines:
2581
+ - account: "1930" # Account code from accounts.yaml
2582
+ debit: 1000.00 # or credit:
2583
+ memo: Account description
2584
+ \`\`\`
2585
+
2586
+ ### Document Metadata (documents.yaml)
2587
+
2588
+ One entry can have multiple supporting documents (e.g., invoice + receipt, contract + amendment).
2589
+
2590
+ \`\`\`yaml
2591
+ - fileName: invoice.pdf
2592
+ mimeType: application/pdf
2593
+ sourceIntegration: {{PROVIDER_LOWER}}
2594
+ sourceId: UUID-1
2595
+ description: Vendor invoice
2596
+ documentDate: 2024-01-15
2597
+ totalAmount:
2598
+ amount: "1250.00"
2599
+ currency: SEK
2600
+ - fileName: receipt.pdf
2601
+ mimeType: application/pdf
2602
+ sourceIntegration: {{PROVIDER_LOWER}}
2603
+ sourceId: UUID-2
2604
+ description: Payment receipt
2605
+ documentDate: 2024-01-16
2606
+ \`\`\`
2607
+
2608
+ ### Fiscal Year (_fiscal-year.yaml)
2609
+
2610
+ \`\`\`yaml
2611
+ name: FY 2024
2612
+ startDate: 2024-01-01
2613
+ endDate: 2024-12-31
2614
+ status: OPEN
2615
+ externalId: "12" # Provider reference
2616
+ \`\`\`
2617
+
2618
+ ## Account Codes
2619
+
2620
+ Swedish BAS account codes are organized by ranges:
2621
+
2622
+ - **1xxx** - Assets (Tillgångar)
2623
+ - **2xxx** - Liabilities & Equity (Skulder och eget kapital)
2624
+ - **3xxx** - Revenue (Intäkter)
2625
+ - **4xxx** - Cost of goods sold (Inköp)
2626
+ - **5xxx-6xxx** - Operating expenses (Kostnader)
2627
+ - **7xxx** - Personnel costs (Personalkostnader)
2628
+ - **8xxx** - Financial items (Finansiella poster)
2629
+ - **9xxx** - Year-end allocations (Bokslutsdispositioner)
2630
+
2631
+ Common accounts:
2632
+ - \`1930\` - Business bank account
2633
+ - \`2440\` - Supplier payables
2634
+ - \`2610\` - Outgoing VAT 25%
2635
+ - \`2640\` - Incoming VAT
2636
+ - \`2820\` - Short-term liabilities (credit card, etc.)
2637
+ - \`4000\` - Cost of goods sold
2638
+ - \`5420\` - Software and IT services
2639
+ - \`6570\` - Bank charges
2640
+ `;
2641
+ function getAgentsTemplate() {
2642
+ return AGENTS_TEMPLATE;
2643
+ }
2644
+ function renderAgentsTemplate(options) {
2645
+ const template = getAgentsTemplate();
2646
+ return template.replace(/\{\{COMPANY_NAME\}\}/g, options.companyName).replace(/\{\{PROVIDER\}\}/g, options.provider).replace(/\{\{PROVIDER_LOWER\}\}/g, options.provider.toLowerCase());
2647
+ }
2648
+ // ../accounting/dist/services/document-download.js
2649
+ import * as fs3 from "node:fs/promises";
2650
+ import * as path3 from "node:path";
846
2651
  function slugify2(text, maxLength = 40) {
847
2652
  return text.toLowerCase().slice(0, maxLength).replace(/[^a-z0-9\s-]/g, "").replace(/\s+/g, "-").replace(/-+/g, "-").replace(/^-|-$/g, "");
848
2653
  }
@@ -865,12 +2670,12 @@ async function downloadFilesForEntry(options) {
865
2670
  let existingDocs = [];
866
2671
  try {
867
2672
  const { content } = await storage.readFile(documentsPath);
868
- existingDocs = parseYaml(content) || [];
2673
+ existingDocs = parseYaml2(content) || [];
869
2674
  } catch {}
870
2675
  const downloadedSourceIds = new Set(existingDocs.filter((d) => d.sourceIntegration === sourceIntegration).map((d) => d.sourceId));
871
2676
  let filesDownloaded = 0;
872
- const absoluteDir = path2.join(repoPath, entryDir);
873
- await fs2.mkdir(absoluteDir, { recursive: true });
2677
+ const absoluteDir = path3.join(repoPath, entryDir);
2678
+ await fs3.mkdir(absoluteDir, { recursive: true });
874
2679
  for (const file of linkedFiles) {
875
2680
  if (downloadedSourceIds.has(file.id)) {
876
2681
  continue;
@@ -886,9 +2691,9 @@ async function downloadFilesForEntry(options) {
886
2691
  filename = `${baseName}-${counter}${ext}`;
887
2692
  counter++;
888
2693
  }
889
- const filePath = path2.join(absoluteDir, filename);
2694
+ const filePath = path3.join(absoluteDir, filename);
890
2695
  const buffer = Buffer.from(result.data);
891
- await fs2.writeFile(filePath, buffer);
2696
+ await fs3.writeFile(filePath, buffer);
892
2697
  existingDocs.push({
893
2698
  fileName: filename,
894
2699
  mimeType: file.contentType || result.contentType,
@@ -902,55 +2707,33 @@ async function downloadFilesForEntry(options) {
902
2707
  }
903
2708
  }
904
2709
  if (filesDownloaded > 0) {
905
- await storage.writeFile(documentsPath, toYaml(existingDocs));
2710
+ await storage.writeFile(documentsPath, toYaml2(existingDocs));
906
2711
  }
907
2712
  return filesDownloaded;
908
2713
  }
909
- // src/lib/git.ts
910
- import simpleGit from "simple-git";
911
- import * as fs3 from "node:fs/promises";
912
- import * as path3 from "node:path";
913
- var GITIGNORE = `# Environment files with secrets
914
- .env
915
- .env.*
916
-
917
- # CLI cache
918
- .kvitton/
919
-
920
- # OS files
921
- .DS_Store
922
- Thumbs.db
923
-
924
- # Editor files
925
- .vscode/
926
- .idea/
927
- *.swp
928
- `;
929
- async function initGitRepo(repoPath) {
930
- const git = simpleGit(repoPath);
931
- await git.init();
932
- await fs3.writeFile(path3.join(repoPath, ".gitignore"), GITIGNORE);
933
- }
934
- async function commitAll(repoPath, message) {
935
- const git = simpleGit(repoPath);
936
- await git.add(".");
937
- const status = await git.status();
938
- if (status.files.length > 0) {
939
- await git.commit(message);
2714
+ // ../accounting/dist/services/fortnox-journal.js
2715
+ async function syncFortnoxChartOfAccounts(client2, storage) {
2716
+ const allAccounts = [];
2717
+ let hasMore = true;
2718
+ let page = 1;
2719
+ while (hasMore) {
2720
+ const response = await client2.getAccounts({ page, pageSize: 500 });
2721
+ for (const account of response.data) {
2722
+ allAccounts.push({
2723
+ code: account.Number.toString(),
2724
+ name: account.Description,
2725
+ description: account.Description
2726
+ });
2727
+ }
2728
+ hasMore = response.pagination.hasNextPage;
2729
+ page++;
940
2730
  }
2731
+ allAccounts.sort((a, b) => parseInt(a.code) - parseInt(b.code));
2732
+ const yamlContent = toYaml2({ accounts: allAccounts });
2733
+ await storage.writeFile("accounts.yaml", yamlContent);
2734
+ return { accountsCount: allAccounts.length };
941
2735
  }
942
-
943
- // src/lib/env.ts
944
- import * as fs4 from "node:fs/promises";
945
- import * as path4 from "node:path";
946
- async function createEnvFile(repoPath, variables) {
947
- const lines = Object.entries(variables).map(([key, value]) => `${key}=${value}`).join(`
948
- `);
949
- await fs4.writeFile(path4.join(repoPath, ".env"), `${lines}
950
- `);
951
- }
952
-
953
- // src/sync/bokio-sync.ts
2736
+ // ../accounting/dist/services/bokio-journal.js
954
2737
  function createBokioDownloader(client2) {
955
2738
  return {
956
2739
  async getFilesForEntry(journalEntryId) {
@@ -968,17 +2751,32 @@ function createBokioDownloader(client2) {
968
2751
  }
969
2752
  };
970
2753
  }
971
- async function syncJournalEntries2(client2, repoPath, options, onProgress) {
972
- const storage2 = new FilesystemStorageService(repoPath);
2754
+ function findFiscalYear(date, fiscalYears) {
2755
+ return fiscalYears.find((fy) => date >= fy.startDate && date <= fy.endDate);
2756
+ }
2757
+ async function syncBokioJournalEntries(client2, storage, options = {}) {
2758
+ const { downloadFiles = false, targetYear, onProgress = () => {} } = options;
973
2759
  const fiscalYearsResponse = await client2.getFiscalYears();
974
- const fiscalYears = fiscalYearsResponse.data;
2760
+ const allFiscalYears = fiscalYearsResponse.data;
2761
+ const fiscalYears = targetYear ? allFiscalYears.filter((fy) => parseInt(fy.startDate.slice(0, 4), 10) === targetYear) : allFiscalYears;
2762
+ if (targetYear && fiscalYears.length === 0) {
2763
+ return {
2764
+ entriesCount: 0,
2765
+ newEntries: 0,
2766
+ existingEntries: 0,
2767
+ fiscalYearsCount: 0,
2768
+ entriesWithFilesDownloaded: 0
2769
+ };
2770
+ }
975
2771
  const firstPage = await client2.getJournalEntries({ page: 1, pageSize: 1 });
976
- const totalEntries = firstPage.pagination.totalItems;
977
- onProgress({ current: 0, total: totalEntries, message: "Starting sync..." });
978
- if (totalEntries === 0) {
979
- await writeFiscalYearsMetadata(storage2, fiscalYears);
2772
+ const totalFromApi = firstPage.pagination.totalItems;
2773
+ onProgress({ current: 0, total: totalFromApi, message: "Starting sync..." });
2774
+ if (totalFromApi === 0) {
2775
+ await writeBokioFiscalYearsMetadata(storage, fiscalYears);
980
2776
  return {
981
2777
  entriesCount: 0,
2778
+ newEntries: 0,
2779
+ existingEntries: 0,
982
2780
  fiscalYearsCount: fiscalYears.length,
983
2781
  entriesWithFilesDownloaded: 0
984
2782
  };
@@ -989,23 +2787,42 @@ async function syncJournalEntries2(client2, repoPath, options, onProgress) {
989
2787
  while (true) {
990
2788
  const response = await client2.getJournalEntries({ page, pageSize });
991
2789
  allEntries.push(...response.data);
992
- onProgress({ current: allEntries.length, total: totalEntries });
2790
+ onProgress({ current: allEntries.length, total: totalFromApi });
993
2791
  if (!response.pagination.hasNextPage)
994
2792
  break;
995
2793
  page++;
996
2794
  }
997
- let entriesWithFilesDownloaded = 0;
998
- const downloader = createBokioDownloader(client2);
999
- for (const entry of allEntries) {
1000
- const fiscalYear = findFiscalYear(entry.date, fiscalYears);
2795
+ const entriesToSync = targetYear ? allEntries.filter((entry) => {
2796
+ const fy = findFiscalYear(entry.date, fiscalYears);
2797
+ return fy !== undefined;
2798
+ }) : allEntries;
2799
+ let newEntries = 0;
2800
+ let existingEntries = 0;
2801
+ const entryDirs = new Map;
2802
+ for (const entry of entriesToSync) {
2803
+ const fiscalYear = findFiscalYear(entry.date, allFiscalYears);
1001
2804
  if (!fiscalYear)
1002
2805
  continue;
1003
2806
  const fyYear = parseInt(fiscalYear.startDate.slice(0, 4), 10);
1004
- const entryDir = await writeJournalEntry(storage2, fyYear, entry);
1005
- if (options.downloadFiles !== false && entryDir) {
2807
+ const result = await writeBokioJournalEntry(storage, fyYear, entry);
2808
+ if (result.isNew) {
2809
+ newEntries++;
2810
+ } else {
2811
+ existingEntries++;
2812
+ }
2813
+ entryDirs.set(entry.id, result.entryDir);
2814
+ }
2815
+ await writeBokioFiscalYearsMetadata(storage, fiscalYears);
2816
+ let entriesWithFilesDownloaded = 0;
2817
+ if (downloadFiles) {
2818
+ const downloader = createBokioDownloader(client2);
2819
+ for (const entry of entriesToSync) {
2820
+ const entryDir = entryDirs.get(entry.id);
2821
+ if (!entryDir)
2822
+ continue;
1006
2823
  const filesDownloaded = await downloadFilesForEntry({
1007
- storage: storage2,
1008
- repoPath,
2824
+ storage,
2825
+ repoPath: "",
1009
2826
  entryDir,
1010
2827
  journalEntryId: entry.id,
1011
2828
  downloader,
@@ -1016,17 +2833,15 @@ async function syncJournalEntries2(client2, repoPath, options, onProgress) {
1016
2833
  }
1017
2834
  }
1018
2835
  }
1019
- await writeFiscalYearsMetadata(storage2, fiscalYears);
1020
2836
  return {
1021
- entriesCount: allEntries.length,
2837
+ entriesCount: entriesToSync.length,
2838
+ newEntries,
2839
+ existingEntries,
1022
2840
  fiscalYearsCount: fiscalYears.length,
1023
2841
  entriesWithFilesDownloaded
1024
2842
  };
1025
2843
  }
1026
- function findFiscalYear(date, fiscalYears) {
1027
- return fiscalYears.find((fy) => date >= fy.startDate && date <= fy.endDate);
1028
- }
1029
- async function writeJournalEntry(storage2, fyYear, entry) {
2844
+ async function writeBokioJournalEntry(storage, fyYear, entry) {
1030
2845
  const journalEntry = mapBokioEntryToJournalEntry({
1031
2846
  id: entry.id,
1032
2847
  journalEntryNumber: entry.journalEntryNumber,
@@ -1039,11 +2854,16 @@ async function writeJournalEntry(storage2, fyYear, entry) {
1039
2854
  }))
1040
2855
  });
1041
2856
  const entryPath = journalEntryPath(fyYear, journalEntry.series ?? null, journalEntry.entryNumber, journalEntry.entryDate, journalEntry.description);
1042
- const yamlContent = toYaml(journalEntry);
1043
- await storage2.writeFile(entryPath, yamlContent);
1044
- return journalEntryDirFromPath(entryPath);
2857
+ const entryDir = journalEntryDirFromPath(entryPath);
2858
+ const exists = await storage.exists(entryPath);
2859
+ if (exists) {
2860
+ return { isNew: false, entryDir };
2861
+ }
2862
+ const yamlContent = toYaml2(journalEntry);
2863
+ await storage.writeFile(entryPath, yamlContent);
2864
+ return { isNew: true, entryDir };
1045
2865
  }
1046
- async function writeFiscalYearsMetadata(storage2, fiscalYears) {
2866
+ async function writeBokioFiscalYearsMetadata(storage, fiscalYears) {
1047
2867
  for (const fy of fiscalYears) {
1048
2868
  const fyDir = fiscalYearDirName({ start_date: fy.startDate });
1049
2869
  const metadataPath = `journal-entries/${fyDir}/_fiscal-year.yaml`;
@@ -1053,38 +2873,1235 @@ async function writeFiscalYearsMetadata(storage2, fiscalYears) {
1053
2873
  endDate: fy.endDate,
1054
2874
  status: fy.status
1055
2875
  };
1056
- const yamlContent = toYaml(metadata);
1057
- await storage2.writeFile(metadataPath, yamlContent);
2876
+ const yamlContent = toYaml2(metadata);
2877
+ await storage.writeFile(metadataPath, yamlContent);
1058
2878
  }
1059
2879
  }
1060
- async function syncChartOfAccounts(client2, repoPath) {
1061
- const storage2 = new FilesystemStorageService(repoPath);
2880
+ async function syncBokioChartOfAccounts(client2, storage) {
1062
2881
  const bokioAccounts = await client2.getChartOfAccounts();
1063
2882
  const accounts = [...bokioAccounts].sort((a, b) => a.account - b.account).map((account) => ({
1064
2883
  code: account.account.toString(),
1065
2884
  name: account.name,
1066
2885
  description: account.name
1067
2886
  }));
1068
- const yamlContent = toYaml({ accounts });
1069
- await storage2.writeFile("accounts.yaml", yamlContent);
2887
+ const yamlContent = toYaml2({ accounts });
2888
+ await storage.writeFile("accounts.yaml", yamlContent);
1070
2889
  return { accountsCount: accounts.length };
1071
2890
  }
2891
+ // ../shared/dist/accounting/tax-codes.js
2892
+ var SE_TAX_CODES2 = [
2893
+ {
2894
+ code: "SE_VAT_25_PURCHASE_DOMESTIC",
2895
+ description: "Inköp i Sverige, moms 25 %",
2896
+ kind: "VAT",
2897
+ direction: "PURCHASE",
2898
+ territory: "DOMESTIC",
2899
+ ratePercent: 25,
2900
+ isReverseCharge: false,
2901
+ isZeroRated: false,
2902
+ isExempt: false,
2903
+ posting: {
2904
+ inputVatAccountCode: "2641"
2905
+ }
2906
+ },
2907
+ {
2908
+ code: "SE_VAT_12_PURCHASE_DOMESTIC",
2909
+ description: "Inköp i Sverige, moms 12 %",
2910
+ kind: "VAT",
2911
+ direction: "PURCHASE",
2912
+ territory: "DOMESTIC",
2913
+ ratePercent: 12,
2914
+ isReverseCharge: false,
2915
+ isZeroRated: false,
2916
+ isExempt: false,
2917
+ posting: {
2918
+ inputVatAccountCode: "2641"
2919
+ }
2920
+ },
2921
+ {
2922
+ code: "SE_VAT_6_PURCHASE_DOMESTIC",
2923
+ description: "Inköp i Sverige, moms 6 %",
2924
+ kind: "VAT",
2925
+ direction: "PURCHASE",
2926
+ territory: "DOMESTIC",
2927
+ ratePercent: 6,
2928
+ isReverseCharge: false,
2929
+ isZeroRated: false,
2930
+ isExempt: false,
2931
+ posting: {
2932
+ inputVatAccountCode: "2641"
2933
+ }
2934
+ },
2935
+ {
2936
+ code: "SE_VAT_0_PURCHASE_DOMESTIC",
2937
+ description: "Inköp i Sverige, 0 % moms (momspliktigt men 0 %)",
2938
+ kind: "VAT",
2939
+ direction: "PURCHASE",
2940
+ territory: "DOMESTIC",
2941
+ ratePercent: 0,
2942
+ isReverseCharge: false,
2943
+ isZeroRated: true,
2944
+ isExempt: false,
2945
+ posting: {}
2946
+ },
2947
+ {
2948
+ code: "SE_VAT_EXEMPT_PURCHASE",
2949
+ description: "Momsfria inköp (utanför momsens tillämpningsområde)",
2950
+ kind: "VAT",
2951
+ direction: "PURCHASE",
2952
+ territory: "DOMESTIC",
2953
+ ratePercent: 0,
2954
+ isReverseCharge: false,
2955
+ isZeroRated: false,
2956
+ isExempt: true,
2957
+ posting: {}
2958
+ },
2959
+ {
2960
+ code: "SE_VAT_25_SALE_DOMESTIC",
2961
+ description: "Försäljning i Sverige, moms 25 %",
2962
+ kind: "VAT",
2963
+ direction: "SALE",
2964
+ territory: "DOMESTIC",
2965
+ ratePercent: 25,
2966
+ isReverseCharge: false,
2967
+ isZeroRated: false,
2968
+ isExempt: false,
2969
+ posting: {
2970
+ outputVatAccountCode: "2611"
2971
+ }
2972
+ },
2973
+ {
2974
+ code: "SE_VAT_12_SALE_DOMESTIC",
2975
+ description: "Försäljning i Sverige, moms 12 %",
2976
+ kind: "VAT",
2977
+ direction: "SALE",
2978
+ territory: "DOMESTIC",
2979
+ ratePercent: 12,
2980
+ isReverseCharge: false,
2981
+ isZeroRated: false,
2982
+ isExempt: false,
2983
+ posting: {
2984
+ outputVatAccountCode: "2611"
2985
+ }
2986
+ },
2987
+ {
2988
+ code: "SE_VAT_6_SALE_DOMESTIC",
2989
+ description: "Försäljning i Sverige, moms 6 %",
2990
+ kind: "VAT",
2991
+ direction: "SALE",
2992
+ territory: "DOMESTIC",
2993
+ ratePercent: 6,
2994
+ isReverseCharge: false,
2995
+ isZeroRated: false,
2996
+ isExempt: false,
2997
+ posting: {
2998
+ outputVatAccountCode: "2611"
2999
+ }
3000
+ },
3001
+ {
3002
+ code: "SE_VAT_0_SALE_DOMESTIC",
3003
+ description: "Momsfri försäljning i Sverige (undantagen/0 %)",
3004
+ kind: "VAT",
3005
+ direction: "SALE",
3006
+ territory: "DOMESTIC",
3007
+ ratePercent: 0,
3008
+ isReverseCharge: false,
3009
+ isZeroRated: false,
3010
+ isExempt: true,
3011
+ posting: {}
3012
+ },
3013
+ {
3014
+ code: "SE_VAT_25_PURCHASE_EU_GOODS_RC",
3015
+ description: "Inköp av varor från annat EU-land, 25 %, omvänd skattskyldighet",
3016
+ kind: "VAT",
3017
+ direction: "PURCHASE",
3018
+ territory: "EU",
3019
+ ratePercent: 25,
3020
+ isReverseCharge: true,
3021
+ isZeroRated: false,
3022
+ isExempt: false,
3023
+ posting: {
3024
+ reverseChargeOutputAccountCode: "2614",
3025
+ reverseChargeInputAccountCode: "2645"
3026
+ }
3027
+ },
3028
+ {
3029
+ code: "SE_VAT_25_PURCHASE_EU_SERVICES_RC",
3030
+ description: "Inköp av tjänster från annat EU-land, 25 %, omvänd skattskyldighet",
3031
+ kind: "VAT",
3032
+ direction: "PURCHASE",
3033
+ territory: "EU",
3034
+ ratePercent: 25,
3035
+ isReverseCharge: true,
3036
+ isZeroRated: false,
3037
+ isExempt: false,
3038
+ posting: {
3039
+ reverseChargeOutputAccountCode: "2614",
3040
+ reverseChargeInputAccountCode: "2645"
3041
+ }
3042
+ },
3043
+ {
3044
+ code: "SE_VAT_25_PURCHASE_NON_EU_SERVICES_RC",
3045
+ description: "Inköp av tjänster från land utanför EU, 25 %, omvänd skattskyldighet",
3046
+ kind: "VAT",
3047
+ direction: "PURCHASE",
3048
+ territory: "OUTSIDE_EU",
3049
+ ratePercent: 25,
3050
+ isReverseCharge: true,
3051
+ isZeroRated: false,
3052
+ isExempt: false,
3053
+ posting: {
3054
+ reverseChargeOutputAccountCode: "2614",
3055
+ reverseChargeInputAccountCode: "2645"
3056
+ }
3057
+ },
3058
+ {
3059
+ code: "SE_VAT_0_SALE_EU_GOODS_B2B",
3060
+ description: "Varuförsäljning till momsregistrerad kund i annat EU-land (0 %)",
3061
+ kind: "VAT",
3062
+ direction: "SALE",
3063
+ territory: "EU",
3064
+ ratePercent: 0,
3065
+ isReverseCharge: false,
3066
+ isZeroRated: true,
3067
+ isExempt: false,
3068
+ posting: {}
3069
+ },
3070
+ {
3071
+ code: "SE_VAT_0_SALE_EU_SERVICES_B2B",
3072
+ description: "Tjänsteförsäljning till momsregistrerad kund i annat EU-land (0 %)",
3073
+ kind: "VAT",
3074
+ direction: "SALE",
3075
+ territory: "EU",
3076
+ ratePercent: 0,
3077
+ isReverseCharge: false,
3078
+ isZeroRated: true,
3079
+ isExempt: false,
3080
+ posting: {}
3081
+ },
3082
+ {
3083
+ code: "SE_VAT_RC_SALE_DOMESTIC_CONSTRUCTION",
3084
+ description: "Försäljning av byggtjänster med omvänd skattskyldighet (ingen moms på fakturan)",
3085
+ kind: "VAT",
3086
+ direction: "SALE",
3087
+ territory: "DOMESTIC",
3088
+ ratePercent: 0,
3089
+ isReverseCharge: true,
3090
+ isZeroRated: false,
3091
+ isExempt: false,
3092
+ posting: {}
3093
+ },
3094
+ {
3095
+ code: "SE_VAT_RC_PURCHASE_DOMESTIC_CONSTRUCTION",
3096
+ description: "Inköp av byggtjänster med omvänd skattskyldighet (du beräknar svensk moms)",
3097
+ kind: "VAT",
3098
+ direction: "PURCHASE",
3099
+ territory: "DOMESTIC",
3100
+ ratePercent: 25,
3101
+ isReverseCharge: true,
3102
+ isZeroRated: false,
3103
+ isExempt: false,
3104
+ posting: {
3105
+ reverseChargeOutputAccountCode: "2617",
3106
+ reverseChargeInputAccountCode: "2647"
3107
+ }
3108
+ }
3109
+ ];
3110
+ var TAX_CODE_MAP2 = new Map(SE_TAX_CODES2.map((code) => [code.code, code]));
3111
+ // ../shared/dist/supabase/client.js
3112
+ import { createClient } from "@supabase/supabase-js";
3113
+ // ../shared/dist/auth/fortnox-login.js
3114
+ import * as http from "node:http";
3115
+
3116
+ // ../../node_modules/.bun/open@10.2.0/node_modules/open/index.js
3117
+ import process7 from "node:process";
3118
+ import { Buffer as Buffer2 } from "node:buffer";
3119
+ import path4 from "node:path";
3120
+ import { fileURLToPath } from "node:url";
3121
+ import { promisify as promisify5 } from "node:util";
3122
+ import childProcess from "node:child_process";
3123
+ import fs8, { constants as fsConstants2 } from "node:fs/promises";
3124
+
3125
+ // ../../node_modules/.bun/wsl-utils@0.1.0/node_modules/wsl-utils/index.js
3126
+ import process3 from "node:process";
3127
+ import fs7, { constants as fsConstants } from "node:fs/promises";
3128
+
3129
+ // ../../node_modules/.bun/is-wsl@3.1.0/node_modules/is-wsl/index.js
3130
+ import process2 from "node:process";
3131
+ import os from "node:os";
3132
+ import fs6 from "node:fs";
3133
+
3134
+ // ../../node_modules/.bun/is-inside-container@1.0.0/node_modules/is-inside-container/index.js
3135
+ import fs5 from "node:fs";
3136
+
3137
+ // ../../node_modules/.bun/is-docker@3.0.0/node_modules/is-docker/index.js
3138
+ import fs4 from "node:fs";
3139
+ var isDockerCached;
3140
+ function hasDockerEnv() {
3141
+ try {
3142
+ fs4.statSync("/.dockerenv");
3143
+ return true;
3144
+ } catch {
3145
+ return false;
3146
+ }
3147
+ }
3148
+ function hasDockerCGroup() {
3149
+ try {
3150
+ return fs4.readFileSync("/proc/self/cgroup", "utf8").includes("docker");
3151
+ } catch {
3152
+ return false;
3153
+ }
3154
+ }
3155
+ function isDocker() {
3156
+ if (isDockerCached === undefined) {
3157
+ isDockerCached = hasDockerEnv() || hasDockerCGroup();
3158
+ }
3159
+ return isDockerCached;
3160
+ }
3161
+
3162
+ // ../../node_modules/.bun/is-inside-container@1.0.0/node_modules/is-inside-container/index.js
3163
+ var cachedResult;
3164
+ var hasContainerEnv = () => {
3165
+ try {
3166
+ fs5.statSync("/run/.containerenv");
3167
+ return true;
3168
+ } catch {
3169
+ return false;
3170
+ }
3171
+ };
3172
+ function isInsideContainer() {
3173
+ if (cachedResult === undefined) {
3174
+ cachedResult = hasContainerEnv() || isDocker();
3175
+ }
3176
+ return cachedResult;
3177
+ }
3178
+
3179
+ // ../../node_modules/.bun/is-wsl@3.1.0/node_modules/is-wsl/index.js
3180
+ var isWsl = () => {
3181
+ if (process2.platform !== "linux") {
3182
+ return false;
3183
+ }
3184
+ if (os.release().toLowerCase().includes("microsoft")) {
3185
+ if (isInsideContainer()) {
3186
+ return false;
3187
+ }
3188
+ return true;
3189
+ }
3190
+ try {
3191
+ return fs6.readFileSync("/proc/version", "utf8").toLowerCase().includes("microsoft") ? !isInsideContainer() : false;
3192
+ } catch {
3193
+ return false;
3194
+ }
3195
+ };
3196
+ var is_wsl_default = process2.env.__IS_WSL_TEST__ ? isWsl : isWsl();
3197
+
3198
+ // ../../node_modules/.bun/wsl-utils@0.1.0/node_modules/wsl-utils/index.js
3199
+ var wslDrivesMountPoint = (() => {
3200
+ const defaultMountPoint = "/mnt/";
3201
+ let mountPoint;
3202
+ return async function() {
3203
+ if (mountPoint) {
3204
+ return mountPoint;
3205
+ }
3206
+ const configFilePath = "/etc/wsl.conf";
3207
+ let isConfigFileExists = false;
3208
+ try {
3209
+ await fs7.access(configFilePath, fsConstants.F_OK);
3210
+ isConfigFileExists = true;
3211
+ } catch {}
3212
+ if (!isConfigFileExists) {
3213
+ return defaultMountPoint;
3214
+ }
3215
+ const configContent = await fs7.readFile(configFilePath, { encoding: "utf8" });
3216
+ const configMountPoint = /(?<!#.*)root\s*=\s*(?<mountPoint>.*)/g.exec(configContent);
3217
+ if (!configMountPoint) {
3218
+ return defaultMountPoint;
3219
+ }
3220
+ mountPoint = configMountPoint.groups.mountPoint.trim();
3221
+ mountPoint = mountPoint.endsWith("/") ? mountPoint : `${mountPoint}/`;
3222
+ return mountPoint;
3223
+ };
3224
+ })();
3225
+ var powerShellPathFromWsl = async () => {
3226
+ const mountPoint = await wslDrivesMountPoint();
3227
+ return `${mountPoint}c/Windows/System32/WindowsPowerShell/v1.0/powershell.exe`;
3228
+ };
3229
+ var powerShellPath = async () => {
3230
+ if (is_wsl_default) {
3231
+ return powerShellPathFromWsl();
3232
+ }
3233
+ return `${process3.env.SYSTEMROOT || process3.env.windir || String.raw`C:\Windows`}\\System32\\WindowsPowerShell\\v1.0\\powershell.exe`;
3234
+ };
3235
+
3236
+ // ../../node_modules/.bun/define-lazy-prop@3.0.0/node_modules/define-lazy-prop/index.js
3237
+ function defineLazyProperty(object, propertyName, valueGetter) {
3238
+ const define = (value) => Object.defineProperty(object, propertyName, { value, enumerable: true, writable: true });
3239
+ Object.defineProperty(object, propertyName, {
3240
+ configurable: true,
3241
+ enumerable: true,
3242
+ get() {
3243
+ const result = valueGetter();
3244
+ define(result);
3245
+ return result;
3246
+ },
3247
+ set(value) {
3248
+ define(value);
3249
+ }
3250
+ });
3251
+ return object;
3252
+ }
3253
+
3254
+ // ../../node_modules/.bun/default-browser@5.4.0/node_modules/default-browser/index.js
3255
+ import { promisify as promisify4 } from "node:util";
3256
+ import process6 from "node:process";
3257
+ import { execFile as execFile4 } from "node:child_process";
3258
+
3259
+ // ../../node_modules/.bun/default-browser-id@5.0.1/node_modules/default-browser-id/index.js
3260
+ import { promisify } from "node:util";
3261
+ import process4 from "node:process";
3262
+ import { execFile } from "node:child_process";
3263
+ var execFileAsync = promisify(execFile);
3264
+ async function defaultBrowserId() {
3265
+ if (process4.platform !== "darwin") {
3266
+ throw new Error("macOS only");
3267
+ }
3268
+ const { stdout } = await execFileAsync("defaults", ["read", "com.apple.LaunchServices/com.apple.launchservices.secure", "LSHandlers"]);
3269
+ const match = /LSHandlerRoleAll = "(?!-)(?<id>[^"]+?)";\s+?LSHandlerURLScheme = (?:http|https);/.exec(stdout);
3270
+ const browserId = match?.groups.id ?? "com.apple.Safari";
3271
+ if (browserId === "com.apple.safari") {
3272
+ return "com.apple.Safari";
3273
+ }
3274
+ return browserId;
3275
+ }
3276
+
3277
+ // ../../node_modules/.bun/run-applescript@7.1.0/node_modules/run-applescript/index.js
3278
+ import process5 from "node:process";
3279
+ import { promisify as promisify2 } from "node:util";
3280
+ import { execFile as execFile2, execFileSync } from "node:child_process";
3281
+ var execFileAsync2 = promisify2(execFile2);
3282
+ async function runAppleScript(script, { humanReadableOutput = true, signal } = {}) {
3283
+ if (process5.platform !== "darwin") {
3284
+ throw new Error("macOS only");
3285
+ }
3286
+ const outputArguments = humanReadableOutput ? [] : ["-ss"];
3287
+ const execOptions = {};
3288
+ if (signal) {
3289
+ execOptions.signal = signal;
3290
+ }
3291
+ const { stdout } = await execFileAsync2("osascript", ["-e", script, outputArguments], execOptions);
3292
+ return stdout.trim();
3293
+ }
3294
+
3295
+ // ../../node_modules/.bun/bundle-name@4.1.0/node_modules/bundle-name/index.js
3296
+ async function bundleName(bundleId) {
3297
+ return runAppleScript(`tell application "Finder" to set app_path to application file id "${bundleId}" as string
3298
+ tell application "System Events" to get value of property list item "CFBundleName" of property list file (app_path & ":Contents:Info.plist")`);
3299
+ }
3300
+
3301
+ // ../../node_modules/.bun/default-browser@5.4.0/node_modules/default-browser/windows.js
3302
+ import { promisify as promisify3 } from "node:util";
3303
+ import { execFile as execFile3 } from "node:child_process";
3304
+ var execFileAsync3 = promisify3(execFile3);
3305
+ var windowsBrowserProgIds = {
3306
+ MSEdgeHTM: { name: "Edge", id: "com.microsoft.edge" },
3307
+ MSEdgeBHTML: { name: "Edge Beta", id: "com.microsoft.edge.beta" },
3308
+ MSEdgeDHTML: { name: "Edge Dev", id: "com.microsoft.edge.dev" },
3309
+ AppXq0fevzme2pys62n3e0fbqa7peapykr8v: { name: "Edge", id: "com.microsoft.edge.old" },
3310
+ ChromeHTML: { name: "Chrome", id: "com.google.chrome" },
3311
+ ChromeBHTML: { name: "Chrome Beta", id: "com.google.chrome.beta" },
3312
+ ChromeDHTML: { name: "Chrome Dev", id: "com.google.chrome.dev" },
3313
+ ChromiumHTM: { name: "Chromium", id: "org.chromium.Chromium" },
3314
+ BraveHTML: { name: "Brave", id: "com.brave.Browser" },
3315
+ BraveBHTML: { name: "Brave Beta", id: "com.brave.Browser.beta" },
3316
+ BraveDHTML: { name: "Brave Dev", id: "com.brave.Browser.dev" },
3317
+ BraveSSHTM: { name: "Brave Nightly", id: "com.brave.Browser.nightly" },
3318
+ FirefoxURL: { name: "Firefox", id: "org.mozilla.firefox" },
3319
+ OperaStable: { name: "Opera", id: "com.operasoftware.Opera" },
3320
+ VivaldiHTM: { name: "Vivaldi", id: "com.vivaldi.Vivaldi" },
3321
+ "IE.HTTP": { name: "Internet Explorer", id: "com.microsoft.ie" }
3322
+ };
3323
+ var _windowsBrowserProgIdMap = new Map(Object.entries(windowsBrowserProgIds));
3324
+
3325
+ class UnknownBrowserError extends Error {
3326
+ }
3327
+ async function defaultBrowser(_execFileAsync = execFileAsync3) {
3328
+ const { stdout } = await _execFileAsync("reg", [
3329
+ "QUERY",
3330
+ " HKEY_CURRENT_USER\\Software\\Microsoft\\Windows\\Shell\\Associations\\UrlAssociations\\http\\UserChoice",
3331
+ "/v",
3332
+ "ProgId"
3333
+ ]);
3334
+ const match = /ProgId\s*REG_SZ\s*(?<id>\S+)/.exec(stdout);
3335
+ if (!match) {
3336
+ throw new UnknownBrowserError(`Cannot find Windows browser in stdout: ${JSON.stringify(stdout)}`);
3337
+ }
3338
+ const { id } = match.groups;
3339
+ const browser = windowsBrowserProgIds[id];
3340
+ if (!browser) {
3341
+ throw new UnknownBrowserError(`Unknown browser ID: ${id}`);
3342
+ }
3343
+ return browser;
3344
+ }
3345
+
3346
+ // ../../node_modules/.bun/default-browser@5.4.0/node_modules/default-browser/index.js
3347
+ var execFileAsync4 = promisify4(execFile4);
3348
+ var titleize = (string) => string.toLowerCase().replaceAll(/(?:^|\s|-)\S/g, (x) => x.toUpperCase());
3349
+ async function defaultBrowser2() {
3350
+ if (process6.platform === "darwin") {
3351
+ const id = await defaultBrowserId();
3352
+ const name = await bundleName(id);
3353
+ return { name, id };
3354
+ }
3355
+ if (process6.platform === "linux") {
3356
+ const { stdout } = await execFileAsync4("xdg-mime", ["query", "default", "x-scheme-handler/http"]);
3357
+ const id = stdout.trim();
3358
+ const name = titleize(id.replace(/.desktop$/, "").replace("-", " "));
3359
+ return { name, id };
3360
+ }
3361
+ if (process6.platform === "win32") {
3362
+ return defaultBrowser();
3363
+ }
3364
+ throw new Error("Only macOS, Linux, and Windows are supported");
3365
+ }
3366
+
3367
+ // ../../node_modules/.bun/open@10.2.0/node_modules/open/index.js
3368
+ var execFile5 = promisify5(childProcess.execFile);
3369
+ var __dirname2 = path4.dirname(fileURLToPath(import.meta.url));
3370
+ var localXdgOpenPath = path4.join(__dirname2, "xdg-open");
3371
+ var { platform, arch } = process7;
3372
+ async function getWindowsDefaultBrowserFromWsl() {
3373
+ const powershellPath = await powerShellPath();
3374
+ const rawCommand = String.raw`(Get-ItemProperty -Path "HKCU:\Software\Microsoft\Windows\Shell\Associations\UrlAssociations\http\UserChoice").ProgId`;
3375
+ const encodedCommand = Buffer2.from(rawCommand, "utf16le").toString("base64");
3376
+ const { stdout } = await execFile5(powershellPath, [
3377
+ "-NoProfile",
3378
+ "-NonInteractive",
3379
+ "-ExecutionPolicy",
3380
+ "Bypass",
3381
+ "-EncodedCommand",
3382
+ encodedCommand
3383
+ ], { encoding: "utf8" });
3384
+ const progId = stdout.trim();
3385
+ const browserMap = {
3386
+ ChromeHTML: "com.google.chrome",
3387
+ BraveHTML: "com.brave.Browser",
3388
+ MSEdgeHTM: "com.microsoft.edge",
3389
+ FirefoxURL: "org.mozilla.firefox"
3390
+ };
3391
+ return browserMap[progId] ? { id: browserMap[progId] } : {};
3392
+ }
3393
+ var pTryEach = async (array, mapper) => {
3394
+ let latestError;
3395
+ for (const item of array) {
3396
+ try {
3397
+ return await mapper(item);
3398
+ } catch (error) {
3399
+ latestError = error;
3400
+ }
3401
+ }
3402
+ throw latestError;
3403
+ };
3404
+ var baseOpen = async (options) => {
3405
+ options = {
3406
+ wait: false,
3407
+ background: false,
3408
+ newInstance: false,
3409
+ allowNonzeroExitCode: false,
3410
+ ...options
3411
+ };
3412
+ if (Array.isArray(options.app)) {
3413
+ return pTryEach(options.app, (singleApp) => baseOpen({
3414
+ ...options,
3415
+ app: singleApp
3416
+ }));
3417
+ }
3418
+ let { name: app, arguments: appArguments = [] } = options.app ?? {};
3419
+ appArguments = [...appArguments];
3420
+ if (Array.isArray(app)) {
3421
+ return pTryEach(app, (appName) => baseOpen({
3422
+ ...options,
3423
+ app: {
3424
+ name: appName,
3425
+ arguments: appArguments
3426
+ }
3427
+ }));
3428
+ }
3429
+ if (app === "browser" || app === "browserPrivate") {
3430
+ const ids = {
3431
+ "com.google.chrome": "chrome",
3432
+ "google-chrome.desktop": "chrome",
3433
+ "com.brave.Browser": "brave",
3434
+ "org.mozilla.firefox": "firefox",
3435
+ "firefox.desktop": "firefox",
3436
+ "com.microsoft.msedge": "edge",
3437
+ "com.microsoft.edge": "edge",
3438
+ "com.microsoft.edgemac": "edge",
3439
+ "microsoft-edge.desktop": "edge"
3440
+ };
3441
+ const flags = {
3442
+ chrome: "--incognito",
3443
+ brave: "--incognito",
3444
+ firefox: "--private-window",
3445
+ edge: "--inPrivate"
3446
+ };
3447
+ const browser = is_wsl_default ? await getWindowsDefaultBrowserFromWsl() : await defaultBrowser2();
3448
+ if (browser.id in ids) {
3449
+ const browserName = ids[browser.id];
3450
+ if (app === "browserPrivate") {
3451
+ appArguments.push(flags[browserName]);
3452
+ }
3453
+ return baseOpen({
3454
+ ...options,
3455
+ app: {
3456
+ name: apps[browserName],
3457
+ arguments: appArguments
3458
+ }
3459
+ });
3460
+ }
3461
+ throw new Error(`${browser.name} is not supported as a default browser`);
3462
+ }
3463
+ let command;
3464
+ const cliArguments = [];
3465
+ const childProcessOptions = {};
3466
+ if (platform === "darwin") {
3467
+ command = "open";
3468
+ if (options.wait) {
3469
+ cliArguments.push("--wait-apps");
3470
+ }
3471
+ if (options.background) {
3472
+ cliArguments.push("--background");
3473
+ }
3474
+ if (options.newInstance) {
3475
+ cliArguments.push("--new");
3476
+ }
3477
+ if (app) {
3478
+ cliArguments.push("-a", app);
3479
+ }
3480
+ } else if (platform === "win32" || is_wsl_default && !isInsideContainer() && !app) {
3481
+ command = await powerShellPath();
3482
+ cliArguments.push("-NoProfile", "-NonInteractive", "-ExecutionPolicy", "Bypass", "-EncodedCommand");
3483
+ if (!is_wsl_default) {
3484
+ childProcessOptions.windowsVerbatimArguments = true;
3485
+ }
3486
+ const encodedArguments = ["Start"];
3487
+ if (options.wait) {
3488
+ encodedArguments.push("-Wait");
3489
+ }
3490
+ if (app) {
3491
+ encodedArguments.push(`"\`"${app}\`""`);
3492
+ if (options.target) {
3493
+ appArguments.push(options.target);
3494
+ }
3495
+ } else if (options.target) {
3496
+ encodedArguments.push(`"${options.target}"`);
3497
+ }
3498
+ if (appArguments.length > 0) {
3499
+ appArguments = appArguments.map((argument) => `"\`"${argument}\`""`);
3500
+ encodedArguments.push("-ArgumentList", appArguments.join(","));
3501
+ }
3502
+ options.target = Buffer2.from(encodedArguments.join(" "), "utf16le").toString("base64");
3503
+ } else {
3504
+ if (app) {
3505
+ command = app;
3506
+ } else {
3507
+ const isBundled = !__dirname2 || __dirname2 === "/";
3508
+ let exeLocalXdgOpen = false;
3509
+ try {
3510
+ await fs8.access(localXdgOpenPath, fsConstants2.X_OK);
3511
+ exeLocalXdgOpen = true;
3512
+ } catch {}
3513
+ const useSystemXdgOpen = process7.versions.electron ?? (platform === "android" || isBundled || !exeLocalXdgOpen);
3514
+ command = useSystemXdgOpen ? "xdg-open" : localXdgOpenPath;
3515
+ }
3516
+ if (appArguments.length > 0) {
3517
+ cliArguments.push(...appArguments);
3518
+ }
3519
+ if (!options.wait) {
3520
+ childProcessOptions.stdio = "ignore";
3521
+ childProcessOptions.detached = true;
3522
+ }
3523
+ }
3524
+ if (platform === "darwin" && appArguments.length > 0) {
3525
+ cliArguments.push("--args", ...appArguments);
3526
+ }
3527
+ if (options.target) {
3528
+ cliArguments.push(options.target);
3529
+ }
3530
+ const subprocess = childProcess.spawn(command, cliArguments, childProcessOptions);
3531
+ if (options.wait) {
3532
+ return new Promise((resolve, reject) => {
3533
+ subprocess.once("error", reject);
3534
+ subprocess.once("close", (exitCode) => {
3535
+ if (!options.allowNonzeroExitCode && exitCode > 0) {
3536
+ reject(new Error(`Exited with code ${exitCode}`));
3537
+ return;
3538
+ }
3539
+ resolve(subprocess);
3540
+ });
3541
+ });
3542
+ }
3543
+ subprocess.unref();
3544
+ return subprocess;
3545
+ };
3546
+ var open = (target, options) => {
3547
+ if (typeof target !== "string") {
3548
+ throw new TypeError("Expected a `target`");
3549
+ }
3550
+ return baseOpen({
3551
+ ...options,
3552
+ target
3553
+ });
3554
+ };
3555
+ function detectArchBinary(binary) {
3556
+ if (typeof binary === "string" || Array.isArray(binary)) {
3557
+ return binary;
3558
+ }
3559
+ const { [arch]: archBinary } = binary;
3560
+ if (!archBinary) {
3561
+ throw new Error(`${arch} is not supported`);
3562
+ }
3563
+ return archBinary;
3564
+ }
3565
+ function detectPlatformBinary({ [platform]: platformBinary }, { wsl }) {
3566
+ if (wsl && is_wsl_default) {
3567
+ return detectArchBinary(wsl);
3568
+ }
3569
+ if (!platformBinary) {
3570
+ throw new Error(`${platform} is not supported`);
3571
+ }
3572
+ return detectArchBinary(platformBinary);
3573
+ }
3574
+ var apps = {};
3575
+ defineLazyProperty(apps, "chrome", () => detectPlatformBinary({
3576
+ darwin: "google chrome",
3577
+ win32: "chrome",
3578
+ linux: ["google-chrome", "google-chrome-stable", "chromium"]
3579
+ }, {
3580
+ wsl: {
3581
+ ia32: "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe",
3582
+ x64: ["/mnt/c/Program Files/Google/Chrome/Application/chrome.exe", "/mnt/c/Program Files (x86)/Google/Chrome/Application/chrome.exe"]
3583
+ }
3584
+ }));
3585
+ defineLazyProperty(apps, "brave", () => detectPlatformBinary({
3586
+ darwin: "brave browser",
3587
+ win32: "brave",
3588
+ linux: ["brave-browser", "brave"]
3589
+ }, {
3590
+ wsl: {
3591
+ ia32: "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe",
3592
+ x64: ["/mnt/c/Program Files/BraveSoftware/Brave-Browser/Application/brave.exe", "/mnt/c/Program Files (x86)/BraveSoftware/Brave-Browser/Application/brave.exe"]
3593
+ }
3594
+ }));
3595
+ defineLazyProperty(apps, "firefox", () => detectPlatformBinary({
3596
+ darwin: "firefox",
3597
+ win32: String.raw`C:\Program Files\Mozilla Firefox\firefox.exe`,
3598
+ linux: "firefox"
3599
+ }, {
3600
+ wsl: "/mnt/c/Program Files/Mozilla Firefox/firefox.exe"
3601
+ }));
3602
+ defineLazyProperty(apps, "edge", () => detectPlatformBinary({
3603
+ darwin: "microsoft edge",
3604
+ win32: "msedge",
3605
+ linux: ["microsoft-edge", "microsoft-edge-dev"]
3606
+ }, {
3607
+ wsl: "/mnt/c/Program Files (x86)/Microsoft/Edge/Application/msedge.exe"
3608
+ }));
3609
+ defineLazyProperty(apps, "browser", () => "browser");
3610
+ defineLazyProperty(apps, "browserPrivate", () => "browserPrivate");
3611
+ var open_default = open;
3612
+
3613
+ // ../shared/dist/auth/token-store.js
3614
+ import * as fs9 from "node:fs";
3615
+ import * as path5 from "node:path";
3616
+ var TOKENS_DIR = ".kvitton/tokens";
3617
+ var FORTNOX_TOKEN_FILE = "fortnox.json";
3618
+ function getTokenPath(cwd) {
3619
+ return path5.join(cwd, TOKENS_DIR, FORTNOX_TOKEN_FILE);
3620
+ }
3621
+ function ensureTokenDir(cwd) {
3622
+ const dir = path5.join(cwd, TOKENS_DIR);
3623
+ if (!fs9.existsSync(dir)) {
3624
+ fs9.mkdirSync(dir, { recursive: true });
3625
+ }
3626
+ }
3627
+ function saveFortnoxToken(cwd, token) {
3628
+ ensureTokenDir(cwd);
3629
+ const expiresAt = new Date(Date.now() + token.expires_in * 1000);
3630
+ const stored = {
3631
+ access_token: token.access_token,
3632
+ refresh_token: token.refresh_token,
3633
+ scope: token.scope,
3634
+ expires_at: expiresAt.toISOString(),
3635
+ created_at: new Date().toISOString()
3636
+ };
3637
+ fs9.writeFileSync(getTokenPath(cwd), JSON.stringify(stored, null, 2), "utf-8");
3638
+ return stored;
3639
+ }
3640
+ function loadFortnoxToken(cwd) {
3641
+ const tokenPath = getTokenPath(cwd);
3642
+ if (!fs9.existsSync(tokenPath)) {
3643
+ return null;
3644
+ }
3645
+ try {
3646
+ const content = fs9.readFileSync(tokenPath, "utf-8");
3647
+ return JSON.parse(content);
3648
+ } catch {
3649
+ return null;
3650
+ }
3651
+ }
3652
+ function tokenExistsAt(cwd) {
3653
+ return fs9.existsSync(getTokenPath(cwd));
3654
+ }
3655
+
3656
+ // ../shared/dist/auth/prompts.js
3657
+ import * as readline from "node:readline";
3658
+ function createInterface2() {
3659
+ return readline.createInterface({
3660
+ input: process.stdin,
3661
+ output: process.stdout
3662
+ });
3663
+ }
3664
+ function question(rl, prompt) {
3665
+ return new Promise((resolve) => {
3666
+ rl.question(prompt, (answer) => {
3667
+ resolve(answer);
3668
+ });
3669
+ });
3670
+ }
3671
+ async function promptForCredentials() {
3672
+ const rl = createInterface2();
3673
+ try {
3674
+ console.log(`
3675
+ Fortnox OAuth credentials required.`);
3676
+ console.log(`Get these from: https://developer.fortnox.se/my-apps
3677
+ `);
3678
+ const clientId = await question(rl, "FORTNOX_CLIENT_ID: ");
3679
+ if (!clientId.trim()) {
3680
+ throw new Error("Client ID is required");
3681
+ }
3682
+ const clientSecret = await question(rl, "FORTNOX_CLIENT_SECRET: ");
3683
+ if (!clientSecret.trim()) {
3684
+ throw new Error("Client Secret is required");
3685
+ }
3686
+ return {
3687
+ clientId: clientId.trim(),
3688
+ clientSecret: clientSecret.trim()
3689
+ };
3690
+ } finally {
3691
+ rl.close();
3692
+ }
3693
+ }
3694
+ async function promptConfirm(message) {
3695
+ const rl = createInterface2();
3696
+ try {
3697
+ const answer = await question(rl, `${message} (y/N): `);
3698
+ return answer.toLowerCase() === "y" || answer.toLowerCase() === "yes";
3699
+ } finally {
3700
+ rl.close();
3701
+ }
3702
+ }
3703
+
3704
+ // ../shared/dist/auth/fortnox-login.js
3705
+ var DEFAULT_PORT = 8585;
3706
+ var DEFAULT_SCOPES = [
3707
+ FORTNOX_SCOPES.BOOKKEEPING,
3708
+ FORTNOX_SCOPES.COMPANYINFORMATION,
3709
+ FORTNOX_SCOPES.INBOX,
3710
+ FORTNOX_SCOPES.ARCHIVE,
3711
+ FORTNOX_SCOPES.CONNECTFILE
3712
+ ];
3713
+ var SUCCESS_HTML = `
3714
+ <!DOCTYPE html>
3715
+ <html>
3716
+ <head>
3717
+ <title>Authentication Complete</title>
3718
+ <style>
3719
+ body {
3720
+ font-family: system-ui, sans-serif;
3721
+ display: flex;
3722
+ justify-content: center;
3723
+ align-items: center;
3724
+ height: 100vh;
3725
+ margin: 0;
3726
+ background: #fff;
3727
+ color: #111;
3728
+ }
3729
+ .container { text-align: center; }
3730
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; }
3731
+ p { color: #666; }
3732
+ </style>
3733
+ </head>
3734
+ <body>
3735
+ <div class="container">
3736
+ <h1>&#10003; Authentication complete</h1>
3737
+ <p>You can close this window.</p>
3738
+ </div>
3739
+ </body>
3740
+ </html>
3741
+ `;
3742
+ var ERROR_HTML = (error) => `
3743
+ <!DOCTYPE html>
3744
+ <html>
3745
+ <head>
3746
+ <title>Authentication Failed</title>
3747
+ <style>
3748
+ body {
3749
+ font-family: system-ui, sans-serif;
3750
+ display: flex;
3751
+ justify-content: center;
3752
+ align-items: center;
3753
+ height: 100vh;
3754
+ margin: 0;
3755
+ background: #fff;
3756
+ color: #111;
3757
+ }
3758
+ .container { text-align: center; }
3759
+ h1 { font-size: 1.5rem; margin-bottom: 0.5rem; color: #c00; }
3760
+ p { color: #666; }
3761
+ </style>
3762
+ </head>
3763
+ <body>
3764
+ <div class="container">
3765
+ <h1>Authentication failed</h1>
3766
+ <p>${error}</p>
3767
+ </div>
3768
+ </body>
3769
+ </html>
3770
+ `;
3771
+ async function startCallbackServer(port) {
3772
+ let resolveCallback;
3773
+ const resultPromise = new Promise((resolve) => {
3774
+ resolveCallback = resolve;
3775
+ });
3776
+ const server = http.createServer((req, res) => {
3777
+ const url = new URL(req.url ?? "/", `http://localhost:${port}`);
3778
+ if (url.pathname === "/callback") {
3779
+ const code = url.searchParams.get("code");
3780
+ const error = url.searchParams.get("error");
3781
+ const errorDescription = url.searchParams.get("error_description");
3782
+ res.setHeader("Content-Type", "text/html");
3783
+ if (error) {
3784
+ resolveCallback({ error, errorDescription: errorDescription ?? undefined });
3785
+ res.statusCode = 200;
3786
+ res.end(ERROR_HTML(errorDescription ?? error));
3787
+ return;
3788
+ }
3789
+ if (code) {
3790
+ resolveCallback({ code });
3791
+ res.statusCode = 200;
3792
+ res.end(SUCCESS_HTML);
3793
+ return;
3794
+ }
3795
+ resolveCallback({ error: "No authorization code received" });
3796
+ res.statusCode = 200;
3797
+ res.end(ERROR_HTML("No authorization code received"));
3798
+ return;
3799
+ }
3800
+ res.statusCode = 404;
3801
+ res.end("Not found");
3802
+ });
3803
+ await new Promise((resolve) => {
3804
+ server.listen(port, () => resolve());
3805
+ });
3806
+ return {
3807
+ result: resultPromise,
3808
+ stop: () => server.close()
3809
+ };
3810
+ }
3811
+ async function loginFortnox(options) {
3812
+ const { cwd, scopes = DEFAULT_SCOPES, port = DEFAULT_PORT, force = false, onStatus = console.log } = options;
3813
+ if (!force && tokenExistsAt(cwd)) {
3814
+ const existingToken = loadFortnoxToken(cwd);
3815
+ if (existingToken) {
3816
+ const overwrite = await promptConfirm("Fortnox token already exists. Overwrite?");
3817
+ if (!overwrite) {
3818
+ onStatus("Login cancelled.");
3819
+ return existingToken;
3820
+ }
3821
+ }
3822
+ }
3823
+ let clientId = options.clientId;
3824
+ let clientSecret = options.clientSecret;
3825
+ if (!clientId || !clientSecret) {
3826
+ const credentials = await promptForCredentials();
3827
+ clientId = credentials.clientId;
3828
+ clientSecret = credentials.clientSecret;
3829
+ }
3830
+ const redirectUri = `http://localhost:${port}/callback`;
3831
+ const config2 = {
3832
+ clientId,
3833
+ clientSecret,
3834
+ redirectUri,
3835
+ scopes
3836
+ };
3837
+ onStatus(`Starting OAuth callback server on port ${port}...`);
3838
+ const { result, stop } = await startCallbackServer(port);
3839
+ try {
3840
+ const state = crypto.randomUUID();
3841
+ const authUrl = buildAuthorizationUrl(config2, state);
3842
+ onStatus("Opening browser for Fortnox authorization...");
3843
+ await open_default(authUrl);
3844
+ onStatus("Waiting for authorization (press Ctrl+C to cancel)...");
3845
+ const callbackResult = await result;
3846
+ if (callbackResult.error) {
3847
+ throw new Error(callbackResult.errorDescription ?? callbackResult.error);
3848
+ }
3849
+ if (!callbackResult.code) {
3850
+ throw new Error("No authorization code received");
3851
+ }
3852
+ onStatus("Exchanging authorization code for token...");
3853
+ const tokenResponse = await exchangeCodeForToken(config2, callbackResult.code);
3854
+ const storedToken = saveFortnoxToken(cwd, tokenResponse);
3855
+ onStatus("Token saved to .kvitton/tokens/fortnox.json");
3856
+ return storedToken;
3857
+ } finally {
3858
+ stop();
3859
+ }
3860
+ }
3861
+ // ../shared/dist/progress/sync-progress.js
3862
+ var import_cli_progress = __toESM(require_cli_progress(), 1);
3863
+
3864
+ class SyncProgressBar {
3865
+ bar;
3866
+ currentPhase = "";
3867
+ started = false;
3868
+ constructor() {
3869
+ this.bar = new import_cli_progress.default.SingleBar({
3870
+ format: " {phase} [{bar}] {percentage}% | {value}/{total}",
3871
+ barCompleteChar: "█",
3872
+ barIncompleteChar: "░",
3873
+ barsize: 20,
3874
+ hideCursor: true,
3875
+ clearOnComplete: false
3876
+ });
3877
+ }
3878
+ start(total, phase) {
3879
+ this.currentPhase = phase ?? "Syncing...";
3880
+ this.started = true;
3881
+ this.bar.start(total, 0, { phase: this.currentPhase });
3882
+ }
3883
+ update(current, phase) {
3884
+ if (phase && phase !== this.currentPhase) {
3885
+ this.currentPhase = phase;
3886
+ }
3887
+ this.bar.update(current, { phase: this.currentPhase });
3888
+ }
3889
+ stop() {
3890
+ if (this.started) {
3891
+ this.bar.stop();
3892
+ this.started = false;
3893
+ }
3894
+ }
3895
+ static createCallback() {
3896
+ const bar = new SyncProgressBar;
3897
+ let barStarted = false;
3898
+ let discoveryShown = false;
3899
+ return {
3900
+ bar,
3901
+ onProgress: (progress) => {
3902
+ if (progress.phase === "discovering") {
3903
+ process.stdout.write(`\r ${progress.message ?? "Discovering..."} ${progress.current.toLocaleString()} found`);
3904
+ discoveryShown = true;
3905
+ } else {
3906
+ if (!barStarted && progress.total > 0) {
3907
+ if (discoveryShown) {
3908
+ console.log();
3909
+ }
3910
+ bar.start(progress.total, progress.message);
3911
+ barStarted = true;
3912
+ }
3913
+ if (barStarted) {
3914
+ bar.update(progress.current, progress.message);
3915
+ }
3916
+ }
3917
+ }
3918
+ };
3919
+ }
3920
+ }
3921
+ // src/lib/env.ts
3922
+ import * as fs10 from "node:fs/promises";
3923
+ import * as path6 from "node:path";
3924
+ async function createEnvFile(repoPath, variables) {
3925
+ const lines = Object.entries(variables).map(([key, value]) => `${key}=${value}`).join(`
3926
+ `);
3927
+ await fs10.writeFile(path6.join(repoPath, ".env"), `${lines}
3928
+ `);
3929
+ }
3930
+
3931
+ // src/sync/bokio-sync.ts
3932
+ async function syncJournalEntries2(client2, repoPath, options, onProgress) {
3933
+ const storage2 = new FilesystemStorageService(repoPath);
3934
+ const result = await syncBokioJournalEntries(client2, storage2, {
3935
+ downloadFiles: options.downloadFiles,
3936
+ onProgress
3937
+ });
3938
+ return {
3939
+ entriesCount: result.entriesCount,
3940
+ fiscalYearsCount: result.fiscalYearsCount,
3941
+ entriesWithFilesDownloaded: result.entriesWithFilesDownloaded
3942
+ };
3943
+ }
3944
+ async function syncChartOfAccounts(client2, repoPath) {
3945
+ const storage2 = new FilesystemStorageService(repoPath);
3946
+ return syncBokioChartOfAccounts(client2, storage2);
3947
+ }
3948
+
3949
+ // src/sync/fortnox-sync.ts
3950
+ async function syncJournalEntries3(client2, repoPath, options, onProgress) {
3951
+ const storage2 = new FilesystemStorageService(repoPath);
3952
+ const fiscalYearsResponse = await client2.getFinancialYears();
3953
+ const fiscalYears = fiscalYearsResponse.data;
3954
+ const voucherRefs = [];
3955
+ const entriesPerFiscalYear = new Map;
3956
+ for (const fy of fiscalYears) {
3957
+ const fyYear = fy.Id;
3958
+ let hasMore = true;
3959
+ let page = 1;
3960
+ let fyEntryCount = 0;
3961
+ while (hasMore) {
3962
+ const response = await client2.getVouchers(fyYear, { page, pageSize: 100 });
3963
+ for (const voucher of response.data) {
3964
+ voucherRefs.push({
3965
+ series: voucher.VoucherSeries,
3966
+ number: voucher.VoucherNumber ?? 0,
3967
+ year: voucher.Year ?? fyYear,
3968
+ fiscalYear: fy
3969
+ });
3970
+ fyEntryCount++;
3971
+ }
3972
+ hasMore = response.pagination.hasNextPage;
3973
+ page++;
3974
+ onProgress({
3975
+ current: voucherRefs.length,
3976
+ total: 0,
3977
+ message: "Discovering entries...",
3978
+ phase: "discovering"
3979
+ });
3980
+ }
3981
+ entriesPerFiscalYear.set(fyYear, fyEntryCount);
3982
+ }
3983
+ const fiscalYearSummaries = fiscalYears.map((fy) => ({
3984
+ id: fy.Id,
3985
+ year: parseInt(fy.FromDate.slice(0, 4), 10),
3986
+ fromDate: fy.FromDate,
3987
+ toDate: fy.ToDate,
3988
+ entryCount: entriesPerFiscalYear.get(fy.Id) ?? 0
3989
+ }));
3990
+ let selectedYearIds;
3991
+ if (options.onSelectFiscalYears) {
3992
+ const selected = await options.onSelectFiscalYears(fiscalYearSummaries);
3993
+ if (selected.length === 0) {
3994
+ return {
3995
+ entriesCount: 0,
3996
+ fiscalYearsCount: 0,
3997
+ entriesWithFilesDownloaded: 0,
3998
+ skipped: true
3999
+ };
4000
+ }
4001
+ selectedYearIds = new Set(selected);
4002
+ } else {
4003
+ selectedYearIds = new Set(fiscalYears.map((fy) => fy.Id));
4004
+ }
4005
+ const filteredRefs = voucherRefs.filter((ref) => selectedYearIds.has(ref.fiscalYear.Id));
4006
+ const selectedFiscalYears = fiscalYears.filter((fy) => selectedYearIds.has(fy.Id));
4007
+ const totalEntries = filteredRefs.length;
4008
+ onProgress({ current: 0, total: totalEntries, message: "Fetching entry details...", phase: "fetching" });
4009
+ if (totalEntries === 0) {
4010
+ await writeFiscalYearsMetadata(storage2, selectedFiscalYears);
4011
+ return {
4012
+ entriesCount: 0,
4013
+ fiscalYearsCount: selectedFiscalYears.length,
4014
+ entriesWithFilesDownloaded: 0
4015
+ };
4016
+ }
4017
+ const entryDirs = new Map;
4018
+ const allVouchers = [];
4019
+ let processed = 0;
4020
+ for (const ref of filteredRefs) {
4021
+ const fullVoucherResponse = await client2.getVoucher(ref.series, ref.number, ref.year);
4022
+ const voucher = fullVoucherResponse.Voucher;
4023
+ const fyYear = parseInt(ref.fiscalYear.FromDate.slice(0, 4), 10);
4024
+ const entryDir = await writeJournalEntry(storage2, fyYear, voucher);
4025
+ const entryId = `${voucher.VoucherSeries}-${voucher.VoucherNumber}-${voucher.Year}`;
4026
+ entryDirs.set(entryId, entryDir);
4027
+ allVouchers.push({ voucher, fiscalYear: ref.fiscalYear });
4028
+ processed++;
4029
+ onProgress({ current: processed, total: totalEntries, phase: "fetching" });
4030
+ }
4031
+ await writeFiscalYearsMetadata(storage2, selectedFiscalYears);
4032
+ let entriesWithFilesDownloaded = 0;
4033
+ if (options.downloadFiles !== false) {
4034
+ for (const { voucher } of allVouchers) {
4035
+ const entryId = `${voucher.VoucherSeries}-${voucher.VoucherNumber}-${voucher.Year}`;
4036
+ const entryDir = entryDirs.get(entryId);
4037
+ if (!entryDir)
4038
+ continue;
4039
+ try {
4040
+ const connections = await client2.getVoucherFileConnectionsForVoucher(voucher.VoucherSeries, voucher.VoucherNumber ?? 0, voucher.Year ?? 0);
4041
+ for (const conn of connections) {
4042
+ try {
4043
+ const fileResult = await client2.downloadFromArchive(conn.FileId);
4044
+ const filename = `attachment-${conn.FileId}.${getExtension(fileResult.contentType)}`;
4045
+ const filePath = `${entryDir}/${filename}`;
4046
+ await storage2.writeFile(filePath, Buffer.from(fileResult.data).toString("base64"));
4047
+ entriesWithFilesDownloaded++;
4048
+ } catch {}
4049
+ }
4050
+ } catch {}
4051
+ }
4052
+ }
4053
+ return {
4054
+ entriesCount: allVouchers.length,
4055
+ fiscalYearsCount: selectedFiscalYears.length,
4056
+ entriesWithFilesDownloaded
4057
+ };
4058
+ }
4059
+ function getExtension(contentType) {
4060
+ const map = {
4061
+ "application/pdf": "pdf",
4062
+ "image/jpeg": "jpg",
4063
+ "image/png": "png",
4064
+ "image/gif": "gif"
4065
+ };
4066
+ return map[contentType] ?? "bin";
4067
+ }
4068
+ async function writeJournalEntry(storage2, fyYear, voucher) {
4069
+ const journalEntry = mapFortnoxVoucherToJournalEntry(voucher);
4070
+ const entryPath = journalEntryPath(fyYear, journalEntry.series ?? null, journalEntry.entryNumber, journalEntry.entryDate, journalEntry.description);
4071
+ const yamlContent = toYaml2(journalEntry);
4072
+ await storage2.writeFile(entryPath, yamlContent);
4073
+ return journalEntryDirFromPath(entryPath);
4074
+ }
4075
+ async function writeFiscalYearsMetadata(storage2, fiscalYears) {
4076
+ for (const fy of fiscalYears) {
4077
+ const fyDir = fiscalYearDirName({ start_date: fy.FromDate });
4078
+ const metadataPath = `journal-entries/${fyDir}/_fiscal-year.yaml`;
4079
+ const metadata = {
4080
+ id: fy.Id,
4081
+ startDate: fy.FromDate,
4082
+ endDate: fy.ToDate
4083
+ };
4084
+ const yamlContent = toYaml2(metadata);
4085
+ await storage2.writeFile(metadataPath, yamlContent);
4086
+ }
4087
+ }
4088
+ async function syncChartOfAccounts2(client2, repoPath) {
4089
+ const storage2 = new FilesystemStorageService(repoPath);
4090
+ return syncFortnoxChartOfAccounts(client2, storage2);
4091
+ }
1072
4092
 
1073
4093
  // src/commands/create.ts
1074
- var __filename2 = fileURLToPath(import.meta.url);
1075
- var __dirname2 = path5.dirname(__filename2);
4094
+ var __filename2 = fileURLToPath2(import.meta.url);
4095
+ var __dirname3 = path7.dirname(__filename2);
1076
4096
  async function createAgentsFile(targetDir, options) {
1077
- const bundledPath = path5.join(__dirname2, "templates/AGENTS.md");
1078
- const devPath = path5.join(__dirname2, "../templates/AGENTS.md");
1079
- const templatePath = await fs5.access(bundledPath).then(() => bundledPath).catch(() => devPath);
1080
- let template = await fs5.readFile(templatePath, "utf-8");
1081
- template = template.replace(/\{\{COMPANY_NAME\}\}/g, options.companyName);
1082
- template = template.replace(/\{\{PROVIDER\}\}/g, options.provider);
1083
- template = template.replace(/\{\{PROVIDER_LOWER\}\}/g, options.provider.toLowerCase());
1084
- const agentsPath = path5.join(targetDir, "AGENTS.md");
1085
- await fs5.writeFile(agentsPath, template, "utf-8");
1086
- const claudePath = path5.join(targetDir, "CLAUDE.md");
1087
- await fs5.symlink("AGENTS.md", claudePath);
4097
+ const content = renderAgentsTemplate({
4098
+ companyName: options.companyName,
4099
+ provider: options.provider
4100
+ });
4101
+ const agentsPath = path7.join(targetDir, "AGENTS.md");
4102
+ await fs11.writeFile(agentsPath, content, "utf-8");
4103
+ const claudePath = path7.join(targetDir, "CLAUDE.md");
4104
+ await fs11.symlink("AGENTS.md", claudePath);
1088
4105
  }
1089
4106
  function isValidUUID(value) {
1090
4107
  return /^[0-9a-f-]{36}$/i.test(value.trim());
@@ -1103,60 +4120,110 @@ async function createBookkeepingRepo(name, options = {}) {
1103
4120
  message: "Select your accounting provider:",
1104
4121
  choices: [
1105
4122
  { name: "Bokio", value: "bokio" },
1106
- { name: "Fortnox (coming soon)", value: "fortnox", disabled: true }
4123
+ { name: "Fortnox", value: "fortnox" }
1107
4124
  ]
1108
4125
  });
1109
4126
  }
1110
- if (provider !== "bokio") {
1111
- console.log("Only Bokio is supported at this time.");
4127
+ if (provider !== "bokio" && provider !== "fortnox") {
4128
+ console.log(`Unknown provider: ${provider}. Supported: bokio, fortnox`);
1112
4129
  process.exit(1);
1113
4130
  }
1114
- const envToken = process.env.BOKIO_TOKEN;
1115
- let token;
1116
- if (envToken) {
1117
- token = envToken;
1118
- console.log(" Using BOKIO_TOKEN from environment");
1119
- } else {
1120
- console.log(`
4131
+ let companyName;
4132
+ let bokioClient;
4133
+ let fortnoxClient;
4134
+ let bokioCompanyId;
4135
+ let bokioToken;
4136
+ let fortnoxTempDir;
4137
+ if (provider === "bokio") {
4138
+ const envToken = process.env.BOKIO_TOKEN;
4139
+ let token;
4140
+ if (envToken) {
4141
+ token = envToken;
4142
+ console.log(" Using BOKIO_TOKEN from environment");
4143
+ } else {
4144
+ console.log(`
1121
4145
  To connect to Bokio, you need:`);
1122
- console.log(" - API Token: Bokio app > Settings > Integrations > Create Integration");
1123
- console.log(` - Company ID: From your Bokio URL (app.bokio.se/{companyId}/...)
4146
+ console.log(" - API Token: Bokio app > Settings > Integrations > Create Integration");
4147
+ console.log(` - Company ID: From your Bokio URL (app.bokio.se/{companyId}/...)
1124
4148
  `);
1125
- console.log(` Tip: Set BOKIO_TOKEN env var to avoid entering token each time
4149
+ console.log(` Tip: Set BOKIO_TOKEN env var to avoid entering token each time
1126
4150
  `);
1127
- token = await password({
1128
- message: "Enter your Bokio API token:",
1129
- mask: "*"
1130
- });
1131
- }
1132
- let companyId = options.companyId;
1133
- if (!companyId) {
1134
- companyId = await input({
1135
- message: "Enter your Bokio Company ID:",
1136
- validate: (value) => {
1137
- if (!value.trim())
1138
- return "Company ID is required";
1139
- if (!isValidUUID(value)) {
1140
- return "Company ID should be a UUID (e.g., 12345678-1234-1234-1234-123456789abc)";
4151
+ token = await password({
4152
+ message: "Enter your Bokio API token:",
4153
+ mask: "*"
4154
+ });
4155
+ }
4156
+ bokioToken = token;
4157
+ let companyId = options.companyId;
4158
+ if (!companyId) {
4159
+ companyId = await input({
4160
+ message: "Enter your Bokio Company ID:",
4161
+ validate: (value) => {
4162
+ if (!value.trim())
4163
+ return "Company ID is required";
4164
+ if (!isValidUUID(value)) {
4165
+ return "Company ID should be a UUID (e.g., 12345678-1234-1234-1234-123456789abc)";
4166
+ }
4167
+ return true;
1141
4168
  }
1142
- return true;
1143
- }
4169
+ });
4170
+ } else if (!isValidUUID(companyId)) {
4171
+ console.error("Error: Company ID should be a UUID (e.g., 12345678-1234-1234-1234-123456789abc)");
4172
+ process.exit(1);
4173
+ }
4174
+ const spinner = ora("Validating credentials...").start();
4175
+ bokioClient = new BokioClient({ token, companyId: companyId.trim() });
4176
+ bokioCompanyId = companyId.trim();
4177
+ try {
4178
+ const companyInfo = await bokioClient.getCompanyInformation();
4179
+ companyName = companyInfo.name;
4180
+ spinner.succeed(`Connected to: ${companyName}`);
4181
+ } catch (error) {
4182
+ spinner.fail("Failed to connect to Bokio");
4183
+ console.error(error instanceof Error ? error.message : "Unknown error");
4184
+ process.exit(1);
4185
+ }
4186
+ } else {
4187
+ console.log(`
4188
+ Fortnox requires OAuth authentication.
4189
+ `);
4190
+ fortnoxTempDir = await fs11.mkdtemp(path7.join(os2.tmpdir(), "kvitton-oauth-"));
4191
+ try {
4192
+ await loginFortnox({
4193
+ cwd: fortnoxTempDir,
4194
+ clientId: process.env.FORTNOX_CLIENT_ID,
4195
+ clientSecret: process.env.FORTNOX_CLIENT_SECRET,
4196
+ force: true,
4197
+ onStatus: (msg) => console.log(` ${msg}`)
4198
+ });
4199
+ } catch (error) {
4200
+ await fs11.rm(fortnoxTempDir, { recursive: true, force: true });
4201
+ console.error(`
4202
+ Failed to authenticate with Fortnox: ${error instanceof Error ? error.message : "Unknown error"}`);
4203
+ process.exit(1);
4204
+ }
4205
+ const token = loadFortnoxToken(fortnoxTempDir);
4206
+ if (!token) {
4207
+ await fs11.rm(fortnoxTempDir, { recursive: true, force: true });
4208
+ console.error(" Failed to load Fortnox token after login");
4209
+ process.exit(1);
4210
+ }
4211
+ fortnoxClient = new FortnoxClient({
4212
+ clientId: process.env.FORTNOX_CLIENT_ID ?? "",
4213
+ clientSecret: process.env.FORTNOX_CLIENT_SECRET ?? "",
4214
+ getAccessToken: async () => token.access_token,
4215
+ silent: true
1144
4216
  });
1145
- } else if (!isValidUUID(companyId)) {
1146
- console.error("Error: Company ID should be a UUID (e.g., 12345678-1234-1234-1234-123456789abc)");
1147
- process.exit(1);
1148
- }
1149
- const spinner = ora("Validating credentials...").start();
1150
- const client2 = new BokioClient({ token, companyId: companyId.trim() });
1151
- let companyName;
1152
- try {
1153
- const companyInfo = await client2.getCompanyInformation();
1154
- companyName = companyInfo.name;
1155
- spinner.succeed(`Connected to: ${companyName}`);
1156
- } catch (error) {
1157
- spinner.fail("Failed to connect to Bokio");
1158
- console.error(error instanceof Error ? error.message : "Unknown error");
1159
- process.exit(1);
4217
+ const spinner = ora("Validating credentials...").start();
4218
+ try {
4219
+ const companyInfo = await fortnoxClient.getCompanyInformation();
4220
+ companyName = companyInfo.CompanyInformation.CompanyName;
4221
+ spinner.succeed(`Connected to: ${companyName}`);
4222
+ } catch (error) {
4223
+ spinner.fail("Failed to connect to Fortnox");
4224
+ console.error(error instanceof Error ? error.message : "Unknown error");
4225
+ process.exit(1);
4226
+ }
1160
4227
  }
1161
4228
  const suggestedName = `kvitton-${sanitizeOrgName(companyName)}`;
1162
4229
  let folderName;
@@ -1183,9 +4250,9 @@ async function createBookkeepingRepo(name, options = {}) {
1183
4250
  }
1184
4251
  });
1185
4252
  }
1186
- const targetDir = path5.resolve(process.cwd(), folderName.trim());
4253
+ const targetDir = path7.resolve(process.cwd(), folderName.trim());
1187
4254
  try {
1188
- await fs5.access(targetDir);
4255
+ await fs11.access(targetDir);
1189
4256
  if (acceptDefaults) {
1190
4257
  console.error(`Error: Directory ${folderName} already exists`);
1191
4258
  process.exit(1);
@@ -1200,22 +4267,46 @@ async function createBookkeepingRepo(name, options = {}) {
1200
4267
  }
1201
4268
  } catch {}
1202
4269
  const gitSpinner = ora("Initializing repository...").start();
1203
- await fs5.mkdir(targetDir, { recursive: true });
4270
+ await fs11.mkdir(targetDir, { recursive: true });
1204
4271
  await initGitRepo(targetDir);
1205
4272
  gitSpinner.succeed("Repository initialized");
1206
- await createEnvFile(targetDir, {
1207
- PROVIDER: "bokio",
1208
- BOKIO_TOKEN: token,
1209
- BOKIO_COMPANY_ID: companyId.trim()
1210
- });
4273
+ if (provider === "bokio") {
4274
+ if (!bokioToken) {
4275
+ console.error("Error: Bokio token is required but not available");
4276
+ process.exit(1);
4277
+ }
4278
+ await createEnvFile(targetDir, {
4279
+ PROVIDER: "bokio",
4280
+ BOKIO_TOKEN: bokioToken,
4281
+ BOKIO_COMPANY_ID: bokioCompanyId ?? ""
4282
+ });
4283
+ } else {
4284
+ await createEnvFile(targetDir, {
4285
+ PROVIDER: "fortnox"
4286
+ });
4287
+ const sourcePath = path7.join(fortnoxTempDir, ".kvitton", "tokens");
4288
+ const targetPath = path7.join(targetDir, ".kvitton", "tokens");
4289
+ await fs11.mkdir(path7.dirname(targetPath), { recursive: true });
4290
+ await fs11.cp(sourcePath, targetPath, { recursive: true });
4291
+ await fs11.rm(fortnoxTempDir, { recursive: true, force: true });
4292
+ }
1211
4293
  console.log(" Created .env with credentials");
1212
4294
  await createAgentsFile(targetDir, {
1213
4295
  companyName,
1214
- provider: "Bokio"
4296
+ provider: provider === "bokio" ? "Bokio" : "Fortnox"
1215
4297
  });
1216
4298
  console.log(" Created AGENTS.md and CLAUDE.md symlink");
1217
4299
  const accountsSpinner = ora("Syncing chart of accounts...").start();
1218
- const { accountsCount } = await syncChartOfAccounts(client2, targetDir);
4300
+ let accountsCount;
4301
+ if (provider === "bokio" && bokioClient) {
4302
+ const result = await syncChartOfAccounts(bokioClient, targetDir);
4303
+ accountsCount = result.accountsCount;
4304
+ } else if (fortnoxClient) {
4305
+ const result = await syncChartOfAccounts2(fortnoxClient, targetDir);
4306
+ accountsCount = result.accountsCount;
4307
+ } else {
4308
+ throw new Error("No client available");
4309
+ }
1219
4310
  accountsSpinner.succeed(`Synced ${accountsCount} accounts to accounts.yaml`);
1220
4311
  let shouldSync;
1221
4312
  if (options.sync !== undefined) {
@@ -1224,28 +4315,57 @@ async function createBookkeepingRepo(name, options = {}) {
1224
4315
  shouldSync = true;
1225
4316
  } else {
1226
4317
  shouldSync = await confirm({
1227
- message: "Sync existing journal entries from Bokio?",
4318
+ message: `Sync existing journal entries from ${provider === "bokio" ? "Bokio" : "Fortnox"}?`,
1228
4319
  default: true
1229
4320
  });
1230
4321
  }
1231
4322
  let entriesCount = 0;
1232
4323
  let fiscalYearsCount = 0;
1233
4324
  if (shouldSync) {
1234
- const shouldDownloadFiles = options.downloadFiles !== false;
1235
- const result = await syncJournalEntries2(client2, targetDir, { downloadFiles: shouldDownloadFiles }, (progress) => {
1236
- process.stdout.write(`\r Syncing: ${progress.current}/${progress.total} entries`);
1237
- });
1238
- entriesCount = result.entriesCount;
1239
- fiscalYearsCount = result.fiscalYearsCount;
1240
- if (result.entriesWithFilesDownloaded > 0) {
1241
- console.log(`
1242
- Downloaded files for ${result.entriesWithFilesDownloaded}/${result.entriesCount} entries`);
4325
+ const shouldDownloadFiles = options.downloadFiles === true;
4326
+ const { bar, onProgress } = SyncProgressBar.createCallback();
4327
+ if (provider === "bokio" && bokioClient) {
4328
+ const result = await syncJournalEntries2(bokioClient, targetDir, { downloadFiles: shouldDownloadFiles }, onProgress);
4329
+ bar.stop();
4330
+ entriesCount = result.entriesCount;
4331
+ fiscalYearsCount = result.fiscalYearsCount;
4332
+ if (result.entriesWithFilesDownloaded > 0) {
4333
+ console.log(` Downloaded files for ${result.entriesWithFilesDownloaded}/${result.entriesCount} entries`);
4334
+ }
4335
+ } else if (fortnoxClient) {
4336
+ const result = await syncJournalEntries3(fortnoxClient, targetDir, {
4337
+ downloadFiles: shouldDownloadFiles,
4338
+ onSelectFiscalYears: async (years) => {
4339
+ console.log();
4340
+ const sorted = [...years].sort((a, b) => b.year - a.year);
4341
+ const selected = await checkbox({
4342
+ message: "Select fiscal years to sync:",
4343
+ choices: sorted.map((fy) => ({
4344
+ name: `FY-${fy.year} (${fy.entryCount.toLocaleString()} entries)`,
4345
+ value: fy.id,
4346
+ checked: true
4347
+ }))
4348
+ });
4349
+ return selected;
4350
+ }
4351
+ }, onProgress);
4352
+ bar.stop();
4353
+ if (result.skipped) {
4354
+ console.log(" Sync skipped (no fiscal years selected)");
4355
+ } else {
4356
+ entriesCount = result.entriesCount;
4357
+ fiscalYearsCount = result.fiscalYearsCount;
4358
+ if (result.entriesWithFilesDownloaded > 0) {
4359
+ console.log(` Downloaded files for ${result.entriesWithFilesDownloaded}/${result.entriesCount} entries`);
4360
+ }
4361
+ }
1243
4362
  }
1244
4363
  console.log(" Sync complete!");
1245
4364
  }
1246
4365
  const commitSpinner = ora("Creating initial commit...").start();
1247
- await commitAll(targetDir, "Initial sync from Bokio");
4366
+ await commitAll(targetDir, "Initial commit");
1248
4367
  commitSpinner.succeed("Initial commit created");
4368
+ const providerName = provider === "bokio" ? "Bokio" : "Fortnox";
1249
4369
  console.log(`
1250
4370
  Success! Created ${folderName} at ${targetDir}
1251
4371
 
@@ -1261,24 +4381,25 @@ async function createBookkeepingRepo(name, options = {}) {
1261
4381
  journal-entries/FY-2024/A-001-2024-01-15-invoice/entry.yaml
1262
4382
 
1263
4383
  Commands:
1264
- kvitton sync-journal Sync journal entries from Bokio
1265
- kvitton sync-inbox Download inbox files from Bokio
4384
+ kvitton sync-journal Sync journal entries from ${providerName}
4385
+ kvitton sync-inbox Download inbox files from ${providerName}
1266
4386
  kvitton company-info Display company information
1267
4387
  `);
1268
4388
  }
1269
4389
 
1270
4390
  // src/index.ts
1271
4391
  var program = new Command;
1272
- program.name("create-kvitton").description("Create a new kvitton bookkeeping git repository").version("0.4.0").argument("[name]", "Repository folder name").option("-p, --provider <provider>", "Accounting provider (bokio)", "bokio").option("-c, --company-id <id>", "Company ID (UUID from provider)").option("-s, --sync", "Sync journal entries after creation", true).option("--no-sync", "Skip syncing journal entries").option("-d, --download-files", "Download files for journal entries", true).option("--no-download-files", "Skip downloading files").option("-y, --yes", "Accept defaults without prompting").addHelpText("after", `
4392
+ program.name("create-kvitton").description("Create a new kvitton bookkeeping git repository").version("0.4.0").argument("[name]", "Repository folder name").option("-p, --provider <provider>", "Accounting provider (bokio, fortnox)").option("-c, --company-id <id>", "Company ID (UUID from provider)").option("-s, --sync", "Sync journal entries after creation", true).option("--no-sync", "Skip syncing journal entries").option("-d, --download-files", "Download files for journal entries", false).option("--no-download-files", "Skip downloading files").option("-y, --yes", "Accept defaults without prompting").addHelpText("after", `
1273
4393
  Environment Variables:
1274
- BOKIO_TOKEN Bokio API token (required for non-interactive mode)
4394
+ BOKIO_TOKEN Bokio API token (for Bokio provider)
4395
+ FORTNOX_CLIENT_ID Fortnox OAuth client ID (for Fortnox provider)
4396
+ FORTNOX_CLIENT_SECRET Fortnox OAuth client secret (for Fortnox provider)
1275
4397
 
1276
4398
  Examples:
1277
4399
  $ create-kvitton
1278
4400
  $ create-kvitton my-company-books
1279
- $ create-kvitton my-repo --company-id abc-123-def --sync
1280
- $ create-kvitton my-repo -c abc-123 --no-download-files
1281
- $ BOKIO_TOKEN=xxx create-kvitton my-repo -c abc-123 -y
4401
+ $ create-kvitton -p fortnox
4402
+ $ create-kvitton my-repo -p bokio -c abc-123 -y
1282
4403
  `).action((name, options) => {
1283
4404
  createBookkeepingRepo(name, options);
1284
4405
  });