create-kvitton 0.4.2 → 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,52 +1457,818 @@ 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);
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);
715
2272
  const content = await fs.readFile(absolutePath, "utf-8");
716
2273
  return {
717
2274
  content,
@@ -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,31 +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
  }
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;
997
2801
  const entryDirs = new Map;
998
- for (const entry of allEntries) {
999
- const fiscalYear = findFiscalYear(entry.date, fiscalYears);
2802
+ for (const entry of entriesToSync) {
2803
+ const fiscalYear = findFiscalYear(entry.date, allFiscalYears);
1000
2804
  if (!fiscalYear)
1001
2805
  continue;
1002
2806
  const fyYear = parseInt(fiscalYear.startDate.slice(0, 4), 10);
1003
- const entryDir = await writeJournalEntry(storage2, fyYear, entry);
1004
- entryDirs.set(entry.id, 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);
1005
2814
  }
1006
- await writeFiscalYearsMetadata(storage2, fiscalYears);
2815
+ await writeBokioFiscalYearsMetadata(storage, fiscalYears);
1007
2816
  let entriesWithFilesDownloaded = 0;
1008
- if (options.downloadFiles !== false) {
2817
+ if (downloadFiles) {
1009
2818
  const downloader = createBokioDownloader(client2);
1010
- for (const entry of allEntries) {
2819
+ for (const entry of entriesToSync) {
1011
2820
  const entryDir = entryDirs.get(entry.id);
1012
2821
  if (!entryDir)
1013
2822
  continue;
1014
2823
  const filesDownloaded = await downloadFilesForEntry({
1015
- storage: storage2,
1016
- repoPath,
2824
+ storage,
2825
+ repoPath: "",
1017
2826
  entryDir,
1018
2827
  journalEntryId: entry.id,
1019
2828
  downloader,
@@ -1025,15 +2834,14 @@ async function syncJournalEntries2(client2, repoPath, options, onProgress) {
1025
2834
  }
1026
2835
  }
1027
2836
  return {
1028
- entriesCount: allEntries.length,
2837
+ entriesCount: entriesToSync.length,
2838
+ newEntries,
2839
+ existingEntries,
1029
2840
  fiscalYearsCount: fiscalYears.length,
1030
2841
  entriesWithFilesDownloaded
1031
2842
  };
1032
2843
  }
1033
- function findFiscalYear(date, fiscalYears) {
1034
- return fiscalYears.find((fy) => date >= fy.startDate && date <= fy.endDate);
1035
- }
1036
- async function writeJournalEntry(storage2, fyYear, entry) {
2844
+ async function writeBokioJournalEntry(storage, fyYear, entry) {
1037
2845
  const journalEntry = mapBokioEntryToJournalEntry({
1038
2846
  id: entry.id,
1039
2847
  journalEntryNumber: entry.journalEntryNumber,
@@ -1046,11 +2854,16 @@ async function writeJournalEntry(storage2, fyYear, entry) {
1046
2854
  }))
1047
2855
  });
1048
2856
  const entryPath = journalEntryPath(fyYear, journalEntry.series ?? null, journalEntry.entryNumber, journalEntry.entryDate, journalEntry.description);
1049
- const yamlContent = toYaml(journalEntry);
1050
- await storage2.writeFile(entryPath, yamlContent);
1051
- 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 };
1052
2865
  }
1053
- async function writeFiscalYearsMetadata(storage2, fiscalYears) {
2866
+ async function writeBokioFiscalYearsMetadata(storage, fiscalYears) {
1054
2867
  for (const fy of fiscalYears) {
1055
2868
  const fyDir = fiscalYearDirName({ start_date: fy.startDate });
1056
2869
  const metadataPath = `journal-entries/${fyDir}/_fiscal-year.yaml`;
@@ -1060,38 +2873,1235 @@ async function writeFiscalYearsMetadata(storage2, fiscalYears) {
1060
2873
  endDate: fy.endDate,
1061
2874
  status: fy.status
1062
2875
  };
1063
- const yamlContent = toYaml(metadata);
1064
- await storage2.writeFile(metadataPath, yamlContent);
2876
+ const yamlContent = toYaml2(metadata);
2877
+ await storage.writeFile(metadataPath, yamlContent);
1065
2878
  }
1066
2879
  }
1067
- async function syncChartOfAccounts(client2, repoPath) {
1068
- const storage2 = new FilesystemStorageService(repoPath);
2880
+ async function syncBokioChartOfAccounts(client2, storage) {
1069
2881
  const bokioAccounts = await client2.getChartOfAccounts();
1070
2882
  const accounts = [...bokioAccounts].sort((a, b) => a.account - b.account).map((account) => ({
1071
2883
  code: account.account.toString(),
1072
2884
  name: account.name,
1073
2885
  description: account.name
1074
2886
  }));
1075
- const yamlContent = toYaml({ accounts });
1076
- await storage2.writeFile("accounts.yaml", yamlContent);
2887
+ const yamlContent = toYaml2({ accounts });
2888
+ await storage.writeFile("accounts.yaml", yamlContent);
1077
2889
  return { accountsCount: accounts.length };
1078
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
+ }
1079
4092
 
1080
4093
  // src/commands/create.ts
1081
- var __filename2 = fileURLToPath(import.meta.url);
1082
- var __dirname2 = path5.dirname(__filename2);
4094
+ var __filename2 = fileURLToPath2(import.meta.url);
4095
+ var __dirname3 = path7.dirname(__filename2);
1083
4096
  async function createAgentsFile(targetDir, options) {
1084
- const bundledPath = path5.join(__dirname2, "templates/AGENTS.md");
1085
- const devPath = path5.join(__dirname2, "../templates/AGENTS.md");
1086
- const templatePath = await fs5.access(bundledPath).then(() => bundledPath).catch(() => devPath);
1087
- let template = await fs5.readFile(templatePath, "utf-8");
1088
- template = template.replace(/\{\{COMPANY_NAME\}\}/g, options.companyName);
1089
- template = template.replace(/\{\{PROVIDER\}\}/g, options.provider);
1090
- template = template.replace(/\{\{PROVIDER_LOWER\}\}/g, options.provider.toLowerCase());
1091
- const agentsPath = path5.join(targetDir, "AGENTS.md");
1092
- await fs5.writeFile(agentsPath, template, "utf-8");
1093
- const claudePath = path5.join(targetDir, "CLAUDE.md");
1094
- 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);
1095
4105
  }
1096
4106
  function isValidUUID(value) {
1097
4107
  return /^[0-9a-f-]{36}$/i.test(value.trim());
@@ -1110,60 +4120,110 @@ async function createBookkeepingRepo(name, options = {}) {
1110
4120
  message: "Select your accounting provider:",
1111
4121
  choices: [
1112
4122
  { name: "Bokio", value: "bokio" },
1113
- { name: "Fortnox (coming soon)", value: "fortnox", disabled: true }
4123
+ { name: "Fortnox", value: "fortnox" }
1114
4124
  ]
1115
4125
  });
1116
4126
  }
1117
- if (provider !== "bokio") {
1118
- console.log("Only Bokio is supported at this time.");
4127
+ if (provider !== "bokio" && provider !== "fortnox") {
4128
+ console.log(`Unknown provider: ${provider}. Supported: bokio, fortnox`);
1119
4129
  process.exit(1);
1120
4130
  }
1121
- const envToken = process.env.BOKIO_TOKEN;
1122
- let token;
1123
- if (envToken) {
1124
- token = envToken;
1125
- console.log(" Using BOKIO_TOKEN from environment");
1126
- } else {
1127
- 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(`
1128
4145
  To connect to Bokio, you need:`);
1129
- console.log(" - API Token: Bokio app > Settings > Integrations > Create Integration");
1130
- 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}/...)
1131
4148
  `);
1132
- 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
1133
4150
  `);
1134
- token = await password({
1135
- message: "Enter your Bokio API token:",
1136
- mask: "*"
1137
- });
1138
- }
1139
- let companyId = options.companyId;
1140
- if (!companyId) {
1141
- companyId = await input({
1142
- message: "Enter your Bokio Company ID:",
1143
- validate: (value) => {
1144
- if (!value.trim())
1145
- return "Company ID is required";
1146
- if (!isValidUUID(value)) {
1147
- 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;
1148
4168
  }
1149
- return true;
1150
- }
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
1151
4216
  });
1152
- } else if (!isValidUUID(companyId)) {
1153
- console.error("Error: Company ID should be a UUID (e.g., 12345678-1234-1234-1234-123456789abc)");
1154
- process.exit(1);
1155
- }
1156
- const spinner = ora("Validating credentials...").start();
1157
- const client2 = new BokioClient({ token, companyId: companyId.trim() });
1158
- let companyName;
1159
- try {
1160
- const companyInfo = await client2.getCompanyInformation();
1161
- companyName = companyInfo.name;
1162
- spinner.succeed(`Connected to: ${companyName}`);
1163
- } catch (error) {
1164
- spinner.fail("Failed to connect to Bokio");
1165
- console.error(error instanceof Error ? error.message : "Unknown error");
1166
- 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
+ }
1167
4227
  }
