ekwo-os 0.4.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 (361) hide show
  1. package/README.md +873 -0
  2. package/dist/args.d.ts +57 -0
  3. package/dist/args.d.ts.map +1 -0
  4. package/dist/args.js +134 -0
  5. package/dist/args.js.map +1 -0
  6. package/dist/assets/expected-objects.json +9432 -0
  7. package/dist/assets/migrations/20260911120000_core_companies.sql +260 -0
  8. package/dist/assets/migrations/20260911120100_accounts_journals.sql +211 -0
  9. package/dist/assets/migrations/20260911120200_contacts.sql +90 -0
  10. package/dist/assets/migrations/20260911120300_taxes.sql +146 -0
  11. package/dist/assets/migrations/20260911120400_entries.sql +338 -0
  12. package/dist/assets/migrations/20260911120500_documents.sql +279 -0
  13. package/dist/assets/migrations/20260911120600_payments_reconciliation.sql +251 -0
  14. package/dist/assets/migrations/20260911120700_bank.sql +178 -0
  15. package/dist/assets/migrations/20260911120800_analytics_attachments.sql +128 -0
  16. package/dist/assets/migrations/20260911120900_post_document.sql +279 -0
  17. package/dist/assets/migrations/20260911121000_reporting.sql +235 -0
  18. package/dist/assets/migrations/20260911121100_country_templates.sql +226 -0
  19. package/dist/assets/migrations/20260911121200_fec.sql +71 -0
  20. package/dist/assets/migrations/20260911130000_instance.sql +77 -0
  21. package/dist/assets/migrations/20260911130100_instance_members.sql +224 -0
  22. package/dist/assets/migrations/20260911140000_instance_admins.sql +112 -0
  23. package/dist/assets/migrations/20260911160000_tax_posting_templates_unique.sql +17 -0
  24. package/dist/assets/migrations/20260911170000_document_amount_paid.sql +82 -0
  25. package/dist/assets/migrations/20260911173000_post_payment.sql +122 -0
  26. package/dist/assets/migrations/20260911173100_sequence_counters_under_rls.sql +84 -0
  27. package/dist/assets/migrations/20260911183000_country_journal_defaults.sql +143 -0
  28. package/dist/assets/migrations/20260911193853_line_account_defaults.sql +274 -0
  29. package/dist/assets/migrations/20260911195054_products.sql +221 -0
  30. package/dist/assets/migrations/20260911210131_anon_surface.sql +85 -0
  31. package/dist/assets/migrations/20260912074712_country_packs.sql +318 -0
  32. package/dist/assets/migrations/20260912080311_report_code_and_region.sql +198 -0
  33. package/dist/assets/migrations/20260912081014_pack_certification_maintained.sql +31 -0
  34. package/dist/assets/migrations/20260912081015_pack_certification_backfill.sql +16 -0
  35. package/dist/assets/migrations/20260912090407_tax_report_boxes.sql +329 -0
  36. package/dist/assets/migrations/20260912091917_tax_on_base_value.sql +25 -0
  37. package/dist/assets/migrations/20260912091918_tax_engine_columns.sql +776 -0
  38. package/dist/assets/migrations/20260912094412_opening_and_closing.sql +759 -0
  39. package/dist/assets/migrations/20260912095825_charts_of_accounts.sql +326 -0
  40. package/dist/assets/migrations/20260912100412_financial_statements.sql +632 -0
  41. package/dist/assets/migrations/20260912104719_one_formula_evaluator.sql +209 -0
  42. package/dist/assets/migrations/20260912105720_entry_kind_appropriation.sql +26 -0
  43. package/dist/assets/migrations/20260912105721_appropriation_entry_kind.sql +444 -0
  44. package/dist/assets/migrations/20260912111751_document_rules.sql +274 -0
  45. package/dist/assets/migrations/20260912112132_cash_basis_vat_and_fx.sql +1189 -0
  46. package/dist/assets/migrations/20260913074512_modules.sql +493 -0
  47. package/dist/assets/migrations/20260913075903_asset_disposal_roles.sql +47 -0
  48. package/dist/assets/migrations/20260913083216_capabilities.sql +637 -0
  49. package/dist/assets/migrations/20260913083901_company_invitations.sql +262 -0
  50. package/dist/assets/migrations/20260913084402_user_preferences.sql +376 -0
  51. package/dist/assets/migrations/20260913084847_company_profile.sql +261 -0
  52. package/dist/assets/migrations/20260913085436_numbering_reads_the_pack.sql +235 -0
  53. package/dist/assets/migrations/20260913085932_api_keys.sql +303 -0
  54. package/dist/assets/migrations/20260913090216_fiscal_year_bounds.sql +159 -0
  55. package/dist/assets/migrations/20260913092527_entries_import.sql +219 -0
  56. package/dist/assets/migrations/20260913101536_null_safe_guards.sql +188 -0
  57. package/dist/assets/migrations/20260913102115_installer_is_named.sql +462 -0
  58. package/dist/assets/migrations/20260913102758_no_currency_default.sql +138 -0
  59. package/dist/assets/migrations/20260913103355_cash_basis_needs_a_box.sql +453 -0
  60. package/dist/assets/migrations/20260913104014_aged_balance_named_group.sql +79 -0
  61. package/dist/assets/migrations/20260913104232_foreign_key_indexes.sql +144 -0
  62. package/dist/assets/migrations/20260913105120_declared_no_reader.sql +41 -0
  63. package/dist/assets/migrations/20260913111407_pack_languages.sql +223 -0
  64. package/dist/assets/migrations/20260913112233_schema_comments_for_a_reader.sql +27 -0
  65. package/dist/assets/migrations/20260913114535_fec_opening_balances.sql +307 -0
  66. package/dist/assets/migrations/20260914103412_audit_log.sql +509 -0
  67. package/dist/assets/migrations/20260914111907_pack_upgrade.sql +428 -0
  68. package/dist/assets/migrations/20260914120500_rounding_reads_the_currency.sql +206 -0
  69. package/dist/assets/migrations/20260914121200_amounts_round_at_the_currency.sql +2403 -0
  70. package/dist/assets/migrations/20260914134325_schema_version_0_2_0.sql +36 -0
  71. package/dist/assets/migrations/20260914143915_accounts_in_use.sql +428 -0
  72. package/dist/assets/migrations/20260914144731_account_code_frozen.sql +99 -0
  73. package/dist/assets/migrations/20260914151207_schema_grants_its_own_rights.sql +284 -0
  74. package/dist/assets/migrations/20260914152840_pack_upgrade_records_its_own_line.sql +237 -0
  75. package/dist/assets/migrations/20260914163943_declaration_periodicity.sql +349 -0
  76. package/dist/assets/migrations/20260915094000_foreign_services_received.sql +37 -0
  77. package/dist/assets/migrations/20260915094500_mentions_read_a_foreign_service.sql +72 -0
  78. package/dist/assets/migrations/20260915153000_document_shares.sql +568 -0
  79. package/dist/assets/migrations/20260915160000_ec_sales_list.sql +181 -0
  80. package/dist/assets/migrations/20260915161842_pack_sources.sql +56 -0
  81. package/dist/assets/migrations/20260915170500_schema_version_0_3_0.sql +45 -0
  82. package/dist/assets/migrations/20260915174500_a_vat_category_is_a_code.sql +177 -0
  83. package/dist/assets/migrations/20260915180000_document_rule_references.sql +85 -0
  84. package/dist/assets/migrations/20260915181000_territories.sql +342 -0
  85. package/dist/assets/migrations/20260915181500_ec_sales_list_reads_the_territories.sql +222 -0
  86. package/dist/assets/migrations/20260915182000_intracom_triangular.sql +53 -0
  87. package/dist/assets/migrations/20260915182500_a_triangular_supply_says_reverse_charge.sql +99 -0
  88. package/dist/assets/migrations/20260915191200_a_document_knows_its_language.sql +633 -0
  89. package/dist/assets/migrations/20260915195000_a_price_that_holds_its_tax.sql +649 -0
  90. package/dist/assets/migrations/20260915200000_a_posting_names_its_boxes.sql +1004 -0
  91. package/dist/assets/migrations/20260916094500_a_company_files_more_than_one_declaration.sql +795 -0
  92. package/dist/assets/migrations/20260916103000_a_ledger_line_names_its_posting.sql +902 -0
  93. package/dist/assets/migrations/20260916123000_a_vocabulary_for_a_tax_that_is_not_a_vat.sql +144 -0
  94. package/dist/assets/migrations/20260916124000_a_box_can_be_a_rate_of_a_box.sql +525 -0
  95. package/dist/assets/migrations/20260916125000_a_tax_follows_the_territory.sql +1318 -0
  96. package/dist/assets/migrations/20260916126000_a_tax_point_and_its_exception.sql +1211 -0
  97. package/dist/assets/migrations/20260917090000_a_counterparty_that_learns.sql +561 -0
  98. package/dist/assets/migrations/20260917120000_what_the_money_pays.sql +545 -0
  99. package/dist/assets/migrations/20260917150000_a_filing_is_a_row.sql +488 -0
  100. package/dist/assets/migrations/20260917170000_when_it_is_due.sql +194 -0
  101. package/dist/assets/migrations/20260917180000_a_box_is_a_number_and_a_kind.sql +213 -0
  102. package/dist/assets/migrations/20260917190000_what_a_declaration_owes.sql +486 -0
  103. package/dist/assets/migrations/20260917200000_what_moved_after_it_went.sql +227 -0
  104. package/dist/assets/migrations/20260918090000_a_form_names_its_file.sql +27 -0
  105. package/dist/assets/migrations/20260918110000_a_deposit_and_what_came_back.sql +286 -0
  106. package/dist/assets/migrations/20260918113741_a_fourth_preset_is_named.sql +9 -0
  107. package/dist/assets/migrations/20260918113807_a_client_reads_and_hands_over.sql +96 -0
  108. package/dist/assets/migrations/20260918114322_two_definer_functions_check_their_caller.sql +75 -0
  109. package/dist/assets/migrations/20260918130000_every_company_somebody_keeps.sql +177 -0
  110. package/dist/assets/migrations/20260918140000_a_session_nobody_prepared.sql +40 -0
  111. package/dist/assets/migrations/20260918141107_a_line_keeps_the_tax_it_was_posted_with.sql +256 -0
  112. package/dist/assets/migrations/20260918141342_a_company_has_an_electronic_address.sql +66 -0
  113. package/dist/assets/migrations/20260918141605_an_invoice_reads_whole_from_the_views.sql +313 -0
  114. package/dist/assets/migrations/20260918141627_a_policy_asks_once.sql +132 -0
  115. package/dist/assets/migrations/20260918143352_a_suggestion_reads_the_words_once.sql +204 -0
  116. package/dist/assets/migrations/20260918143417_a_reference_of_the_caller_and_a_rehearsal.sql +115 -0
  117. package/dist/assets/migrations/20260918150712_a_company_leaves_with_its_books.sql +1119 -0
  118. package/dist/assets/migrations/20260918150931_a_statement_is_imported_once.sql +654 -0
  119. package/dist/assets/migrations/20260918161204_a_posted_document_does_not_move.sql +437 -0
  120. package/dist/assets/migrations/20260918161538_a_posted_entry_does_not_move.sql +252 -0
  121. package/dist/assets/migrations/20260918171946_an_entry_is_posted_by_post_entry.sql +289 -0
  122. package/dist/assets/migrations/20260918174312_schema_version_0_4_0.sql +53 -0
  123. package/dist/assets/modules/assets/module.json +13 -0
  124. package/dist/assets/modules/assets/supabase/migrations/20260913081447_assets.sql +1190 -0
  125. package/dist/assets/modules/assets/supabase/migrations/20260913104234_foreign_key_indexes.sql +47 -0
  126. package/dist/assets/modules/assets/supabase/migrations/20260913104500_assets_capabilities.sql +145 -0
  127. package/dist/assets/modules/assets/supabase/migrations/20260914122100_assets_round_at_the_currency.sql +564 -0
  128. package/dist/assets/modules/assets/supabase/migrations/20260914145012_assets_accounts_in_use.sql +37 -0
  129. package/dist/assets/modules/assets/supabase/migrations/20260914151530_assets_grants_its_own_rights.sql +63 -0
  130. package/dist/assets/modules/assets/supabase/migrations/20260918113912_assets_client_reads.sql +20 -0
  131. package/dist/assets/modules/assets/supabase/migrations/20260918150801_assets_leave_with_the_company.sql +41 -0
  132. package/dist/assets/modules/budgets/module.json +12 -0
  133. package/dist/assets/modules/budgets/supabase/migrations/20260913083012_budgets.sql +210 -0
  134. package/dist/assets/modules/budgets/supabase/migrations/20260913104233_foreign_key_indexes.sql +37 -0
  135. package/dist/assets/modules/budgets/supabase/migrations/20260913104501_budgets_capabilities.sql +72 -0
  136. package/dist/assets/modules/budgets/supabase/migrations/20260914122400_budgets_round_at_the_currency.sql +68 -0
  137. package/dist/assets/modules/budgets/supabase/migrations/20260914145048_budgets_accounts_in_use.sql +30 -0
  138. package/dist/assets/modules/budgets/supabase/migrations/20260914151812_budgets_grants_its_own_rights.sql +31 -0
  139. package/dist/assets/modules/budgets/supabase/migrations/20260918113954_budgets_client_reads.sql +20 -0
  140. package/dist/assets/modules/budgets/supabase/migrations/20260918150823_budgets_leave_with_the_company.sql +37 -0
  141. package/dist/assets/modules/schema/module.1.json +69 -0
  142. package/dist/assets/seed/00_currencies.sql +15 -0
  143. package/dist/assets/seed/00_territories.sql +280 -0
  144. package/dist/assets/seed/05_framework_generic.sql +110 -0
  145. package/dist/assets/seed/10_pack_be.sql +1284 -0
  146. package/dist/assets/seed/11_pack_fr.sql +1112 -0
  147. package/dist/assets/seed/12_pack_lu.sql +2484 -0
  148. package/dist/assets/seed/13_pack_ee.sql +733 -0
  149. package/dist/assets/seed/14_pack_gb.sql +796 -0
  150. package/dist/assets/seed/15_pack_us.sql +751 -0
  151. package/dist/assets/seed/90_demo_company.sql +368 -0
  152. package/dist/assets/seed/modules/assets/10_pack_be.sql +58 -0
  153. package/dist/assets/seed/modules/assets/11_pack_fr.sql +57 -0
  154. package/dist/assets/seed/modules/assets/14_pack_gb.sql +58 -0
  155. package/dist/assets/seed/modules/assets/15_pack_us.sql +60 -0
  156. package/dist/auth.d.ts +59 -0
  157. package/dist/auth.d.ts.map +1 -0
  158. package/dist/auth.js +134 -0
  159. package/dist/auth.js.map +1 -0
  160. package/dist/bin.d.ts +6 -0
  161. package/dist/bin.d.ts.map +1 -0
  162. package/dist/bin.js +8 -0
  163. package/dist/bin.js.map +1 -0
  164. package/dist/books.d.ts +60 -0
  165. package/dist/books.d.ts.map +1 -0
  166. package/dist/books.js +168 -0
  167. package/dist/books.js.map +1 -0
  168. package/dist/bootstrap.d.ts +236 -0
  169. package/dist/bootstrap.d.ts.map +1 -0
  170. package/dist/bootstrap.js +459 -0
  171. package/dist/bootstrap.js.map +1 -0
  172. package/dist/bundle.d.ts +27 -0
  173. package/dist/bundle.d.ts.map +1 -0
  174. package/dist/bundle.js +47 -0
  175. package/dist/bundle.js.map +1 -0
  176. package/dist/checklist.d.ts +35 -0
  177. package/dist/checklist.d.ts.map +1 -0
  178. package/dist/checklist.js +64 -0
  179. package/dist/checklist.js.map +1 -0
  180. package/dist/cli.d.ts +14 -0
  181. package/dist/cli.d.ts.map +1 -0
  182. package/dist/cli.js +318 -0
  183. package/dist/cli.js.map +1 -0
  184. package/dist/commands/company.d.ts +59 -0
  185. package/dist/commands/company.d.ts.map +1 -0
  186. package/dist/commands/company.js +287 -0
  187. package/dist/commands/company.js.map +1 -0
  188. package/dist/commands/contact.d.ts +9 -0
  189. package/dist/commands/contact.d.ts.map +1 -0
  190. package/dist/commands/contact.js +82 -0
  191. package/dist/commands/contact.js.map +1 -0
  192. package/dist/commands/demo.d.ts +19 -0
  193. package/dist/commands/demo.d.ts.map +1 -0
  194. package/dist/commands/demo.js +72 -0
  195. package/dist/commands/demo.js.map +1 -0
  196. package/dist/commands/doctor.d.ts +12 -0
  197. package/dist/commands/doctor.d.ts.map +1 -0
  198. package/dist/commands/doctor.js +68 -0
  199. package/dist/commands/doctor.js.map +1 -0
  200. package/dist/commands/document.d.ts +17 -0
  201. package/dist/commands/document.d.ts.map +1 -0
  202. package/dist/commands/document.js +217 -0
  203. package/dist/commands/document.js.map +1 -0
  204. package/dist/commands/init.d.ts +31 -0
  205. package/dist/commands/init.d.ts.map +1 -0
  206. package/dist/commands/init.js +535 -0
  207. package/dist/commands/init.js.map +1 -0
  208. package/dist/commands/login.d.ts +24 -0
  209. package/dist/commands/login.d.ts.map +1 -0
  210. package/dist/commands/login.js +128 -0
  211. package/dist/commands/login.js.map +1 -0
  212. package/dist/commands/migrate.d.ts +30 -0
  213. package/dist/commands/migrate.d.ts.map +1 -0
  214. package/dist/commands/migrate.js +115 -0
  215. package/dist/commands/migrate.js.map +1 -0
  216. package/dist/commands/module.d.ts +32 -0
  217. package/dist/commands/module.d.ts.map +1 -0
  218. package/dist/commands/module.js +285 -0
  219. package/dist/commands/module.js.map +1 -0
  220. package/dist/commands/pack.d.ts +22 -0
  221. package/dist/commands/pack.d.ts.map +1 -0
  222. package/dist/commands/pack.js +481 -0
  223. package/dist/commands/pack.js.map +1 -0
  224. package/dist/commands/payment.d.ts +15 -0
  225. package/dist/commands/payment.d.ts.map +1 -0
  226. package/dist/commands/payment.js +97 -0
  227. package/dist/commands/payment.js.map +1 -0
  228. package/dist/commands/register.d.ts +20 -0
  229. package/dist/commands/register.d.ts.map +1 -0
  230. package/dist/commands/register.js +134 -0
  231. package/dist/commands/register.js.map +1 -0
  232. package/dist/commands/status.d.ts +8 -0
  233. package/dist/commands/status.d.ts.map +1 -0
  234. package/dist/commands/status.js +134 -0
  235. package/dist/commands/status.js.map +1 -0
  236. package/dist/commands/whoami.d.ts +21 -0
  237. package/dist/commands/whoami.d.ts.map +1 -0
  238. package/dist/commands/whoami.js +133 -0
  239. package/dist/commands/whoami.js.map +1 -0
  240. package/dist/company.d.ts +16 -0
  241. package/dist/company.d.ts.map +1 -0
  242. package/dist/company.js +26 -0
  243. package/dist/company.js.map +1 -0
  244. package/dist/config.d.ts +24 -0
  245. package/dist/config.d.ts.map +1 -0
  246. package/dist/config.js +50 -0
  247. package/dist/config.js.map +1 -0
  248. package/dist/connection.d.ts +79 -0
  249. package/dist/connection.d.ts.map +1 -0
  250. package/dist/connection.js +116 -0
  251. package/dist/connection.js.map +1 -0
  252. package/dist/context.d.ts +47 -0
  253. package/dist/context.d.ts.map +1 -0
  254. package/dist/context.js +93 -0
  255. package/dist/context.js.map +1 -0
  256. package/dist/doctor.d.ts +45 -0
  257. package/dist/doctor.d.ts.map +1 -0
  258. package/dist/doctor.js +448 -0
  259. package/dist/doctor.js.map +1 -0
  260. package/dist/grants.d.ts +88 -0
  261. package/dist/grants.d.ts.map +1 -0
  262. package/dist/grants.js +174 -0
  263. package/dist/grants.js.map +1 -0
  264. package/dist/identity.d.ts +54 -0
  265. package/dist/identity.d.ts.map +1 -0
  266. package/dist/identity.js +132 -0
  267. package/dist/identity.js.map +1 -0
  268. package/dist/index.d.ts +44 -0
  269. package/dist/index.d.ts.map +1 -0
  270. package/dist/index.js +43 -0
  271. package/dist/index.js.map +1 -0
  272. package/dist/inventory.d.ts +155 -0
  273. package/dist/inventory.d.ts.map +1 -0
  274. package/dist/inventory.js +303 -0
  275. package/dist/inventory.js.map +1 -0
  276. package/dist/migrations.d.ts +76 -0
  277. package/dist/migrations.d.ts.map +1 -0
  278. package/dist/migrations.js +226 -0
  279. package/dist/migrations.js.map +1 -0
  280. package/dist/module/read.d.ts +91 -0
  281. package/dist/module/read.d.ts.map +1 -0
  282. package/dist/module/read.js +172 -0
  283. package/dist/module/read.js.map +1 -0
  284. package/dist/output.d.ts +110 -0
  285. package/dist/output.d.ts.map +1 -0
  286. package/dist/output.js +173 -0
  287. package/dist/output.js.map +1 -0
  288. package/dist/pack/certification.d.ts +27 -0
  289. package/dist/pack/certification.d.ts.map +1 -0
  290. package/dist/pack/certification.js +38 -0
  291. package/dist/pack/certification.js.map +1 -0
  292. package/dist/pack/compile.d.ts +75 -0
  293. package/dist/pack/compile.d.ts.map +1 -0
  294. package/dist/pack/compile.js +801 -0
  295. package/dist/pack/compile.js.map +1 -0
  296. package/dist/pack/filing.d.ts +48 -0
  297. package/dist/pack/filing.d.ts.map +1 -0
  298. package/dist/pack/filing.js +56 -0
  299. package/dist/pack/filing.js.map +1 -0
  300. package/dist/pack/read.d.ts +766 -0
  301. package/dist/pack/read.d.ts.map +1 -0
  302. package/dist/pack/read.js +2336 -0
  303. package/dist/pack/read.js.map +1 -0
  304. package/dist/pack/schema.d.ts +26 -0
  305. package/dist/pack/schema.d.ts.map +1 -0
  306. package/dist/pack/schema.js +181 -0
  307. package/dist/pack/schema.js.map +1 -0
  308. package/dist/pack/territories.d.ts +98 -0
  309. package/dist/pack/territories.d.ts.map +1 -0
  310. package/dist/pack/territories.js +0 -0
  311. package/dist/pack/territories.js.map +1 -0
  312. package/dist/pack/upgrade.d.ts +86 -0
  313. package/dist/pack/upgrade.d.ts.map +1 -0
  314. package/dist/pack/upgrade.js +76 -0
  315. package/dist/pack/upgrade.js.map +1 -0
  316. package/dist/pack/vat-codes.d.ts +273 -0
  317. package/dist/pack/vat-codes.d.ts.map +1 -0
  318. package/dist/pack/vat-codes.js +513 -0
  319. package/dist/pack/vat-codes.js.map +1 -0
  320. package/dist/profiles.d.ts +67 -0
  321. package/dist/profiles.d.ts.map +1 -0
  322. package/dist/profiles.js +187 -0
  323. package/dist/profiles.js.map +1 -0
  324. package/dist/prompt.d.ts +40 -0
  325. package/dist/prompt.d.ts.map +1 -0
  326. package/dist/prompt.js +120 -0
  327. package/dist/prompt.js.map +1 -0
  328. package/dist/registry.d.ts +84 -0
  329. package/dist/registry.d.ts.map +1 -0
  330. package/dist/registry.js +104 -0
  331. package/dist/registry.js.map +1 -0
  332. package/dist/rest.d.ts +81 -0
  333. package/dist/rest.d.ts.map +1 -0
  334. package/dist/rest.js +245 -0
  335. package/dist/rest.js.map +1 -0
  336. package/dist/schema.d.ts +15 -0
  337. package/dist/schema.d.ts.map +1 -0
  338. package/dist/schema.js +15 -0
  339. package/dist/schema.js.map +1 -0
  340. package/dist/seeds.d.ts +27 -0
  341. package/dist/seeds.d.ts.map +1 -0
  342. package/dist/seeds.js +47 -0
  343. package/dist/seeds.js.map +1 -0
  344. package/dist/session.d.ts +39 -0
  345. package/dist/session.d.ts.map +1 -0
  346. package/dist/session.js +92 -0
  347. package/dist/session.js.map +1 -0
  348. package/dist/sql.d.ts +48 -0
  349. package/dist/sql.d.ts.map +1 -0
  350. package/dist/sql.js +112 -0
  351. package/dist/sql.js.map +1 -0
  352. package/dist/status.d.ts +94 -0
  353. package/dist/status.d.ts.map +1 -0
  354. package/dist/status.js +120 -0
  355. package/dist/status.js.map +1 -0
  356. package/dist/ui.d.ts +55 -0
  357. package/dist/ui.d.ts.map +1 -0
  358. package/dist/ui.js +114 -0
  359. package/dist/ui.js.map +1 -0
  360. package/package.json +29 -0
  361. package/schema/output.1.json +613 -0
