@moatless/bookkeeping 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (93) hide show
  1. package/dist/accounting/index.d.ts +9 -0
  2. package/dist/accounting/index.js +14 -0
  3. package/dist/accounting/line-generator.d.ts +34 -0
  4. package/dist/accounting/line-generator.js +136 -0
  5. package/dist/accounting/tax-codes.d.ts +32 -0
  6. package/dist/accounting/tax-codes.js +279 -0
  7. package/dist/accounting/traktamente-rates.d.ts +48 -0
  8. package/dist/accounting/traktamente-rates.js +325 -0
  9. package/dist/accounting/types.d.ts +69 -0
  10. package/dist/accounting/types.js +5 -0
  11. package/dist/accounting/validation.d.ts +41 -0
  12. package/dist/accounting/validation.js +118 -0
  13. package/dist/auth/fortnox-login.d.ts +15 -0
  14. package/dist/auth/fortnox-login.js +170 -0
  15. package/dist/auth/index.d.ts +3 -0
  16. package/dist/auth/index.js +3 -0
  17. package/dist/auth/prompts.d.ts +6 -0
  18. package/dist/auth/prompts.js +56 -0
  19. package/dist/auth/token-store.d.ts +19 -0
  20. package/dist/auth/token-store.js +54 -0
  21. package/dist/index.d.ts +10 -0
  22. package/dist/index.js +21 -0
  23. package/dist/progress/index.d.ts +1 -0
  24. package/dist/progress/index.js +1 -0
  25. package/dist/progress/sync-progress.d.ts +31 -0
  26. package/dist/progress/sync-progress.js +65 -0
  27. package/dist/services/bokio-journal.d.ts +29 -0
  28. package/dist/services/bokio-journal.js +175 -0
  29. package/dist/services/document-download.d.ts +46 -0
  30. package/dist/services/document-download.js +105 -0
  31. package/dist/services/fortnox-inbox.d.ts +18 -0
  32. package/dist/services/fortnox-inbox.js +150 -0
  33. package/dist/services/fortnox-journal.d.ts +22 -0
  34. package/dist/services/fortnox-journal.js +166 -0
  35. package/dist/services/index.d.ts +6 -0
  36. package/dist/services/index.js +6 -0
  37. package/dist/services/journal-sync.d.ts +23 -0
  38. package/dist/services/journal-sync.js +124 -0
  39. package/dist/services/journal.service.d.ts +45 -0
  40. package/dist/services/journal.service.js +204 -0
  41. package/dist/storage/filesystem.d.ts +49 -0
  42. package/dist/storage/filesystem.js +122 -0
  43. package/dist/storage/index.d.ts +2 -0
  44. package/dist/storage/index.js +1 -0
  45. package/dist/storage/interface.d.ts +48 -0
  46. package/dist/storage/interface.js +5 -0
  47. package/dist/sync-types.d.ts +61 -0
  48. package/dist/sync-types.js +1 -0
  49. package/dist/transformers/bokio.d.ts +10 -0
  50. package/dist/transformers/bokio.js +56 -0
  51. package/dist/transformers/fortnox.d.ts +6 -0
  52. package/dist/transformers/fortnox.js +39 -0
  53. package/dist/transformers/index.d.ts +3 -0
  54. package/dist/transformers/index.js +2 -0
  55. package/dist/types/discarded-item.d.ts +29 -0
  56. package/dist/types/discarded-item.js +1 -0
  57. package/dist/types/document.d.ts +63 -0
  58. package/dist/types/document.js +9 -0
  59. package/dist/types/exported-document.d.ts +61 -0
  60. package/dist/types/exported-document.js +9 -0
  61. package/dist/types/exported-fiscal-year.d.ts +10 -0
  62. package/dist/types/exported-fiscal-year.js +1 -0
  63. package/dist/types/exported-inbox-document.d.ts +14 -0
  64. package/dist/types/exported-inbox-document.js +10 -0
  65. package/dist/types/fiscal-year.d.ts +10 -0
  66. package/dist/types/fiscal-year.js +1 -0
  67. package/dist/types/index.d.ts +10 -0
  68. package/dist/types/index.js +10 -0
  69. package/dist/types/journal-entry.d.ts +79 -0
  70. package/dist/types/journal-entry.js +12 -0
  71. package/dist/types/ledger-account.d.ts +5 -0
  72. package/dist/types/ledger-account.js +1 -0
  73. package/dist/utils/file-namer.d.ts +48 -0
  74. package/dist/utils/file-namer.js +80 -0
  75. package/dist/utils/git.d.ts +9 -0
  76. package/dist/utils/git.js +41 -0
  77. package/dist/utils/index.d.ts +6 -0
  78. package/dist/utils/index.js +6 -0
  79. package/dist/utils/paths.d.ts +17 -0
  80. package/dist/utils/paths.js +24 -0
  81. package/dist/utils/retry.d.ts +17 -0
  82. package/dist/utils/retry.js +48 -0
  83. package/dist/utils/templates.d.ts +12 -0
  84. package/dist/utils/templates.js +222 -0
  85. package/dist/utils/yaml.d.ts +12 -0
  86. package/dist/utils/yaml.js +47 -0
  87. package/dist/yaml/entry-helpers.d.ts +57 -0
  88. package/dist/yaml/entry-helpers.js +125 -0
  89. package/dist/yaml/index.d.ts +2 -0
  90. package/dist/yaml/index.js +2 -0
  91. package/dist/yaml/yaml-serializer.d.ts +21 -0
  92. package/dist/yaml/yaml-serializer.js +60 -0
  93. package/package.json +37 -0