1168
4228
  const suggestedName = `kvitton-${sanitizeOrgName(companyName)}`;
1169
4229
  let folderName;
@@ -1190,9 +4250,9 @@ async function createBookkeepingRepo(name, options = {}) {
1190
4250
  }
1191
4251
  });
1192
4252
  }
1193
- const targetDir = path5.resolve(process.cwd(), folderName.trim());
4253
+ const targetDir = path7.resolve(process.cwd(), folderName.trim());
1194
4254
  try {
1195
- await fs5.access(targetDir);
4255
+ await fs11.access(targetDir);
1196
4256
  if (acceptDefaults) {
1197
4257
  console.error(`Error: Directory ${folderName} already exists`);
1198
4258
  process.exit(1);
@@ -1207,22 +4267,46 @@ async function createBookkeepingRepo(name, options = {}) {
1207
4267
  }
1208
4268
  } catch {}
1209
4269
  const gitSpinner = ora("Initializing repository...").start();
1210
- await fs5.mkdir(targetDir, { recursive: true });
4270
+ await fs11.mkdir(targetDir, { recursive: true });
1211
4271
  await initGitRepo(targetDir);
1212
4272
  gitSpinner.succeed("Repository initialized");
1213
- await createEnvFile(targetDir, {
1214
- PROVIDER: "bokio",
1215
- BOKIO_TOKEN: token,
1216
- BOKIO_COMPANY_ID: companyId.trim()
1217
- });
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
+ }
1218
4293
  console.log(" Created .env with credentials");
