libmodulor 0.3.0 → 0.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (49) hide show
  1. package/CHANGELOG.md +11 -0
  2. package/README.md +7 -182
  3. package/dist/esm/index.react-native-pure.d.ts +10 -0
  4. package/dist/esm/index.react-native-pure.js +10 -0
  5. package/dist/esm/target/lib/react/entrypoint.d.ts +2 -0
  6. package/dist/esm/target/lib/rn/input.d.ts +8 -0
  7. package/dist/esm/target/lib/rn/input.js +26 -0
  8. package/dist/esm/target/react-native-pure/UCAutoExecLoader.d.ts +2 -0
  9. package/dist/esm/target/react-native-pure/UCAutoExecLoader.js +5 -0
  10. package/dist/esm/target/react-native-pure/UCEntrypointTouchable.d.ts +4 -0
  11. package/dist/esm/target/react-native-pure/UCEntrypointTouchable.js +6 -0
  12. package/dist/esm/target/react-native-pure/UCExecTouchable.d.ts +4 -0
  13. package/dist/esm/target/react-native-pure/UCExecTouchable.js +9 -0
  14. package/dist/esm/target/react-native-pure/UCForm.d.ts +4 -0
  15. package/dist/esm/target/react-native-pure/UCForm.js +13 -0
  16. package/dist/esm/target/react-native-pure/UCFormField.d.ts +11 -0
  17. package/dist/esm/target/react-native-pure/UCFormField.js +32 -0
  18. package/dist/esm/target/react-native-pure/UCFormFieldControl.d.ts +11 -0
  19. package/dist/esm/target/react-native-pure/UCFormFieldControl.js +36 -0
  20. package/dist/esm/target/react-native-pure/UCFormFieldDesc.d.ts +7 -0
  21. package/dist/esm/target/react-native-pure/UCFormFieldDesc.js +11 -0
  22. package/dist/esm/target/react-native-pure/UCFormFieldErr.d.ts +7 -0
  23. package/dist/esm/target/react-native-pure/UCFormFieldErr.js +5 -0
  24. package/dist/esm/target/react-native-pure/UCFormFieldLabel.d.ts +7 -0
  25. package/dist/esm/target/react-native-pure/UCFormFieldLabel.js +8 -0
  26. package/dist/esm/target/react-native-pure/UCFormSubmitControl.d.ts +9 -0
  27. package/dist/esm/target/react-native-pure/UCFormSubmitControl.js +8 -0
  28. package/dist/esm/target/react-web-pure/UCEntrypointTouchable.d.ts +1 -1
  29. package/dist/esm/target/react-web-pure/UCExecTouchable.d.ts +1 -1
  30. package/dist/esm/target/react-web-pure/UCExecTouchable.js +1 -1
  31. package/dist/esm/target/react-web-pure/UCFormFieldControl.d.ts +1 -1
  32. package/dist/esm/target/react-web-pure/UCFormFieldControl.js +3 -3
  33. package/package.json +12 -5
  34. package/docs/assets/trading-buy-asset-sequence-diagram.png +0 -0
  35. package/docs/assets/trading-target-mcp-server.png +0 -0
  36. package/docs/assets/trading-target-web-human.png +0 -0
  37. package/docs/assets/trading-target-web.png +0 -0
  38. package/docs/getting-started/001_Create_the_project.md +0 -168
  39. package/docs/getting-started/002_Create_the_App.md +0 -49
  40. package/docs/getting-started/003_Create_the_UseCase.md +0 -205
  41. package/docs/getting-started/004_Test_the_App.md +0 -114
  42. package/docs/getting-started/005_Create_the_Product.md +0 -46
  43. package/docs/getting-started/006_Create_the_server_Target.md +0 -130
  44. package/docs/getting-started/007_Create_the_web_Target.md +0 -262
  45. package/docs/getting-started/008_Switch_to_a_persistent_data_storage.md +0 -55
  46. package/docs/getting-started/009_Define_wording_for_humans.md +0 -42
  47. package/docs/getting-started/010_Create_the_cli_Target.md +0 -102
  48. package/docs/getting-started/011_Create_the_mcp_server_Target.md +0 -157
  49. package/docs/getting-started/012_Summary.md +0 -29
