shutro-erp 0.3.0 → 0.3.2

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 (137) hide show
  1. package/README.md +58 -58
  2. package/dist/account_portal/.kosh/bindings.js +1 -1
  3. package/dist/account_portal/.kosh/schema.d.ts +31 -1
  4. package/dist/account_portal/index.js +3 -2
  5. package/dist/accounting/.kosh/bindings.js +1 -1
  6. package/dist/accounting/.kosh/schema.d.ts +31 -1
  7. package/dist/accounting/index.js +4 -0
  8. package/dist/assistant/routes.js +1 -1
  9. package/dist/automation/engine.js +1 -1
  10. package/dist/automation/web/messages.js +2 -2
  11. package/dist/cli/apps.d.ts +1 -1
  12. package/dist/cli/apps.js +14 -14
  13. package/dist/cli/bin.js +3 -0
  14. package/dist/cli/index.js +11 -11
  15. package/dist/cli/init.d.ts +1 -1
  16. package/dist/cli/init.js +60 -36
  17. package/dist/cli/launch.js +10 -7
  18. package/dist/cli/project.d.ts +7 -7
  19. package/dist/cli/project.js +25 -25
  20. package/dist/cli/prompt.js +2 -2
  21. package/dist/cli/templates.d.ts +3 -3
  22. package/dist/cli/templates.js +33 -28
  23. package/dist/cli/ui.d.ts +1 -8
  24. package/dist/cli/ui.js +36 -12
  25. package/dist/contacts/.kosh/bindings.js +1 -1
  26. package/dist/contacts/.kosh/schema.d.ts +31 -1
  27. package/dist/integrations/inbound.d.ts +36 -0
  28. package/dist/integrations/inbound.js +171 -0
  29. package/dist/integrations/index.d.ts +18 -0
  30. package/dist/integrations/index.js +400 -0
  31. package/dist/integrations/models.d.ts +6 -0
  32. package/dist/integrations/models.js +119 -0
  33. package/dist/integrations/web/DeliveryTools.d.ts +4 -0
  34. package/dist/integrations/web/DeliveryTools.js +15 -0
  35. package/dist/integrations/web/EndpointTools.d.ts +7 -0
  36. package/dist/integrations/web/EndpointTools.js +25 -0
  37. package/dist/integrations/web/InboundTools.d.ts +4 -0
  38. package/dist/integrations/web/InboundTools.js +15 -0
  39. package/dist/integrations/web/WebhookTools.d.ts +7 -0
  40. package/dist/integrations/web/WebhookTools.js +25 -0
  41. package/dist/integrations/web/index.d.ts +16 -0
  42. package/dist/integrations/web/index.js +27 -0
  43. package/dist/integrations/web/messages.d.ts +2 -0
  44. package/dist/integrations/web/messages.js +53 -0
  45. package/dist/integrations/webhooks.d.ts +63 -0
  46. package/dist/integrations/webhooks.js +217 -0
  47. package/dist/l10n_bd/.kosh/bindings.js +1 -1
  48. package/dist/l10n_bd/.kosh/schema.d.ts +31 -1
  49. package/dist/l10n_in/.kosh/bindings.js +1 -1
  50. package/dist/l10n_in/.kosh/schema.d.ts +31 -1
  51. package/dist/l10n_in/einvoice.js +1 -1
  52. package/dist/l10n_in/web/messages.js +2 -2
  53. package/dist/mail/index.js +2 -2
  54. package/dist/modules-sdk/index.d.ts +1 -1
  55. package/dist/modules-sdk/loader.js +2 -2
  56. package/dist/modules-sdk/manifest.d.ts +48 -0
  57. package/dist/notify/messages.js +3 -3
  58. package/dist/notify/routes.js +1 -1
  59. package/dist/notify/web/messages.js +1 -1
  60. package/dist/payment/.kosh/bindings.d.ts +22 -0
  61. package/dist/payment/.kosh/bindings.js +15 -0
  62. package/dist/payment/.kosh/schema.d.ts +1553 -0
  63. package/dist/payment/index.d.ts +34 -0
  64. package/dist/payment/index.js +375 -0
  65. package/dist/payment/module.d.ts +6853 -0
  66. package/dist/payment/module.js +74 -0
  67. package/dist/payment/providers.d.ts +64 -0
  68. package/dist/payment/providers.js +77 -0
  69. package/dist/payment/settle.d.ts +11 -0
  70. package/dist/payment/settle.js +59 -0
  71. package/dist/payment/sslcommerz.d.ts +12 -0
  72. package/dist/payment/sslcommerz.js +185 -0
  73. package/dist/portal/.kosh/bindings.js +1 -1
  74. package/dist/portal/.kosh/schema.d.ts +31 -1
  75. package/dist/portal/routes-documents.js +5 -0
  76. package/dist/product/.kosh/bindings.js +1 -1
  77. package/dist/product/.kosh/schema.d.ts +31 -1
  78. package/dist/project/.kosh/bindings.js +1 -1
  79. package/dist/project/.kosh/schema.d.ts +31 -1
  80. package/dist/project_portal/.kosh/bindings.js +1 -1
  81. package/dist/project_portal/.kosh/schema.d.ts +31 -1
  82. package/dist/purchase/.kosh/bindings.js +1 -1
  83. package/dist/purchase/.kosh/schema.d.ts +31 -1
  84. package/dist/purchase_account/.kosh/bindings.js +1 -1
  85. package/dist/purchase_account/.kosh/schema.d.ts +31 -1
  86. package/dist/purchase_portal/.kosh/bindings.js +1 -1
  87. package/dist/purchase_portal/.kosh/schema.d.ts +31 -1
  88. package/dist/sale_account/.kosh/bindings.js +1 -1
  89. package/dist/sale_account/.kosh/schema.d.ts +31 -1
  90. package/dist/sale_portal/.kosh/bindings.js +1 -1
  91. package/dist/sale_portal/.kosh/schema.d.ts +31 -1
  92. package/dist/sale_project/.kosh/bindings.js +1 -1
  93. package/dist/sale_project/.kosh/schema.d.ts +31 -1
  94. package/dist/sales/.kosh/bindings.js +1 -1
  95. package/dist/sales/.kosh/schema.d.ts +31 -1
  96. package/dist/sales/index.js +2 -0
  97. package/dist/server/api/client.js +1 -1
  98. package/dist/server/api/openapi.js +17 -1
  99. package/dist/server/api/rest.d.ts +6 -1
  100. package/dist/server/api/rest.js +11 -0
  101. package/dist/server/apps.js +5 -5
  102. package/dist/server/auth/api-keys.d.ts +13 -0
  103. package/dist/server/auth/api-keys.js +65 -0
  104. package/dist/server/auth/base.js +24 -0
  105. package/dist/server/auth/crypto.js +1 -1
  106. package/dist/server/auth/scopes.d.ts +12 -0
  107. package/dist/server/auth/scopes.js +51 -0
  108. package/dist/server/auth/service.d.ts +57 -1
  109. package/dist/server/auth/service.js +229 -5
  110. package/dist/server/demo.d.ts +1 -1
  111. package/dist/server/demo.js +1 -1
  112. package/dist/server/env-names.d.ts +2 -0
  113. package/dist/server/env-names.js +16 -0
  114. package/dist/server/host.js +5 -1
  115. package/dist/server/identity.d.ts +7 -0
  116. package/dist/server/index.d.ts +2 -1
  117. package/dist/server/index.js +2 -1
  118. package/dist/server/registry.js +1 -1
  119. package/dist/server/server.d.ts +5 -0
  120. package/dist/server/server.js +45 -5
  121. package/dist/settings/routes.js +1 -1
  122. package/dist/settings/web/ApiKeysPage.d.ts +8 -0
  123. package/dist/settings/web/ApiKeysPage.js +70 -0
  124. package/dist/settings/web/index.d.ts +1 -0
  125. package/dist/settings/web/index.js +4 -0
  126. package/dist/settings/web/messages.js +62 -0
  127. package/dist/storage/routes.js +1 -1
  128. package/dist/studio/build.js +1 -1
  129. package/dist/studio/generate.js +4 -4
  130. package/dist/studio/module.js +5 -5
  131. package/dist/studio/web/messages.js +2 -2
  132. package/dist/ui/app.js +26 -3
  133. package/dist/ui/i18n/messages.js +2 -2
  134. package/dist/ui/pwa/worker.js +2 -2
  135. package/dist/ui/registry.d.ts +15 -0
  136. package/dist/ui/registry.js +20 -0
  137. package/package.json +15 -3