1219
4294
  await createAgentsFile(targetDir, {
1220
4295
  companyName,
1221
- provider: "Bokio"
4296
+ provider: provider === "bokio" ? "Bokio" : "Fortnox"
1222
4297
  });
1223
4298
  console.log(" Created AGENTS.md and CLAUDE.md symlink");
1224
4299
  const accountsSpinner = ora("Syncing chart of accounts...").start();
1225
- 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
+ }
1226
4310
  accountsSpinner.succeed(`Synced ${accountsCount} accounts to accounts.yaml`);
1227
4311
  let shouldSync;
1228
4312
  if (options.sync !== undefined) {
@@ -1231,28 +4315,57 @@ async function createBookkeepingRepo(name, options = {}) {
1231
4315
  shouldSync = true;
1232
4316
  } else {
1233
4317
  shouldSync = await confirm({
1234
- message: "Sync existing journal entries from Bokio?",
4318
+ message: `Sync existing journal entries from ${provider === "bokio" ? "Bokio" : "Fortnox"}?`,
1235
4319
  default: true
1236
4320
  });
1237
4321
  }
1238
4322
  let entriesCount = 0;
1239
4323
  let fiscalYearsCount = 0;
1240
4324
  if (shouldSync) {
1241
- const shouldDownloadFiles = options.downloadFiles !== false;
1242
- const result = await syncJournalEntries2(client2, targetDir, { downloadFiles: shouldDownloadFiles }, (progress) => {
1243
- process.stdout.write(`\r Syncing: ${progress.current}/${progress.total} entries`);
1244
- });
1245
- entriesCount = result.entriesCount;
1246
- fiscalYearsCount = result.fiscalYearsCount;
1247
- if (result.entriesWithFilesDownloaded > 0) {
1248
- console.log(`
1249
- 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
+ }
1250
4362
  }
1251
4363
  console.log(" Sync complete!");
1252
4364
  }
1253
4365
  const commitSpinner = ora("Creating initial commit...").start();
1254
4366
  await commitAll(targetDir, "Initial commit");
1255
4367
  commitSpinner.succeed("Initial commit created");
4368
+ const providerName = provider === "bokio" ? "Bokio" : "Fortnox";
1256
4369
  console.log(`
1257
4370
  Success! Created ${folderName} at ${targetDir}
1258
4371
 
@@ -1268,24 +4381,25 @@ async function createBookkeepingRepo(name, options = {}) {
1268
4381
  journal-entries/FY-2024/A-001-2024-01-15-invoice/entry.yaml
1269
4382
 
1270
4383
  Commands:
1271
- kvitton sync-journal Sync journal entries from Bokio
1272
- 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}
1273
4386
  kvitton company-info Display company information
1274
4387
  `);
1275
4388
  }
1276
4389
 
1277
4390
  // src/index.ts
1278
4391
  var program = new Command;
1279
- 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", `
1280
4393
  Environment Variables:
1281
- 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)
1282
4397
 
1283
4398
  Examples:
1284
4399
  $ create-kvitton
1285
4400
  $ create-kvitton my-company-books
1286
- $ create-kvitton my-repo --company-id abc-123-def --sync
1287
- $ create-kvitton my-repo -c abc-123 --no-download-files
1288
- $ 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
1289
4403
  `).action((name, options) => {
1290
4404
  createBookkeepingRepo(name, options);
1291
4405
  });