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.
- package/README.md +148 -207
- package/dist/citadel.es.js +1289 -1449
- package/dist/citadel.umd.js +19 -19
- package/dist/command_examples/basic-commands.d.ts +2 -83
- package/dist/src/__test-utils__/factories.d.ts +29 -10
- package/dist/src/components/Citadel/Citadel.d.ts +11 -7
- package/dist/src/components/Citadel/components/AvailableCommands.d.ts +1 -8
- package/dist/src/components/Citadel/components/CommandInput.d.ts +0 -2
- package/dist/src/components/Citadel/config/CitadelConfigContext.d.ts +5 -2
- package/dist/src/components/Citadel/config/defaults.d.ts +15 -7
- package/dist/src/components/Citadel/config/types.d.ts +32 -20
- package/dist/src/components/Citadel/hooks/useCitadelState.d.ts +2 -1
- package/dist/src/components/Citadel/hooks/useCommandHistory.d.ts +19 -9
- package/dist/src/components/Citadel/hooks/useCommandParser.d.ts +18 -15
- package/dist/src/components/Citadel/hooks/useSegmentStack.d.ts +14 -0
- package/dist/src/components/Citadel/hooks/useSegmentStackVersion.d.ts +1 -0
- package/dist/src/components/Citadel/storage/BaseStorage.d.ts +2 -2
- package/dist/src/components/Citadel/storage/LocalStorage.d.ts +1 -1
- package/dist/src/components/Citadel/storage/MemoryStorage.d.ts +2 -2
- package/dist/src/components/Citadel/types/__tests__/command-registry.test.d.ts +1 -0
- package/dist/src/components/Citadel/types/__tests__/segment-stack.test.d.ts +1 -0
- package/dist/src/components/Citadel/types/command-registry.d.ts +84 -0
- package/dist/src/components/Citadel/types/command-trie.d.ts +49 -203
- package/dist/src/components/Citadel/types/help-command.d.ts +3 -3
- package/dist/src/components/Citadel/types/segment-stack.d.ts +62 -0
- package/dist/src/components/Citadel/types/state.d.ts +8 -22
- package/dist/src/components/Citadel/types/storage.d.ts +4 -3
- package/dist/src/components/Citadel/utils/logger.d.ts +21 -0
- package/dist/src/index.d.ts +0 -1
- 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
|
+

|
|
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
|
-
|
|
28
|
+
import { Citadel } from "citadel_cli";
|
|
29
|
+
|
|
24
30
|
function App() {
|
|
25
31
|
return (
|
|
26
|
-
|
|
27
|
-
<Citadel
|
|
28
|
-
|
|
29
|
-
</div>
|
|
32
|
+
<>
|
|
33
|
+
<Citadel />
|
|
34
|
+
</>
|
|
30
35
|
);
|
|
31
36
|
}
|
|
32
37
|
```
|
|
33
38
|
|
|
34
|
-
|
|
39
|
+
Press <kbd>.</kbd> (period) to activate Citadel.
|
|
35
40
|
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
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
|
+

|
|
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
|
-
|
|
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
|
-
|
|
48
|
-
|
|
49
|
-
|
|
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
|
-
|
|
84
|
+
Word segments are autocompleted, whereas argument segments are used to store
|
|
85
|
+
user-entered values.
|
|
53
86
|
|
|
54
|
-
|
|
87
|
+
A few notes on arguments:
|
|
55
88
|
|
|
56
|
-
|
|
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
|
-
|
|
59
|
-
|
|
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
|
-
|
|
102
|
+
```
|
|
103
|
+
async (args: string[]) => new TextCommandResult(`Hello, ${args[0]}!`)
|
|
104
|
+
```
|
|
63
105
|
|
|
64
|
-
|
|
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
|
-
```
|
|
67
|
-
|
|
68
|
-
['refresh', 'cache'],
|
|
69
|
-
'Refresh application cache',
|
|
70
|
-
async () => {
|
|
71
|
-
// Clear local storage
|
|
111
|
+
```
|
|
112
|
+
async (_args: string[]) => {
|
|
72
113
|
localStorage.clear();
|
|
73
|
-
|
|
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
|
-
|
|
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
|
-
```
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
|
|
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
|
-
|
|
133
|
+
At the time of this writing the following command result types are available:
|
|
103
134
|
|
|
104
|
-
|
|
135
|
+
- `ErrorCommandResult`
|
|
136
|
+
- `ImageCommandResult`
|
|
137
|
+
- `JsonCommandResult`
|
|
138
|
+
- `TextCommandResult`
|
|
105
139
|
|
|
106
|
-
|
|
107
|
-
|
|
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
|
-
|
|
146
|
+
const cmdRegistry = new CommandRegistry();
|
|
142
147
|
|
|
143
|
-
|
|
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
|
-
{
|
|
159
|
-
{ name: '
|
|
160
|
-
|
|
161
|
-
|
|
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
|
-
|
|
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
|
-
|
|
164
|
+
The result of this should look like this:
|
|
202
165
|
|
|
203
|
-
|
|
166
|
+

|
|
204
167
|
|
|
205
|
-
|
|
168
|
+
Go forth and make your application experience better!
|
|
206
169
|
|
|
207
|
-
|
|
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
|
-
|
|
172
|
+
Certain configuration options can be passed to the Citadel component. These are
|
|
173
|
+
given below, along with their default values.
|
|
220
174
|
|
|
221
|
-
```
|
|
222
|
-
const
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
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
|
-
|
|
194
|
+
Then to make the component aware of them:
|
|
234
195
|
|
|
235
|
-
### Project Structure
|
|
236
196
|
```
|
|
237
|
-
|
|
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
|
-
|
|
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/
|
|
254
|
-
cd
|
|
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/
|
|
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
|
-
[](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.
|