@ruska/cli 0.1.3 → 0.1.5
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 +71 -11
- package/dist/cli.js +47 -21
- package/dist/commands/chat.d.ts +3 -0
- package/dist/commands/chat.js +51 -13
- package/dist/commands/health.d.ts +1 -0
- package/dist/commands/health.js +78 -0
- package/dist/commands/version.d.ts +1 -0
- package/dist/commands/version.js +68 -0
- package/dist/components/model-select.d.ts +2 -2
- package/dist/components/model-select.js +2 -2
- package/dist/hooks/use-stream.js +4 -4
- package/dist/lib/api.d.ts +5 -1
- package/dist/lib/api.js +39 -0
- package/dist/lib/output/truncate.d.ts +16 -0
- package/dist/lib/output/truncate.js +53 -0
- package/dist/lib/tools.d.ts +23 -0
- package/dist/lib/tools.js +42 -0
- package/dist/types/index.d.ts +8 -0
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -35,18 +35,23 @@ $ ruska --help
|
|
|
35
35
|
auth Configure API authentication
|
|
36
36
|
assistants List your assistants
|
|
37
37
|
assistant <id> Get assistant by ID
|
|
38
|
-
chat <message> Chat with
|
|
38
|
+
chat <message> Chat with the LLM (optionally with an assistant)
|
|
39
39
|
create Create a new assistant
|
|
40
40
|
models List available models
|
|
41
|
+
version Show CLI and API version
|
|
42
|
+
health Check API health status
|
|
41
43
|
|
|
42
44
|
Options
|
|
43
45
|
--ui Launch interactive TUI mode
|
|
44
46
|
|
|
45
47
|
Chat Options
|
|
46
|
-
-a, --assistant Assistant ID
|
|
48
|
+
-a, --assistant Assistant ID (optional, uses default chat if omitted)
|
|
47
49
|
-t, --thread Thread ID to continue a conversation
|
|
48
50
|
-m, --message Message (alternative to positional arg)
|
|
49
51
|
--json Output as newline-delimited JSON (auto-enabled when piped)
|
|
52
|
+
--truncate <n> Max characters for tool output (default: 500)
|
|
53
|
+
--truncate-lines Max lines for tool output (default: 10)
|
|
54
|
+
--full-output Disable truncation (show full output)
|
|
50
55
|
|
|
51
56
|
Create Options
|
|
52
57
|
--name Assistant name (required)
|
|
@@ -59,8 +64,9 @@ $ ruska --help
|
|
|
59
64
|
Examples
|
|
60
65
|
$ ruska auth # Configure API key and host
|
|
61
66
|
$ ruska assistants # List your assistants
|
|
62
|
-
$ ruska assistant
|
|
63
|
-
$ ruska chat "Hello"
|
|
67
|
+
$ ruska assistant abc-123 # Get assistant details
|
|
68
|
+
$ ruska chat "Hello" # Direct chat with default LLM
|
|
69
|
+
$ ruska chat "Hello" -a <assistant-id> # Chat with specific assistant
|
|
64
70
|
$ ruska chat "Follow up" -t <thread-id> # Continue existing thread
|
|
65
71
|
$ ruska chat "Hello" -a <id> --json # Output as NDJSON
|
|
66
72
|
$ ruska chat "Query" -a <id> | jq '.type' # Pipe to jq
|
|
@@ -128,7 +134,13 @@ Tools: get_exchange_rate, convert_currency
|
|
|
128
134
|
|
|
129
135
|
### `ruska chat <message>`
|
|
130
136
|
|
|
131
|
-
Chat with
|
|
137
|
+
Chat with the LLM (optionally with an assistant) using streaming. Requires authentication.
|
|
138
|
+
|
|
139
|
+
**Direct chat with default LLM:**
|
|
140
|
+
|
|
141
|
+
```bash
|
|
142
|
+
$ ruska chat "Hello, how are you?"
|
|
143
|
+
```
|
|
132
144
|
|
|
133
145
|
**Start a new conversation with an assistant:**
|
|
134
146
|
|
|
@@ -159,12 +171,38 @@ $ ruska chat "Hello" -a <assistant-id> | jq '.type'
|
|
|
159
171
|
|
|
160
172
|
**Options:**
|
|
161
173
|
|
|
162
|
-
| Option
|
|
163
|
-
|
|
|
164
|
-
| `-a, --assistant`
|
|
165
|
-
| `-t, --thread`
|
|
166
|
-
| `-m, --message`
|
|
167
|
-
| `--
|
|
174
|
+
| Option | Description |
|
|
175
|
+
| ------------------ | ----------------------------------------------------- |
|
|
176
|
+
| `-a, --assistant` | Assistant ID (optional, uses default chat if omitted) |
|
|
177
|
+
| `-t, --thread` | Thread ID to continue an existing conversation |
|
|
178
|
+
| `-m, --message` | Message to send (alternative to positional arg) |
|
|
179
|
+
| `--tools` | Tools for the chat session (see below for modes) |
|
|
180
|
+
| `--json` | Output as newline-delimited JSON (NDJSON) |
|
|
181
|
+
| `--truncate <n>` | Max characters for tool output (default: 500) |
|
|
182
|
+
| `--truncate-lines` | Max lines for tool output (default: 10) |
|
|
183
|
+
| `--full-output` | Disable truncation (show full output) |
|
|
184
|
+
|
|
185
|
+
**Tool options:**
|
|
186
|
+
|
|
187
|
+
| Value | Behavior |
|
|
188
|
+
| --------------------- | --------------------------------------------------------------------------------------- |
|
|
189
|
+
| (not provided) | Uses default tools: web_search, web_scrape, math_calculator, think_tool, python_sandbox |
|
|
190
|
+
| `--tools=disabled` | Disables all tools |
|
|
191
|
+
| `--tools=tool1,tool2` | Uses only the specified tools |
|
|
192
|
+
|
|
193
|
+
**Examples with tools:**
|
|
194
|
+
|
|
195
|
+
```bash
|
|
196
|
+
# Chat with default tools (web search, scrape, calculator, think, python)
|
|
197
|
+
$ ruska chat "What's the weather in Dallas?"
|
|
198
|
+
|
|
199
|
+
# Chat without any tools
|
|
200
|
+
$ ruska chat "Tell me a joke" --tools=disabled
|
|
201
|
+
|
|
202
|
+
# Chat with specific tools only
|
|
203
|
+
$ ruska chat "Calculate 2+2" --tools=math_calculator
|
|
204
|
+
$ ruska chat "Search and analyze" --tools=web_search,think_tool
|
|
205
|
+
```
|
|
168
206
|
|
|
169
207
|
**Exit codes:**
|
|
170
208
|
|
|
@@ -228,6 +266,28 @@ All Models (15):
|
|
|
228
266
|
...
|
|
229
267
|
```
|
|
230
268
|
|
|
269
|
+
### `ruska version`
|
|
270
|
+
|
|
271
|
+
Show CLI and API version information.
|
|
272
|
+
|
|
273
|
+
```bash
|
|
274
|
+
$ ruska version
|
|
275
|
+
|
|
276
|
+
Ruska CLI v0.1.3
|
|
277
|
+
API: https://chat.ruska.ai
|
|
278
|
+
```
|
|
279
|
+
|
|
280
|
+
### `ruska health`
|
|
281
|
+
|
|
282
|
+
Check API health status.
|
|
283
|
+
|
|
284
|
+
```bash
|
|
285
|
+
$ ruska health
|
|
286
|
+
|
|
287
|
+
API Health Check
|
|
288
|
+
Status: healthy
|
|
289
|
+
```
|
|
290
|
+
|
|
231
291
|
### `ruska --ui`
|
|
232
292
|
|
|
233
293
|
Launch the interactive TUI (Terminal User Interface) mode with a full-screen interface.
|
package/dist/cli.js
CHANGED
|
@@ -10,6 +10,8 @@ import { runAssistantCommand } from './commands/assistant.js';
|
|
|
10
10
|
import { runModelsCommand } from './commands/models.js';
|
|
11
11
|
import { runCreateAssistantCommand } from './commands/create-assistant.js';
|
|
12
12
|
import { runChatCommand } from './commands/chat.js';
|
|
13
|
+
import { runVersionCommand } from './commands/version.js';
|
|
14
|
+
import { runHealthCommand } from './commands/health.js';
|
|
13
15
|
const cli = meow(`
|
|
14
16
|
Usage
|
|
15
17
|
$ ruska <command> [options]
|
|
@@ -18,18 +20,26 @@ const cli = meow(`
|
|
|
18
20
|
auth Configure API authentication
|
|
19
21
|
assistants List your assistants
|
|
20
22
|
assistant <id> Get assistant by ID
|
|
21
|
-
chat <message> Chat with
|
|
23
|
+
chat <message> Chat with the LLM (optionally with an assistant)
|
|
22
24
|
create Create a new assistant
|
|
23
25
|
models List available models
|
|
26
|
+
version Show CLI and API version
|
|
27
|
+
health Check API health status
|
|
24
28
|
|
|
25
29
|
Options
|
|
26
30
|
--ui Launch interactive TUI mode
|
|
27
31
|
|
|
28
32
|
Chat Options
|
|
29
|
-
-a, --assistant Assistant ID
|
|
33
|
+
-a, --assistant Assistant ID (optional, uses default chat if omitted)
|
|
30
34
|
-t, --thread Thread ID to continue a conversation
|
|
31
35
|
-m, --message Message (alternative to positional arg)
|
|
36
|
+
--tools Tools to enable (default: web_search,web_scrape,math_calculator,think_tool,python_sandbox)
|
|
37
|
+
Use --tools=disabled to disable all tools
|
|
38
|
+
Use --tools=tool1,tool2 for specific tools
|
|
32
39
|
--json Output as newline-delimited JSON (auto-enabled when piped)
|
|
40
|
+
--truncate <n> Max characters for tool output (default: 500)
|
|
41
|
+
--truncate-lines Max lines for tool output (default: 10)
|
|
42
|
+
--full-output Disable truncation (show full output)
|
|
33
43
|
|
|
34
44
|
Create Options
|
|
35
45
|
--name Assistant name (required)
|
|
@@ -43,7 +53,10 @@ const cli = meow(`
|
|
|
43
53
|
$ ruska auth # Configure API key and host
|
|
44
54
|
$ ruska assistants # List your assistants
|
|
45
55
|
$ ruska assistant abc-123 # Get assistant details
|
|
46
|
-
$ ruska chat "Hello"
|
|
56
|
+
$ ruska chat "Hello" # Chat with default tools enabled
|
|
57
|
+
$ ruska chat "Hello" --tools=disabled # Chat without any tools
|
|
58
|
+
$ ruska chat "Search X" --tools=web_search # Chat with specific tools
|
|
59
|
+
$ ruska chat "Hello" -a <assistant-id> # Chat with specific assistant
|
|
47
60
|
$ ruska chat "Follow up" -t <thread-id> # Continue existing thread
|
|
48
61
|
$ ruska chat "Hello" -a <id> --json # Output as NDJSON
|
|
49
62
|
$ ruska chat "Query" -a <id> | jq '.type' # Pipe to jq
|
|
@@ -74,6 +87,18 @@ const cli = meow(`
|
|
|
74
87
|
type: 'string',
|
|
75
88
|
shortFlag: 't',
|
|
76
89
|
},
|
|
90
|
+
truncate: {
|
|
91
|
+
type: 'number',
|
|
92
|
+
default: 500,
|
|
93
|
+
},
|
|
94
|
+
truncateLines: {
|
|
95
|
+
type: 'number',
|
|
96
|
+
default: 10,
|
|
97
|
+
},
|
|
98
|
+
fullOutput: {
|
|
99
|
+
type: 'boolean',
|
|
100
|
+
default: false,
|
|
101
|
+
},
|
|
77
102
|
interactive: {
|
|
78
103
|
type: 'boolean',
|
|
79
104
|
shortFlag: 'i',
|
|
@@ -96,7 +121,7 @@ const cli = meow(`
|
|
|
96
121
|
},
|
|
97
122
|
},
|
|
98
123
|
});
|
|
99
|
-
const [command, ...
|
|
124
|
+
const [command, ...arguments_] = cli.input;
|
|
100
125
|
// Route to appropriate command
|
|
101
126
|
async function main() {
|
|
102
127
|
if (cli.flags.ui) {
|
|
@@ -114,7 +139,7 @@ async function main() {
|
|
|
114
139
|
break;
|
|
115
140
|
}
|
|
116
141
|
case 'assistant': {
|
|
117
|
-
const assistantId =
|
|
142
|
+
const assistantId = arguments_[0];
|
|
118
143
|
if (!assistantId) {
|
|
119
144
|
console.error('Usage: ruska assistant <id>');
|
|
120
145
|
console.log('Run `ruska assistants` to list available assistants');
|
|
@@ -133,30 +158,23 @@ async function main() {
|
|
|
133
158
|
cli.flags['a']?.toString();
|
|
134
159
|
const threadId = cli.flags.thread ??
|
|
135
160
|
cli.flags['t']?.toString();
|
|
136
|
-
const message =
|
|
137
|
-
// Require either assistant or thread
|
|
138
|
-
if (!assistantId && !threadId) {
|
|
139
|
-
console.error('Usage: ruska chat "<message>" -a <assistant-id>');
|
|
140
|
-
console.error(' ruska chat "<message>" -t <thread-id>');
|
|
141
|
-
console.log('');
|
|
142
|
-
console.log('Options:');
|
|
143
|
-
console.log(' -a, --assistant Assistant ID for new conversations');
|
|
144
|
-
console.log(' -t, --thread Thread ID to continue a conversation');
|
|
145
|
-
console.log('');
|
|
146
|
-
console.log('Examples:');
|
|
147
|
-
console.log(' ruska chat "Hello" -a abc-123');
|
|
148
|
-
console.log(' ruska chat "Follow up" -t thread-456');
|
|
149
|
-
process.exit(1);
|
|
150
|
-
}
|
|
161
|
+
const message = arguments_.join(' ') || cli.flags.message;
|
|
151
162
|
if (!message) {
|
|
152
163
|
console.error('Error: Message is required');
|
|
153
|
-
console.error('Usage: ruska chat "<message>" -a <assistant-id>');
|
|
164
|
+
console.error('Usage: ruska chat "<message>" [-a <assistant-id>]');
|
|
154
165
|
process.exit(1);
|
|
155
166
|
}
|
|
156
167
|
await runChatCommand(message, {
|
|
157
168
|
json: cli.flags.json,
|
|
158
169
|
assistantId,
|
|
159
170
|
threadId,
|
|
171
|
+
tools: cli.flags.tools,
|
|
172
|
+
truncateOptions: cli.flags.fullOutput
|
|
173
|
+
? undefined
|
|
174
|
+
: {
|
|
175
|
+
maxLength: cli.flags.truncate,
|
|
176
|
+
maxLines: cli.flags.truncateLines,
|
|
177
|
+
},
|
|
160
178
|
});
|
|
161
179
|
break;
|
|
162
180
|
}
|
|
@@ -174,6 +192,14 @@ async function main() {
|
|
|
174
192
|
});
|
|
175
193
|
break;
|
|
176
194
|
}
|
|
195
|
+
case 'version': {
|
|
196
|
+
await runVersionCommand();
|
|
197
|
+
break;
|
|
198
|
+
}
|
|
199
|
+
case 'health': {
|
|
200
|
+
await runHealthCommand();
|
|
201
|
+
break;
|
|
202
|
+
}
|
|
177
203
|
case 'help':
|
|
178
204
|
case undefined: {
|
|
179
205
|
cli.showHelp();
|
package/dist/commands/chat.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
* Chat command for streaming LLM responses
|
|
3
3
|
* Implements Golden Path: Beta architecture + Gamma output + Alpha timeout
|
|
4
4
|
*/
|
|
5
|
+
import { type TruncateOptions } from '../lib/output/truncate.js';
|
|
5
6
|
/**
|
|
6
7
|
* Run the chat command
|
|
7
8
|
*/
|
|
@@ -9,4 +10,6 @@ export declare function runChatCommand(message: string, options?: {
|
|
|
9
10
|
json?: boolean;
|
|
10
11
|
assistantId?: string;
|
|
11
12
|
threadId?: string;
|
|
13
|
+
tools?: string;
|
|
14
|
+
truncateOptions?: TruncateOptions;
|
|
12
15
|
}): Promise<void>;
|
package/dist/commands/chat.js
CHANGED
|
@@ -13,6 +13,8 @@ import { OutputFormatter } from '../lib/output/formatter.js';
|
|
|
13
13
|
import { classifyError, exitCodes } from '../lib/output/error-handler.js';
|
|
14
14
|
import { writeJson, checkIsTty } from '../lib/output/writers.js';
|
|
15
15
|
import { StreamService, StreamConnectionError, } from '../lib/services/stream-service.js';
|
|
16
|
+
import { truncate } from '../lib/output/truncate.js';
|
|
17
|
+
import { parseToolsFlag } from '../lib/tools.js';
|
|
16
18
|
/**
|
|
17
19
|
* Group messages into blocks by type + name boundaries
|
|
18
20
|
*/
|
|
@@ -42,6 +44,21 @@ function groupMessagesIntoBlocks(messages) {
|
|
|
42
44
|
}
|
|
43
45
|
return blocks;
|
|
44
46
|
}
|
|
47
|
+
/**
|
|
48
|
+
* Extract model name from stream events
|
|
49
|
+
*/
|
|
50
|
+
function extractModelFromEvents(events) {
|
|
51
|
+
for (let i = events.length - 1; i >= 0; i--) {
|
|
52
|
+
const event = events[i];
|
|
53
|
+
if (event?.type === 'messages') {
|
|
54
|
+
const message = event.payload[0];
|
|
55
|
+
const modelName = message?.response_metadata?.['model_name'];
|
|
56
|
+
if (modelName)
|
|
57
|
+
return String(modelName);
|
|
58
|
+
}
|
|
59
|
+
}
|
|
60
|
+
return undefined;
|
|
61
|
+
}
|
|
45
62
|
/**
|
|
46
63
|
* Status indicator component for TUI mode
|
|
47
64
|
*/
|
|
@@ -67,7 +84,7 @@ function StatusIndicator({ status }) {
|
|
|
67
84
|
/**
|
|
68
85
|
* TUI mode chat command using React hook
|
|
69
86
|
*/
|
|
70
|
-
function ChatCommandTui({ message, assistantId, threadId, }) {
|
|
87
|
+
function ChatCommandTui({ message, assistantId, threadId, tools, truncateOptions, }) {
|
|
71
88
|
const { exit } = useApp();
|
|
72
89
|
const [config, setConfig] = useState();
|
|
73
90
|
const [authError, setAuthError] = useState(false);
|
|
@@ -90,15 +107,16 @@ function ChatCommandTui({ message, assistantId, threadId, }) {
|
|
|
90
107
|
const request = useMemo(() => config
|
|
91
108
|
? {
|
|
92
109
|
input: { messages: [{ role: 'user', content: message }] },
|
|
110
|
+
tools,
|
|
93
111
|
metadata: {
|
|
94
112
|
...(assistantId && { assistant_id: assistantId }),
|
|
95
113
|
...(threadId && { thread_id: threadId }),
|
|
96
114
|
},
|
|
97
115
|
}
|
|
98
|
-
: undefined, [config, assistantId, message, threadId]);
|
|
116
|
+
: undefined, [config, assistantId, message, threadId, tools]);
|
|
99
117
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
100
118
|
// Stream
|
|
101
|
-
const { status, messages, error } = useStream(config, request);
|
|
119
|
+
const { status, messages, events, error } = useStream(config, request);
|
|
102
120
|
// Group messages into blocks by type + name boundaries
|
|
103
121
|
const messageBlocks = useMemo(() => groupMessagesIntoBlocks(messages), [messages]);
|
|
104
122
|
// Exit on completion
|
|
@@ -132,16 +150,33 @@ function ChatCommandTui({ message, assistantId, threadId, }) {
|
|
|
132
150
|
React.createElement(Text, { dimColor: true, color: "cyan" },
|
|
133
151
|
"Tool Output",
|
|
134
152
|
block.name ? `: ${block.name}` : ''),
|
|
135
|
-
React.createElement(Box, { marginLeft: 2 },
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
153
|
+
React.createElement(Box, { marginLeft: 2, flexDirection: "column" }, (() => {
|
|
154
|
+
if (!truncateOptions) {
|
|
155
|
+
return React.createElement(Text, { dimColor: true }, block.content);
|
|
156
|
+
}
|
|
157
|
+
const result = truncate(block.content, truncateOptions);
|
|
158
|
+
return (React.createElement(React.Fragment, null,
|
|
159
|
+
React.createElement(Text, { dimColor: true }, result.text),
|
|
160
|
+
result.wasTruncated && (React.createElement(Text, { dimColor: true, color: "yellow" }, "(use --full-output for full output)"))));
|
|
161
|
+
})()))) : (React.createElement(Text, null, block.content))))),
|
|
162
|
+
status === 'done' && (React.createElement(Box, { marginTop: 1, flexDirection: "column" },
|
|
163
|
+
React.createElement(Text, { color: "green" }, "Done"),
|
|
164
|
+
request?.metadata?.thread_id && (React.createElement(Text, { dimColor: true },
|
|
165
|
+
"Thread: ",
|
|
166
|
+
request.metadata.thread_id)),
|
|
167
|
+
extractModelFromEvents(events) && (React.createElement(Text, { dimColor: true },
|
|
168
|
+
"Model: ",
|
|
169
|
+
extractModelFromEvents(events))),
|
|
170
|
+
request?.metadata?.thread_id && (React.createElement(Text, { dimColor: true },
|
|
171
|
+
"Continue: ruska chat \"message\" -t",
|
|
172
|
+
' ',
|
|
173
|
+
request.metadata.thread_id))))));
|
|
139
174
|
}
|
|
140
175
|
/**
|
|
141
176
|
* JSON mode chat command - direct streaming without React hooks
|
|
142
177
|
* Outputs NDJSON for downstream consumption
|
|
143
178
|
*/
|
|
144
|
-
async function runJsonMode(message, assistantId, threadId) {
|
|
179
|
+
async function runJsonMode(message, assistantId, threadId, tools) {
|
|
145
180
|
const config = await loadConfig();
|
|
146
181
|
if (!config) {
|
|
147
182
|
const formatter = new OutputFormatter();
|
|
@@ -157,6 +192,7 @@ async function runJsonMode(message, assistantId, threadId) {
|
|
|
157
192
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
158
193
|
const request = {
|
|
159
194
|
input: { messages: [{ role: 'user', content: message }] },
|
|
195
|
+
tools,
|
|
160
196
|
metadata: {
|
|
161
197
|
...(assistantId && { assistant_id: assistantId }),
|
|
162
198
|
...(threadId && { thread_id: threadId }),
|
|
@@ -211,22 +247,22 @@ async function runJsonMode(message, assistantId, threadId) {
|
|
|
211
247
|
/**
|
|
212
248
|
* Main chat command component - handles JSON mode branching
|
|
213
249
|
*/
|
|
214
|
-
function ChatCommand({ message, isJsonMode, assistantId, threadId, }) {
|
|
250
|
+
function ChatCommand({ message, isJsonMode, assistantId, threadId, tools, truncateOptions, }) {
|
|
215
251
|
const { exit } = useApp();
|
|
216
252
|
useEffect(() => {
|
|
217
253
|
if (isJsonMode) {
|
|
218
254
|
// JSON mode runs outside React, just exit immediately
|
|
219
|
-
void runJsonMode(message, assistantId, threadId).finally(() => {
|
|
255
|
+
void runJsonMode(message, assistantId, threadId, tools).finally(() => {
|
|
220
256
|
exit();
|
|
221
257
|
});
|
|
222
258
|
}
|
|
223
|
-
}, [message, isJsonMode, assistantId, threadId, exit]);
|
|
259
|
+
}, [message, isJsonMode, assistantId, threadId, tools, exit]);
|
|
224
260
|
// JSON mode: no UI (handled in useEffect)
|
|
225
261
|
if (isJsonMode) {
|
|
226
262
|
return null;
|
|
227
263
|
}
|
|
228
264
|
// TUI mode
|
|
229
|
-
return (React.createElement(ChatCommandTui, { message: message, assistantId: assistantId, threadId: threadId }));
|
|
265
|
+
return (React.createElement(ChatCommandTui, { message: message, assistantId: assistantId, threadId: threadId, tools: tools, truncateOptions: truncateOptions }));
|
|
230
266
|
}
|
|
231
267
|
/**
|
|
232
268
|
* Run the chat command
|
|
@@ -234,6 +270,8 @@ function ChatCommand({ message, isJsonMode, assistantId, threadId, }) {
|
|
|
234
270
|
export async function runChatCommand(message, options = {}) {
|
|
235
271
|
// Auto-detect: use JSON mode if not TTY (piped) or explicitly requested
|
|
236
272
|
const isJsonMode = options.json ?? !checkIsTty();
|
|
237
|
-
|
|
273
|
+
// Parse tools flag (undefined = defaults, 'disabled' = [], 'a,b' = ['a', 'b'])
|
|
274
|
+
const parsedTools = parseToolsFlag(options.tools);
|
|
275
|
+
const { waitUntilExit } = render(React.createElement(ChatCommand, { message: message, isJsonMode: isJsonMode, assistantId: options.assistantId, threadId: options.threadId, tools: parsedTools, truncateOptions: options.truncateOptions }));
|
|
238
276
|
await waitUntilExit();
|
|
239
277
|
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runHealthCommand(): Promise<void>;
|
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { render, Text, Box, useApp } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { hostPresets } from '../types/index.js';
|
|
5
|
+
import { loadConfig } from '../lib/config.js';
|
|
6
|
+
import { fetchHealth } from '../lib/api.js';
|
|
7
|
+
function HealthCommand() {
|
|
8
|
+
const { exit } = useApp();
|
|
9
|
+
const [status, setStatus] = useState('loading');
|
|
10
|
+
const [health, setHealth] = useState(undefined);
|
|
11
|
+
const [error, setError] = useState(undefined);
|
|
12
|
+
const [host, setHost] = useState('');
|
|
13
|
+
useEffect(() => {
|
|
14
|
+
const checkHealth = async () => {
|
|
15
|
+
// Try to load config for host, fall back to production
|
|
16
|
+
const config = await loadConfig();
|
|
17
|
+
const targetHost = config?.host ?? hostPresets.production;
|
|
18
|
+
setHost(targetHost);
|
|
19
|
+
// Fetch health status
|
|
20
|
+
const result = await fetchHealth(targetHost);
|
|
21
|
+
if (result.success && result.data) {
|
|
22
|
+
setHealth(result.data);
|
|
23
|
+
setStatus('success');
|
|
24
|
+
}
|
|
25
|
+
else {
|
|
26
|
+
setError(result.error ?? 'Failed to fetch health status');
|
|
27
|
+
setStatus('error');
|
|
28
|
+
}
|
|
29
|
+
setTimeout(() => {
|
|
30
|
+
exit();
|
|
31
|
+
}, 100);
|
|
32
|
+
};
|
|
33
|
+
void checkHealth();
|
|
34
|
+
}, [exit]);
|
|
35
|
+
if (status === 'loading') {
|
|
36
|
+
return (React.createElement(Box, null,
|
|
37
|
+
React.createElement(Text, { color: "cyan" },
|
|
38
|
+
React.createElement(Spinner, { type: "dots" })),
|
|
39
|
+
React.createElement(Text, null, " Checking health...")));
|
|
40
|
+
}
|
|
41
|
+
if (status === 'error') {
|
|
42
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
43
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
44
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Health Check"),
|
|
45
|
+
React.createElement(Text, { dimColor: true },
|
|
46
|
+
" (",
|
|
47
|
+
host,
|
|
48
|
+
")")),
|
|
49
|
+
React.createElement(Text, null,
|
|
50
|
+
React.createElement(Text, { dimColor: true }, "Status: "),
|
|
51
|
+
React.createElement(Text, { bold: true, color: "red" }, "unhealthy")),
|
|
52
|
+
React.createElement(Text, null,
|
|
53
|
+
React.createElement(Text, { dimColor: true }, "Error: "),
|
|
54
|
+
React.createElement(Text, { color: "red" }, error))));
|
|
55
|
+
}
|
|
56
|
+
// Success - display health info
|
|
57
|
+
const statusColor = health?.status === 'healthy' ? 'green' : 'yellow';
|
|
58
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
59
|
+
React.createElement(Box, { marginBottom: 1 },
|
|
60
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "Health Check"),
|
|
61
|
+
React.createElement(Text, { dimColor: true },
|
|
62
|
+
" (",
|
|
63
|
+
host,
|
|
64
|
+
")")),
|
|
65
|
+
React.createElement(Text, null,
|
|
66
|
+
React.createElement(Text, { dimColor: true }, "Status: "),
|
|
67
|
+
React.createElement(Text, { bold: true, color: statusColor }, health?.status)),
|
|
68
|
+
React.createElement(Text, null,
|
|
69
|
+
React.createElement(Text, { dimColor: true }, "Message: "),
|
|
70
|
+
React.createElement(Text, null, health?.message)),
|
|
71
|
+
React.createElement(Text, null,
|
|
72
|
+
React.createElement(Text, { dimColor: true }, "Version: "),
|
|
73
|
+
React.createElement(Text, null, health?.version))));
|
|
74
|
+
}
|
|
75
|
+
export async function runHealthCommand() {
|
|
76
|
+
const { waitUntilExit } = render(React.createElement(HealthCommand, null));
|
|
77
|
+
await waitUntilExit();
|
|
78
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
export declare function runVersionCommand(): Promise<void>;
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
import React, { useState, useEffect } from 'react';
|
|
2
|
+
import { render, Text, Box, useApp } from 'ink';
|
|
3
|
+
import Spinner from 'ink-spinner';
|
|
4
|
+
import { hostPresets } from '../types/index.js';
|
|
5
|
+
import { loadConfig } from '../lib/config.js';
|
|
6
|
+
import { fetchHealth } from '../lib/api.js';
|
|
7
|
+
const cliVersion = '0.1.3';
|
|
8
|
+
function VersionCommand() {
|
|
9
|
+
const { exit } = useApp();
|
|
10
|
+
const [status, setStatus] = useState('loading');
|
|
11
|
+
const [health, setHealth] = useState(undefined);
|
|
12
|
+
const [error, setError] = useState(undefined);
|
|
13
|
+
const [host, setHost] = useState('');
|
|
14
|
+
useEffect(() => {
|
|
15
|
+
const getVersion = async () => {
|
|
16
|
+
// Try to load config for host, fall back to production
|
|
17
|
+
const config = await loadConfig();
|
|
18
|
+
const targetHost = config?.host ?? hostPresets.production;
|
|
19
|
+
setHost(targetHost);
|
|
20
|
+
// Fetch health to get API version
|
|
21
|
+
const result = await fetchHealth(targetHost);
|
|
22
|
+
if (result.success && result.data) {
|
|
23
|
+
setHealth(result.data);
|
|
24
|
+
setStatus('success');
|
|
25
|
+
}
|
|
26
|
+
else {
|
|
27
|
+
setError(result.error ?? 'Failed to fetch API version');
|
|
28
|
+
setStatus('error');
|
|
29
|
+
}
|
|
30
|
+
setTimeout(() => {
|
|
31
|
+
exit();
|
|
32
|
+
}, 100);
|
|
33
|
+
};
|
|
34
|
+
void getVersion();
|
|
35
|
+
}, [exit]);
|
|
36
|
+
if (status === 'loading') {
|
|
37
|
+
return (React.createElement(Box, null,
|
|
38
|
+
React.createElement(Text, { color: "cyan" },
|
|
39
|
+
React.createElement(Spinner, { type: "dots" })),
|
|
40
|
+
React.createElement(Text, null, " Loading version info...")));
|
|
41
|
+
}
|
|
42
|
+
// Always show CLI version
|
|
43
|
+
return (React.createElement(Box, { flexDirection: "column" },
|
|
44
|
+
React.createElement(Text, null,
|
|
45
|
+
React.createElement(Text, { bold: true, color: "cyan" }, "@ruska/cli"),
|
|
46
|
+
React.createElement(Text, null,
|
|
47
|
+
" v",
|
|
48
|
+
cliVersion)),
|
|
49
|
+
status === 'error' ? (React.createElement(Text, null,
|
|
50
|
+
React.createElement(Text, { bold: true }, "API:"),
|
|
51
|
+
React.createElement(Text, { color: "red" }, " unavailable"),
|
|
52
|
+
React.createElement(Text, { dimColor: true },
|
|
53
|
+
" (",
|
|
54
|
+
error,
|
|
55
|
+
")"))) : (React.createElement(Text, null,
|
|
56
|
+
React.createElement(Text, { bold: true }, "API:"),
|
|
57
|
+
React.createElement(Text, { color: "green" },
|
|
58
|
+
" v",
|
|
59
|
+
health?.version),
|
|
60
|
+
React.createElement(Text, { dimColor: true },
|
|
61
|
+
" (",
|
|
62
|
+
host,
|
|
63
|
+
")")))));
|
|
64
|
+
}
|
|
65
|
+
export async function runVersionCommand() {
|
|
66
|
+
const { waitUntilExit } = render(React.createElement(VersionCommand, null));
|
|
67
|
+
await waitUntilExit();
|
|
68
|
+
}
|
|
@@ -1,10 +1,10 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
|
-
type
|
|
2
|
+
type ModelSelectProperties = {
|
|
3
3
|
readonly models: string[];
|
|
4
4
|
readonly value: string;
|
|
5
5
|
readonly onChange: (value: string) => void;
|
|
6
6
|
readonly onSubmit: (value: string) => void;
|
|
7
7
|
readonly onEscape?: () => void;
|
|
8
8
|
};
|
|
9
|
-
export declare function ModelSelect({ models, value, onChange, onSubmit, onEscape, }:
|
|
9
|
+
export declare function ModelSelect({ models, value, onChange, onSubmit, onEscape, }: ModelSelectProperties): React.JSX.Element;
|
|
10
10
|
export {};
|
|
@@ -61,8 +61,8 @@ export function ModelSelect({ models, value, onChange, onSubmit, onEscape, }) {
|
|
|
61
61
|
" \u2191 ",
|
|
62
62
|
startIndex,
|
|
63
63
|
" more"),
|
|
64
|
-
visibleModels.map((model,
|
|
65
|
-
const actualIndex = startIndex +
|
|
64
|
+
visibleModels.map((model, index) => {
|
|
65
|
+
const actualIndex = startIndex + index;
|
|
66
66
|
const isSelected = actualIndex === selectedIndex;
|
|
67
67
|
return (React.createElement(Box, { key: model },
|
|
68
68
|
React.createElement(Text, { color: isSelected ? 'cyan' : undefined }, isSelected ? '❯ ' : ' '),
|
package/dist/hooks/use-stream.js
CHANGED
|
@@ -15,9 +15,9 @@ export function useStream(config, request) {
|
|
|
15
15
|
const [finalResponse, setFinalResponse] = useState();
|
|
16
16
|
const [error, setError] = useState();
|
|
17
17
|
const [errorCode, setErrorCode] = useState();
|
|
18
|
-
const
|
|
18
|
+
const handleReference = useRef();
|
|
19
19
|
const abort = useCallback(() => {
|
|
20
|
-
|
|
20
|
+
handleReference.current?.abort();
|
|
21
21
|
}, []);
|
|
22
22
|
useEffect(() => {
|
|
23
23
|
if (!config || !request)
|
|
@@ -33,7 +33,7 @@ export function useStream(config, request) {
|
|
|
33
33
|
setErrorCode(undefined);
|
|
34
34
|
try {
|
|
35
35
|
const handle = await service.connect(request);
|
|
36
|
-
|
|
36
|
+
handleReference.current = handle;
|
|
37
37
|
if (cancelled) {
|
|
38
38
|
handle.abort();
|
|
39
39
|
return;
|
|
@@ -90,7 +90,7 @@ export function useStream(config, request) {
|
|
|
90
90
|
void run();
|
|
91
91
|
return () => {
|
|
92
92
|
cancelled = true;
|
|
93
|
-
|
|
93
|
+
handleReference.current?.abort();
|
|
94
94
|
};
|
|
95
95
|
}, [config, request]);
|
|
96
96
|
return {
|
package/dist/lib/api.d.ts
CHANGED
|
@@ -1,4 +1,4 @@
|
|
|
1
|
-
import { type Config, type ApiResponse, type UserInfo, type AssistantsSearchResponse, type ModelsResponse, type CreateAssistantRequest, type CreateAssistantResponse } from '../types/index.js';
|
|
1
|
+
import { type Config, type ApiResponse, type UserInfo, type AssistantsSearchResponse, type ModelsResponse, type HealthResponse, type CreateAssistantRequest, type CreateAssistantResponse } from '../types/index.js';
|
|
2
2
|
/**
|
|
3
3
|
* API client for making authenticated requests to the Orchestra backend
|
|
4
4
|
*/
|
|
@@ -39,3 +39,7 @@ export declare function validateApiKey(host: string, apiKey: string): Promise<Ap
|
|
|
39
39
|
* Fetch available models (no auth required)
|
|
40
40
|
*/
|
|
41
41
|
export declare function fetchModels(host: string, apiKey?: string): Promise<ApiResponse<ModelsResponse>>;
|
|
42
|
+
/**
|
|
43
|
+
* Fetch health status (no auth required)
|
|
44
|
+
*/
|
|
45
|
+
export declare function fetchHealth(host: string): Promise<ApiResponse<HealthResponse>>;
|
package/dist/lib/api.js
CHANGED
|
@@ -149,3 +149,42 @@ export async function fetchModels(host, apiKey) {
|
|
|
149
149
|
};
|
|
150
150
|
}
|
|
151
151
|
}
|
|
152
|
+
/**
|
|
153
|
+
* Fetch health status (no auth required)
|
|
154
|
+
*/
|
|
155
|
+
export async function fetchHealth(host) {
|
|
156
|
+
const url = `${host.replace(/\/$/, '')}/api/info/health`;
|
|
157
|
+
try {
|
|
158
|
+
const response = await fetch(url, {
|
|
159
|
+
headers: {
|
|
160
|
+
'Content-Type': 'application/json',
|
|
161
|
+
},
|
|
162
|
+
});
|
|
163
|
+
if (!response.ok) {
|
|
164
|
+
const errorText = await response.text();
|
|
165
|
+
let errorMessage;
|
|
166
|
+
try {
|
|
167
|
+
const errorJson = JSON.parse(errorText);
|
|
168
|
+
errorMessage = errorJson.detail ?? `HTTP ${response.status}`;
|
|
169
|
+
}
|
|
170
|
+
catch {
|
|
171
|
+
errorMessage = errorText || `HTTP ${response.status}`;
|
|
172
|
+
}
|
|
173
|
+
return {
|
|
174
|
+
success: false,
|
|
175
|
+
error: errorMessage,
|
|
176
|
+
};
|
|
177
|
+
}
|
|
178
|
+
const data = (await response.json());
|
|
179
|
+
return {
|
|
180
|
+
success: true,
|
|
181
|
+
data,
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
catch (error) {
|
|
185
|
+
return {
|
|
186
|
+
success: false,
|
|
187
|
+
error: error instanceof Error ? error.message : 'Unknown error occurred',
|
|
188
|
+
};
|
|
189
|
+
}
|
|
190
|
+
}
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncation utilities for CLI output
|
|
3
|
+
*/
|
|
4
|
+
export type TruncateOptions = {
|
|
5
|
+
maxLength?: number;
|
|
6
|
+
maxLines?: number;
|
|
7
|
+
indicator?: string;
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Truncate text by both line count and character count
|
|
11
|
+
* Applies line truncation first, then character truncation
|
|
12
|
+
*/
|
|
13
|
+
export declare function truncate(input: string, options?: TruncateOptions): {
|
|
14
|
+
text: string;
|
|
15
|
+
wasTruncated: boolean;
|
|
16
|
+
};
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Truncation utilities for CLI output
|
|
3
|
+
*/
|
|
4
|
+
const defaults = {
|
|
5
|
+
maxLength: 500,
|
|
6
|
+
maxLines: 10,
|
|
7
|
+
indicator: '... [truncated]',
|
|
8
|
+
};
|
|
9
|
+
/**
|
|
10
|
+
* Truncate text by line count
|
|
11
|
+
*/
|
|
12
|
+
function truncateByLines(input, maxLines, indicator) {
|
|
13
|
+
const lines = input.split('\n');
|
|
14
|
+
if (lines.length <= maxLines) {
|
|
15
|
+
return { text: input, wasTruncated: false };
|
|
16
|
+
}
|
|
17
|
+
const truncated = lines.slice(0, maxLines).join('\n') + '\n' + indicator;
|
|
18
|
+
return { text: truncated, wasTruncated: true };
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Truncate text by character count
|
|
22
|
+
*/
|
|
23
|
+
function truncateByLength(input, maxLength, indicator) {
|
|
24
|
+
if (input.length <= maxLength) {
|
|
25
|
+
return { text: input, wasTruncated: false };
|
|
26
|
+
}
|
|
27
|
+
const allowedLength = maxLength - indicator.length;
|
|
28
|
+
if (allowedLength <= 0) {
|
|
29
|
+
return { text: indicator, wasTruncated: true };
|
|
30
|
+
}
|
|
31
|
+
const truncated = input.slice(0, allowedLength) + indicator;
|
|
32
|
+
return { text: truncated, wasTruncated: true };
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Truncate text by both line count and character count
|
|
36
|
+
* Applies line truncation first, then character truncation
|
|
37
|
+
*/
|
|
38
|
+
export function truncate(input, options = {}) {
|
|
39
|
+
const maxLength = options.maxLength ?? defaults.maxLength;
|
|
40
|
+
const maxLines = options.maxLines ?? defaults.maxLines;
|
|
41
|
+
const indicator = options.indicator ?? defaults.indicator;
|
|
42
|
+
// Apply line truncation first
|
|
43
|
+
const result = truncateByLines(input, maxLines, indicator);
|
|
44
|
+
// Then apply character truncation
|
|
45
|
+
if (result.text.length > maxLength) {
|
|
46
|
+
const charResult = truncateByLength(result.text, maxLength, indicator);
|
|
47
|
+
return {
|
|
48
|
+
text: charResult.text,
|
|
49
|
+
wasTruncated: result.wasTruncated || charResult.wasTruncated,
|
|
50
|
+
};
|
|
51
|
+
}
|
|
52
|
+
return result;
|
|
53
|
+
}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default tools and parsing for chat command
|
|
3
|
+
* @see frontend/src/components/menus/BaseToolMenu.tsx for frontend equivalent
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Default tools enabled for chat command
|
|
7
|
+
* Mirrors frontend DEFAULT_AGENT_TOOLS in BaseToolMenu.tsx
|
|
8
|
+
*/
|
|
9
|
+
export declare const defaultAgentTools: readonly ["web_search", "web_scrape", "math_calculator", "think_tool", "python_sandbox"];
|
|
10
|
+
export type DefaultAgentTool = (typeof defaultAgentTools)[number];
|
|
11
|
+
/**
|
|
12
|
+
* Parse --tools flag value into array of tool names
|
|
13
|
+
*
|
|
14
|
+
* @param value - Raw flag value (undefined, 'disabled', or comma-separated)
|
|
15
|
+
* @returns Array of tool names to send to API
|
|
16
|
+
*
|
|
17
|
+
* @example
|
|
18
|
+
* parseToolsFlag(undefined) // returns DEFAULT_AGENT_TOOLS
|
|
19
|
+
* parseToolsFlag('') // returns DEFAULT_AGENT_TOOLS
|
|
20
|
+
* parseToolsFlag('disabled') // returns []
|
|
21
|
+
* parseToolsFlag('web_search,think_tool') // returns ['web_search', 'think_tool']
|
|
22
|
+
*/
|
|
23
|
+
export declare function parseToolsFlag(value: string | undefined): string[];
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Default tools and parsing for chat command
|
|
3
|
+
* @see frontend/src/components/menus/BaseToolMenu.tsx for frontend equivalent
|
|
4
|
+
*/
|
|
5
|
+
/**
|
|
6
|
+
* Default tools enabled for chat command
|
|
7
|
+
* Mirrors frontend DEFAULT_AGENT_TOOLS in BaseToolMenu.tsx
|
|
8
|
+
*/
|
|
9
|
+
export const defaultAgentTools = [
|
|
10
|
+
'web_search',
|
|
11
|
+
'web_scrape',
|
|
12
|
+
'math_calculator',
|
|
13
|
+
'think_tool',
|
|
14
|
+
'python_sandbox',
|
|
15
|
+
];
|
|
16
|
+
/**
|
|
17
|
+
* Parse --tools flag value into array of tool names
|
|
18
|
+
*
|
|
19
|
+
* @param value - Raw flag value (undefined, 'disabled', or comma-separated)
|
|
20
|
+
* @returns Array of tool names to send to API
|
|
21
|
+
*
|
|
22
|
+
* @example
|
|
23
|
+
* parseToolsFlag(undefined) // returns DEFAULT_AGENT_TOOLS
|
|
24
|
+
* parseToolsFlag('') // returns DEFAULT_AGENT_TOOLS
|
|
25
|
+
* parseToolsFlag('disabled') // returns []
|
|
26
|
+
* parseToolsFlag('web_search,think_tool') // returns ['web_search', 'think_tool']
|
|
27
|
+
*/
|
|
28
|
+
export function parseToolsFlag(value) {
|
|
29
|
+
// No flag or empty string: use defaults
|
|
30
|
+
if (value === undefined || value.trim() === '') {
|
|
31
|
+
return [...defaultAgentTools];
|
|
32
|
+
}
|
|
33
|
+
// Explicit disable
|
|
34
|
+
if (value.toLowerCase() === 'disabled') {
|
|
35
|
+
return [];
|
|
36
|
+
}
|
|
37
|
+
// Parse comma-separated list
|
|
38
|
+
return value
|
|
39
|
+
.split(',')
|
|
40
|
+
.map(t => t.trim())
|
|
41
|
+
.filter(Boolean);
|
|
42
|
+
}
|
package/dist/types/index.d.ts
CHANGED
|
@@ -79,6 +79,14 @@ export type ModelsResponse = {
|
|
|
79
79
|
free: string[];
|
|
80
80
|
models: string[];
|
|
81
81
|
};
|
|
82
|
+
/**
|
|
83
|
+
* Response from GET /api/health
|
|
84
|
+
*/
|
|
85
|
+
export type HealthResponse = {
|
|
86
|
+
status: string;
|
|
87
|
+
message: string;
|
|
88
|
+
version: string;
|
|
89
|
+
};
|
|
82
90
|
/**
|
|
83
91
|
* Host presets for environment selection
|
|
84
92
|
*/
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@ruska/cli",
|
|
3
|
-
"version": "0.1.
|
|
3
|
+
"version": "0.1.5",
|
|
4
4
|
"description": "CLI for Orchestra - AI Agent Orchestration Platform",
|
|
5
5
|
"license": "Apache-2.0",
|
|
6
6
|
"author": "RUSKA <reggleston@ruska.ai>",
|
|
@@ -47,7 +47,7 @@
|
|
|
47
47
|
"dev": "tsc --watch",
|
|
48
48
|
"build": "tsc",
|
|
49
49
|
"build:clean": "node -e \"require('fs').rmSync('dist',{recursive:true,force:true})\" && npm run build",
|
|
50
|
-
"format": "prettier --write \"**/*.{ts,tsx,json,md}\"
|
|
50
|
+
"format": "xo --fix && prettier --write \"**/*.{ts,tsx,json,md}\"",
|
|
51
51
|
"format:check": "prettier --check .",
|
|
52
52
|
"lint": "xo",
|
|
53
53
|
"test": "npm run lint && npm run build && ava",
|
|
@@ -87,7 +87,7 @@
|
|
|
87
87
|
"ts-node": "^10.9.1",
|
|
88
88
|
"tsx": "^4.21.0",
|
|
89
89
|
"typescript": "^5.0.3",
|
|
90
|
-
"xo": "^0.
|
|
90
|
+
"xo": "^0.60.0"
|
|
91
91
|
},
|
|
92
92
|
"ava": {
|
|
93
93
|
"files": [
|
|
@@ -103,7 +103,16 @@
|
|
|
103
103
|
"rules": {
|
|
104
104
|
"react/prop-types": "off",
|
|
105
105
|
"unicorn/expiring-todo-comments": "off",
|
|
106
|
-
"ava/no-ignored-test-files": "off"
|
|
106
|
+
"ava/no-ignored-test-files": "off",
|
|
107
|
+
"unicorn/prevent-abbreviations": "off",
|
|
108
|
+
"@typescript-eslint/switch-exhaustiveness-check": "off",
|
|
109
|
+
"@typescript-eslint/no-unsafe-assignment": "off",
|
|
110
|
+
"@typescript-eslint/no-unsafe-call": "off",
|
|
111
|
+
"@typescript-eslint/restrict-plus-operands": "off",
|
|
112
|
+
"promise/prefer-await-to-then": "off",
|
|
113
|
+
"complexity": "off",
|
|
114
|
+
"unicorn/prefer-at": "off",
|
|
115
|
+
"prettier/prettier": "off"
|
|
107
116
|
}
|
|
108
117
|
},
|
|
109
118
|
"prettier": "@vdemedes/prettier-config"
|