@@ -0,0 +1,29 @@
1
+ /**
2
+ * Error information from a failed deletion attempt
3
+ */
4
+ export interface DeletionError {
5
+ /** When the deletion was attempted */
6
+ attemptedAt: string;
7
+ /** HTTP status code from the API */
8
+ statusCode?: number;
9
+ /** Error code from the integration (e.g., Fortnox error code) */
10
+ errorCode?: number | string;
11
+ /** Human-readable error message */
12
+ message: string;
13
+ }
14
+ /**
15
+ * Represents a discarded inbox item that should be removed from the source integration.
16
+ * Stored in inbox/discarded.json in the organization's storage.
17
+ */
18
+ export interface DiscardedItem {
19
+ /** Directory name in the inbox folder */
20
+ dir: string;
21
+ /** ID of the item in the source integration (e.g., Fortnox file ID) */
22
+ sourceId: string;
23
+ /** Which integration the item came from */
24
+ sourceIntegration: "fortnox" | "bokio";
25
+ /** Error from the last failed deletion attempt (if any) */
26
+ lastDeletionError?: DeletionError;
27
+ /** Number of failed deletion attempts */
28
+ deletionAttempts?: number;
29
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,63 @@
1
+ /**
2
+ * Exported document format - optimized for Git-based workflow
3
+ * Using camelCase for TypeScript compatibility
4
+ *
5
+ * This format is designed for human-readable YAML exports stored in Git repositories.
6
+ * It removes database-specific metadata (UUIDs, timestamps, sync data) and focuses
7
+ * on the document content and parsed data. Git provides version history and timestamps.
8
+ */
9
+ /**
10
+ * Monetary amount with currency
11
+ * Amount is stored as a string to preserve precision
12
+ */
13
+ export interface DocumentMoneyAmount {
14
+ amount: string;
15
+ currency: string;
16
+ }
17
+ /**
18
+ * Tax detail line item
19
+ */
20
+ export interface TaxDetail {
21
+ rate: number;
22
+ amount: string;
23
+ description?: string;
24
+ }
25
+ /**
26
+ * Document kind/type
27
+ */
28
+ export type DocumentKind = "RECEIPT" | "SUPPLIER_INVOICE" | "CUSTOMER_INVOICE" | "TRAKTAMENTE" | "OTHER";
29
+ /**
30
+ * Document status in workflow
31
+ */
32
+ export type DocumentStatus = "DRAFT" | "REVIEWED" | "READY_TO_EXPORT" | "EXPORTED" | "ERROR" | "DISCARDED" | "POSTED";
33
+ /**
34
+ * Complete exported document with parsed data
35
+ *
36
+ * Git-based workflow assumptions:
37
+ * - Creation/update timestamps: Use git log
38
+ * - Author information: Use git commit author
39
+ * - File location: Same directory as YAML file
40
+ * - Organization context: Repository structure
41
+ */
42
+ export interface Document {
43
+ kind: DocumentKind;
44
+ status: DocumentStatus;
45
+ fileName: string;
46
+ mimeType: string;
47
+ documentDate?: string;
48
+ documentNumber?: string;
49
+ description?: string;
50
+ counterparty?: string;
51
+ totalAmount?: DocumentMoneyAmount;
52
+ totalTaxAmount?: DocumentMoneyAmount;
53
+ subTotal?: DocumentMoneyAmount;
54
+ taxDetails?: TaxDetail[];
55
+ dueDate?: string;
56
+ currencyRate?: number;
57
+ vendorTaxId?: string;
58
+ interpretationError?: string;
59
+ sourceIntegration?: "fortnox" | "bokio";
60
+ sourceId?: string;
61
+ uploadedAt?: string;
62
+ interpretedAt?: string;
63
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Exported document format - optimized for Git-based workflow
3
+ * Using camelCase for TypeScript compatibility
4
+ *
5
+ * This format is designed for human-readable YAML exports stored in Git repositories.
6
+ * It removes database-specific metadata (UUIDs, timestamps, sync data) and focuses
7
+ * on the document content and parsed data. Git provides version history and timestamps.
8
+ */
9
+ export {};
@@ -0,0 +1,61 @@
1
+ /**
2
+ * Exported document format - optimized for Git-based workflow
3
+ * Using camelCase for TypeScript compatibility
4
+ *
5
+ * This format is designed for human-readable YAML exports stored in Git repositories.
6
+ * It removes database-specific metadata (UUIDs, timestamps, sync data) and focuses
7
+ * on the document content and parsed data. Git provides version history and timestamps.
8
+ */
9
+ /**
10
+ * Monetary amount with currency
11
+ * Amount is stored as a string to preserve precision
12
+ */
13
+ export interface ExportedMoneyAmount {
14
+ amount: string;
15
+ currency: string;
16
+ }
17
+ /**
18
+ * Tax detail line item
19
+ */
20
+ export interface ExportedTaxDetail {
21
+ rate: number;
22
+ amount: string;
23
+ description?: string;
24
+ }
25
+ /**
26
+ * Document kind/type
27
+ */
28
+ export type DocumentKind = "RECEIPT" | "SUPPLIER_INVOICE" | "CUSTOMER_INVOICE" | "TRAKTAMENTE" | "OTHER";
29
+ /**
30
+ * Document status in workflow
31
+ */
32
+ export type DocumentStatus = "DRAFT" | "REVIEWED" | "READY_TO_EXPORT" | "EXPORTED" | "ERROR" | "DISCARDED" | "POSTED";
33
+ /**
34
+ * Complete exported document with parsed data
35
+ *
36
+ * Git-based workflow assumptions:
37
+ * - Creation/update timestamps: Use git log
38
+ * - Author information: Use git commit author
39
+ * - File location: Same directory as YAML file
40
+ * - Organization context: Repository structure
41
+ */
42
+ export interface ExportedDocument {
43
+ kind: DocumentKind;
44
+ status: DocumentStatus;
45
+ fileName: string;
46
+ mimeType: string;
47
+ documentDate?: string;
48
+ documentNumber?: string;
49
+ description?: string;
50
+ counterparty?: string;
51
+ totalAmount?: ExportedMoneyAmount;
52
+ totalTaxAmount?: ExportedMoneyAmount;
53
+ subTotal?: ExportedMoneyAmount;
54
+ taxDetails?: ExportedTaxDetail[];
55
+ dueDate?: string;
56
+ currencyRate?: number;
57
+ vendorTaxId?: string;
58
+ interpretationError?: string;
59
+ sourceIntegration?: "fortnox" | "bokio";
60
+ sourceId?: string;
61
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Exported document format - optimized for Git-based workflow
3
+ * Using camelCase for TypeScript compatibility
4
+ *
5
+ * This format is designed for human-readable YAML exports stored in Git repositories.
6
+ * It removes database-specific metadata (UUIDs, timestamps, sync data) and focuses
7
+ * on the document content and parsed data. Git provides version history and timestamps.
8
+ */
9
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Exported fiscal year structure for Git storage (YAML format)
3
+ */
4
+ export interface ExportedFiscalYear {
5
+ name: string;
6
+ startDate: string;
7
+ endDate: string;
8
+ status: "OPEN" | "CLOSED";
9
+ externalId?: string;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,14 @@
1
+ /**
2
+ * Exported inbox document format - extends ExportedDocument for Git workflow
3
+ *
4
+ * Inbox documents are documents that haven't been fully processed yet:
5
+ * - Not yet linked to a journal entry (journal_entry_id IS NULL), OR
6
+ * - Linked to a DRAFT journal entry (not yet posted)
7
+ *
8
+ * This format includes source tracking and optional suggested journal entries.
9
+ */
10
+ import type { ExportedDocument } from "./exported-document";
11
+ export interface ExportedInboxDocument extends ExportedDocument {
12
+ uploadedAt?: string;
13
+ interpretedAt?: string;
14
+ }
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Exported inbox document format - extends ExportedDocument for Git workflow
3
+ *
4
+ * Inbox documents are documents that haven't been fully processed yet:
5
+ * - Not yet linked to a journal entry (journal_entry_id IS NULL), OR
6
+ * - Linked to a DRAFT journal entry (not yet posted)
7
+ *
8
+ * This format includes source tracking and optional suggested journal entries.
9
+ */
10
+ export {};
@@ -0,0 +1,10 @@
1
+ /**
2
+ * Exported fiscal year structure for Git storage (YAML format)
3
+ */
4
+ export interface FiscalYear {
5
+ name: string;
6
+ startDate: string;
7
+ endDate: string;
8
+ status: "OPEN" | "CLOSED";
9
+ externalId?: string;
10
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,10 @@
1
+ export type ApiResponse = {
2
+ message: string;
3
+ success: true;
4
+ };
5
+ export * from "./journal-entry";
6
+ export * from "./document";
7
+ export type { Document as InboxDocument } from "./document";
8
+ export * from "./fiscal-year";
9
+ export * from "./ledger-account";
10
+ export * from "./discarded-item";
@@ -0,0 +1,10 @@
1
+ // Journal entry types (used across Git YAML, API, and client)
2
+ export * from "./journal-entry";
3
+ // Document types
4
+ export * from "./document";
5
+ // Fiscal year
6
+ export * from "./fiscal-year";
7
+ // Ledger account
8
+ export * from "./ledger-account";
9
+ // Discarded items
10
+ export * from "./discarded-item";
@@ -0,0 +1,79 @@
1
+ /**
2
+ * Unified Journal Entry types
3
+ *
4
+ * These types are used throughout the stack:
5
+ * - Git YAML storage (entry.yaml files)
6
+ * - Server API responses
7
+ * - Client state
8
+ *
9
+ * All monetary amounts use NUMBER type (not strings).
10
+ * All field names use camelCase.
11
+ */
12
+ /**
13
+ * Monetary amount with currency
14
+ */
15
+ export interface MoneyAmount {
16
+ amount: number;
17
+ currency: string;
18
+ originalAmount?: number;
19
+ originalCurrency?: string;
20
+ }
21
+ /**
22
+ * Single line in a journal entry
23
+ */
24
+ export interface JournalLine {
25
+ lineNumber: number;
26
+ account: string;
27
+ debit: MoneyAmount;
28
+ credit: MoneyAmount;
29
+ memo?: string;
30
+ taxCode?: string;
31
+ costCenter?: string;
32
+ /** Populated by API when account details are available */
33
+ ledgerAccountName?: string;
34
+ }
35
+ /**
36
+ * Journal entry series
37
+ * Different series are used for different transaction types:
38
+ * - A: General/miscellaneous transactions
39
+ * - B: Customer invoices (sales)
40
+ * - C: Customer payments (collections)
41
+ * - D: Supplier invoices (purchases)
42
+ * - E: Supplier payments (disbursements)
43
+ * - I: Period-end accruals and adjustments
44
+ * - M: Monthly/quarterly tax reporting
45
+ */
46
+ export type JournalEntrySeries = "A" | "B" | "C" | "D" | "E" | "I" | "M";
47
+ /**
48
+ * Journal entry status
49
+ */
50
+ export type JournalEntryStatus = "DRAFT" | "POSTED";
51
+ /**
52
+ * Complete journal entry
53
+ */
54
+ export interface JournalEntry {
55
+ /** Populated by API (derived from directory name), not stored in YAML */
56
+ id?: string;
57
+ series?: JournalEntrySeries;
58
+ entryNumber: number;
59
+ entryDate: string;
60
+ description: string;
61
+ status: JournalEntryStatus;
62
+ currency: string;
63
+ externalId?: string;
64
+ totalDebit: MoneyAmount;
65
+ totalCredit: MoneyAmount;
66
+ lines: JournalLine[];
67
+ postedAt?: string;
68
+ errorMessage?: string;
69
+ postingCheckpoint?: string;
70
+ voucherNumber?: number;
71
+ voucherSeriesCode?: string;
72
+ fortnoxFileId?: string;
73
+ sourceIntegration?: "fortnox" | "bokio";
74
+ sourceSyncedAt?: string;
75
+ /** If this entry is a reversal, the external ID of the entry it reverses */
76
+ reversingEntryExternalId?: string;
77
+ /** If this entry was reversed/cancelled, the external ID of the reversing entry */
78
+ reversedByEntryExternalId?: string;
79
+ }
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Unified Journal Entry types
3
+ *
4
+ * These types are used throughout the stack:
5
+ * - Git YAML storage (entry.yaml files)
6
+ * - Server API responses
7
+ * - Client state
8
+ *
9
+ * All monetary amounts use NUMBER type (not strings).
10
+ * All field names use camelCase.
11
+ */
12
+ export {};
@@ -0,0 +1,5 @@
1
+ export interface LedgerAccount {
2
+ code: string;
3
+ name: string;
4
+ description?: string;
5
+ }
@@ -0,0 +1 @@
1
+ export {};
@@ -0,0 +1,48 @@
1
+ /**
2
+ * File naming utilities for journal entries and fiscal years
3
+ */
4
+ interface JournalEntryInput {
5
+ voucher_series_code: string | null;
6
+ voucher_number: number | null;
7
+ entry_date: string;
8
+ description: string | null;
9
+ id?: string;
10
+ }
11
+ /**
12
+ * Generate directory name for a journal entry
13
+ * Format: {series}-{voucher-number}-{entry-date}-{description-truncated}
14
+ * Examples:
15
+ * - A-042-2024-01-15-invoice-from-acme-corp
16
+ * - B-001-2024-12-31-year-end-adjustment
17
+ * - 042-2024-01-15-purchase-equipment (no series)
18
+ */
19
+ export declare function journalEntryDirName(entry: JournalEntryInput): string;
20
+ /**
21
+ * Generate file path for a journal entry
22
+ * Format: journal-entries/FY-{year}/{entry-dir}/entry.yaml
23
+ */
24
+ export declare function journalEntryPath(fiscalYear: number, series: string | null, entryNumber: number, entryDate: string, description: string): string;
25
+ /**
26
+ * Get directory path from entry file path
27
+ * e.g., "journal-entries/FY-2024/A-042-2024-01-15-description/entry.yaml"
28
+ * -> "journal-entries/FY-2024/A-042-2024-01-15-description"
29
+ */
30
+ export declare function journalEntryDirFromPath(entryPath: string): string;
31
+ /**
32
+ * Generate directory name for a fiscal year
33
+ * Format: FY-{year}
34
+ * Examples:
35
+ * - FY-2024
36
+ * - FY-2025
37
+ */
38
+ export declare function fiscalYearDirName(year: number | {
39
+ start_date: string;
40
+ }): string;
41
+ /**
42
+ * Sanitize organization name for use as directory name
43
+ * Examples:
44
+ * - "Acme Corp AB" → "acme-corp-ab"
45
+ * - "Widgets & Gadgets Inc." → "widgets-gadgets-inc"
46
+ */
47
+ export declare function sanitizeOrgName(name: string): string;
48
+ export {};
@@ -0,0 +1,80 @@
1
+ /**
2
+ * File naming utilities for journal entries and fiscal years
3
+ */
4
+ /**
5
+ * Generate directory name for a journal entry
6
+ * Format: {series}-{voucher-number}-{entry-date}-{description-truncated}
7
+ * Examples:
8
+ * - A-042-2024-01-15-invoice-from-acme-corp
9
+ * - B-001-2024-12-31-year-end-adjustment
10
+ * - 042-2024-01-15-purchase-equipment (no series)
11
+ */
12
+ export function journalEntryDirName(entry) {
13
+ const series = entry.voucher_series_code || "";
14
+ const voucher = String(entry.voucher_number || "000").padStart(3, "0");
15
+ const date = entry.entry_date;
16
+ const description = slugify(entry.description || entry.id?.slice(0, 8) || "entry");
17
+ // Build parts array and filter out empty strings
18
+ const parts = [series, voucher, date, description].filter(Boolean);
19
+ return parts.join("-");
20
+ }
21
+ /**
22
+ * Generate file path for a journal entry
23
+ * Format: journal-entries/FY-{year}/{entry-dir}/entry.yaml
24
+ */
25
+ export function journalEntryPath(fiscalYear, series, entryNumber, entryDate, description) {
26
+ const fyDir = fiscalYearDirName(fiscalYear);
27
+ const entryDir = journalEntryDirName({
28
+ voucher_series_code: series,
29
+ voucher_number: entryNumber,
30
+ entry_date: entryDate,
31
+ description,
32
+ });
33
+ return `journal-entries/${fyDir}/${entryDir}/entry.yaml`;
34
+ }
35
+ /**
36
+ * Get directory path from entry file path
37
+ * e.g., "journal-entries/FY-2024/A-042-2024-01-15-description/entry.yaml"
38
+ * -> "journal-entries/FY-2024/A-042-2024-01-15-description"
39
+ */
40
+ export function journalEntryDirFromPath(entryPath) {
41
+ return entryPath.replace(/\/entry\.yaml$/, "");
42
+ }
43
+ /**
44
+ * Generate directory name for a fiscal year
45
+ * Format: FY-{year}
46
+ * Examples:
47
+ * - FY-2024
48
+ * - FY-2025
49
+ */
50
+ export function fiscalYearDirName(year) {
51
+ if (typeof year === "number") {
52
+ return `FY-${year}`;
53
+ }
54
+ return `FY-${year.start_date.slice(0, 4)}`;
55
+ }
56
+ /**
57
+ * Convert text to a URL-friendly slug
58
+ * - Converts to lowercase
59
+ * - Removes special characters
60
+ * - Replaces spaces with hyphens
61
+ * - Truncates to maxLength
62
+ */
63
+ function slugify(text, maxLength = 30) {
64
+ return text
65
+ .toLowerCase()
66
+ .slice(0, maxLength)
67
+ .replace(/[^a-z0-9\s-]/g, "") // Remove special chars
68
+ .replace(/\s+/g, "-") // Spaces to hyphens
69
+ .replace(/-+/g, "-") // Collapse multiple hyphens
70
+ .replace(/^-|-$/g, ""); // Trim hyphens
71
+ }
72
+ /**
73
+ * Sanitize organization name for use as directory name
74
+ * Examples:
75
+ * - "Acme Corp AB" → "acme-corp-ab"
76
+ * - "Widgets & Gadgets Inc." → "widgets-gadgets-inc"
77
+ */
78
+ export function sanitizeOrgName(name) {
79
+ return slugify(name, 50);
80
+ }
@@ -0,0 +1,9 @@
1
+ /**
2
+ * Initialize a new git repository with a .gitignore file
3
+ */
4
+ export declare function initGitRepo(repoPath: string): Promise<void>;
5
+ /**
6
+ * Stage all changes and commit with the given message.
7
+ * Returns true if a commit was made, false if there were no changes to commit.
8
+ */
9
+ export declare function commitAll(repoPath: string, message: string): Promise<boolean>;
@@ -0,0 +1,41 @@
1
+ import simpleGit from "simple-git";
2
+ import * as fs from "node:fs/promises";
3
+ import * as path from "node:path";
4
+ const DEFAULT_GITIGNORE = `# Environment files with secrets
5
+ .env
6
+ .env.*
7
+
8
+ # CLI cache
9
+ .kvitton/
10
+
11
+ # OS files
12
+ .DS_Store
13
+ Thumbs.db
14
+
15
+ # Editor files
16
+ .vscode/
17
+ .idea/
18
+ *.swp
19
+ `;
20
+ /**
21
+ * Initialize a new git repository with a .gitignore file
22
+ */
23
+ export async function initGitRepo(repoPath) {
24
+ const git = simpleGit(repoPath);
25
+ await git.init();
26
+ await fs.writeFile(path.join(repoPath, ".gitignore"), DEFAULT_GITIGNORE);
27
+ }
28
+ /**
29
+ * Stage all changes and commit with the given message.
30
+ * Returns true if a commit was made, false if there were no changes to commit.
31
+ */
32
+ export async function commitAll(repoPath, message) {
33
+ const git = simpleGit(repoPath);
34
+ await git.add(".");
35
+ const status = await git.status();
36
+ if (status.files.length > 0) {
37
+ await git.commit(message);
38
+ return true;
39
+ }
40
+ return false;
41
+ }
@@ -0,0 +1,6 @@
1
+ export { journalEntryDirName, journalEntryPath, journalEntryDirFromPath, fiscalYearDirName, sanitizeOrgName, } from "./file-namer";
2
+ export { parseYaml, toYaml } from "./yaml";
3
+ export { withRetry, isRateLimitError, type RetryOptions } from "./retry";
4
+ export { initGitRepo, commitAll } from "./git";
5
+ export { getAgentsTemplate, renderAgentsTemplate, type RenderAgentsTemplateOptions, } from "./templates";
6
+ export { KVITTON_DIR, getKvittonDir, getTokensDir, getCacheDir, } from "./paths";
@@ -0,0 +1,6 @@
1
+ export { journalEntryDirName, journalEntryPath, journalEntryDirFromPath, fiscalYearDirName, sanitizeOrgName, } from "./file-namer";
2
+ export { parseYaml, toYaml } from "./yaml";
3
+ export { withRetry, isRateLimitError } from "./retry";
4
+ export { initGitRepo, commitAll } from "./git";
5
+ export { getAgentsTemplate, renderAgentsTemplate, } from "./templates";
6
+ export { KVITTON_DIR, getKvittonDir, getTokensDir, getCacheDir, } from "./paths";
@@ -0,0 +1,17 @@
1
+ /**
2
+ * The hidden directory used for internal storage (tokens, cache, etc.)
3
+ * inside the accounting repository.
4
+ */
5
+ export declare const KVITTON_DIR = ".kvitton";
6
+ /**
7
+ * Get the base .kvitton directory path for a given working directory.
8
+ */
9
+ export declare function getKvittonDir(cwd: string): string;
10
+ /**
11
+ * Get the tokens directory path for storing OAuth tokens.
12
+ */
13
+ export declare function getTokensDir(cwd: string): string;
14
+ /**
15
+ * Get the cache directory path for storing cached data (e.g., currency rates).
16
+ */
17
+ export declare function getCacheDir(cwd: string): string;
@@ -0,0 +1,24 @@
1
+ import * as path from "node:path";
2
+ /**
3
+ * The hidden directory used for internal storage (tokens, cache, etc.)
4
+ * inside the accounting repository.
5
+ */
6
+ export const KVITTON_DIR = ".kvitton";
7
+ /**
8
+ * Get the base .kvitton directory path for a given working directory.
9
+ */
10
+ export function getKvittonDir(cwd) {
11
+ return path.join(cwd, KVITTON_DIR);
12
+ }
13
+ /**
14
+ * Get the tokens directory path for storing OAuth tokens.
15
+ */
16
+ export function getTokensDir(cwd) {
17
+ return path.join(cwd, KVITTON_DIR, "tokens");
18
+ }
19
+ /**
20
+ * Get the cache directory path for storing cached data (e.g., currency rates).
21
+ */
22
+ export function getCacheDir(cwd) {
23
+ return path.join(cwd, KVITTON_DIR, "cache");
24
+ }
@@ -0,0 +1,17 @@
1
+ /**
2
+ * Retry utility with exponential backoff
3
+ */
4
+ export interface RetryOptions {
5
+ maxRetries?: number;
6
+ baseDelayMs?: number;
7
+ shouldRetry?: (error: unknown) => boolean;
8
+ onRetry?: (attempt: number, error: unknown, delayMs: number) => void;
9
+ }
10
+ /**
11
+ * Default check for rate limit errors
12
+ */
13
+ export declare function isRateLimitError(error: unknown): boolean;
14
+ /**
15
+ * Execute a function with retry logic and exponential backoff
16
+ */
17
+ export declare function withRetry<T>(fn: () => Promise<T>, options?: RetryOptions): Promise<T>;
@@ -0,0 +1,48 @@
1
+ /**
2
+ * Retry utility with exponential backoff
3
+ */
4
+ /**
5
+ * Default check for rate limit errors
6
+ */
7
+ export function isRateLimitError(error) {
8
+ if (error && typeof error === "object") {
9
+ const err = error;
10
+ if (err.code === "RATE_LIMIT_EXCEEDED")
11
+ return true;
12
+ if (err.statusCode === 429)
13
+ return true;
14
+ }
15
+ return false;
16
+ }
17
+ /**
18
+ * Execute a function with retry logic and exponential backoff
19
+ */
20
+ export async function withRetry(fn, options = {}) {
21
+ const { maxRetries = 3, baseDelayMs = 2000, shouldRetry = isRateLimitError, onRetry, } = options;
22
+ let lastError;
23
+ for (let attempt = 0; attempt <= maxRetries; attempt++) {
24
+ try {
25
+ return await fn();
26
+ }
27
+ catch (err) {
28
+ lastError = err;
29
+ // Check if we should retry
30
+ if (attempt < maxRetries && shouldRetry(err)) {
31
+ // Exponential backoff: 2s, 4s, 8s, ...
32
+ const delayMs = baseDelayMs * 2 ** attempt;
33
+ onRetry?.(attempt + 1, err, delayMs);
34
+ await sleep(delayMs);
35
+ continue;
36
+ }
37
+ // Don't retry - rethrow
38
+ throw err;
39
+ }
40
+ }
41
+ throw lastError;
42
+ }
43
+ /**
44
+ * Sleep for a given number of milliseconds
45
+ */
46
+ function sleep(ms) {
47
+ return new Promise((resolve) => setTimeout(resolve, ms));
48
+ }