package/README.md CHANGED
@@ -1,6 +1,6 @@
1
1
  # shutro-erp
2
2
 
3
- **Kosh** is an ERP framework for Node.js, TypeScript and PostgreSQL, built on the
3
+ **Shutro ERP** is an ERP framework for Node.js, TypeScript and PostgreSQL, built on the
4
4
  [Shutro](https://www.npmjs.com/package/shutro) ORM. Define a model once and get a REST API with OpenAPI, a typed
5
5
  client, list / form / kanban / pivot / graph / calendar / gantt / map / dashboard views, exports, PDF documents and a
6
6
  chatter on every record. Permissions are checked on the server on every route.
@@ -20,13 +20,13 @@ chatter on every record. Permissions are checked on the server on every route.
20
20
  ## Requirements
21
21
 
22
22
  - **Node.js 24** or later (`node --version`)
23
- - **PostgreSQL 16** or later, or **Docker** (Kosh can start PostgreSQL for you)
23
+ - **PostgreSQL 16** or later, or **Docker** (Shutro ERP can start PostgreSQL for you)
24
24
  - An authenticator app for the administrator's second factor (Google Authenticator, Microsoft Authenticator,
25
25
  1Password, …)
26
26
 
27
27
  ## Install with the terminal
28
28
 
29
- One command creates the project, installs Kosh and asks for everything it needs:
29
+ One command creates the project, installs Shutro ERP and asks for everything it needs:
30
30
 
31
31
  ```sh
32
32
  npx shutro-erp@latest init my-erp # the project my-erp
@@ -37,7 +37,7 @@ npx shutro-erp@latest init # asks for the project name first
37
37
 
38
38
  ```sh
39
39
  npm install shutro-erp # Shutro, the ORM, comes with it
40
- npx kosh init
40
+ npx shutro-erp init
41
41
  ```
42
42
 
43
43
  The setup asks for what the installation needs, one question at a time: type an answer or press **Enter** for
@@ -58,7 +58,7 @@ the default shown, pick from lists with **↑ ↓** and **Enter**, tick apps wit
58
58
  | Authenticator code | `123456` | scan the printed secret, type a code; **keep the recovery codes** it prints |
59
59
 
60
60
  Then it writes the project, creates the database tables (`migrations/0001_init.sql`), the company and the
61
- administrator, and loads the apps' default data. **Then it starts Kosh for you**: it installs the project's
61
+ administrator, and loads the apps' default data. **Then it starts Shutro ERP for you**: it installs the project's
62
62
  dependencies (with the package manager you ran it with: npm, pnpm, yarn or bun), starts the API and the
63
63
  back-office, waits until both answer, and opens the back-office in your browser on the web port. Both keep
64
64
  running in that terminal; **Ctrl+C** stops them. Sign in with the administrator's login, password and a code from
@@ -77,21 +77,21 @@ npm run web # the back-office on http://localhost:5173, in a second ter
77
77
  ```sh
78
78
  npx shutro-erp@latest init my-erp --yes --docker-db --admin-password "a long passphrase here"
79
79
 
80
- npx kosh init --yes \
80
+ npx shutro-erp init --yes \
81
81
  --database-url postgres://postgres:postgres@localhost:5432/my_erp --create-database \
82
82
  --company "Padma Traders" --currency BDT --lang bn --tz Asia/Dhaka \
83
83
  --admin-login admin --admin-password "a long passphrase here" \
84
84
  --apps permissions,reporting,messaging --storage-dir ./var/attachments --port 3000 --web-port 5173
