optimal-cli 1.0.0 → 1.0.1

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 (135) hide show
  1. package/dist/bin/optimal.d.ts +2 -0
  2. package/dist/bin/optimal.js +1590 -0
  3. package/dist/lib/assets/index.d.ts +79 -0
  4. package/dist/lib/assets/index.js +153 -0
  5. package/dist/lib/assets.d.ts +20 -0
  6. package/dist/lib/assets.js +112 -0
  7. package/dist/lib/auth/index.d.ts +83 -0
  8. package/dist/lib/auth/index.js +146 -0
  9. package/dist/lib/board/index.d.ts +39 -0
  10. package/dist/lib/board/index.js +285 -0
  11. package/dist/lib/board/types.d.ts +111 -0
  12. package/dist/lib/board/types.js +1 -0
  13. package/dist/lib/bot/claim.d.ts +3 -0
  14. package/dist/lib/bot/claim.js +20 -0
  15. package/dist/lib/bot/coordinator.d.ts +27 -0
  16. package/dist/lib/bot/coordinator.js +178 -0
  17. package/dist/lib/bot/heartbeat.d.ts +6 -0
  18. package/dist/lib/bot/heartbeat.js +30 -0
  19. package/dist/lib/bot/index.d.ts +9 -0
  20. package/dist/lib/bot/index.js +6 -0
  21. package/dist/lib/bot/protocol.d.ts +12 -0
  22. package/dist/lib/bot/protocol.js +74 -0
  23. package/dist/lib/bot/reporter.d.ts +3 -0
  24. package/dist/lib/bot/reporter.js +27 -0
  25. package/dist/lib/bot/skills.d.ts +26 -0
  26. package/dist/lib/bot/skills.js +69 -0
  27. package/dist/lib/budget/projections.d.ts +115 -0
  28. package/dist/lib/budget/projections.js +384 -0
  29. package/dist/lib/budget/scenarios.d.ts +93 -0
  30. package/dist/lib/budget/scenarios.js +214 -0
  31. package/dist/lib/cms/publish-blog.d.ts +62 -0
  32. package/dist/lib/cms/publish-blog.js +74 -0
  33. package/dist/lib/cms/strapi-client.d.ts +123 -0
  34. package/dist/lib/cms/strapi-client.js +213 -0
  35. package/dist/lib/config/registry.d.ts +17 -0
  36. package/dist/lib/config/registry.js +182 -0
  37. package/dist/lib/config/schema.d.ts +31 -0
  38. package/dist/lib/config/schema.js +25 -0
  39. package/dist/lib/config.d.ts +55 -0
  40. package/dist/lib/config.js +206 -0
  41. package/dist/lib/errors.d.ts +25 -0
  42. package/dist/lib/errors.js +91 -0
  43. package/dist/lib/format.d.ts +28 -0
  44. package/dist/lib/format.js +98 -0
  45. package/dist/lib/infra/deploy.d.ts +29 -0
  46. package/dist/lib/infra/deploy.js +58 -0
  47. package/dist/lib/infra/migrate.d.ts +34 -0
  48. package/dist/lib/infra/migrate.js +103 -0
  49. package/dist/lib/newsletter/distribute.d.ts +52 -0
  50. package/dist/lib/newsletter/distribute.js +193 -0
  51. package/{lib/newsletter/generate-insurance.ts → dist/lib/newsletter/generate-insurance.d.ts} +7 -24
  52. package/dist/lib/newsletter/generate-insurance.js +36 -0
  53. package/dist/lib/newsletter/generate.d.ts +104 -0
  54. package/dist/lib/newsletter/generate.js +571 -0
  55. package/dist/lib/returnpro/anomalies.d.ts +64 -0
  56. package/dist/lib/returnpro/anomalies.js +166 -0
  57. package/dist/lib/returnpro/audit.d.ts +32 -0
  58. package/dist/lib/returnpro/audit.js +147 -0
  59. package/dist/lib/returnpro/diagnose.d.ts +52 -0
  60. package/dist/lib/returnpro/diagnose.js +281 -0
  61. package/dist/lib/returnpro/kpis.d.ts +32 -0
  62. package/dist/lib/returnpro/kpis.js +192 -0
  63. package/dist/lib/returnpro/templates.d.ts +48 -0
  64. package/dist/lib/returnpro/templates.js +229 -0
  65. package/dist/lib/returnpro/upload-income.d.ts +25 -0
  66. package/dist/lib/returnpro/upload-income.js +235 -0
  67. package/dist/lib/returnpro/upload-netsuite.d.ts +37 -0
  68. package/dist/lib/returnpro/upload-netsuite.js +566 -0
  69. package/dist/lib/returnpro/upload-r1.d.ts +48 -0
  70. package/dist/lib/returnpro/upload-r1.js +398 -0
  71. package/dist/lib/returnpro/validate.d.ts +37 -0
  72. package/dist/lib/returnpro/validate.js +124 -0
  73. package/dist/lib/social/meta.d.ts +90 -0
  74. package/dist/lib/social/meta.js +160 -0
  75. package/dist/lib/social/post-generator.d.ts +83 -0
  76. package/dist/lib/social/post-generator.js +333 -0
  77. package/dist/lib/social/publish.d.ts +66 -0
  78. package/dist/lib/social/publish.js +226 -0
  79. package/dist/lib/social/scraper.d.ts +67 -0
  80. package/dist/lib/social/scraper.js +361 -0
  81. package/dist/lib/supabase.d.ts +4 -0
  82. package/dist/lib/supabase.js +20 -0
  83. package/dist/lib/transactions/delete-batch.d.ts +60 -0
  84. package/dist/lib/transactions/delete-batch.js +203 -0
  85. package/dist/lib/transactions/ingest.d.ts +43 -0
  86. package/dist/lib/transactions/ingest.js +555 -0
  87. package/dist/lib/transactions/stamp.d.ts +51 -0
  88. package/dist/lib/transactions/stamp.js +524 -0
  89. package/package.json +3 -4
  90. package/bin/optimal.ts +0 -1731
  91. package/lib/assets/index.ts +0 -225
  92. package/lib/assets.ts +0 -124
  93. package/lib/auth/index.ts +0 -189
  94. package/lib/board/index.ts +0 -309
  95. package/lib/board/types.ts +0 -124
  96. package/lib/bot/claim.ts +0 -43
  97. package/lib/bot/coordinator.ts +0 -254
  98. package/lib/bot/heartbeat.ts +0 -37
  99. package/lib/bot/index.ts +0 -9
  100. package/lib/bot/protocol.ts +0 -99
  101. package/lib/bot/reporter.ts +0 -42
  102. package/lib/bot/skills.ts +0 -81
  103. package/lib/budget/projections.ts +0 -561
  104. package/lib/budget/scenarios.ts +0 -312
  105. package/lib/cms/publish-blog.ts +0 -129
  106. package/lib/cms/strapi-client.ts +0 -302
  107. package/lib/config/registry.ts +0 -228
  108. package/lib/config/schema.ts +0 -58
  109. package/lib/config.ts +0 -247
  110. package/lib/errors.ts +0 -129
  111. package/lib/format.ts +0 -120
  112. package/lib/infra/.gitkeep +0 -0
  113. package/lib/infra/deploy.ts +0 -70
  114. package/lib/infra/migrate.ts +0 -141
  115. package/lib/newsletter/.gitkeep +0 -0
  116. package/lib/newsletter/distribute.ts +0 -256
  117. package/lib/newsletter/generate.ts +0 -735
  118. package/lib/returnpro/.gitkeep +0 -0
  119. package/lib/returnpro/anomalies.ts +0 -258
  120. package/lib/returnpro/audit.ts +0 -194
  121. package/lib/returnpro/diagnose.ts +0 -400
  122. package/lib/returnpro/kpis.ts +0 -255
  123. package/lib/returnpro/templates.ts +0 -323
  124. package/lib/returnpro/upload-income.ts +0 -311
  125. package/lib/returnpro/upload-netsuite.ts +0 -696
  126. package/lib/returnpro/upload-r1.ts +0 -563
  127. package/lib/returnpro/validate.ts +0 -154
  128. package/lib/social/meta.ts +0 -228
  129. package/lib/social/post-generator.ts +0 -468
  130. package/lib/social/publish.ts +0 -301
  131. package/lib/social/scraper.ts +0 -503
  132. package/lib/supabase.ts +0 -25
  133. package/lib/transactions/delete-batch.ts +0 -258
  134. package/lib/transactions/ingest.ts +0 -659
  135. package/lib/transactions/stamp.ts +0 -654