@@ -1,130 +0,0 @@
1
- # Create the server Target
2
-
3
- We'll use the pre-built [express](https://expressjs.com) `ServerManager`.
4
-
5
- ```sh
6
- mkdir src/products/SuperTrader/server
7
- touch src/products/SuperTrader/server/{container.ts,index.ts}
8
- touch tsconfig.build.json
9
- touch .env
10
- ```
11
-
12
- ## container.ts
13
-
14
- ```typescript
15
- import {
16
- CONTAINER_OPTS,
17
- EnvSettingsManager,
18
- type ServerManager,
19
- type ServerManagerSettings,
20
- type SettingsManager,
21
- TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
22
- bindCommon,
23
- bindProduct,
24
- } from 'libmodulor';
25
- import {
26
- NodeExpressServerManager,
27
- bindNodeCore,
28
- bindServer,
29
- } from 'libmodulor/node';
30
- import { Container } from 'inversify';
31
-
32
- import { I18n } from '../i18n.js';
33
- import { Manifest } from '../manifest.js';
34
-
35
- type S = ServerManagerSettings;
36
-
37
- const container = new Container(CONTAINER_OPTS);
38
-
39
- bindCommon<S>(container, () => ({
40
- ...TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
41
- }));
42
- bindNodeCore(container);
43
- bindServer(container);
44
- bindProduct(container, Manifest, I18n);
45
-
46
- container.rebind<SettingsManager>('SettingsManager').to(EnvSettingsManager);
47
-
48
- container.bind<ServerManager>('ServerManager').to(NodeExpressServerManager);
49
-
50
- export default container;
51
- ```
52
-
53
- ## index.ts
54
-
55
- ```typescript
56
- import {
57
- APPS_ROOT_DIR_NAME,
58
- type FSManager,
59
- type I18nManager,
60
- ServerBooter,
61
- } from 'libmodulor';
62
-
63
- import container from './container.js';
64
-
65
- await container.get<I18nManager>('I18nManager').init();
66
-
67
- await container.resolve(ServerBooter).exec({
68
- appsRootPath: container
69
- .get<FSManager>('FSManager')
70
- .path('..', '..', '..', APPS_ROOT_DIR_NAME),
71
- srcImporter: (path) => import(path),
72
- });
73
- ```
74
-
75
- ## tsconfig.build.json
76
-
77
- ```json
78
- {
79
- "extends": "./tsconfig.json",
80
- "compilerOptions": {
81
- "noEmit": false,
82
- "outDir": "dist"
83
- },
84
- "include": ["src"]
85
- }
86
- ```
87
-
88
- ## .env
89
-
90
- ```properties
91
- app_logger_level=trace # the default is 'debug'
92
- ```
93
-
94
- > [!TIP]
95
- > A setting named `my_setting` in the code can be overriden with an environment variable called `app_my_setting`.
96
-
97
- ## Build & Run
98
-
99
- Update `package.json` to add new entries to the `scripts`.
100
-
101
- ```json
102
- "build": "tsc --project tsconfig.build.json && cp .env dist/products/SuperTrader/server/.env",
103
- "run:server": "cd dist/products/SuperTrader/server && node --env-file .env index.js",
104
- ```
105
-
106
- ```sh
107
- yarn build && yarn run:server
108
- ```
109
-
110
- Et voilà ! The server is running !
111
-
112
- ```sh
113
- curl -X POST -H "Content-Type: application/json" http://localhost:7443/api/v1/Trading_BuyAsset
114
- # ❌ {"message":"Invalid credentials"}
115
- curl -X POST -H "Content-Type: application/json" -H "X-API-Key: PublicApiKeyToBeChangedWhenDeploying" http://localhost:7443/api/v1/Trading_BuyAsset
116
- # ❌ {"message":"isin must be filled"}
117
- curl -X POST -H "Content-Type: application/json" -H "X-API-Key: PublicApiKeyToBeChangedWhenDeploying" -d '{"isin":"US02079K3059","limit":123.5,"qty":150}' http://localhost:7443/api/v1/Trading_BuyAsset
118
- # ✅ {"parts":{"_0":{"items":[{"executedDirectly":false,"id":"95dddca5-5e9d-48ac-a90c-71a58d4e8554"}],"total":1}}}
119
- ```
120
-
121
- As you can see, validation comes out of the box. Later we'll see how to add even more precise rules to the data types.
122
-
123
- > [!NOTE]
124
- > The `public_api_key` is just a first layer of security to "authenticate" the client apps calling the server. Hopefully this is not the only security mechanism because of course, this key must be present in clear client side (web, cli, curl...). We'll dive deeper in security when we study the policies.
125
-
126
- ```sh
127
- yarn lint && git add . && git commit -m "feat: add the server target"
128
- ```
129
-
130
- Now that's done, let's [Create the web Target](./007_Create_the_web_Target.md).
@@ -1,262 +0,0 @@
1
- # Create the web Target
2
-
3
- We'll use the pre-built [React](https://react.dev) components to build a SPA (Single Page Application), bundled with [vite](https://vite.dev) and served with the server defined above.
4
-
5
- > [!WARNING]
6
- > For readers used to "beautiful" websites à la Linear, Vercel and related, your eyes will burn. You're going to discover the simple and pure CSS-less Web. The most beautiful one.
7
- > Of course, feel free to add CSS if you want to. The main goal here is to focus on the essence of the UI and not the UI design.
8
-
9
- ```sh
10
- yarn add --dev "@types/react@^18.3.17" "@types/react-dom@^18.3.5"
11
- yarn add "react@^18.3.1" "react-dom@^18.3.1"
12
-
13
- mkdir -p src/products/SuperTrader/web/components
14
- touch src/products/SuperTrader/vite.config.web.ts
15
- touch src/products/SuperTrader/web/{container.ts,index.html,index.tsx}
16
- touch src/products/SuperTrader/web/components/App.tsx
17
- ```
18
-
19
- ## vite.config.web.ts
20
-
21
- ```typescript
22
- import { join } from 'node:path';
23
-
24
- import { StripUCDLifecycleServerPlugin } from 'libmodulor/vite';
25
- import { defineConfig } from 'vite';
26
-
27
- const base = process.cwd();
28
- const root = join('src', 'products', 'SuperTrader', 'web');
29
- const outDir = join(
30
- base,
31
- 'dist',
32
- 'products',
33
- 'SuperTrader',
34
- 'server',
35
- 'public',
36
- );
37
-
38
- export default defineConfig({
39
- build: {
40
- emptyOutDir: true,
41
- outDir,
42
- },
43
- plugins: [StripUCDLifecycleServerPlugin],
44
- root,
45
- });
46
- ```
47
-
48
- ## container.ts
49
-
50
- ```typescript
51
- import {
52
- CONTAINER_OPTS,
53
- type ServerClientManagerSettings,
54
- TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
55
- bindCommon,
56
- bindProduct,
57
- } from 'libmodulor';
58
- import { bindWeb } from 'libmodulor/web';
59
- import { Container } from 'inversify';
60
-
61
- import { I18n } from '../i18n.js';
62
- import { Manifest } from '../manifest.js';
63
-
64
- type S = ServerClientManagerSettings;
65
-
66
- const container = new Container(CONTAINER_OPTS);
67
-
68
- bindCommon<S>(container, () => ({
69
- ...TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
70
- }));
71
- bindWeb(container);
72
- bindProduct(container, Manifest, I18n);
73
-
74
- export default container;
75
- ```
76
-
77
- ## index.html
78
-
79
- ```html
80
- <!DOCTYPE html>
81
- <html lang="en">
82
- <head>
83
- <meta charset="utf-8" />
84
- <meta content="width=device-width, initial-scale=1" name="viewport"
85
- />
86
- </head>
87
- <body>
88
- <div id="root"></div>
89
- <script type="module" src="/index.tsx"></script>
90
- </body>
91
- </html>
92
- ```
93
-
94
- ## App.tsx
95
-
96
- Update `src/apps/Trading/index.ts` to expose the use case.
97
-
98
- ```typescript
99
- export { BuyAssetUCD } from './src/ucds/BuyAssetUCD.js';
100
- ```
101
-
102
- Naturally, in real life scenarios, we would never have such a bloated `App.tsx`. Instead, we would create fine-grained components. Everybody does that, right ? Right ?
103
-
104
- ```tsx
105
- import { type Logger, type ProductManifest, UCOutputReader } from 'libmodulor';
106
- import {
107
- UCPanel,
108
- type UCPanelOnError,
109
- useDIContext,
110
- useUC,
111
- useUCOR,
112
- } from 'libmodulor/react';
113
- import {
114
- UCAutoExecLoader,
115
- UCExecTouchable,
116
- UCForm,
117
- } from 'libmodulor/react-web-pure';
118
- import React, { useEffect, useState, type ReactElement } from 'react';
119
-
120
- import { BuyAssetUCD, Manifest } from '../../../../apps/Trading/index.js';
121
-
122
- export default function App(): ReactElement {
123
- const { container, i18nManager, wordingManager } = useDIContext();
124
- const [logger] = useState(container.get<Logger>('Logger'));
125
- const [productManifest] = useState(
126
- container.get<ProductManifest>('ProductManifest'),
127
- );
128
-
129
- const [buyAssetUC] = useUC(Manifest, BuyAssetUCD, null);
130
- const [buyAssetPart0, _buyAssetPart1, { append0 }] = useUCOR(
131
- new UCOutputReader(BuyAssetUCD, undefined),
132
- );
133
-
134
- const [loading, setLoading] = useState(true);
135
-
136
- useEffect(() => {
137
- (async () => {
138
- logger.debug('Initializing i18n');
139
- await i18nManager.init();
140
- logger.debug('Done initializing i18n');
141
- setLoading(false);
142
- })();
143
-
144
- const { slogan } = wordingManager.p();
145
- document.title = `${productManifest.name} : ${slogan}`;
146
- }, [i18nManager, logger, productManifest, wordingManager]);
147
-
148
- const onError: UCPanelOnError = async (err) => alert(err.message);
149
-
150
- const { slogan } = wordingManager.p();
151
- const { label } = wordingManager.uc(buyAssetUC.def);
152
- const { label: idLabel } = wordingManager.ucof('id');
153
- const { label: executedDirectlyLabel } =
154
- wordingManager.ucof('executedDirectly');
155
-
156
- return (
157
- <div>
158
- {loading && 'Loading...'}
159
-
160
- {!loading && (
161
- <>
162
- <h1>
163
- {productManifest.name} : {slogan}
164
- </h1>
165
-
166
- <h2>{label}</h2>
167
-
168
- <UCPanel
169
- clearAfterExec={false}
170
- onDone={async (ucor) => append0(ucor)}
171
- onError={onError}
172
- renderAutoExecLoader={UCAutoExecLoader}
173
- renderExecTouchable={UCExecTouchable}
174
- renderForm={UCForm}
175
- sleepInMs={200} // Fake delay to see submit wording changing
176
- uc={buyAssetUC}
177
- />
178
-
179
- <table>
180
- <thead>
181
- <tr>
182
- <th>{idLabel}</th>
183
- <th>{executedDirectlyLabel}</th>
184
- </tr>
185
- </thead>
186
- <tbody>
187
- {buyAssetPart0?.items.map((i) => (
188
- <tr key={i.id}>
189
- <td>{i.id}</td>
190
- <td>{i.executedDirectly ? '✅' : '❌'}</td>
191
- </tr>
192
- ))}
193
- </tbody>
194
- <tfoot>
195
- <tr>
196
- <th>{i18nManager.t('total')}</th>
197
- <th>{buyAssetPart0?.pagination.total}</th>
198
- </tr>
199
- </tfoot>
200
- </table>
201
- </>
202
- )}
203
- </div>
204
- );
205
- }
206
- ```
207
-
208
- ## index.tsx
209
-
210
- ```typescript
211
- import { DIContextProvider } from 'libmodulor/react';
212
- import React, { StrictMode } from 'react';
213
- import ReactDOM from 'react-dom/client';
214
-
215
- import App from './components/App.js';
216
- import container from './container.js';
217
-
218
- const rootElt = document.getElementById('root');
219
- if (!rootElt) {
220
- throw new Error('Add a div#root in index.html');
221
- }
222
-
223
- ReactDOM.createRoot(rootElt).render(
224
- <StrictMode>
225
- <DIContextProvider container={container}>
226
- <App />
227
- </DIContextProvider>
228
- </StrictMode>,
229
- );
230
- ```
231
-
232
- ## Build & Run
233
-
234
- Update `package.json` to add the `web` build to the `build` command.
235
-
236
- ```json
237
- "build": "tsc --project tsconfig.build.json && cp .env dist/products/SuperTrader/server/.env && vite -c src/products/SuperTrader/vite.config.web.ts build",
238
- ```
239
-
240
- Update `src/products/SuperTrader/server/container.ts` to mount the `public` directory.
241
-
242
- ```diff
243
- ...TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
244
- +server_static_dir_path: 'public',
245
- ```
246
-
247
- Press <kbd>ctrl</kbd> + <kbd>C</kbd> to stop the server (we'll setup hot reload later).
248
-
249
- ```sh
250
- yarn build && yarn run:server
251
- open http://localhost:7443
252
- ```
253
-
254
- Et voilà ! The server is running ! Fill the form and see how it automatically submits to the server with client side and server side validation out of the box.
255
-
256
- <img src="/docs/assets/trading-target-web.png" width="600px">
257
-
258
- ```sh
259
- yarn lint && git add . && git commit -m "feat: add the web target"
260
- ```
261
-
262
- Now that's done, let's [Switch to a persistent data storage](./008_Switch_to_a_persistent_data_storage.md).
@@ -1,55 +0,0 @@
1
- # Switch to a persistent data storage
2
-
3
- By default, the data is stored in memory on the server. Therefore, whenever we restart it, we lose everything. That is not very practical in real life scenarios. Let's use SQLite instead.
4
-
5
- ```sh
6
- yarn add "knex@^3.1.0" "sqlite3@^5.1.7"
7
- ```
8
-
9
- Update `src/products/SuperTrader/server/container.ts` to change the implementation.
10
-
11
- ```diff
12
- [...]
13
- TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
14
- + type UCDataStore,
15
- [...]
16
- +import {
17
- + KnexUCDataStore,
18
- + type KnexUCDataStoreSettings,
19
- +} from 'libmodulor/uc-data-store-knex';
20
- [...]
21
- +type S = KnexUCDataStoreSettings & ServerManagerSettings;
22
- [...]
23
- ...TARGET_DEFAULT_SERVER_MANAGER_SETTINGS,
24
- + knex_uc_data_store_conn_string: 'postgresql://toto',
25
- + knex_uc_data_store_file_path: 'uc-data-store.sqlite',
26
- + knex_uc_data_store_pool_max: 5,
27
- + knex_uc_data_store_pool_min: 0,
28
- + knex_uc_data_store_type: 'sqlite3',
29
- server_static_dir_path: 'public',
30
- [...]
31
- container.rebind<SettingsManager>('SettingsManager').to(EnvSettingsManager);
32
- +container.rebind<UCDataStore>('UCDataStore').to(KnexUCDataStore);
33
- ```
34
-
35
- Press <kbd>ctrl</kbd> + <kbd>C</kbd> to stop the server.
36
-
37
- ```sh
38
- yarn build && yarn run:server
39
- ```
40
-
41
- Fill and submit the use case multiple times.
42
-
43
- Open the SQLite database with your favorite DB editor (e.g. TablePlus, DBeaver...).
44
-
45
- ```sh
46
- open dist/products/SuperTrader/server/uc-data-store.sqlite
47
- ```
48
-
49
- You should see all your submissions stored in the database.
50
-
51
- ```sh
52
- yarn lint && git add . && git commit -m "feat: persist data in SQLite"
53
- ```
54
-
55
- Now that's done, let's [Define wording for humans](./009_Define_wording_for_humans.md).
@@ -1,42 +0,0 @@
1
- # Define wording for humans
2
-
3
- By default, the UI simply "humanizes" the use case field keys. It's fine for technical people, but not for humans.
4
-
5
- Update `src/apps/Trading/src/i18n.ts` and add the following keys to `en`.
6
-
7
- ```typescript
8
- uc_BuyAsset_label: 'Buy an asset',
9
- uc_BuyAsset_i_submit_idle: 'Send buy order',
10
- uc_BuyAsset_i_submit_submitting: 'Sending',
11
- ucif_isin_label: 'ISIN',
12
- ucif_qty_label: 'Quantity',
13
- ucof_executedDirectly_label: '🚀 Executed directly',
14
- ucof_id_label: 'Identifier',
15
- validation_format_ISIN:
16
- 'Must be 2 uppercase letters, followed by 9 alphanumeric characters and 1 digit',
17
- ```
18
-
19
- Update `src/products/SuperTrader/i18n.ts` and add the following keys to `en`.
20
-
21
- ```typescript
22
- total: 'Total',
23
- ```
24
-
25
- > [!NOTE]
26
- > We distinguish what's related to the app from what's related to the product. Usually, in the app's `i18n`, you'll add only translations following a certain convention like `dt_*` (data type choices), `err_*` (error messages), `uc_*` (use cases), `ucif_*` (use case input fields), `ucof_*` (use case output fields), `validation_*` (validation messages), etc.
27
-
28
- Press <kbd>ctrl</kbd> + <kbd>C</kbd> to stop the server.
29
-
30
- ```sh
31
- yarn build && yarn run:server
32
- ```
33
-
34
- Refresh the page. You should see a better wording. Try to type an invalid `ISIN` and see how the full validation message is displayed as well.
35
-
36
- <img src="/docs/assets/trading-target-web-human.png" width="600px">
37
-
38
- ```sh
39
- yarn lint && git add . && git commit -m "feat: define wording for humans"
40
- ```
41
-
42
- Now that's done, let's [Create the cli Target](./010_Create_the_cli_Target.md).
@@ -1,102 +0,0 @@
1
- # Create the cli Target
2
-
3
- We'll use the pre-built [Node.js parseArgs](https://nodejs.org/api/util.html#utilparseargsconfig) `CLIManager`.
4
-
5
- ```sh
6
- mkdir src/products/SuperTrader/cli
7
- touch src/products/SuperTrader/cli/{container.ts,index.ts}
8
- ```
9
-
10
- ## container.ts
11
-
12
- ```typescript
13
- import {
14
- CONTAINER_OPTS,
15
- type ServerClientManagerSettings,
16
- TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
17
- bindCommon,
18
- bindProduct,
19
- } from 'libmodulor';
20
- import { bindNodeCLI, bindNodeCore } from 'libmodulor/node';
21
- import { Container } from 'inversify';
22
-
23
- import { I18n } from '../i18n.js';
24
- import { Manifest } from '../manifest.js';
25
-
26
- const container = new Container(CONTAINER_OPTS);
27
-
28
- bindCommon<ServerClientManagerSettings>(container, () => ({
29
- ...TARGET_DEFAULT_SERVER_CLIENT_MANAGER_SETTINGS,
30
- }));
31
- bindNodeCore(container);
32
- bindNodeCLI(container);
33
- bindProduct(container, Manifest, I18n);
34
-
35
- export default container;
36
- ```
37
-
38
- ## index.ts
39
-
40
- ```typescript
41
- import {
42
- APPS_ROOT_DIR_NAME,
43
- type FSManager,
44
- type I18nManager,
45
- } from 'libmodulor';
46
- import { NodeCoreCLIManager } from 'libmodulor/node';
47
-
48
- import container from './container.js';
49
-
50
- await container.get<I18nManager>('I18nManager').init();
51
-
52
- await container.resolve(NodeCoreCLIManager).handleCommand({
53
- appsRootPath: container
54
- .get<FSManager>('FSManager')
55
- .path('..', '..', '..', APPS_ROOT_DIR_NAME),
56
- srcImporter: (path) => import(path),
57
- });
58
- ```
59
-
60
- ## Build & Run
61
-
62
- Update `package.json` to add a new entry to the `scripts`.
63
-
64
- ```json
65
- "run:cli": "cd dist/products/SuperTrader/cli && node index.js",
66
- ```
67
-
68
- ```sh
69
- yarn build && yarn run:cli
70
- ```
71
-
72
- You can see the CLI help appearing with the available commands.
73
-
74
- > [!TIP]
75
- > Update the app's `i18n.ts` to add `uc_BuyAsset_desc` and `ucif_isin_desc` to have a more detailed help section.
76
-
77
- Start the server if it's not running.
78
-
79
- ```sh
80
- yarn run:server
81
- ```
82
-
83
- Execute the CLI in another terminal or tab.
84
-
85
- ```sh
86
- yarn run:cli Trading_BuyAsset
87
- # ❌ ISIN must be filled
88
- yarn run:cli Trading_BuyAsset --isin US02079K3059 --limit 123.5 --qty 150
89
- # ✅ {"parts":{"_0":{"items":[{"executedDirectly":false,"id":"da3dc295-6d7c-41b1-a00a-62683f3e6ab9"}],"total":1}}}
90
- ```
91
-
92
- Open the SQLite database with your favorite DB editor (e.g. TablePlus, DBeaver...).
93
-
94
- ```sh
95
- open dist/products/SuperTrader/server/uc-data-store.sqlite
96
- ```
97
-
98
- ```sh
99
- yarn lint && git add . && git commit -m "feat: add the cli target"
100
- ```
101
-
102
- Now that's done, let's [Create the mcp-server Target](./011_Create_the_mcp_server_Target.md).