package/README.md ADDED
@@ -0,0 +1,873 @@
1
+ # ekwo
2
+
3
+ The installer and the operator's tool for [Ekwo OS](https://github.com/Ekwo-ai/ekwo-os).
4
+ One command turns a Supabase project you already own into a set of double-entry
5
+ books: the schema, the chart of accounts, the VAT codes, the first
6
+ administrator, the first company and its first financial year.
7
+
8
+ ```sh
9
+ npx ekwo-os init
10
+ ```
11
+
12
+ The package is `ekwo-os` and the command it installs is `ekwo`: `npx ekwo-os
13
+ <command>` runs it without installing anything, and after
14
+ `npm install -g ekwo-os` every example below that starts with `ekwo ` works as
15
+ written.
16
+
17
+ You need Node 20 or later. That is the whole list. The Supabase CLI is not
18
+ required — this talks to Postgres directly — and Docker is not required
19
+ either.
20
+
21
+ ## From a free Supabase account to a first invoice
22
+
23
+ 1. **Create a project** at [supabase.com](https://supabase.com). The free plan
24
+ is enough to start. Ekwo does not create it, does not pay for it and has no
25
+ access to it: it is yours from the first row.
26
+ 2. **Copy two things** from the dashboard:
27
+ - Project Settings → Database → **Connection string** (URI). It contains
28
+ your database password.
29
+ - Project Settings → API → **`service_role` key**, and the **Project URL**.
30
+ These are used once, to create the first administrator in your own
31
+ Supabase Auth, and are never written to disk.
32
+
33
+ **Take the pooler string, not the direct one, unless you know you have
34
+ IPv6.** The direct host `db.<ref>.supabase.co` resolves to an IPv6 address
35
+ only on any recent project, so from an IPv4-only network it simply never
36
+ connects. The session pooler answers on IPv4 and supports everything a
37
+ migration needs:
38
+
39
+ ```
40
+ postgresql://postgres.<ref>:<password>@aws-1-<region>.pooler.supabase.com:5432/postgres
41
+ ```
42
+
43
+ The region is in the hostname the dashboard gives you, and so is the
44
+ generation prefix, which the region does not determine: verified on a real
45
+ project on 11 September 2026 in `eu-west-3`, where `aws-1` worked and
46
+ `aws-0` answered "Tenant or user not found". Copy the line from the
47
+ dashboard — Connect → Session pooler — rather than building it by hand.
48
+ 3. **Run the installer.**
49
+
50
+ ```sh
51
+ npx ekwo-os init
52
+ ```
53
+
54
+ It asks for the connection string, the country, the chart of accounts and
55
+ the language where the pack offers a choice, your organisation, the
56
+ currency, the first company, the address of the first administrator and —
57
+ optionally — the IBAN of your main bank account, then does the rest. Five
58
+ to ten seconds on a free project. Nothing is preselected for you on the
59
+ three questions whose wrong answer is expensive: the country, the chart and
60
+ the language.
61
+
62
+ 4. **Sign in** to your project as that administrator and start booking. Until
63
+ the Community web application lands, the interface is the REST API Supabase
64
+ generates from the schema, or `psql`, or `@ekwo-ai/core`.
65
+
66
+ 5. **Do the four things below**, while the dashboard is still open. The
67
+ installer prints them at the end of a successful run, because three of them
68
+ are settings of your project rather than rows in your database, and nothing
69
+ holding a connection string can reach them.
70
+
71
+ Everything above in one non-interactive line:
72
+
73
+ ```sh
74
+ npx ekwo-os init \
75
+ --db-url "postgresql://postgres.YOURREF:PASSWORD@aws-1-eu-west-3.pooler.supabase.com:5432/postgres" \
76
+ --supabase-url "https://YOURREF.supabase.co" \
77
+ --service-role-key "$SUPABASE_SERVICE_ROLE_KEY" \
78
+ --country BE \
79
+ --chart default \
80
+ --language fr \
81
+ --org "My Organisation" \
82
+ --company "My Company" \
83
+ --admin-email "you@example.com" \
84
+ --admin-password "a-long-password" \
85
+ --fiscal-year 2026 \
86
+ --iban "BE71096123456769" \
87
+ --yes
88
+ ```
89
+
90
+ `--chart` and `--language` are in that line because the Belgian pack offers a
91
+ choice on both, and `--yes` means there is nobody to ask. See "Installing
92
+ without a terminal" below.
93
+
94
+ ## Installing without a terminal
95
+
96
+ `--yes` turns off every question, and then every answer has to arrive as a flag
97
+ or an environment variable. Two of them are worth knowing about before you
98
+ write the script, because `ekwo init` **refuses rather than picking one for
99
+ you**:
100
+
101
+ - **the chart of accounts**, where the country publishes more than one. Belgium
102
+ publishes two, a company chart and an association chart. Pass `--chart`; the
103
+ refusal lists the codes the pack carries.
104
+ - **the language of the books**, where the pack publishes more than one. Pass
105
+ `--language`; the refusal lists them. The choice decides which label of the
106
+ pack lands in `accounts.name`, and the others stay beside it in `name_i18n`,
107
+ so it is not irreversible — but it is not a question a script should answer
108
+ by accident either.
109
+
110
+ `--country` behaves the same way and has no default at all: the refusal names
111
+ the packs the database holds. A preselected country is a chart of accounts
112
+ nobody chose.
113
+
114
+ The same is true of the financial year: a pack that declares no usual opening
115
+ month makes `--fiscal-year-start` required. Both packs shipped here open on the
116
+ calendar year, so it rarely comes up.
117
+
118
+ ## Where table access comes from
119
+
120
+ **The schema grants its own rights.** Every table, view and function of Ekwo
121
+ names the roles that may reach it — `anon`, `authenticated`, `service_role` —
122
+ in the migration that creates it. `ekwo doctor` reads the privileges of a live
123
+ database and reports a grant that is missing, a grant wider than the release
124
+ declares, and a table `anon` can reach at all.
125
+
126
+ Two rules follow, and both are worth knowing before you change anything by
127
+ hand.
128
+
129
+ **`anon` holds no privilege on any table.** The anonymous role — the one behind
130
+ the publishable key your front end ships — may execute the ten helper functions
131
+ row level security calls on its behalf, and nothing else. An anonymous request
132
+ to a table is refused at the privilege, before any policy is read. If part of
133
+ your application reads a table without signing a user in, it will stop working,
134
+ and that is the intended answer: sign the user in, or grant a function
135
+ deliberately.
136
+
137
+ **`authenticated` may attempt exactly the verbs a policy of that table is
138
+ prepared to judge.** A grant and a policy are two halves of one sentence: a
139
+ grant says which verbs may be attempted, a policy says on which rows they
140
+ succeed. The reference tables a country pack fills, the tables written only by
141
+ a `security definer` function, and the audit trail are readable and not
142
+ writable — by privilege as well as by policy.
143
+
144
+ **It was not always so, and the history explains a symptom you may still meet
145
+ on an installation nobody has migrated.** Until the migration of 14 September
146
+ 2026, nothing in `supabase/migrations` granted table access at all. Row level
147
+ security was written in the migrations in full and the underlying `GRANT` was
148
+ not: on a Supabase project it came from that project's own default privileges
149
+ on the `public` schema, which are there before Ekwo is. Those privileges live
150
+ in `pg_default_acl`, keyed by the schema, so dropping and recreating `public`
151
+ took them away — and then the reinstall succeeded, `ekwo doctor` reported a
152
+ healthy installation, and the first read through PostgREST answered
153
+ `permission denied for table companies`. Nothing was wrong with the schema; the
154
+ grant that had never been in it was missing.
155
+
156
+ On an installation that has run `ekwo migrate` since, that cannot happen: the
157
+ migrations put the privileges back themselves, and they take away the blanket
158
+ table access the project's defaults had handed `anon`. Dropping `public` is
159
+ still not something to do on a project you intend to keep — it takes your books
160
+ with it. The decision and what it changed are in
161
+ [`docs/decisions.md`](../../docs/decisions.md).
162
+
163
+ ## Before you go live: four things on your project
164
+
165
+ An installation leaves four things undone, and they are undone on purpose:
166
+ they are yours to decide, on a project Ekwo does not have access to. `ekwo
167
+ init` prints this list at the end of a successful run. `ekwo doctor` does not
168
+ check it and does not mention it — a database connection cannot see the
169
+ settings of the project it is connected to.
170
+
171
+ **1. Turn off self sign-up on your project.**
172
+ Supabase dashboard → **Authentication → Sign In / Providers → "Allow new users
173
+ to sign up"**, and switch it off. A fresh Supabase project accepts anyone who
174
+ posts an e-mail address and a password to its authentication endpoint, which is
175
+ the right default for a public application and the wrong one for a set of
176
+ books. An Ekwo installation is closed: the people who keep the books are
177
+ invited to it. Row level security means a stranger who signs up sees nothing —
178
+ they are a member of no company — but they are a row in `auth.users` that
179
+ nobody asked for, on a project whose sign-up endpoint is open to the internet.
180
+
181
+ **2. Keep two administrators.**
182
+ An instance administrator is what claims the instance and invites everybody
183
+ else. With one, a lost password, a closed mailbox or a person on holiday is a
184
+ set of books that nobody can let anyone into. Create the second account in your
185
+ Supabase Auth and add it with `claim_instance_admin()`, or invite it from the
186
+ application once it is signed in.
187
+
188
+ **3. Keep the service_role key off every machine that does not need it.**
189
+ It is not a powerful user: it is the absence of a door. A request carrying it
190
+ bypasses row level security entirely and reads every company in the instance.
191
+ This CLI reads it from a flag, an environment variable or a masked prompt, uses
192
+ it once to create the first account, and writes it nowhere — see
193
+ [Secrets](#secrets). Anywhere else it sits, it sits as a copy of your whole
194
+ ledger. `--admin-user-id` installs against an account that already exists and
195
+ needs no key at all.
196
+
197
+ **4. Read DISCLAIMER.md before you file anything.**
198
+ [`DISCLAIMER.md`](../../DISCLAIMER.md), at the root of the repository. A
199
+ country pack is a reading of a country's rules at the date of its version, and
200
+ its golden test proves that the pack agrees with itself — not that it agrees
201
+ with the law. `ekwo init` prints the certification status of the pack it
202
+ installs for the same reason. The books are yours, in every country where you
203
+ file.
204
+
205
+ None of these is an action Ekwo performs on your project, now or later. The
206
+ project is yours from the first row: the settings are yours to change, the key
207
+ is yours to hold, and what you file is yours to answer for.
208
+
209
+ Automatic verification of the first three is a phase 1 question, and it is not
210
+ free: they are answered by the Supabase management API, so checking them means
211
+ handing `ekwo doctor` a management token, and a token that can read a project's
212
+ settings can change them. Until that trade is worth making, the list is printed
213
+ and read by a person.
214
+
215
+ ## What `init` does, step by step
216
+
217
+ | Step | What happens | Why it is done this way |
218
+ |---|---|---|
219
+ | 1 | Applies `supabase/migrations/*.sql` in order | Recorded in `supabase_migrations.schema_migrations`, the Supabase CLI's own history table, so `supabase db push` and `ekwo migrate` stay interchangeable |
220
+ | 2 | Applies the six reference seeds, in file-name order: `00_currencies.sql`, `05_framework_generic.sql`, `10_pack_be.sql`, `11_pack_fr.sql`, `12_pack_lu.sql`, `13_pack_ee.sql` | The currencies, the country-less financial statements every chart falls back on, and the four country packs. They are exactly the six `supabase/config.toml` lists, so `supabase db push` installs the same set; a test compares both paths row by row. `90_demo_company.sql` is sample data and is never applied here |
221
+ | 3 | Creates the first administrator through the Supabase Auth admin API | See below: a database connection cannot be a signed-in user |
222
+ | 4 | `init_instance()`, `claim_instance_admin()`, the company, `company_members` as owner, `install_country_template()`, the first financial year, and the bank account when an IBAN was given | The six steps of the root README, in the same order, plus the one thing nobody can derive |
223
+ | 5 | Writes `ekwo.json` | Project URL, country, schema version. Nothing else, ever |
224
+ | 6 | Asks whether to register with Ekwo | The default answer is no, and no is a supported answer forever |
225
+
226
+ Every step checks before it acts. Running `ekwo init` twice on the same
227
+ project reports what was already there and creates nothing a second time.
228
+
229
+ ### Why the first user goes through Supabase Auth
230
+
231
+ Every row level security policy in the schema compares `auth.uid()` against a
232
+ row, and `auth.uid()` reads the JWT of the request. The CLI holds a Postgres
233
+ connection, not a session: it runs as the database owner, `auth.uid()` is
234
+ NULL, and row level security is *bypassed* rather than satisfied. So the
235
+ installer cannot be the first user. It can only create one and then write the
236
+ rows that user will be recognised by.
237
+
238
+ Creating that user in SQL is not an option either. `auth.users` belongs to
239
+ GoTrue — the password hash, the confirmation state, the identity row — and
240
+ writing it by hand produces an account that looks right and cannot sign in.
241
+ Hence the order: the admin API first, its user id second, `instance_admins`
242
+ and `company_members` third.
243
+
244
+ This is the only reason `--service-role-key` exists. Pass `--admin-user-id`
245
+ instead if the account already exists, and no key is needed.
246
+
247
+ ## Commands
248
+
249
+ | Command | What it does |
250
+ |---|---|
251
+ | `ekwo init` | The whole installation, interactive or not. |
252
+ | `ekwo migrate` | Applies the migrations this release adds, after showing the gap — the socle's, then the modules'. Re-applies the reference seeds, which are idempotent. `--no-modules` leaves the modules alone. |
253
+ | `ekwo status` | Schema version installed against available, pending migrations, the instance, its administrators, the country packs it holds and, per company, the pack version it copied. Exits 1 when something is pending. |
254
+ | `ekwo doctor` | Every object this release defines and every privilege it grants, against what the database holds; row level security on every table, a policy on every protected table, no pending migration, no membership pointing at a deleted user, every company with a bank account, statements that tie to their lines, posted entries that balance. Exits 1 on a problem, 0 on warnings. |
255
+ | `ekwo register` | Opt in to security advisories and release notes. Also the retry when the announcement did not go through. |
256
+ | `ekwo unregister` | Opt back out. Clears the address and the date on the instance row. |
257
+ | `ekwo demo` | Loads the sample company. Fictional data, explicit request only. |
258
+ | `ekwo module` | What is installed beside the socle, applies a module's migrations and its country seeds, and turns one on or off for a company. |
259
+ | `ekwo company` | One company leaves an installation with its books — `export` writes an archive anybody can read, as a member under row level security — and arrives in another one alive: `import` takes it in whole or not at all. |
260
+ | `ekwo pack` | Compiles a country pack into its seed, and refuses a seed that is no longer the output of its pack. Runs in a checkout of the repository only. |
261
+ | `ekwo login` | Signs in to an instance as yourself and keeps the session, in your own configuration directory. See [acting as a person](#acting-as-a-person-login-use-whoami). |
262
+ | `ekwo logout` | Ends that session, here and on the instance. |
263
+ | `ekwo use <company>` | Picks the company the next commands run on. |
264
+ | `ekwo whoami` | Who you are on which instance, the companies you can see, and what you may do on the one in use. |
265
+ | `ekwo contact add` / `list` | A customer or a supplier, and finding one again. |
266
+ | `ekwo invoice new` / `invoice line add` | A draft document — any kind, with `--type` — and one more line on it. A draft books nothing. |
267
+ | `ekwo post <document>` | Books it, through `post_document()`. `--dry-run` shows the entry the database would write and writes nothing. |
268
+ | `ekwo payment record` | Money in or out, booked and matched. With `--doc`, against that document. |
269
+ | `ekwo match <transaction> <document>` | A bank statement line pays a document, through `settle_from_statement()`. |
270
+ | `ekwo doc list` / `show` | What exists, and with `--unpaid` what is posted and still owed. See [keeping books](#keeping-books). |
271
+
272
+ There is no `eject`, because there is nothing to eject from. The schema is in
273
+ your database, the migrations are in the repository under AGPL-3.0, and
274
+ `supabase db push` applies them without this CLI ever running again.
275
+
276
+ ## What a command answers: `--json` and the exit codes
277
+
278
+ Every command prints for a person by default — aligned columns, colour only on
279
+ a terminal and never when `NO_COLOR` is set, no spinner and no line redrawn in
280
+ place, so the output reads the same in a file or a CI log — and takes `--json`
281
+ for a program.
282
+
283
+ Under `--json` the standard output is **one JSON document and nothing else**;
284
+ the prose still goes by, on the standard error. The document has the same
285
+ shape whatever happened:
286
+
287
+ ```json
288
+ {
289
+ "ok": false,
290
+ "command": "module enable",
291
+ "exitCode": 3,
292
+ "warnings": [],
293
+ "error": {
294
+ "kind": "refusal",
295
+ "name": "not_allowed",
296
+ "message": "not_allowed: enabling a module on this company needs company.write",
297
+ "sqlstate": "42501"
298
+ }
299
+ }
300
+ ```
301
+
302
+ | Field | |
303
+ |---|---|
304
+ | `ok` | `exitCode` is 0. |
305
+ | `command` | The words that named it: `status`, `pack upgrade`. |
306
+ | `exitCode` | The code the process ends on. |
307
+ | `data` | What the command has to say. Its shape is per command, under `$defs/data/<command>` of the schema. Absent when it failed before having anything to say. |
308
+ | `warnings` | Every warning the command printed, without the colours. |
309
+ | `error` | Only when something went wrong: `kind` (`refusal`, `usage` or `technical`), the `message` word for word, the `name` it starts with when it has one, and the `sqlstate`, `detail` and `hint` when the database gave them. |
310
+
311
+ The shape is published as
312
+ [`schema/output.1.json`](schema/output.1.json), ships in the package, and is
313
+ what `tests/cli/output-contract.test.ts` validates every command against. An
314
+ amount is a decimal string and never a JSON number; a date is ISO 8601.
315
+
316
+ | Exit code | Means |
317
+ |---|---|
318
+ | `0` | Done. |
319
+ | `1` | It failed for a reason that is not the books — the network, a database that does not answer, a bug — **or a check found something**: a `doctor` problem, a pending migration in `status`, a stale seed in `pack check`, a company behind its pack. In the second case `data` says what and there is no `error`. |
320
+ | `2` | The command was called wrong: an unknown option, a missing argument, or a question that needed an answer with no terminal to ask it on. |
321
+ | `3` | **The database refused.** A locked period, a capability you do not hold, a row level security policy, a constraint. The call was well formed and everything worked; the accounting said no. |
322
+
323
+ A refusal is printed as the database wrote it — `period_locked: …`,
324
+ `tax_territory_mismatch: …` — and never rephrased; the CLI does not move a
325
+ date or retry differently to get past one. Its name is the part to match on,
326
+ in a field of its own under `--json`.
327
+
328
+ **No command waits on a question when there is nobody to answer.** Off a
329
+ terminal, or under `--json`, or with `--yes`, a missing answer is exit code 2
330
+ with the flag to pass. That holds underneath the commands too: a prompt that
331
+ is reached with no terminal stops instead of waiting.
332
+
333
+ ## Acting as a person: `login`, `use`, `whoami`
334
+
335
+ The commands above install and operate, and connect as the owner of the
336
+ database — they say so when they connect. Anything that keeps books acts as
337
+ **a person**, through the instance's API, under row level security: the same
338
+ route, and the same functions of the schema, as the MCP server.
339
+
340
+ ```bash
341
+ ekwo login --supabase-url https://<ref>.supabase.co --anon-key <publishable key> --email you@example.test
342
+ ekwo whoami
343
+ ekwo use "Example One"
344
+ ekwo whoami --json
345
+ ```
346
+
347
+ `login` asks the instance for a session and keeps it. In a directory that has
348
+ an `ekwo.json`, the URL is read from it. The password is prompted, masked, when
349
+ `--password` and `EKWO_PASSWORD` are absent; it is sent to the instance once
350
+ and written nowhere. Signing in again after a session ended is `ekwo login`
351
+ and a password: the profile remembers the rest.
352
+
353
+ **Where the session is kept.** In `$EKWO_CONFIG_DIR`, else
354
+ `$XDG_CONFIG_HOME/ekwo`, else `~/.config/ekwo`, in two files written `0600` in
355
+ a `0700` directory: `profiles.json` says where each profile points and holds
356
+ no token; `credentials.json` holds the access token and the refresh token.
357
+ The CLI refuses — `config_dir_in_repository` — to write either inside a
358
+ repository, where one `git add .` would publish them. The access token lasts
359
+ about an hour and is renewed on its own, ahead of time and again if the
360
+ instance answers 401 anyway; the rotated refresh token replaces the old one
361
+ on disk before the call is retried. A session that cannot be renewed is
362
+ `session_expired`, exit code 2, and the fix is `ekwo login`.
363
+
364
+ **Profiles.** `--profile <name>`, or `EKWO_PROFILE`: a demo instance,
365
+ production, one client of a firm. Each holds one instance, one person and one
366
+ company in use. The profile last signed in to is the one used when none is
367
+ named.
368
+
369
+ **The environment comes first, and touches no file.** With `SUPABASE_URL`,
370
+ `SUPABASE_ANON_KEY` and either `EKWO_ACCESS_TOKEN` or `EKWO_EMAIL` with
371
+ `EKWO_PASSWORD` — the variables the MCP server reads — a command signs in for
372
+ its own duration, reads no profile and writes nothing: a CI job. The
373
+ environment is taken whole: a user there with no instance beside it is a wrong
374
+ call, never a fallback on a profile's instance. It has nowhere to keep a
375
+ company, so pass `--company`.
376
+
377
+ **The company in use.** `ekwo use <name or id>` checks the company against the
378
+ instance, as you, and records it; `--company` names another for one command.
379
+ A company you cannot see is `unknown_company`, and is not named in the
380
+ refusal. Under `--json` every answer of a command that acts as a person
381
+ carries a `context` — the profile, the instance and **the company the answer
382
+ was rendered for**, `null` when none is in use — on success and on a refusal
383
+ alike. A caller that keeps two sets of books reads it before it believes the
384
+ rest.
385
+
386
+ ```json
387
+ { "ok": true, "command": "whoami", "exitCode": 0,
388
+ "context": { "profile": "default", "instance": "https://<ref>.supabase.co",
389
+ "company": { "id": "…", "name": "Example One" } },
390
+ "data": { "user": { "id": "…", "email": "you@example.test" },
391
+ "instance": { "url": "…", "schemaVersion": "0.3.0" },
392
+ "capabilities": ["…"], "companies": [ … ] },
393
+ "warnings": [] }
394
+ ```
395
+
396
+ `whoami` works nothing out. The companies are the rows the policies let you
397
+ read, the capabilities are what `member_capabilities()` answers for the
398
+ company in use — the function behind `your_capabilities` in the MCP server —
399
+ and the schema version is `ekwo_schema_version()`.
400
+
401
+ **Never a `service_role` key.** It is refused by name, `service_role_refused`,
402
+ with exit code 2 and before anything is sent, at every door it can arrive by:
403
+ `--anon-key` or `SUPABASE_ANON_KEY`, `EKWO_ACCESS_TOKEN`, a session file
404
+ somebody edited, and `--service-role-key` typed out of habit. The test for it
405
+ and the sentence are in `@ekwo-ai/core`, where the MCP server reads them too.
406
+ `ekwo init` remains the one command that takes that key, to create the first
407
+ user, and never keeps it.
408
+
409
+ | Refusal of the CLI's own | Exit code | Means |
410
+ |---|---|---|
411
+ | `not_signed_in`, `unknown_profile`, `session_expired` | 2 | There is nobody to act as. `ekwo login`. |
412
+ | `service_role_refused`, `config_dir_in_repository`, `missing_configuration` | 2 | The call has to change, not be retried. |
413
+ | `unknown_company`, `ambiguous_company` | 2 | Name it differently, or by its id. |
414
+ | `no_company` | 2 | A verb that keeps books ran with no company in use. `ekwo use`, or `--company`. |
415
+ | `unknown_contact`, `ambiguous_contact`, `unknown_document`, `unknown_account_code`, `unknown_tax_code`, `document_not_draft`, `nothing_open`, `bad_line`, `unknown_field`, `bad_json` | 2 | Decided before the database was asked, by the CLI or by the functions it shares with the MCP server. The call has to change. |
416
+ | `sign_in_failed`, `instance_unreachable` | 1 | The instance declined the address and the password, or did not answer. |
417
+
418
+ A refusal of the database that arrives over this route is still exit code 3:
419
+ PostgREST passes on the SQLSTATE, the detail and the hint, and the CLI reads
420
+ them as it reads a driver's.
421
+
422
+ ## Keeping books
423
+
424
+ ```bash
425
+ ekwo contact add "Client Example" --country <cc> --ref crm-42
426
+ ekwo invoice new --contact client --date 2026-06-15 --ref job-7 \
427
+ --line "name=Audit,price=1500.00,account=<account code>,tax=<tax code>"
428
+ ekwo invoice line add job-7 --name Travel --price 250.00 --account <account code>
429
+ ekwo post job-7 --dry-run # the entry the database would write; nothing is written
430
+ ekwo post job-7 # post_document()
431
+ ekwo payment record --doc job-7 --amount 1750.00 --date 2026-06-30 --bank-account <id> --ref bank-1
432
+ ekwo match <bank transaction id> job-9
433
+ ekwo doc list --unpaid --since 2026-06-01 --json
434
+ ```
435
+
436
+ They run as the person signed in, on the company in use ([above](#acting-as-a-person-login-use-whoami)),
437
+ and none is ever picked for you: with no company in use a verb ends on
438
+ `no_company`, exit code 2, and `context.company` is `null`.
439
+
440
+ **Each verb is one function, and it is not ours.** The functions live in
441
+ `@ekwo-ai/core` and the MCP server calls the same ones: `contact add` is
442
+ `create_contact`, `invoice new` is `create_document`, `post` is
443
+ `post_document`, `payment record` is `record_payment`, `doc list` and
444
+ `doc show` are `list_documents` and `get_document`. Underneath them the rules
445
+ are the schema's — the balance, the numbering, the locks, the taxes, the
446
+ territory, the tax point. **This CLI computes no amount**: what you type goes
447
+ in as text, what is printed is what came back, and
448
+ `tests/cli/no-rules.test.ts` reads the commands to keep it that way. An amount
449
+ is a decimal string in both directions, `1500.00`.
450
+
451
+ **A refusal is the answer.** A locked period, a policy, a constraint: exit
452
+ code 3, the database's sentence word for word, its name in `error.name`. The
453
+ CLI does not move a date or try something else. What is refused *before* the
454
+ database is asked — an account code that does not exist, a document that is
455
+ not a draft, a document with nothing open — is exit code 2: the call has to
456
+ change.
457
+
458
+ **`--ref`, so that nothing is created twice.** On what creates (`contact add`,
459
+ `invoice new`, `payment record`), `--ref <your reference>` is kept on the row,
460
+ unique per company. The same reference a second time returns what the first
461
+ call created, with `"replayed": true`, and writes nothing — and finishes what
462
+ a dropped connection left half done: a draft whose lines never arrived, a
463
+ payment inserted and never booked. Two callers racing each other are settled
464
+ by the unique index, which refuses the slower one with exit code 3. A
465
+ `<document>` is its id, its number, or the `--ref` it was created under, which
466
+ is how a draft — it has no number yet — is named.
467
+
468
+ **`--dry-run`, where the database can answer without writing.** Today that is
469
+ `post`. `rehearse_post_document()` calls `post_document()` for real inside a
470
+ block it then rolls back, so the entry shown is the one that would be written,
471
+ under the number it would take, and a rehearsal is refused exactly as posting
472
+ would be. No other verb has one, because for no other verb does the database
473
+ know how.
474
+
475
+ **`--stdin`, the form that is authoritative.** One JSON object on the standard
476
+ input, with the fields of the MCP tool of the same meaning (`contact_type`,
477
+ `document_date`, `lines: [{ name, unit_price, account_code, tax_code, … }]`,
478
+ `client_ref`). A field nobody defined is refused rather than dropped. Flags
479
+ given beside it win.
480
+
481
+ ```bash
482
+ echo '{"contact":"client","document_date":"2026-06-15","client_ref":"job-8",
483
+ "lines":[{"name":"Review, \"urgent\"","unit_price":"200.00","account_code":"<code>"}]}' \
484
+ | ekwo invoice new --stdin --json
485
+ ```
486
+
487
+ **`--line`, for a person.** Named fields, never positions: `name`, `price`,
488
+ `qty`, `account`, `tax`, `product`, `unit`, `discount`, `description`; a comma
489
+ inside a value is `\,`. A tax and an account are named by their **code** —
490
+ never a rate, since several taxes share one. The free-text form
491
+ (`"Audit 1 500 EUR@21"`) is not accepted: `1 500` is one number or two, `@21`
492
+ is a rate where the books need a tax, and a currency belongs to the document
493
+ (`--currency`), not to a line.
494
+
495
+ Two values are supplied when nobody gives them, and said when they are:
496
+ `--type` is `sale_invoice`, and `--date` is today on the machine running the
497
+ command. Whether that date may be booked on is the database's decision.
498
+
499
+ ## `ekwo module`
500
+
501
+ A module is a Postgres schema beside the socle — `assets` for fixed assets,
502
+ `budgets` for a plan against the ledger. Its migrations travel with this
503
+ package, and `ekwo migrate` applies them by default.
504
+
505
+ ```sh
506
+ ekwo module list # what this release carries, and what the database holds
507
+ ekwo module migrate [<code>] # the migrations, and the country seeds they need
508
+ ekwo module enable assets --company "…" # turn it on for one company
509
+ ekwo module disable assets --company "…" # turn it off; nothing it wrote is deleted
510
+ ```
511
+
512
+ `enable` and `disable` go through `enable_module()` and `disable_module()`
513
+ rather than writing the table: the guard is in the function, so it applies to
514
+ psql and PostgREST alike. The CLI sets the request claim for an owner of the
515
+ company, the way `ekwo register` does, and `--as-user <uuid>` names another.
516
+
517
+ **One thing this CLI cannot do**, and says so every time: PostgREST serves a
518
+ schema other than `public` only once the project lists it under its exposed
519
+ schemas. That is a setting of the API, not of the database, so `ekwo module
520
+ enable` prints the line to add — Supabase dashboard → Project Settings → API,
521
+ or `[api] schemas` in `supabase/config.toml`.
522
+
523
+ **Before `supabase db push`**, run `ekwo migrate --no-modules`. The Supabase
524
+ CLI knows the socle's migration files and not a module's, so it would report
525
+ them as history it has no file for.
526
+
527
+ ## `ekwo company`
528
+
529
+ A firm keeps several companies in one installation, and each of them belongs to
530
+ somebody. These two commands are how one of them leaves, and arrives somewhere
531
+ else.
532
+
533
+ ```sh
534
+ ekwo company export "My Company" --out ./my-company # manifest.json + data/<schema>.<table>.jsonl
535
+ ekwo company import ./my-company --owner <user id> # whole, or not at all
536
+ ```
537
+
538
+ **`export` runs as a member, under row level security**, although the
539
+ connection belongs to the owner of the database: inside one transaction the CLI
540
+ steps down to `authenticated` with the claim of the member it acts for —
541
+ `--as-user`, an owner of the company by default. That member needs
542
+ `company.export`, which the `owner` and `client` presets hold. An archive is
543
+ whole or it is not written: a member who may not read one of the tables is
544
+ refused, by table, with exit code 3. The act is written on the audit trail of
545
+ the company.
546
+
547
+ **`import` is for the installer or an administrator of the installation**
548
+ (`--as-user`), the two who may create a company. The files are checked against
549
+ the manifest before the database is asked anything; then `import_company()`
550
+ takes all of it or none of it. A company already there is refused — exit code
551
+ 3, `company_already_here` — which is also what running the command twice gets.
552
+ Members do not travel: `--owner` names the first one.
553
+
554
+ **The files the attachments point at are not carried.** They are in the storage
555
+ bucket, not in the database; `manifest.json` lists them and both commands say
556
+ how many are left to copy.
557
+
558
+ The format, what travels and what does not, and every refusal are in
559
+ [`docs/company-archive.md`](../../docs/company-archive.md).
560
+
561
+ ## `ekwo doctor`
562
+
563
+ What a healthy installation is true of, and nothing in the schema can enforce
564
+ on its own. It reads and reports; it never repairs, because the fix for a
565
+ missing policy is a migration and the fix for an orphaned membership is a
566
+ decision about who should have access.
567
+
568
+ ```sh
569
+ ekwo doctor --db-url "$URL" # readable
570
+ ekwo doctor --db-url "$URL" --json # the whole report, findings included
571
+ ```
572
+
573
+ **The `catalogue` check compares your database to an inventory of everything
574
+ this release defines** — tables and their columns, views, functions with their
575
+ identity arguments, policies, triggers and types. That inventory is
576
+ [`assets/expected-objects.json`](assets/expected-objects.json), generated from
577
+ the migrations themselves and shipped inside this package, so it cannot be a
578
+ list somebody forgot to update. In `--json` output it is the check named
579
+ `catalogue`. Four outcomes, and they are not the same thing:
580
+
581
+ | Finding | What it means | Severity |
582
+ |---|---|---|
583
+ | Missing | The installation is behind or has been damaged. | Problem |
584
+ | Extra | Your own table, function or trigger. Reported so you know it is there. | Information |
585
+ | Extra or missing **policy** on a table of this schema | Row level security is the security model. A policy that is gone closes everything; one that was added is a grant nobody reviewed. | Problem |
586
+ | A column whose type has moved | The schema was patched by hand. Reported as **changed**, not as missing: "missing" would send you looking for a migration that did land. | Problem |
587
+
588
+ A module's objects are required only of a database that carries the module.
589
+ One you never installed is named and skipped.
590
+
591
+ **A database older than this CLI is still compared.** The report says which
592
+ schema version the inventory describes and which one the database reports, and
593
+ goes on to list what differs — refusing to look would be refusing the case the
594
+ check exists for.
595
+
596
+ **Exit codes.** `0` when there is no problem, warnings and information
597
+ included; `1` when there is at least one problem, or when the schema is not
598
+ installed at all. Nothing else. So `ekwo doctor` is usable as a deployment
599
+ gate, and an operator's own extra table never turns a pipeline red.
600
+
601
+ **What the catalogue does not cover.** Constraints, indexes and the bodies of
602
+ functions. A dropped unique index is real damage and this check will not see
603
+ it: the question it answers is "is the object there, and is it still that
604
+ shape". `docs/schema.md` lists the constraints for a human reader, and the
605
+ argument against putting them in the inventory is that each is an order of
606
+ magnitude more text for a diff that would move on every Postgres upgrade — and
607
+ an inventory whose diff nobody reads is worth nothing.
608
+
609
+ **The `grants` check compares the privileges**, from the same inventory: the
610
+ `grants` section of each schema says which of `anon`, `authenticated` and
611
+ `service_role` may reach each table, view and function, and with which verbs.
612
+ Its own check rather than a category of `catalogue`, because the rule is not
613
+ the same.
614
+
615
+ | Finding | What it means | Severity |
616
+ |---|---|---|
617
+ | A privilege the release grants and the database does not hold | Nothing else notices it, and it reaches a client as `permission denied for table companies`. | Problem |
618
+ | Any privilege `anon` holds beyond what the release grants | The anonymous role reaches the ten policy helpers and no table. One more is a surface nobody reviewed. | Problem |
619
+ | A privilege `authenticated` or `service_role` holds and the release does not grant | Usually a local customisation. Row level security is then the only thing refusing a verb the schema meant to withhold. | Warning |
620
+ | A default privilege still standing on a schema | A privilege that comes from there comes from something no migration wrote, and a recreated schema takes it away. | Warning |
621
+
622
+ In a checkout, `npm run inventory` regenerates the inventory from the
623
+ migrations; the CI regenerates it and fails on any difference, the way it does
624
+ for `docs/schema.md`, and a second job checks that the copy shipped in `dist`
625
+ is the one in the repository.
626
+
627
+ ## `ekwo pack`, in a checkout
628
+
629
+ A country is data: `packs/<cc>/` holds a manifest, the chart of accounts as
630
+ CSV, the taxes and where they post, the boxes of the declaration, the financial
631
+ statements, the sentences the country requires on an invoice, the translations,
632
+ and a year of books with the figures it produces. The compiler turns one into
633
+ `supabase/seed/<n>_pack_<cc>.sql`, which is committed — and, where a pack
634
+ carries a section for a module, into
635
+ `supabase/seed/modules/<code>/<n>_pack_<cc>.sql`, applied by the module
636
+ migration runner and by nothing else.
637
+
638
+ ```sh
639
+ ekwo pack list # the packs this checkout carries, and their certification
640
+ ekwo pack build be # write supabase/seed/10_pack_be.sql from packs/be
641
+ ekwo pack build --all
642
+ ekwo pack check be # validate one pack and compare its seed
643
+ ekwo pack check --all # exit 1 if a committed seed is not the output of its pack
644
+ ```
645
+
646
+ `check` validates every file of the pack against
647
+ [`packs/schema/pack.1.json`](../../packs/schema/pack.1.json) and against the
648
+ rest of the pack, then compares the committed seed with what the compiler makes
649
+ of it now. It is what the CI runs, so the SQL cannot drift from the pack. Every
650
+ rule it applies is listed in [`docs/packs.md`](../../docs/packs.md), under
651
+ "What `ekwo pack check` refuses".
652
+
653
+ Neither command touches a database: the seed is applied by `ekwo init`,
654
+ `supabase db push` or `psql -f`, like every other seed. A published
655
+ installation has the compiled seeds and no `packs/` folder, and the command
656
+ says so rather than guessing.
657
+
658
+ Two commands under `ekwo pack` do the opposite and read an installation rather
659
+ than a checkout, so they take a connection and work without `packs/`:
660
+
661
+ ```sh
662
+ ekwo pack status --db-url "$EKWO_DB_URL" # which pack version each company copied
663
+ ekwo pack upgrade "My Company" --db-url "…" # move it to the version this installation holds
664
+ ```
665
+
666
+ `status` changes nothing and exits 1 while a company is behind, so a scheduled
667
+ job can ask. `upgrade` applies an addition and a closed validity by itself,
668
+ lists everything else for a person to read, and never removes anything from a
669
+ company's books; `--apply` is what accepts the differences it listed.
670
+
671
+ ## Flags
672
+
673
+ Every command takes the connection flags:
674
+
675
+ | Flag | Meaning |
676
+ |---|---|
677
+ | `--db-url <url>` | Postgres connection string. The reliable way. |
678
+ | `--project-ref <ref>` | With `--db-password` and `--db-region`, the session pooler host. |
679
+ | `--db-password <pw>` | Database password. Prompted, masked, when omitted. |
680
+ | `--db-region <region>` | With `--project-ref`, the session pooler in that region. Both generation prefixes are tried and the one that answers is kept. |
681
+ | `--supabase-url <url>` | `https://<ref>.supabase.co`. Needed only to create a user. |
682
+ | `--service-role-key <key>` | Needed only to create a user. |
683
+ | `--yes`, `-y` | Never ask a question. Everything must come from flags or the environment. |
684
+
685
+ `--project-ref` with `--db-password` and `--db-region` builds the session
686
+ pooler host. It no longer guesses which one: the pooler hostname carries a
687
+ generation prefix as well as a region, and the region does not determine it,
688
+ so `aws-0-<region>` and `aws-1-<region>` are both opened on port 5432 and the
689
+ one that answers is kept and printed. Without `--db-region` nothing is
690
+ derived — the CLI asks for the connection string, because the direct host
691
+ `db.<ref>.supabase.co` is IPv6-only on recent projects and deriving it
692
+ silently produces a hang rather than an error. `--db-url`, copied from the
693
+ dashboard under Connect → Session pooler, is the form that is never derived.
694
+
695
+ `ekwo init` adds:
696
+
697
+ | Flag | Meaning |
698
+ |---|---|
699
+ | `--country <cc>` | Which country pack: its chart of accounts, its journals, its taxes and its declaration. One of the packs the database holds — `ekwo pack list` names them, and there is no default. |
700
+ | `--chart <code>` | Which chart of accounts, where the country publishes several. Required outside a terminal when it does. |
701
+ | `--org <name>` | Your organisation, written on the instance row. |
702
+ | `--company <name>` | The first company. Defaults to `--org`. |
703
+ | `--admin-email <address>` | The first administrator, created in your Supabase Auth. |
704
+ | `--admin-password <pw>` | Their password. Omitted, an invite link is generated and printed. |
705
+ | `--admin-user-id <uuid>` | Use an account that already exists, instead of creating one. |
706
+ | `--fiscal-year <year>` | Calendar year of the first financial year. Defaults to this year. |
707
+ | `--fiscal-year-start <date>` | The day that year opens, as `YYYY-MM-DD`. Needed only where the pack names no usual opening month; the two packs shipped both open on the calendar year. |
708
+ | `--currency <code>` | Currency of the company. Defaults to what the country model says: `EUR` for both countries shipped. |
709
+ | `--language <xx>` | Language of the books, two letters. Defaults to `country_defaults.language_default`, which the pack fills. It decides which label of the pack lands on each account; the others are kept in `name_i18n`. |
710
+ | `--iban <iban>` | Creates the main bank account, wired to the bank journal and its ledger account. Omitted, no bank account is created and `ekwo doctor` says so. |
711
+ | `--bic <bic>` | Optional, on that account. |
712
+ | `--bank-name <name>` | Optional. It also names the account in the books. |
713
+ | `--demo` | Also load the sample company. |
714
+ | `--register` | Register without being asked. `--register-email` sets the address. |
715
+ | `--registry-url <url>` | Where the registration is announced. |
716
+
717
+ Every command takes `--json`; see [what a command answers](#what-a-command-answers---json-and-the-exit-codes).
718
+ `ekwo migrate` takes `--skip-seeds`.
719
+
720
+ ## Environment variables
721
+
722
+ | Variable | Same as |
723
+ |---|---|
724
+ | `EKWO_DB_URL` | `--db-url`. `SUPABASE_DB_URL` also works. |
725
+ | `EKWO_DB_PASSWORD` | `--db-password` |
726
+ | `SUPABASE_URL` | `--supabase-url` |
727
+ | `SUPABASE_SERVICE_ROLE_KEY` | `--service-role-key`. `ekwo init` only. |
728
+ | `SUPABASE_ANON_KEY` | `--anon-key` |
729
+ | `EKWO_EMAIL`, `EKWO_PASSWORD` | Sign in for one command, writing nothing. `ekwo login` reads them too. |
730
+ | `EKWO_ACCESS_TOKEN` | The same, with a session token already in hand. It is not renewed. |
731
+ | `EKWO_PROFILE` | `--profile` |
732
+ | `EKWO_CONFIG_DIR` | Where profiles and sessions are kept. |
733
+ | `EKWO_REGISTRY_URL` | `--registry-url`. Default `https://api.ekwo.ai/v1/registrations`. |
734
+ | `NO_COLOR` | Plain output. |
735
+
736
+ See [`.env.example`](../../.env.example) at the root of the repository.
737
+
738
+ ## Secrets
739
+
740
+ The CLI never writes a password or a key to disk. The database password and
741
+ the `service_role` key are read from a flag, an environment variable or a
742
+ masked prompt, used, and forgotten; nothing is in `ekwo.json` but the project
743
+ URL, the country and the schema version.
744
+
745
+ One thing is kept, since `ekwo login`: the session of the person who signed
746
+ in — an access token and the refresh token that renews it — in their own
747
+ configuration directory, readable by them alone, and refused anywhere inside a
748
+ repository. It is in a file and not in the keychain of the operating system;
749
+ `ekwo logout` removes it and ends it on the instance, and a job that should
750
+ keep nothing sets the environment variables instead.
751
+
752
+ It has one runtime dependency from outside this repository, the Postgres
753
+ driver. Argument parsing, prompts and the masked input are a few dozen lines
754
+ each in this package rather than packages from the registry, because
755
+ everything this CLI is handed is a secret and every dependency added is one
756
+ more thing that could read it. The other dependency is `@ekwo-ai/core`, this
757
+ repository's own, where the CLI and the MCP server read a refusal of the
758
+ database the same way.
759
+
760
+ ## Registering with Ekwo
761
+
762
+ At the end of `ekwo init` you are asked:
763
+
764
+ > Register this installation with Ekwo to receive security advisories and
765
+ > release notes?
766
+
767
+ The default answer is no, and no is supported forever. Community works
768
+ unregistered: nothing in the schema and nothing in this CLI reads
769
+ `contact_email` or `registered_at` to decide what you may do, and `edition`
770
+ gates no feature.
771
+
772
+ If you say yes, two things happen, independently. `register_instance(email)`
773
+ writes the address and a date onto your instance row, and a POST goes to
774
+ `EKWO_REGISTRY_URL` carrying exactly six fields:
775
+
776
+ ```json
777
+ {
778
+ "instance_id": "…",
779
+ "organization": "My Organisation",
780
+ "country": "BE",
781
+ "edition": "community",
782
+ "schema_version": "0.2.0",
783
+ "contact_email": "you@example.com"
784
+ }
785
+ ```
786
+
787
+ No ledger data, no user list, no connection string. `instance_id` is generated
788
+ locally by your own database and is not a licence key: no code path anywhere
789
+ checks it.
790
+
791
+ **The endpoint does not exist yet.** A failed POST is a soft message, not a
792
+ failed install: the local record stands and `ekwo register` retries it later.
793
+ `ekwo unregister` clears the local fields; it sends nothing, because the CLI
794
+ only holds the local row.
795
+
796
+ ## Testing it against a real project
797
+
798
+ The test suite runs against Postgres compiled to WebAssembly, so it proves the
799
+ migration runner, the installation sequence and the checks without a Supabase
800
+ project. Four things it cannot prove: the network driver, PostgREST, GoTrue,
801
+ and the extensions a hosted project has.
802
+
803
+ **The automated way.** From a checkout of the repository, against an empty
804
+ project you can throw away:
805
+
806
+ ```sh
807
+ npm run e2e:supabase
808
+ ```
809
+
810
+ It installs, migrates, upgrades the pack, signs in, books, files the
811
+ declaration and closes the year, and prints a pass/fail table with **how long
812
+ each step took** — which is the number worth reading, because what matters
813
+ about a release is which step holds it rather than the total. Everything comes
814
+ from the environment and no secret reaches the output; it refuses a database
815
+ that already holds an `instance` row, and `--reset` empties a throwaway project
816
+ so a failed run can be replayed.
817
+
818
+ Point `EKWO_E2E_PREVIOUS` at the last tag to make the run upgrade an
819
+ installation instead of creating one. The packages are not on npm yet, so it
820
+ takes **a path to a built binary of an older checkout** rather than a version:
821
+
822
+ ```sh
823
+ git worktree add /tmp/prev v0.2.0
824
+ (cd /tmp/prev && npm ci && npm run build)
825
+ EKWO_E2E_PREVIOUS=/tmp/prev/packages/cli/dist/bin.js npm run e2e:supabase
826
+ ```
827
+
828
+ [`docs/releasing.md`](../../docs/releasing.md) lists every variable it reads and
829
+ every refusal it makes. It is run by hand before a release is tagged, never by
830
+ the CI.
831
+
832
+ **By hand**, if you want to watch each step:
833
+
834
+ ```sh
835
+ npm install && npm run build
836
+
837
+ # 1. A project you can throw away. Note its ref, password, URL and key.
838
+ # --chart and --language are required here and not optional: the Belgian
839
+ # pack publishes two charts of accounts and four languages, and `ekwo init`
840
+ # refuses to pick either for you when there is nobody to ask.
841
+ node packages/cli/dist/bin.js init \
842
+ --db-url "postgresql://postgres.SCRATCHREF:PASSWORD@aws-1-REGION.pooler.supabase.com:5432/postgres" \
843
+ --supabase-url "https://SCRATCHREF.supabase.co" \
844
+ --service-role-key "$KEY" \
845
+ --country BE --chart default --language fr \
846
+ --org "Scratch" --company "Scratch BV" \
847
+ --admin-email "you@example.com" --admin-password "a-long-password" \
848
+ --fiscal-year 2026 --iban "BE71096123456769" --yes
849
+
850
+ # 2. Everything should be green, and nothing pending.
851
+ node packages/cli/dist/bin.js status --db-url "$URL"
852
+ node packages/cli/dist/bin.js doctor --db-url "$URL"
853
+
854
+ # 3. The history must be the Supabase one: this should report no difference.
855
+ supabase link --project-ref SCRATCHREF
856
+ supabase migration list
857
+
858
+ # 4. And the other direction: db push finds nothing left to do.
859
+ supabase db push
860
+
861
+ # 5. Run init again. Every step should say it was already there.
862
+ node packages/cli/dist/bin.js init --db-url "$URL" --country BE \
863
+ --chart default --language fr \
864
+ --org "Scratch" --company "Scratch BV" --admin-email "you@example.com" \
865
+ --admin-user-id "<the uuid from step 1>" --fiscal-year 2026 --yes
866
+
867
+ # 6. Sign in as the administrator and confirm row level security really binds:
868
+ # a company you were not invited to must be invisible.
869
+ ```
870
+
871
+ ## Licence
872
+
873
+ [AGPL-3.0-only](../../LICENSE) © Ekwo AI.