citadel_cli 1.0.0 → 1.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (30) hide show
  1. package/README.md +148 -207
  2. package/dist/citadel.es.js +1289 -1449
  3. package/dist/citadel.umd.js +19 -19
  4. package/dist/command_examples/basic-commands.d.ts +2 -83
  5. package/dist/src/__test-utils__/factories.d.ts +29 -10
  6. package/dist/src/components/Citadel/Citadel.d.ts +11 -7
  7. package/dist/src/components/Citadel/components/AvailableCommands.d.ts +1 -8
  8. package/dist/src/components/Citadel/components/CommandInput.d.ts +0 -2
  9. package/dist/src/components/Citadel/config/CitadelConfigContext.d.ts +5 -2
  10. package/dist/src/components/Citadel/config/defaults.d.ts +15 -7
  11. package/dist/src/components/Citadel/config/types.d.ts +32 -20
  12. package/dist/src/components/Citadel/hooks/useCitadelState.d.ts +2 -1
  13. package/dist/src/components/Citadel/hooks/useCommandHistory.d.ts +19 -9
  14. package/dist/src/components/Citadel/hooks/useCommandParser.d.ts +18 -15
  15. package/dist/src/components/Citadel/hooks/useSegmentStack.d.ts +14 -0
  16. package/dist/src/components/Citadel/hooks/useSegmentStackVersion.d.ts +1 -0
  17. package/dist/src/components/Citadel/storage/BaseStorage.d.ts +2 -2
  18. package/dist/src/components/Citadel/storage/LocalStorage.d.ts +1 -1
  19. package/dist/src/components/Citadel/storage/MemoryStorage.d.ts +2 -2
  20. package/dist/src/components/Citadel/types/__tests__/command-registry.test.d.ts +1 -0
  21. package/dist/src/components/Citadel/types/__tests__/segment-stack.test.d.ts +1 -0
  22. package/dist/src/components/Citadel/types/command-registry.d.ts +84 -0
  23. package/dist/src/components/Citadel/types/command-trie.d.ts +49 -203
  24. package/dist/src/components/Citadel/types/help-command.d.ts +3 -3
  25. package/dist/src/components/Citadel/types/segment-stack.d.ts +62 -0
  26. package/dist/src/components/Citadel/types/state.d.ts +8 -22
  27. package/dist/src/components/Citadel/types/storage.d.ts +4 -3
  28. package/dist/src/components/Citadel/utils/logger.d.ts +21 -0
  29. package/dist/src/index.d.ts +0 -1
  30. package/package.json +3 -3
package/README.md CHANGED
@@ -1,257 +1,210 @@
1
- # Citadel
1
+ # Citadel CLI
2
2
 
3
3
  A hierarchical command-line interface (CLI) for web applications.
4
4
 
