@ruska/cli 0.1.4 → 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 +23 -0
- package/dist/cli.js +10 -4
- package/dist/commands/chat.d.ts +1 -0
- package/dist/commands/chat.js +13 -8
- 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/tools.d.ts +23 -0
- package/dist/lib/tools.js +42 -0
- package/package.json +13 -4
package/README.md
CHANGED
|
@@ -176,11 +176,34 @@ $ ruska chat "Hello" -a <assistant-id> | jq '.type'
|
|
|
176
176
|
| `-a, --assistant` | Assistant ID (optional, uses default chat if omitted) |
|
|
177
177
|
| `-t, --thread` | Thread ID to continue an existing conversation |
|
|
178
178
|
| `-m, --message` | Message to send (alternative to positional arg) |
|
|
179
|
+
| `--tools` | Tools for the chat session (see below for modes) |
|
|
179
180
|
| `--json` | Output as newline-delimited JSON (NDJSON) |
|
|
180
181
|
| `--truncate <n>` | Max characters for tool output (default: 500) |
|
|
181
182
|
| `--truncate-lines` | Max lines for tool output (default: 10) |
|
|
182
183
|
| `--full-output` | Disable truncation (show full output) |
|
|
183
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
|
+
```
|
|
206
|
+
|
|
184
207
|
**Exit codes:**
|
|
185
208
|
|
|
186
209
|
| Code | Meaning |
|
package/dist/cli.js
CHANGED
|
@@ -33,6 +33,9 @@ const cli = meow(`
|
|
|
33
33
|
-a, --assistant Assistant ID (optional, uses default chat if omitted)
|
|
34
34
|
-t, --thread Thread ID to continue a conversation
|
|
35
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
|
|
36
39
|
--json Output as newline-delimited JSON (auto-enabled when piped)
|
|
37
40
|
--truncate <n> Max characters for tool output (default: 500)
|
|
38
41
|
--truncate-lines Max lines for tool output (default: 10)
|
|
@@ -50,7 +53,9 @@ const cli = meow(`
|
|
|
50
53
|
$ ruska auth # Configure API key and host
|
|
51
54
|
$ ruska assistants # List your assistants
|
|
52
55
|
$ ruska assistant abc-123 # Get assistant details
|
|
53
|
-
$ 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
|
|
54
59
|
$ ruska chat "Hello" -a <assistant-id> # Chat with specific assistant
|
|
55
60
|
$ ruska chat "Follow up" -t <thread-id> # Continue existing thread
|
|
56
61
|
$ ruska chat "Hello" -a <id> --json # Output as NDJSON
|
|
@@ -116,7 +121,7 @@ const cli = meow(`
|
|
|
116
121
|
},
|
|
117
122
|
},
|
|
118
123
|
});
|
|
119
|
-
const [command, ...
|
|
124
|
+
const [command, ...arguments_] = cli.input;
|
|
120
125
|
// Route to appropriate command
|
|
121
126
|
async function main() {
|
|
122
127
|
if (cli.flags.ui) {
|
|
@@ -134,7 +139,7 @@ async function main() {
|
|
|
134
139
|
break;
|
|
135
140
|
}
|
|
136
141
|
case 'assistant': {
|
|
137
|
-
const assistantId =
|
|
142
|
+
const assistantId = arguments_[0];
|
|
138
143
|
if (!assistantId) {
|
|
139
144
|
console.error('Usage: ruska assistant <id>');
|
|
140
145
|
console.log('Run `ruska assistants` to list available assistants');
|
|
@@ -153,7 +158,7 @@ async function main() {
|
|
|
153
158
|
cli.flags['a']?.toString();
|
|
154
159
|
const threadId = cli.flags.thread ??
|
|
155
160
|
cli.flags['t']?.toString();
|
|
156
|
-
const message =
|
|
161
|
+
const message = arguments_.join(' ') || cli.flags.message;
|
|
157
162
|
if (!message) {
|
|
158
163
|
console.error('Error: Message is required');
|
|
159
164
|
console.error('Usage: ruska chat "<message>" [-a <assistant-id>]');
|
|
@@ -163,6 +168,7 @@ async function main() {
|
|
|
163
168
|
json: cli.flags.json,
|
|
164
169
|
assistantId,
|
|
165
170
|
threadId,
|
|
171
|
+
tools: cli.flags.tools,
|
|
166
172
|
truncateOptions: cli.flags.fullOutput
|
|
167
173
|
? undefined
|
|
168
174
|
: {
|
package/dist/commands/chat.d.ts
CHANGED
package/dist/commands/chat.js
CHANGED
|
@@ -14,6 +14,7 @@ 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
16
|
import { truncate } from '../lib/output/truncate.js';
|
|
17
|
+
import { parseToolsFlag } from '../lib/tools.js';
|
|
17
18
|
/**
|
|
18
19
|
* Group messages into blocks by type + name boundaries
|
|
19
20
|
*/
|
|
@@ -83,7 +84,7 @@ function StatusIndicator({ status }) {
|
|
|
83
84
|
/**
|
|
84
85
|
* TUI mode chat command using React hook
|
|
85
86
|
*/
|
|
86
|
-
function ChatCommandTui({ message, assistantId, threadId, truncateOptions, }) {
|
|
87
|
+
function ChatCommandTui({ message, assistantId, threadId, tools, truncateOptions, }) {
|
|
87
88
|
const { exit } = useApp();
|
|
88
89
|
const [config, setConfig] = useState();
|
|
89
90
|
const [authError, setAuthError] = useState(false);
|
|
@@ -106,12 +107,13 @@ function ChatCommandTui({ message, assistantId, threadId, truncateOptions, }) {
|
|
|
106
107
|
const request = useMemo(() => config
|
|
107
108
|
? {
|
|
108
109
|
input: { messages: [{ role: 'user', content: message }] },
|
|
110
|
+
tools,
|
|
109
111
|
metadata: {
|
|
110
112
|
...(assistantId && { assistant_id: assistantId }),
|
|
111
113
|
...(threadId && { thread_id: threadId }),
|
|
112
114
|
},
|
|
113
115
|
}
|
|
114
|
-
: undefined, [config, assistantId, message, threadId]);
|
|
116
|
+
: undefined, [config, assistantId, message, threadId, tools]);
|
|
115
117
|
/* eslint-enable @typescript-eslint/naming-convention */
|
|
116
118
|
// Stream
|
|
117
119
|
const { status, messages, events, error } = useStream(config, request);
|
|
@@ -174,7 +176,7 @@ function ChatCommandTui({ message, assistantId, threadId, truncateOptions, }) {
|
|
|
174
176
|
* JSON mode chat command - direct streaming without React hooks
|
|
175
177
|
* Outputs NDJSON for downstream consumption
|
|
176
178
|
*/
|
|
177
|
-
async function runJsonMode(message, assistantId, threadId) {
|
|
179
|
+
async function runJsonMode(message, assistantId, threadId, tools) {
|
|
178
180
|
const config = await loadConfig();
|
|
179
181
|
if (!config) {
|
|
180
182
|
const formatter = new OutputFormatter();
|
|
@@ -190,6 +192,7 @@ async function runJsonMode(message, assistantId, threadId) {
|
|
|
190
192
|
/* eslint-disable @typescript-eslint/naming-convention */
|
|
191
193
|
const request = {
|
|
192
194
|
input: { messages: [{ role: 'user', content: message }] },
|
|
195
|
+
tools,
|
|
193
196
|
metadata: {
|
|
194
197
|
...(assistantId && { assistant_id: assistantId }),
|
|
195
198
|
...(threadId && { thread_id: threadId }),
|
|
@@ -244,22 +247,22 @@ async function runJsonMode(message, assistantId, threadId) {
|
|
|
244
247
|
/**
|
|
245
248
|
* Main chat command component - handles JSON mode branching
|
|
246
249
|
*/
|
|
247
|
-
function ChatCommand({ message, isJsonMode, assistantId, threadId, truncateOptions, }) {
|
|
250
|
+
function ChatCommand({ message, isJsonMode, assistantId, threadId, tools, truncateOptions, }) {
|
|
248
251
|
const { exit } = useApp();
|
|
249
252
|
useEffect(() => {
|
|
250
253
|
if (isJsonMode) {
|
|
251
254
|
// JSON mode runs outside React, just exit immediately
|
|
252
|
-
void runJsonMode(message, assistantId, threadId).finally(() => {
|
|
255
|
+
void runJsonMode(message, assistantId, threadId, tools).finally(() => {
|
|
253
256
|
exit();
|
|
254
257
|
});
|
|
255
258
|
}
|
|
256
|
-
}, [message, isJsonMode, assistantId, threadId, exit]);
|
|
259
|
+
}, [message, isJsonMode, assistantId, threadId, tools, exit]);
|
|
257
260
|
// JSON mode: no UI (handled in useEffect)
|
|
258
261
|
if (isJsonMode) {
|
|
259
262
|
return null;
|
|
260
263
|
}
|
|
261
264
|
// TUI mode
|
|
262
|
-
return (React.createElement(ChatCommandTui, { message: message, assistantId: assistantId, threadId: threadId, truncateOptions: truncateOptions }));
|
|
265
|
+
return (React.createElement(ChatCommandTui, { message: message, assistantId: assistantId, threadId: threadId, tools: tools, truncateOptions: truncateOptions }));
|
|
263
266
|
}
|
|
264
267
|
/**
|
|
265
268
|
* Run the chat command
|
|
@@ -267,6 +270,8 @@ function ChatCommand({ message, isJsonMode, assistantId, threadId, truncateOptio
|
|
|
267
270
|
export async function runChatCommand(message, options = {}) {
|
|
268
271
|
// Auto-detect: use JSON mode if not TTY (piped) or explicitly requested
|
|
269
272
|
const isJsonMode = options.json ?? !checkIsTty();
|
|
270
|
-
|
|
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 }));
|
|
271
276
|
await waitUntilExit();
|
|
272
277
|
}
|
|
@@ -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 {
|
|
@@ -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/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"
|