@@ -0,0 +1,60 @@
1
+ /**
2
+ * Transaction & Staging Batch Deletion — Safe Preview and Execute
3
+ *
4
+ * Provides safe batch deletion of transactions (OptimalOS) and staging
5
+ * financials (ReturnPro) with preview mode defaulting to dryRun=true.
6
+ *
7
+ * Tables:
8
+ * - transactions → OptimalOS Supabase (getSupabase('optimal'))
9
+ * - stg_financials_raw → ReturnPro Supabase (getSupabase('returnpro'))
10
+ *
11
+ * Columns:
12
+ * transactions: id, user_id, date, description, amount, category, source, stamp_match_type, created_at
13
+ * stg_financials_raw: id, account_code, account_name, amount (TEXT), month (YYYY-MM), source, user_id, created_at
14
+ */
15
+ import 'dotenv/config';
16
+ export interface DeleteBatchOptions {
17
+ table: 'transactions' | 'stg_financials_raw';
18
+ userId?: string;
19
+ filters: {
20
+ dateFrom?: string;
21
+ dateTo?: string;
22
+ source?: string;
23
+ category?: string;
24
+ accountCode?: string;
25
+ month?: string;
26
+ };
27
+ dryRun?: boolean;
28
+ }
29
+ export interface DeleteBatchResult {
30
+ table: string;
31
+ deletedCount: number;
32
+ dryRun: boolean;
33
+ filters: Record<string, string>;
34
+ }
35
+ export interface PreviewResult {
36
+ table: string;
37
+ matchCount: number;
38
+ sample: Array<Record<string, unknown>>;
39
+ groupedCounts: Record<string, number>;
40
+ }
41
+ /**
42
+ * Preview what would be deleted without touching any data.
43
+ *
44
+ * Returns:
45
+ * - matchCount: total rows matching the filters
46
+ * - sample: first 10 matching rows
47
+ * - groupedCounts: row counts grouped by `source` (transactions) or `account_code` (staging)
48
+ */
49
+ export declare function previewBatch(opts: DeleteBatchOptions): Promise<PreviewResult>;
50
+ /**
51
+ * Delete matching rows in batch — or preview them without deleting (dryRun=true).
52
+ *
53
+ * Safety: dryRun defaults to TRUE. Caller must explicitly pass dryRun=false
54
+ * to execute an actual deletion.
55
+ *
56
+ * In dryRun mode: counts matching rows and returns deletedCount=0.
57
+ * In execute mode: issues a Supabase DELETE with the same filters and returns
58
+ * the number of rows deleted.
59
+ */
60
+ export declare function deleteBatch(opts: DeleteBatchOptions): Promise<DeleteBatchResult>;
@@ -0,0 +1,203 @@
1
+ /**
2
+ * Transaction & Staging Batch Deletion — Safe Preview and Execute
3
+ *
4
+ * Provides safe batch deletion of transactions (OptimalOS) and staging
5
+ * financials (ReturnPro) with preview mode defaulting to dryRun=true.
6
+ *
7
+ * Tables:
8
+ * - transactions → OptimalOS Supabase (getSupabase('optimal'))
9
+ * - stg_financials_raw → ReturnPro Supabase (getSupabase('returnpro'))
10
+ *
11
+ * Columns:
12
+ * transactions: id, user_id, date, description, amount, category, source, stamp_match_type, created_at
13
+ * stg_financials_raw: id, account_code, account_name, amount (TEXT), month (YYYY-MM), source, user_id, created_at
14
+ */
15
+ import 'dotenv/config';
16
+ import { getSupabase } from '../supabase.js';
17
+ // =============================================================================
18
+ // INTERNAL HELPERS
19
+ // =============================================================================
20
+ /**
21
+ * Return the correct Supabase client for the given table.
22
+ */
23
+ function getClientForTable(table) {
24
+ return table === 'transactions'
25
+ ? getSupabase('optimal')
26
+ : getSupabase('returnpro');
27
+ }
28
+ /**
29
+ * Apply the shared set of filters to a Supabase query builder.
30
+ * Works for both SELECT and DELETE queries because both are PostgREST filters.
31
+ *
32
+ * For `stg_financials_raw`:
33
+ * - dateFrom / dateTo are ignored (use `month` instead)
34
+ * - month is applied as an eq filter on the `month` column
35
+ * - accountCode is applied as an eq filter on `account_code`
36
+ *
37
+ * For `transactions`:
38
+ * - dateFrom / dateTo are applied as gte/lte on `date`
39
+ * - source is applied as an eq filter on `source`
40
+ * - category is applied as an eq filter on `category`
41
+ * - userId is applied as an eq filter on `user_id`
42
+ */
43
+ // eslint-disable-next-line @typescript-eslint/no-explicit-any
44
+ function applyFilters(query, table, userId, filters) {
45
+ let q = query;
46
+ if (table === 'transactions') {
47
+ if (userId)
48
+ q = q.eq('user_id', userId);
49
+ if (filters.dateFrom)
50
+ q = q.gte('date', filters.dateFrom);
51
+ if (filters.dateTo)
52
+ q = q.lte('date', filters.dateTo);
53
+ if (filters.source)
54
+ q = q.eq('source', filters.source);
55
+ if (filters.category)
56
+ q = q.eq('category', filters.category);
57
+ }
58
+ else {
59
+ // stg_financials_raw
60
+ if (userId)
61
+ q = q.eq('user_id', userId);
62
+ if (filters.month)
63
+ q = q.eq('month', filters.month);
64
+ if (filters.accountCode)
65
+ q = q.eq('account_code', filters.accountCode);
66
+ if (filters.source)
67
+ q = q.eq('source', filters.source);
68
+ }
69
+ return q;
70
+ }
71
+ /**
72
+ * Serialize active filters for the result record (human-readable).
73
+ */
74
+ function serializeFilters(table, userId, filters) {
75
+ const out = {};
76
+ if (userId)
77
+ out.user_id = userId;
78
+ if (table === 'transactions') {
79
+ if (filters.dateFrom)
80
+ out.dateFrom = filters.dateFrom;
81
+ if (filters.dateTo)
82
+ out.dateTo = filters.dateTo;
83
+ if (filters.source)
84
+ out.source = filters.source;
85
+ if (filters.category)
86
+ out.category = filters.category;
87
+ }
88
+ else {
89
+ if (filters.month)
90
+ out.month = filters.month;
91
+ if (filters.accountCode)
92
+ out.accountCode = filters.accountCode;
93
+ if (filters.source)
94
+ out.source = filters.source;
95
+ }
96
+ return out;
97
+ }
98
+ // =============================================================================
99
+ // PUBLIC FUNCTIONS
100
+ // =============================================================================
101
+ /**
102
+ * Preview what would be deleted without touching any data.
103
+ *
104
+ * Returns:
105
+ * - matchCount: total rows matching the filters
106
+ * - sample: first 10 matching rows
107
+ * - groupedCounts: row counts grouped by `source` (transactions) or `account_code` (staging)
108
+ */
109
+ export async function previewBatch(opts) {
110
+ const { table, userId, filters } = opts;
111
+ const supabase = getClientForTable(table);
112
+ // --- Count matching rows ---
113
+ const countQuery = supabase
114
+ .from(table)
115
+ .select('*', { count: 'exact', head: true });
116
+ const countQueryWithFilters = applyFilters(countQuery, table, userId, filters);
117
+ const { count, error: countError } = await countQueryWithFilters;
118
+ if (countError) {
119
+ throw new Error(`previewBatch count error on ${table}: ${countError.message}`);
120
+ }
121
+ const matchCount = count ?? 0;
122
+ // --- Fetch sample rows (first 10) ---
123
+ const sampleQuery = supabase
124
+ .from(table)
125
+ .select('*')
126
+ .limit(10);
127
+ const sampleQueryWithFilters = applyFilters(sampleQuery, table, userId, filters);
128
+ const { data: sampleData, error: sampleError } = await sampleQueryWithFilters;
129
+ if (sampleError) {
130
+ throw new Error(`previewBatch sample error on ${table}: ${sampleError.message}`);
131
+ }
132
+ const sample = (sampleData ?? []);
133
+ // --- Grouped counts ---
134
+ // Group by `source` for transactions, `account_code` for staging
135
+ const groupCol = table === 'transactions' ? 'source' : 'account_code';
136
+ const groupQuery = supabase
137
+ .from(table)
138
+ .select(groupCol);
139
+ const groupQueryWithFilters = applyFilters(groupQuery, table, userId, filters);
140
+ const { data: groupData, error: groupError } = await groupQueryWithFilters;
141
+ if (groupError) {
142
+ throw new Error(`previewBatch group error on ${table}: ${groupError.message}`);
143
+ }
144
+ const groupedCounts = {};
145
+ for (const row of (groupData ?? [])) {
146
+ const key = row[groupCol] ?? '(unknown)';
147
+ groupedCounts[key] = (groupedCounts[key] ?? 0) + 1;
148
+ }
149
+ return {
150
+ table,
151
+ matchCount,
152
+ sample,
153
+ groupedCounts,
154
+ };
155
+ }
156
+ /**
157
+ * Delete matching rows in batch — or preview them without deleting (dryRun=true).
158
+ *
159
+ * Safety: dryRun defaults to TRUE. Caller must explicitly pass dryRun=false
160
+ * to execute an actual deletion.
161
+ *
162
+ * In dryRun mode: counts matching rows and returns deletedCount=0.
163
+ * In execute mode: issues a Supabase DELETE with the same filters and returns
164
+ * the number of rows deleted.
165
+ */
166
+ export async function deleteBatch(opts) {
167
+ const { table, userId, filters } = opts;
168
+ const dryRun = opts.dryRun ?? true; // safe by default
169
+ const supabase = getClientForTable(table);
170
+ const serializedFilters = serializeFilters(table, userId, filters);
171
+ if (dryRun) {
172
+ // Count matching rows without deleting
173
+ const countQuery = supabase
174
+ .from(table)
175
+ .select('*', { count: 'exact', head: true });
176
+ const countQueryWithFilters = applyFilters(countQuery, table, userId, filters);
177
+ const { count, error } = await countQueryWithFilters;
178
+ if (error) {
179
+ throw new Error(`deleteBatch dry-run count error on ${table}: ${error.message}`);
180
+ }
181
+ return {
182
+ table,
183
+ deletedCount: 0,
184
+ dryRun: true,
185
+ filters: serializedFilters,
186
+ };
187
+ }
188
+ // Execute deletion
189
+ const deleteQuery = supabase
190
+ .from(table)
191
+ .delete({ count: 'exact' });
192
+ const deleteQueryWithFilters = applyFilters(deleteQuery, table, userId, filters);
193
+ const { count, error } = await deleteQueryWithFilters;
194
+ if (error) {
195
+ throw new Error(`deleteBatch execute error on ${table}: ${error.message}`);
196
+ }
197
+ return {
198
+ table,
199
+ deletedCount: count ?? 0,
200
+ dryRun: false,
201
+ filters: serializedFilters,
202
+ };
203
+ }
@@ -0,0 +1,43 @@
1
+ /**
2
+ * Transaction Ingestion — CSV Parsing & Deduplication
3
+ *
4
+ * Ported from OptimalOS:
5
+ * - /home/optimal/optimalos/app/api/csv/ingest/route.ts
6
+ * - /home/optimal/optimalos/lib/csv/upload.ts
7
+ * - /home/optimal/optimalos/lib/stamp-engine/normalizers/
8
+ * - /home/optimal/optimalos/lib/stamp-engine/format-detector.ts
9
+ *
10
+ * Reads a CSV file from disk, auto-detects bank format, parses into
11
+ * normalized transactions, deduplicates against existing rows in Supabase,
12
+ * and batch-inserts new records into the `transactions` table.
13
+ */
14
+ export interface RawTransaction {
15
+ date: string;
16
+ description: string;
17
+ amount: number;
18
+ originalCategory?: string;
19
+ transactionType?: string;
20
+ postDate?: string;
21
+ balance?: number;
22
+ extendedDetails?: string;
23
+ merchantAddress?: string;
24
+ }
25
+ export type BankFormat = 'chase_checking' | 'chase_credit' | 'discover' | 'amex' | 'generic' | 'unknown';
26
+ export interface IngestResult {
27
+ inserted: number;
28
+ skipped: number;
29
+ failed: number;
30
+ errors: string[];
31
+ format: BankFormat;
32
+ }
33
+ /**
34
+ * Ingest transactions from a CSV file.
35
+ *
36
+ * 1. Read & detect format
37
+ * 2. Parse into normalized transactions
38
+ * 3. Deduplicate against existing rows (by hash)
39
+ * 4. Batch-insert new rows into `transactions`
40
+ *
41
+ * @returns count of inserted, skipped (duplicate), and failed rows
42
+ */
43
+ export declare function ingestTransactions(filePath: string, userId: string): Promise<IngestResult>;