85
85
  ```
86
86
 
87
- With `--yes` nothing is asked, so the authenticator is not enrolled: run `npx kosh init` again in a terminal to
88
- enrol it (administrator rights wait for it). `npx kosh init --help` lists every flag. Replies by email: set `MAIL_INBOUND_DOMAIN` in `.env` (the secrets are generated) and point your email provider's inbound webhook at `/api/v1/mail/inbound` with the `MAIL_INBOUND_WEBHOOK_SECRET`. `--demo` (or a yes to the
87
+ With `--yes` nothing is asked, so the authenticator is not enrolled: run `npx shutro-erp init` again in a terminal to
88
+ enrol it (administrator rights wait for it). `npx shutro-erp init --help` lists every flag. Replies by email: set `MAIL_INBOUND_DOMAIN` in `.env` (the secrets are generated) and point your email provider's inbound webhook at `/api/v1/mail/inbound` with the `MAIL_INBOUND_WEBHOOK_SECRET`. `--demo` (or a yes to the
89
89
  question after the apps) loads a sample company with contacts, products, orders, invoices and projects, made by
90
90
  the demo users `dina`, `rahim`, `karim` and `sumi` (one shared password, printed at the end); not for production.
91
91
 
92
92
  ## Install with Docker
93
93
 
94
- `kosh init` writes `Dockerfile`, `compose.yaml`, `nginx.conf` and `.dockerignore` into every project.
94
+ `shutro-erp init` writes `Dockerfile`, `compose.yaml`, `nginx.conf` and `.dockerignore` into every project.
95
95
 
96
96
  **PostgreSQL in Docker, the app on your machine** (development):
97
97
 
@@ -117,21 +117,21 @@ docker compose up -d --build # db + api + web; the back-office on http://
117
117
  | Service | What | Port |
118
118
  |---|---|---|
119
119
  | `db` | PostgreSQL 18, data in the `db` volume | `127.0.0.1:5432` (`POSTGRES_PORT`) |
120
- | `api` | the Kosh API; runs `kosh migrate` on every start, then serves | inside the network (`api:3000`) |
120
+ | `api` | the Shutro ERP API; runs `shutro-erp migrate` on every start, then serves | inside the network (`api:3000`) |
121
121
  | `web` | nginx with the built back-office, `/api` proxied to `api` | `8080` (`WEB_PORT`) |
122
122
 
123
123
  Useful commands:
124
124
 
125
125
  ```sh
126
126
  docker compose logs -f api # the API's log
127
- docker compose exec api npx kosh migrate status # any kosh command inside the container
128
- docker compose exec api npx kosh seed
127
+ docker compose exec api npx shutro-erp migrate status # any shutro-erp command inside the container
128
+ docker compose exec api npx shutro-erp seed
129
129
  docker compose up -d --build api # after changing modules: rebuild and migrate
130
130
  docker compose down # stop (volumes keep the data)
131
- docker compose exec db pg_dump -U kosh my_erp > backup.sql # a backup
131
+ docker compose exec db pg_dump -U shutro my_erp > backup.sql # a backup
132
132
  ```
133
133
 
134
- Behind HTTPS, set `KOSH_SECURE_COOKIES: "true"` in `compose.yaml`. To use a PostgreSQL server outside Docker, set
134
+ Behind HTTPS, set `SHUTRO_ERP_SECURE_COOKIES: "true"` in `compose.yaml`. To use a PostgreSQL server outside Docker, set
135
135
  `DOCKER_DATABASE_URL` in `.env` (an address the container can reach, not `localhost`).
136
136
 
137
137
  ## Project structure
@@ -144,7 +144,7 @@ my-erp/
144
144
  ├── src/
145
145
  │ ├── modules.ts the installed modules: base, the apps, your modules
146
146
  │ ├── server.ts the API server (npm start)
147
- │ ├── modules/ your modules (kosh module create <name>)
147
+ │ ├── modules/ your modules (shutro-erp module create <name>)
148
148
  │ │ └── library/
149
149
  │ │ ├── index.ts the module: groups, access levels, app, menus, actions, views
150
150
  │ │ ├── models.ts its models (fields, access per group, record rules)
@@ -152,7 +152,7 @@ my-erp/
152
152
  │ │ └── migrations.ts its data migrations
153
153
  │ └── web/
154
154
  │ ├── main.tsx the back-office (React): renderers of each installed app
155
- │ └── index.css Tailwind + Kosh's design tokens
155
+ │ └── index.css Tailwind + Shutro ERP's design tokens
156
156
  ├── index.html, vite.config.ts the back-office build (npm run web / build:web)
157
157
  ├── Dockerfile, compose.yaml, nginx.conf, .dockerignore
158
158
  └── var/attachments/ uploaded files (with folder storage)
@@ -163,8 +163,8 @@ my-erp/
163
163
  A module is a folder with its models and a manifest. Create one:
164
164
 
165
165
  ```sh
166
- npx kosh module create library
167
- npx kosh migrate # creates its tables, records it, loads its seed data
166
+ npx shutro-erp module create library
167
+ npx shutro-erp migrate # creates its tables, records it, loads its seed data
168
168
  ```
169
169
 
170
170
  It writes `src/modules/library/` and adds `library` to `src/modules.ts`. After `npm start` and a reload of the
@@ -198,7 +198,7 @@ export const Book = defineModel('library.book', {
198
198
  ```
199
199
 
200
200
  **`index.ts`**: the module: its Shutro part (name, version, groups, models, seed data, data migrations) and its
201
- Kosh part (access levels, app, menus, actions, views):
201
+ Shutro ERP part (access levels, app, menus, actions, views):
202
202
 
203
203
  ```ts
204
204
  import { base, baseShutro } from 'shutro-erp';
@@ -259,29 +259,29 @@ export const modules = [base, messaging, library];
259
259
  ```
260
260
 
261
261
  ```sh
262
- npx kosh migrate # tables, the module's version (shutro_module), its seed data
262
+ npx shutro-erp migrate # tables, the module's version (shutro_module), its seed data
263
263
  ```
264
264
 
265
265
  A module published on npm installs the same way: `npm install <package>`, import it in `src/modules.ts`, then