5
+ Use cases:
6
+
7
+ - Developers: Perform (multiple) REST API calls & view results, view/modify
8
+ cookies/localstorage. Do JavaScript things without affecting the application.
9
+ - "Poor man's Postman": execute API calls within the context of your application
10
+ - Devops: Improve how you interface with your existing CI/CD web app
11
+ - Power users: Provide a hook for advanced users of your internal or external
12
+ applications to quickly perform complex actions
13
+
14
+ ![Animated screenshot of Citadel CLI](https://github.com/user-attachments/assets/b64da0f7-a4a0-4f76-bc03-c0e40c0e14e5)
15
+
16
+ # Installation
17
+
18
+ ```bash
19
+ npm install citadel_cli
20
+ ```
21
+
5
22
  ## Quick Start
6
23
 
24
+ In your application:
25
+
7
26
  ```typescript
8
- import { Citadel, CommandTrie } from 'citadel';
9
-
10
- // Initialize command system
11
- const commands = new CommandTrie();
12
-
13
- // Register commands
14
- commands.addCommand(
15
- ['customer', 'search'],
16
- 'Search for customer records',
17
- async (args) => ({
18
- json: await customerService.search(args.query)
19
- }),
20
- { name: 'query', description: 'Customer name or ID' }
21
- );
22
27
 
23
- // Mount in your React app
28
+ import { Citadel } from "citadel_cli";
29
+
24
30
  function App() {
25
31
  return (
26
- <div>
27
- <Citadel commands={commands} />
28
- {/* Your app content */}
29
- </div>
32
+ <>
33
+ <Citadel />
34
+ </>
30
35
  );
31
36
  }
32
37
  ```
33
38
 
34
- ## Command Usage
39
+ Press <kbd>.</kbd> (period) to activate Citadel.
35
40
 
36
- Citadel commands composed of one or more words followed by zero or one arguments. For example:
37
- ```
38
- > ticket new customer 1234
39
- ```
40
- Where the user typed "tnc1234" to achieve the above result.
41
+ Now this doesn't do much, yet: it just shows the "help" command. You can
42
+ execute it by pressing <kbd>h[Enter]</kbd>. If you do then you should see the
43
+ following:
44
+
45
+ ![screenshot_help_cmd](https://github.com/user-attachments/assets/1cc6fd58-7591-45f1-980a-46da15a1843a)
46
+
47
+ When you execute a command the result is displayed in the output area. It shows
48
+ the command that was executed, a timestamp, whether the command succesfully
49
+ executed, and the command's output.
50
+
51
+ Adding your own commands is pretty straightforward. There are three steps to doing so.
41
52
 
53
+ 1. Create a `CommandRegistry`
54
+ 2. Add commands to the registry
55
+ 3. Pass the registry to the `Citadel` component
42
56
 
43
- ### Quick Execution
57
+ Let's add a simple `greet` command to demonstrate this.
44
58
 
45
- Commands can be typed using abbreviated forms. For example:
46
59
  ```
47
- ticket new → tn
48
- deploy status → ds
49
- customer history → ch
60
+ import { CommandRegistry, TextCommandResult } from "citadel_cli";
61
+
62
+ // 1. Create the registry where your commands will be stored
63
+ const cmdRegistry = new CommandRegistry();
64
+
65
+ // 2. Add a command to the registry. This can be called as many times as you like.
66
+ cmdRegistry.addCommand(
67
+ [
68
+ { type: 'word', name: 'greet' },
69
+ { type: 'argument', name: 'name', description: 'Enter your name' }
70
+ ],
71
+ 'Say hello to the world', // The description of this command. Used by "help".
72
+
73
+ // Next comes the "handler", which is what will get called when the user hits enter.
74
+ // The return type for this handler is `TextCommandResult`. There are other
75
+ // types of command result that we'll cover later.
76
+ async (args: string[]) => new TextCommandResult(`Hello, ${args[0]}!`)
77
+ );
50
78
  ```
79
+ The first argument to the `addCommand` function is an array of "command
80
+ segments". There are two types of command segments: `word`s and `argument`s.
81
+ Here we are defining a command with two segments named `greet` and `name`.
82
+ `greet` being a `word` segment and `name` being an `argument`.
51
83
 
52
- Simply type the first letter of each word in the command and press Enter.
84
+ Word segments are autocompleted, whereas argument segments are used to store
85
+ user-entered values.
53
86
 
54
- ### Arguments
87
+ A few notes on arguments:
55
88
 
56
- Commands can have zero or more arguments.
89
+ 1. You can have zero or more arguments in a command, and they can appear in any
90
+ order.
91
+ 2. The arguments the user enters will be passed to the handler as an array of
92
+ strings.
93
+ 3. Arguments can be single- or double-quoted. This allows users to enter in
94
+ values that have spaces or other special characters.
57
95
 
58
- ```bash
59
- > ticket new customer 1234
60
- ```
96
+ Continuing on with our `greet` example, after the segments are defined is a
97
+ description ("Say hello..."). This is the text that will be shown by the help
98
+ command.
99
+
100
+ The final argument to `addCommand` is the *handler*. Let's go over that:
61
101
 
62
- ### JavaScript Integration
102
+ ```
103
+ async (args: string[]) => new TextCommandResult(`Hello, ${args[0]}!`)
104
+ ```
63
105
 
64
- Commands can execute any JavaScript code, making them powerful automation tools:
106
+ As mentioned before this is what will be called after the user hits Enter. The
107
+ values for the arguments entered by the user (if any) are passed in to the
108
+ handler as `args: string[]`. What you do inside the handler is completely up to
109
+ your imagination. For example, say you wanted to clear the localstorage:
65
110
 
66
- ```typescript
67
- commands.addCommand(
68
- ['refresh', 'cache'],
69
- 'Refresh application cache',
70
- async () => {
71
- // Clear local storage
111
+ ```
112
+ async (_args: string[]) => {
72
113
  localStorage.clear();
73
- // Reset Redux store
74
- store.dispatch(resetState());
75
- // Reload application
76
- window.location.reload();
77
- return { json: { status: 'Cache cleared' } };
114
+ return new TextCommandResult('localStorage cleared!');
78
115
  }
79
- );
80
116
  ```
81
117
 
82
- ### Dynamic Results
83
-
84
- Commands can return different types of results:
85
- - JSON data for structured information
86
- - React components for rich visualizations
87
- - Plain text for simple outputs
88
- - Promises for async operations
118
+ Or perhaps make an HTTP POST and return the result as JSON:
89
119
 
90
- ```typescript
91
- commands.addCommand(
92
- ['user', 'activity'],
93
- 'Show user activity',
94
- async (args) => ({
95
- // Return both data and visualization
96
- json: await getUserActivity(args.userId),
97
- component: <ActivityGraph userId={args.userId} />
98
- })
99
- );
120
+ ```
121
+ async (args: string[]) => {
122
+ const response = await fetch('https://api.example.com/endpoint', {
123
+ method: 'POST',
124
+ headers: {
125
+ 'Content-Type': 'application/json',
126
+ },
127
+ body: JSON.stringify({ name: args[0] }),
128
+ });
129
+ return new JsonCommandResult(await response.json());
130
+ }
100
131
  ```
101
132
 
102
- ## Real-World Examples
133
+ At the time of this writing the following command result types are available:
103
134
 
104
- ### Customer Service Application
135
+ - `ErrorCommandResult`
136
+ - `ImageCommandResult`
137
+ - `JsonCommandResult`
138
+ - `TextCommandResult`
105
139
 
106
- ```typescript
107
- // Customer service command configuration
108
- commands.addCommand(
109
- ['ticket', 'new'],
110
- 'Create support ticket',
111
- async (args) => {
112
- const ticket = await ticketService.create({
113
- customerId: args.customer,
114
- priority: args.priority,
115
- description: args.description
116
- });
117
- return { json: { ticketId: ticket.id, status: 'created' } };
118
- },
119
- [
120
- { name: 'customer', description: 'Customer ID' },
121
- { name: 'priority', description: 'Ticket priority (high|medium|low)' },
122
- { name: 'description', description: 'Issue description' }
123
- ]
124
- );
140
+ Back to our `greeting` command. The final code for it (without comments) should
141
+ now look like this:
125
142
 
126
- commands.addCommand(
127
- ['customer', 'history'],
128
- 'View customer interaction history',
129
- async (args) => ({
130
- json: await customerService.getHistory(args.id, {
131
- last: args.days || 30
132
- })
133
- }),
134
- [
135
- { name: 'id', description: 'Customer ID' },
136
- { name: 'days', description: 'Number of days (default: 30)' }
137
- ]
138
- );
139
143
  ```
144
+ import { CommandRegistry, TextCommandResult } from "citadel_cli";
140
145
 
141
- ### Trading Platform Integration
146
+ const cmdRegistry = new CommandRegistry();
142
147
 
143
- ```typescript
144
- // Trading command configuration
145
- commands.addCommand(
146
- ['trade', 'limit'],
147
- 'Place limit order',
148
- async (args) => {
149
- const order = await tradingService.placeLimitOrder({
150
- pair: args.pair,
151
- price: args.price,
152
- quantity: args.quantity,
153
- side: args.side || 'buy'
154
- });
155
- return { json: { orderId: order.id, status: order.status } };
156
- },
148
+ cmdRegistry.addCommand(
157
149
  [
158
- { name: 'pair', description: 'Trading pair (e.g., btc-usd)' },
159
- { name: 'price', description: 'Limit price' },
160
- { name: 'quantity', description: 'Order quantity' },
161
- { name: 'side', description: 'Order side (buy|sell)' }
162
- ]
150
+ { type: 'word', name: 'greet' },
151
+ { type: 'argument', name: 'name', description: 'Enter your name' }
152
+ ],
153
+ 'Say hello to the world',
154
+ async (args: string[]) => new TextCommandResult(`Hello, ${args[0]}!`)
163
155
  );
164
156
  ```
157
+ Now that the command has been added all that is left is to pass the registry to
158
+ the `Citadel` component:
165
159
 
166
- ### DevOps Dashboard Commands
167
-
168
- ```typescript
169
- // Deployment command configuration
170
- commands.addCommand(
171
- ['deploy', 'status'],
172
- 'Check deployment status',
173
- async (args) => ({
174
- json: await deploymentService.getStatus(args.service, args.environment)
175
- }),
176
- [
177
- { name: 'service', description: 'Service name' },
178
- { name: 'environment', description: 'Environment (dev|staging|prod)' }
179
- ]
180
- );
181
-
182
- commands.addCommand(
183
- ['metrics', 'alert'],
184
- 'Configure service alerts',
185
- async (args) => ({
186
- json: await metricsService.setAlert(args.service, {
187
- metric: args.metric,
188
- threshold: args.threshold,
189
- duration: args.duration
190
- })
191
- }),
192
- [
193
- { name: 'service', description: 'Service name' },
194
- { name: 'metric', description: 'Metric name (cpu|memory|latency)' },
195
- { name: 'threshold', description: 'Alert threshold' },
196
- { name: 'duration', description: 'Duration in minutes' }
197
- ]
198
- );
160
+ ```
161
+ <Citadel commandRegistry={cmdRegistry} />
199
162
  ```
200
163
 
201
- ## Advanced Configuration
164
+ The result of this should look like this:
202
165
 
203
- ### Custom Command Rendering
166
+ ![screenshot_greeting_cmd](https://github.com/user-attachments/assets/a3c1acad-69b3-4079-87af-0425aea3980a)
204
167
 
205
- Citadel supports custom rendering of command results:
168
+ Go forth and make your application experience better!
206
169
 
207
- ```typescript
208
- commands.addCommand(
209
- ['dashboard', 'metrics'],
210
- 'Show service metrics',
211
- async (args) => ({
212
- component: <MetricsVisualization serviceId={args.service} />,
213
- json: await metricsService.get(args.service)
214
- }),
215
- { name: 'service', description: 'Service ID' }
216
- );
217
- ```
170
+ ## Configuration
218
171
 
219
- ### Authentication Integration
172
+ Certain configuration options can be passed to the Citadel component. These are
173
+ given below, along with their default values.
220
174
 
221
- ```typescript
222
- const citadelConfig = {
223
- commandFilter: (command) => {
224
- const userPermissions = getUserPermissions();
225
- return command.requiredPermissions.every(
226
- (perm) => userPermissions.includes(perm)
227
- );
228
- },
229
- // ... other config
175
+ ```
176
+ const config = {
177
+ commandTimeoutMs: 10000,
178
+ includeHelpCommand: true,
179
+ maxHeight: '80vh',
180
+ initialHeight: '40vh',
181
+ minHeight: '200',
182
+ outputFontSize: '0.875rem',
183
+ resetStateOnHide: false,
184
+ showCitadelKey: '.',
185
+ cursorType: 'bbs', // 'blink', 'spin', 'solid', or 'bbs'
186
+ cursorSpeed: 530,
187
+ storage: {
188
+ type: 'localStorage',
189
+ maxCommands: 100
190
+ }
230
191
  };
231
192
  ```
232
193
 
233
- ## Development
194
+ Then to make the component aware of them:
234
195
 
235
- ### Project Structure
236
196
  ```
237
- citadel/
238
- ├── src/
239
- │ ├── components/ # React components
240
- │ ├── styles/ # CSS and theme files
241
- │ ├── types/ # TypeScript definitions
242
- │ └── utils/ # Utility functions
243
- ├── public/ # Static assets
244
- └── vite.config.ts # Vite configuration
197
+ <Citadel commandRegistry={cmdRegistry} config={config} />
245
198
  ```
246
199
 
247
- ### Contributing
200
+ ## Contributing
248
201
 
249
202
  Contributions are welcome.
250
203
 
251
204
  1. Clone the repository:
252
205
  ```bash
253
- git clone https://github.com/jchilders/citadel.git
254
- cd citadel
206
+ git clone https://github.com/jchilders/citadel_cli.git
207
+ cd citadel_cli
255
208
  ```
256
209
 
257
210
  2. Install dependencies:
@@ -271,10 +224,12 @@ npm link
271
224
 
272
225
  5. (Optional) From the directory of the project you want to import Citadel into:
273
226
  ```bash
274
- npm link @jchilders/citadel
227
+ npm link @jchilders/citadel_cli
275
228
  # ... your normal build/run steps ...
276
229
  ```
277
230
 
231
+ Load your appliation and press <kbd>.</kbd>
232
+
278
233
  ### Bug Reports and Feature Requests
279
234
 
280
235
  - Use the GitHub Issues section to report bugs or suggest features
@@ -321,17 +276,3 @@ npm link @jchilders/citadel
321
276
  - Keep functions focused and modular
322
277
  - Use consistent formatting (the project uses ESLint and Prettier)
323
278
 
324
- ## License
325
-
326
- [![License: MIT](https://img.shields.io/badge/License-MIT-yellow.svg)](https://opensource.org/licenses/MIT)
327
-
328
- Citadel is licensed under the MIT License. See the [LICENSE](LICENSE) file for details.
329
-
330
- This means you can use Citadel in your own projects (commercial or non-commercial) as long as you include the original copyright notice and license terms. The MIT License is simple and permissive, allowing you to:
331
-
332
- - Use the code commercially
333
- - Modify the code
334
- - Distribute the code
335
- - Use in private/closed-source projects
336
-
337
- All we ask is that you include the original license and copyright notice in any copy or substantial portion of the software.