266
- `npx kosh migrate`. Installing does not give anyone access: grant its roles to users ([Users and roles](#users-and-roles)).
266
+ `npx shutro-erp migrate`. Installing does not give anyone access: grant its roles to users ([Users and roles](#users-and-roles)).
267
267
 
268
- **Remove a module**: take it out of `src/modules.ts`; `npx kosh migrate` then plans dropping its tables, which is
269
- destructive: it stops for review, and `npx kosh migrate --allow-destructive` applies it. Back up first.
268
+ **Remove a module**: take it out of `src/modules.ts`; `npx shutro-erp migrate` then plans dropping its tables, which is
269
+ destructive: it stops for review, and `npx shutro-erp migrate --allow-destructive` applies it. Back up first.
270
270
 
271
271
  ## Migrations
272
272
 
273
273
  ```sh
274
- npx kosh migrate # plan + apply + seed
275
- npx kosh migrate --plan # only write the plan, to review it
276
- npx kosh migrate --name add_isbn # name the new file (migrations/0002_add_isbn.sql)
277
- npx kosh migrate status # applied and pending files
278
- npx kosh migrate --allow-destructive
274
+ npx shutro-erp migrate # plan + apply + seed
275
+ npx shutro-erp migrate --plan # only write the plan, to review it
276
+ npx shutro-erp migrate --name add_isbn # name the new file (migrations/0002_add_isbn.sql)
277
+ npx shutro-erp migrate status # applied and pending files
278
+ npx shutro-erp migrate --allow-destructive
279
279
  ```
280
280
 
281
- - **Schema migrations** are SQL files in `migrations/`. `kosh migrate` compares your modules with the live database
281
+ - **Schema migrations** are SQL files in `migrations/`. `shutro-erp migrate` compares your modules with the live database
282
282
  and writes the differences as the next file (`0002_update.sql`), applies every pending file in order (each in its
283
283
  own transaction, recorded in `shutro_migration`), then loads the seed data. Commit the files: a deployment runs the
284
- same ones (the Docker `api` service runs `kosh migrate` on start).
284
+ same ones (the Docker `api` service runs `shutro-erp migrate` on start).
285
285
  - **Destructive changes** (dropping a column or a table, narrowing a type) are written but not applied without
286
286
  `--allow-destructive`. A file written earlier and not applied yet is applied first.
287
287
  - **Per-module data migrations** change existing data when a module is upgraded. Declare them in the module's
@@ -305,7 +305,7 @@ export const migrations: DataMigration[] = [
305
305
 
306
306
  ```sh
307
307
  # index.ts: version: '0.2.0'
308
- npx kosh migrate # … module library: 0.1.0 → 0.2.0, data migration library@0.2.0
308
+ npx shutro-erp migrate # … module library: 0.1.0 → 0.2.0, data migration library@0.2.0
309
309
  ```
310
310
 
311
311
  ## Seed data
@@ -329,18 +329,18 @@ export const data = [
329
329
  ```
330
330
 
331
331
  ```sh
332
- npx kosh seed # every module's seed data, and the apps' defaults
333
- npx kosh seed library # one module (or several: kosh seed library inventory)
332
+ npx shutro-erp seed # every module's seed data, and the apps' defaults
333
+ npx shutro-erp seed library # one module (or several: shutro-erp seed library inventory)
334
334
  ```
335
335
 
336
- `kosh migrate` seeds too (skip with `--no-seed`). The apps' defaults are also seeded: approval policies
336
+ `shutro-erp migrate` seeds too (skip with `--no-seed`). The apps' defaults are also seeded: approval policies
337
337
  (permissions), document templates (reporting), activity types (messaging); users may edit them afterwards.
338
338
 
339
339
  ## Users and roles
340
340
 
341
341
  ```sh
342
- npx kosh user create rahim --name "Rahim Uddin" --email rahim@example.com --roles library.user,messaging.user
343
- npx kosh user password rahim # a new password (asked, hidden)
342
+ npx shutro-erp user create rahim --name "Rahim Uddin" --email rahim@example.com --roles library.user,messaging.user
343
+ npx shutro-erp user password rahim # a new password (asked, hidden)
344
344
  ```
345
345
 
346
346
  Roles are the groups of the installed modules' access levels (`library.user`, `library.manager`,
@@ -354,31 +354,31 @@ their roles grants it. Administrators manage roles, field security and grants in
354
354
  |---|---|
355
355
  | `DATABASE_URL` | PostgreSQL: `postgres://user:password@host:5432/database` |
356
356
  | `PORT`, `HOST` | the API's port (3000) and address (127.0.0.1; 0.0.0.0 in Docker) |
357
- | `KOSH_STORAGE_DIR` | attachments in this folder, or: |
357
+ | `SHUTRO_ERP_STORAGE_DIR` | attachments in this folder, or: |
358
358
  | `S3_ENDPOINT`, `S3_BUCKET`, `S3_ACCESS_KEY`, `S3_SECRET_KEY`, `S3_REGION` | S3-compatible storage |
359
- | `KOSH_ATTACHMENT_MAX_BYTES` | upload limit (25 MB) |
359
+ | `SHUTRO_ERP_ATTACHMENT_MAX_BYTES` | upload limit (25 MB) |
360
360
  | `VALKEY_URL` | optional Valkey/Redis: rate limits and caches across servers |
361
- | `KOSH_SECURE_COOKIES` | `true` behind HTTPS (default in production), `false` for plain HTTP |
362
- | `KOSH_SCHEDULES` | `off` stops the scheduled-report ticker on this server |
361
+ | `SHUTRO_ERP_SECURE_COOKIES` | `true` behind HTTPS (default in production), `false` for plain HTTP |
362
+ | `SHUTRO_ERP_SCHEDULES` | `off` stops the scheduled-report ticker on this server |
363
363
  | `POSTGRES_USER`, `POSTGRES_PASSWORD`, `POSTGRES_DB`, `POSTGRES_PORT` | the Docker `db` service |
364
364
  | `DOCKER_DATABASE_URL`, `WEB_PORT` | Docker: another database for `api`; the back-office's port (8080) |
365
365
 
366
366
  ## Command reference
367
367
 
368
- Run them in the project folder (`npx kosh …`, or `npm run migrate` / `npm run seed`). They read `.env` and
368
+ Run them in the project folder (`npx shutro-erp …`, or `npm run migrate` / `npm run seed`). They read `.env` and
369
369
  `src/modules.ts` (`--modules <file>` for another list).
370
370
 
371
371
  | Command | What |
372
372
  |---|---|
373
- | `kosh init [folder]` | set up an installation (questions, or flags with `--yes`); again later to enrol 2FA |
374
- | `kosh migrate` | apply pending files, plan what is missing into a new file and apply it, run data migrations, seed |
375
- | `kosh migrate --plan` · `--name <n>` · `--allow-destructive` · `--no-seed` | review only · file name · drops · no seeding |
376
- | `kosh migrate status` | applied and pending migration files |
377
- | `kosh seed [module …]` | seed data of all modules, or of those named |
378
- | `kosh module create <name>` | a new module in `src/modules/<name>/`, listed in `src/modules.ts` |
379
- | `kosh user create <login>` | a user: `--name`, `--email`, `--roles`, `--password` (asked when missing) |
380
- | `kosh user password <login>` | a new password |
381
- | `kosh generate client` | a typed API client for your front-end (`--out src/api/kosh.gen.ts`) |
373
+ | `shutro-erp init [folder]` | set up an installation (questions, or flags with `--yes`); again later to enrol 2FA |
374
+ | `shutro-erp migrate` | apply pending files, plan what is missing into a new file and apply it, run data migrations, seed |
375
+ | `shutro-erp migrate --plan` · `--name <n>` · `--allow-destructive` · `--no-seed` | review only · file name · drops · no seeding |
376
+ | `shutro-erp migrate status` | applied and pending migration files |
377
+ | `shutro-erp seed [module …]` | seed data of all modules, or of those named |
378
+ | `shutro-erp module create <name>` | a new module in `src/modules/<name>/`, listed in `src/modules.ts` |
379
+ | `shutro-erp user create <login>` | a user: `--name`, `--email`, `--roles`, `--password` (asked when missing) |
380
+ | `shutro-erp user password <login>` | a new password |
381
+ | `shutro-erp generate client` | a typed API client for your front-end (`--out src/api/shutro-erp.gen.ts`) |
382
382
  | `npm start` · `npm run web` · `npm run build:web` | the API · the back-office (dev) · the back-office build (`dist/`) |
383
383
  | `npm run docker:up` | `docker compose up -d --build` |
384
384
 
@@ -387,26 +387,26 @@ Run them in the project folder (`npx kosh …`, or `npm run migrate` / `npm run
387
387
  | Import | What |
388
388
  |---|---|
389
389
  | `shutro-erp` | the server: `createServer`, `startServer`, `base`, REST + OpenAPI, sessions and 2FA |
390
- | `shutro-erp/orm` | Shutro (models, fields, groups, records, migrations) as Kosh uses it |
390
+ | `shutro-erp/orm` | Shutro (models, fields, groups, records, migrations) as Shutro ERP uses it |
391
391
  | `shutro-erp/modules-sdk` | `defineModule`: manifests, access levels, menus, actions |
392
392
  | `shutro-erp/views`, `shutro-erp/search` | view definitions (`list`, `form`, `kanban`, `pivot`, …) and the search layer |
393
393
  | `shutro-erp/permissions` | roles, field security, approvals (`/web`: the approval panel) |
394
394
  | `shutro-erp/reporting` | pivots, exports, documents, scheduled reports (`/web`: the report views) |
395
395
  | `shutro-erp/messaging`, `shutro-erp/storage` | the chatter, activities, attachments (`/web`: the chatter) |
396
396
  | `shutro-erp/ui` | the React app shell, design system and renderers; `shutro-erp/ui/styles.css` the tokens |
397
- | `shutro-erp/cli` | the `kosh` command's functions |
397
+ | `shutro-erp/cli` | the `shutro-erp` command's functions |
398
398
 
399
399
  ## Troubleshooting
400
400
 
401
401
  - **"PostgreSQL did not answer"**: nothing listens at that host and port. Start your PostgreSQL service, or
402
402
  answer `2` (Docker) to the first question; Docker Desktop must be running.
403
403
  - **"password authentication failed"**: the user or password in `DATABASE_URL` is wrong.
404
- - **Signed in but no apps**: the user has no role granting them (`kosh user create … --roles`); administrators need
405
- their authenticator enrolled (`npx kosh init` again).
406
- - **Sign-in works on the API but not in the browser over plain HTTP**: `KOSH_SECURE_COOKIES=false`.
407
- - **Lost the phone**: sign in with one of the recovery codes `kosh init` printed.
408
- - **"It drops or changes existing data"**: `kosh migrate` found a destructive change; read the new file in
409
- `migrations/`, back up, then `npx kosh migrate --allow-destructive`.
404
+ - **Signed in but no apps**: the user has no role granting them (`shutro-erp user create … --roles`); administrators need
405
+ their authenticator enrolled (`npx shutro-erp init` again).
406
+ - **Sign-in works on the API but not in the browser over plain HTTP**: `SHUTRO_ERP_SECURE_COOKIES=false`.
407
+ - **Lost the phone**: sign in with one of the recovery codes `shutro-erp init` printed.
408
+ - **"It drops or changes existing data"**: `shutro-erp migrate` found a destructive change; read the new file in
409
+ `migrations/`, back up, then `npx shutro-erp migrate --allow-destructive`.
410
410
 
411
411
  Documentation, design notes and the source: https://github.com/isahaq1/kosh
412
412
 
@@ -1,4 +1,4 @@
1
- // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: d1607b05570077f9
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 0ea5d21ac5b24166
2
2
  // Bindings of module `account_portal`: import the definition API from here to get types scoped to what it can see.
3
3
  export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
4
4
  import { definitions } from '../../server/orm.js';
@@ -1,4 +1,4 @@
1
- // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: d1607b05570077f9
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 0ea5d21ac5b24166
2
2
  // Schema view of module `account_portal` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
3
3
  import type { F } from '../../server/orm.js';
4
4
 
@@ -22,6 +22,7 @@ export interface SchemaView {
22
22
  readonly 'approval.policy': ApprovalPolicy;
23
23
  readonly 'approval.request': ApprovalRequest;
24
24
  readonly 'audit.event': AuditEvent;
25
+ readonly 'auth.api_key': AuthApiKey;
25
26
  readonly 'auth.credential': AuthCredential;
26
27
  readonly 'auth.session': AuthSession;
27
28
  readonly 'auth.user_grant': AuthUserGrant;
@@ -582,6 +583,34 @@ export interface AuditEvent {
582
583
  export interface AuditEventMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'audit.event'> {
583
584
  }
584
585
 
586
+ /** `auth.api_key` (owner: base). */
587
+ export interface AuthApiKey {
588
+ readonly name: 'auth.api_key';
589
+ readonly fields: {
590
+ readonly id: F.Id<'auth.api_key'>;
591
+ readonly createdAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
592
+ readonly updatedAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
593
+ readonly createdBy: F.Many2one<'res.users', { readonly readonly: true }>;
594
+ readonly updatedBy: F.Many2one<'res.users', { readonly readonly: true }>;
595
+ readonly displayName: F.Char<{ readonly required: true; readonly readonly: true; readonly compute: true }>;
596
+ readonly name: F.Char<{ readonly required: true }>;
597
+ readonly user: F.Many2one<'res.users', { readonly required: true }>;
598
+ readonly tokenHash: F.Char<{ readonly required: true }>;
599
+ readonly prefix: F.Char<{ readonly required: true }>;
600
+ readonly scopes: F.Text<{ readonly required: true }>;
601
+ readonly expiresAt: F.Datetime<{ readonly required: true }>;
602
+ readonly lastUsedAt: F.Datetime;
603
+ readonly lastUsedIp: F.Char;
604
+ readonly revoked: F.Bool<{ readonly required: true; readonly default: true }>;
605
+ readonly issuedBy: F.Many2one<'res.users'>;
606
+ };
607
+ readonly methods: AuthApiKeyMethods;
608
+ }
609
+
610
+ /** Methods of `auth.api_key`: declared ones and transitions. */
611
+ export interface AuthApiKeyMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'auth.api_key'> {
612
+ }
613
+
585
614
  /** `auth.credential` (owner: base). */
586
615
  export interface AuthCredential {
587
616
  readonly name: 'auth.credential';
@@ -1292,6 +1321,7 @@ export interface ResUsers {
1292
1321
  readonly companies: F.Many2many<'res.company', { readonly required: true }>;
1293
1322
  readonly lang: F.Char<{ readonly default: true }>;
1294
1323
  readonly tz: F.Char<{ readonly default: true }>;
1324
+ readonly isService: F.Bool<{ readonly required: true; readonly default: true }>;
1295
1325
  readonly portalPartner: F.Many2one<'res.partner'>; // portal
1296
1326
  };
1297
1327
  readonly methods: ResUsersMethods;
@@ -2,8 +2,8 @@
2
2
  * @packageDocumentation
3
3
  * `shutro-erp/account_portal`: Accounting in the portal (RFC 0015 §5.4). Proposed whenever Accounting and the portal
4
4
  * are installed. Customers see their posted invoices and credit notes, what is still due, print them and write on
5
- * them; vendors see the bills recorded for them. Payment is by the details on the invoice (online payment comes
6
- * with the integrations RFC).
5
+ * them; vendors see the bills recorded for them. Open invoices are payable online when the `payment` module and
6
+ * a provider are installed (RFC 0017 §5.5); otherwise by the payment details on the invoice.
7
7
  */
8
8
  import { accounting } from '../accounting/index.js';
9
9
  import { defineModule } from '../modules-sdk/index.js';
@@ -55,6 +55,7 @@ export const accountPortal = defineModule({
55
55
  thread: true,
56
56
  attention: { kind: 'out_invoice', paymentState: { in: ['not_paid', 'partial'] } },
57
57
  announce: { state: 'posted' },
58
+ payment: { when: { kind: 'out_invoice', paymentState: { in: ['not_paid', 'partial'] } } },
58
59
  },
59
60
  {
60
61
  key: 'bills',
@@ -1,4 +1,4 @@
1
- // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 5c7f5b1c2e181d35
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: c83d584195f8dd43
2
2
  // Bindings of module `accounting`: import the definition API from here to get types scoped to what it can see.
3
3
  export { defineModel, defineModule, extendModel, f, replace } from '../../server/orm.js';
4
4
  import { definitions } from '../../server/orm.js';
@@ -1,4 +1,4 @@
1
- // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: 5c7f5b1c2e181d35
1
+ // GENERATED by `pnpm generate:models` (Shutro’s generator). Do not edit. definitions-hash: c83d584195f8dd43
2
2
  // Schema view of module `accounting` (RFC 0002 §7.7): the models it owns or depends on, with the extensions it can see.
3
3
  import type { F } from '../../server/orm.js';
4
4
 
@@ -22,6 +22,7 @@ export interface SchemaView {
22
22
  readonly 'approval.policy': ApprovalPolicy;
23
23
  readonly 'approval.request': ApprovalRequest;
24
24
  readonly 'audit.event': AuditEvent;
25
+ readonly 'auth.api_key': AuthApiKey;
25
26
  readonly 'auth.credential': AuthCredential;
26
27
  readonly 'auth.session': AuthSession;
27
28
  readonly 'auth.user_grant': AuthUserGrant;
@@ -579,6 +580,34 @@ export interface AuditEvent {
579
580
  export interface AuditEventMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'audit.event'> {
580
581
  }
581
582
 
583
+ /** `auth.api_key` (owner: base). */
584
+ export interface AuthApiKey {
585
+ readonly name: 'auth.api_key';
586
+ readonly fields: {
587
+ readonly id: F.Id<'auth.api_key'>;
588
+ readonly createdAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
589
+ readonly updatedAt: F.Datetime<{ readonly required: true; readonly readonly: true }>;
590
+ readonly createdBy: F.Many2one<'res.users', { readonly readonly: true }>;
591
+ readonly updatedBy: F.Many2one<'res.users', { readonly readonly: true }>;
592
+ readonly displayName: F.Char<{ readonly required: true; readonly readonly: true; readonly compute: true }>;
593
+ readonly name: F.Char<{ readonly required: true }>;
594
+ readonly user: F.Many2one<'res.users', { readonly required: true }>;
595
+ readonly tokenHash: F.Char<{ readonly required: true }>;
596
+ readonly prefix: F.Char<{ readonly required: true }>;
597
+ readonly scopes: F.Text<{ readonly required: true }>;
598
+ readonly expiresAt: F.Datetime<{ readonly required: true }>;
599
+ readonly lastUsedAt: F.Datetime;
600
+ readonly lastUsedIp: F.Char;
601
+ readonly revoked: F.Bool<{ readonly required: true; readonly default: true }>;
602
+ readonly issuedBy: F.Many2one<'res.users'>;
603
+ };
604
+ readonly methods: AuthApiKeyMethods;
605
+ }
606
+
607
+ /** Methods of `auth.api_key`: declared ones and transitions. */
608
+ export interface AuthApiKeyMethods extends F.Methods<typeof import('../../server/index.js').baseShutro, 'auth.api_key'> {
609
+ }
610
+
582
611
  /** `auth.credential` (owner: base). */
583
612
  export interface AuthCredential {
584
613
  readonly name: 'auth.credential';
@@ -1216,6 +1245,7 @@ export interface ResUsers {
1216
1245
  readonly companies: F.Many2many<'res.company', { readonly required: true }>;
1217
1246
  readonly lang: F.Char<{ readonly default: true }>;
1218
1247
  readonly tz: F.Char<{ readonly default: true }>;
1248
+ readonly isService: F.Bool<{ readonly required: true; readonly default: true }>;
1219
1249
  };
1220
1250
  readonly methods: ResUsersMethods;
1221
1251
  }
@@ -404,6 +404,10 @@ export const accounting = defineModule({
404
404
  sequence: 5,
405
405
  },
406
406
  ],
407
+ // What webhooks may subscribe to besides record changes (RFC 0017 §5.3).
408
+ events: [
409
+ { topic: 'account.move.posted', model: 'account.move', label: 'Entry, invoice or bill posted' },
410
+ ],
407
411
  approvals: [
408
412
  {
409
413
  name: 'Vendor bill approvals',
@@ -209,7 +209,7 @@ export function registerAssistantRoutes(context, options) {
209
209
  ? 'No API key is configured on this server (ANTHROPIC_API_KEY).'
210
210
  : 'An administrator has not switched the assistant on for this company.');
211
211
  if ((await usedThisMonth(env.companyId)) >= options.monthlyTokens)
212
- throw new HttpProblem(429, 'assistant-budget', "The month's assistant budget is used up", `This company used its ${options.monthlyTokens} tokens of the month.`, 'An administrator can raise KOSH_ASSISTANT_MONTHLY_TOKENS.');
212
+ throw new HttpProblem(429, 'assistant-budget', "The month's assistant budget is used up", `This company used its ${options.monthlyTokens} tokens of the month.`, 'An administrator can raise SHUTRO_ERP_ASSISTANT_MONTHLY_TOKENS.');
213
213
  return client;
214
214
  };
215
215
  /** Logs a call in a transaction of its own (after the model answered: no transaction stays open meanwhile). */
@@ -832,7 +832,7 @@ export function webhookProblem(url, hosts = webhookHosts()) {
832
832
  return 'Webhooks call https URLs (http only for localhost).';
833
833
  const allowed = hosts.some((h) => (h.startsWith('*.') ? host.endsWith(h.slice(1)) : host === h));
834
834
  if (!allowed)
835
- return `The host ${host} is not on the webhook allow-list (KOSH_AUTOMATION_WEBHOOK_HOSTS).`;
835
+ return `The host ${host} is not on the webhook allow-list (SHUTRO_ERP_AUTOMATION_WEBHOOK_HOSTS).`;
836
836
  return null;
837
837
  }
838
838
  /** The signature of a webhook body: `sha256=<hex HMAC>` with the rule's secret. */
@@ -62,7 +62,7 @@ export const messages = {
62
62
  cronHint: 'Minute hour day month weekday: "0 2 * * *" is every night at 02:00; @daily, @weekly, @monthly too. Runs on the records the condition matches.',
63
63
  url: 'URL',
64
64
  webhookFields: 'Fields to send',
65
- webhookHint: "A signed JSON POST (X-Kosh-Signature: sha256 HMAC with the rule's webhook secret), retried on failure; the host must be on the allow-list (KOSH_AUTOMATION_WEBHOOK_HOSTS).",
65
+ webhookHint: "A signed JSON POST (X-Kosh-Signature: sha256 HMAC with the rule's webhook secret), retried on failure; the host must be on the allow-list (SHUTRO_ERP_AUTOMATION_WEBHOOK_HOSTS).",
66
66
  condition: 'And the record matches',
67
67
  conditionJson: 'Condition (domain)',
68
68
  field: 'Field',
@@ -169,7 +169,7 @@ export const messages = {
169
169
  cronHint: 'মিনিট ঘণ্টা দিন মাস সপ্তাহের দিন: "0 2 * * *" মানে প্রতি রাত ০২:০০; @daily, @weekly, @monthly-ও চলে। শর্তে মেলা রেকর্ডে চলে।',
170
170
  url: 'URL',
171
171
  webhookFields: 'যে ফিল্ড পাঠাবে',
172
- webhookHint: 'স্বাক্ষরিত JSON POST (X-Kosh-Signature: নিয়মের ওয়েবহুক সিক্রেট দিয়ে sha256 HMAC), ব্যর্থ হলে আবার চেষ্টা; হোস্ট অনুমোদিত তালিকায় থাকতে হবে (KOSH_AUTOMATION_WEBHOOK_HOSTS)।',
172
+ webhookHint: 'স্বাক্ষরিত JSON POST (X-Kosh-Signature: নিয়মের ওয়েবহুক সিক্রেট দিয়ে sha256 HMAC), ব্যর্থ হলে আবার চেষ্টা; হোস্ট অনুমোদিত তালিকায় থাকতে হবে (SHUTRO_ERP_AUTOMATION_WEBHOOK_HOSTS)।',
173
173
  condition: 'এবং রেকর্ড মেলে',
174
174
  conditionJson: 'শর্ত (ডোমেইন)',
175
175
  field: 'ফিল্ড',
@@ -8,5 +8,5 @@ export interface AppsIo {
8
8
  /** The confirmation question (tests script it). Default: the terminal. */
9
9
  readonly prompter?: Prompter;
10
10
  }
11
- /** Runs `kosh apps …`. Returns the exit code. */
11
+ /** Runs `shutro-erp apps …`. Returns the exit code. */
12
12
  export declare function runApps(argv: readonly string[], io: AppsIo): Promise<number>;
package/dist/cli/apps.js CHANGED
@@ -1,5 +1,5 @@
1
1
  /**
2
- * `kosh apps` (RFC 0008): the App manager in the terminal. Lists the catalogue with each app's state, and installs,
2
+ * `shutro-erp apps` (RFC 0008): the App manager in the terminal. Lists the catalogue with each app's state, and installs,
3
3
  * enables or disables apps: the plan is shown first (modules, dependencies, cascades, schema statements), then
4
4
  * applied in one transaction by `shutro-erp` (`prepareApps` / `applyApps`).
5
5
  */
@@ -13,14 +13,14 @@ import { PROTECTED_APPS } from '../settings/index.js';
13
13
  import { modulesOf, seedWith } from './project.js';
14
14
  import { defaultsPrompter, terminalPrompter } from './prompt.js';
15
15
  import { createUi, PLAIN_TERMINAL } from './ui.js';
16
- export const APPS_USAGE = `Usage: kosh apps [command] [app …] [options]
16
+ export const APPS_USAGE = `Usage: shutro-erp apps [command] [app …] [options]
17
17
 
18
- kosh apps the catalogue: installed, disabled and available apps
19
- kosh apps install <app …> install apps and what they need, in one transaction
20
- kosh apps enable <app …> serve disabled apps again (their data is still there)
21
- kosh apps disable <app …> switch apps off: menus, API and access go, tables and data stay
18
+ shutro-erp apps the catalogue: installed, disabled and available apps
19
+ shutro-erp apps install <app …> install apps and what they need, in one transaction
20
+ shutro-erp apps enable <app …> serve disabled apps again (their data is still there)
21
+ shutro-erp apps disable <app …> switch apps off: menus, API and access go, tables and data stay
22
22
  (apps that depend on them are disabled too)
23
- kosh apps upgrade [app …] move installed apps to their code's version: schema, data migrations,
23
+ shutro-erp apps upgrade [app …] move installed apps to their code's version: schema, data migrations,
24
24
  seed data (default: every app whose code is newer)
25
25
 
26
26
  Options:
@@ -32,7 +32,7 @@ Options:
32
32
 
33
33
  After a change, restart the API (npm start) so it serves the new set.
34
34
  `;
35
- /** Pictographs for the apps Kosh ships; other modules get a puzzle piece. */
35
+ /** Pictographs for the apps Shutro ERP ships; other modules get a puzzle piece. */
36
36
  const APP_ICONS = {
37
37
  base: '🏠',
38
38
  settings: '⚙️',
@@ -74,7 +74,7 @@ async function list(ui, driver, catalogue) {
74
74
  const label = m.app?.label ?? '';
75
75
  const why = row?.reason === 'dependency' ? ui.style('gray', ' (needed by another app)') : '';
76
76
  const behind = row !== undefined && row.version !== m.version
77
- ? ui.style('cyan', ` (installed ${row.version}: kosh apps upgrade ${m.name})`)
77
+ ? ui.style('cyan', ` (installed ${row.version}: shutro-erp apps upgrade ${m.name})`)
78
78
  : '';
79
79
  ui.write(` ${marks[s]} ${iconOf(ui, m.name)} ${ui.style('bold', m.name.padEnd(width))} ${ui.style('gray', m.version.padEnd(8))} ${s.padEnd(9)} ${label}${why}${behind}\n`);
80
80
  }
@@ -103,7 +103,7 @@ function showPlan(ui, prepared, catalogue) {
103
103
  if (plan.disable.length > 0)
104
104
  ui.detail('tables and data stay; enable brings everything back');
105
105
  }
106
- /** Runs `kosh apps …`. Returns the exit code. */
106
+ /** Runs `shutro-erp apps …`. Returns the exit code. */
107
107
  export async function runApps(argv, io) {
108
108
  const { values, positionals } = parseArgs({
109
109
  args: [...argv],
@@ -131,11 +131,11 @@ export async function runApps(argv, io) {
131
131
  }
132
132
  if (command === 'uninstall') {
133
133
  ui.warn('Removing an app and its data comes with a later step of RFC 0008.');
134
- ui.info(`Meanwhile: kosh apps disable ${names.join(' ') || '<app>'} (reversible, keeps the data).`);
134
+ ui.info(`Meanwhile: shutro-erp apps disable ${names.join(' ') || '<app>'} (reversible, keeps the data).`);
135
135
  return 2;
136
136
  }
137
137
  if (command !== 'list' && command !== 'upgrade' && names.length === 0) {
138
- ui.fail(`Name the app(s): kosh apps ${command} <app …>`);
138
+ ui.fail(`Name the app(s): shutro-erp apps ${command} <app …>`);
139
139
  return 2;
140
140
  }
141
141
  const envFile = join(io.cwd, '.env');
@@ -143,14 +143,14 @@ export async function runApps(argv, io) {
143
143
  process.loadEnvFile(envFile);
144
144
  const databaseUrl = process.env['DATABASE_URL'];
145
145
  if (databaseUrl === undefined || databaseUrl === '') {
146
- ui.fail('DATABASE_URL is not set: run kosh init, or add it to .env.');
146
+ ui.fail('DATABASE_URL is not set: run shutro-erp init, or add it to .env.');
147
147
  return 1;
148
148
  }
149
149
  const driver = pg.createDriver({
150
150
  url: databaseUrl,
151
151
  max: 2,
152
152
  parseNumeric: orm.toDecimal,
153
- applicationName: 'kosh apps',
153
+ applicationName: 'shutro-erp apps',
154
154
  });
155
155
  const prompter = io.prompter ??
156
156
  (values.yes === true || values.plan === true ? defaultsPrompter() : terminalPrompter());
package/dist/cli/bin.js CHANGED
@@ -1,6 +1,9 @@
1
1
  #!/usr/bin/env node
2
+ import { applyEnvironmentNames } from '../server/index.js';
2
3
  import { runCli } from './index.js';
3
4
  import { detectTerminal } from './ui.js';
5
+ // SHUTRO_ERP_* settings, read under their older KOSH_* names.
6
+ applyEnvironmentNames();
4
7
  process.exitCode = await runCli(process.argv.slice(2), (text) => process.stdout.write(text), {
5
8
  terminal: detectTerminal(process.stdout),
6
9
  });