@almyty/chat 0.2.0 → 1.2.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 +8 -4
- package/dist/app.d.ts +9 -1
- package/dist/app.js +195 -12
- package/dist/commands.d.ts +21 -1
- package/dist/commands.js +44 -1
- package/dist/components.d.ts +26 -8
- package/dist/components.js +24 -0
- package/package.json +16 -4
package/README.md
CHANGED
|
@@ -40,10 +40,14 @@ Commands:
|
|
|
40
40
|
|
|
41
41
|
Requires `npx @almyty/auth login` first. Reads credentials from `~/.almyty/credentials.json`. Override with `ALMYTY_TOKEN` and `ALMYTY_URL` environment variables.
|
|
42
42
|
|
|
43
|
-
##
|
|
43
|
+
## About almyty
|
|
44
44
|
|
|
45
|
-
|
|
45
|
+
almyty is the full-stack platform for AI agents, agnostic by design: any LLM, any
|
|
46
|
+
API turned into tools, served over MCP, A2A, UTCP, and Agent Skills. Open source,
|
|
47
|
+
no lock-in.
|
|
46
48
|
|
|
47
|
-
|
|
49
|
+
- Website — https://almyty.com
|
|
50
|
+
- Docs — https://docs.almyty.com
|
|
51
|
+
- Source — https://github.com/almyty-inc/almyty
|
|
48
52
|
|
|
49
|
-
|
|
53
|
+
Apache-2.0 © Almyty Inc.
|
package/dist/app.d.ts
CHANGED
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import React from 'react';
|
|
1
2
|
import type { AlmytyClient, GatewayClient, AgentInfo } from '@almyty/client';
|
|
2
3
|
import type { Message } from './components.js';
|
|
3
4
|
export interface AppState {
|
|
@@ -8,10 +9,17 @@ export interface AppState {
|
|
|
8
9
|
conversationId: string | null;
|
|
9
10
|
pendingRunId: string | null;
|
|
10
11
|
}
|
|
12
|
+
/** Active coding session driven from the REPL (chat-to-runner bridge). */
|
|
13
|
+
export interface CodingSessionState {
|
|
14
|
+
runnerId: string;
|
|
15
|
+
runnerName: string;
|
|
16
|
+
agent: string;
|
|
17
|
+
sessionId: string;
|
|
18
|
+
}
|
|
11
19
|
export declare let exitMessage: string;
|
|
12
20
|
export declare function ChatApp({ client, initialAgent, gw, resumeConversationId }: {
|
|
13
21
|
client: AlmytyClient;
|
|
14
22
|
initialAgent: AgentInfo;
|
|
15
23
|
gw: GatewayClient;
|
|
16
24
|
resumeConversationId?: string;
|
|
17
|
-
}):
|
|
25
|
+
}): React.JSX.Element;
|
package/dist/app.js
CHANGED
|
@@ -1,9 +1,9 @@
|
|
|
1
1
|
import { jsx as _jsx, jsxs as _jsxs } from "react/jsx-runtime";
|
|
2
|
-
import { useState, useCallback, useEffect } from 'react';
|
|
2
|
+
import { useState, useCallback, useEffect, useRef } from 'react';
|
|
3
3
|
import { Box, Text, useApp, useInput } from 'ink';
|
|
4
4
|
import TextInput from 'ink-text-input';
|
|
5
|
-
import { Header, MessageView, LoadingIndicator, AgentSelector } from './components.js';
|
|
6
|
-
import { SLASH_COMMANDS, COMMAND_DESCS, resolveSlash, getSuggestion, ALIASES } from './commands.js';
|
|
5
|
+
import { Header, MessageView, LoadingIndicator, AgentSelector, CodingModeIndicator, ChoiceSelector, } from './components.js';
|
|
6
|
+
import { SLASH_COMMANDS, COMMAND_DESCS, resolveSlash, getSuggestion, ALIASES, classifyInput, buildCodeChoices, } from './commands.js';
|
|
7
7
|
// Mutable module-level variable; written by ChatApp, read by main() after exit
|
|
8
8
|
export let exitMessage = '';
|
|
9
9
|
// ── Chat app ────────────────────────────────────────────────────
|
|
@@ -45,6 +45,11 @@ export function ChatApp({ client, initialAgent, gw, resumeConversationId }) {
|
|
|
45
45
|
const [historyIdx, setHistoryIdx] = useState(-1);
|
|
46
46
|
const [showPicker, setShowPicker] = useState(false);
|
|
47
47
|
const [pickerAgents, setPickerAgents] = useState([]);
|
|
48
|
+
// Active coding session (input routes to the runner while set).
|
|
49
|
+
const [coding, setCoding] = useState(null);
|
|
50
|
+
// Pending runner x CLI pick for /code when multiple targets exist.
|
|
51
|
+
const [codeChoices, setCodeChoices] = useState(null);
|
|
52
|
+
const codingAbortRef = useRef(null);
|
|
48
53
|
// Command palette matches
|
|
49
54
|
const slashMatches = input.startsWith('/') && !input.includes(' ')
|
|
50
55
|
? SLASH_COMMANDS.filter(c => c.startsWith(input.slice(1).toLowerCase()))
|
|
@@ -98,6 +103,81 @@ export function ChatApp({ client, initialAgent, gw, resumeConversationId }) {
|
|
|
98
103
|
const addMessage = useCallback((msg) => {
|
|
99
104
|
setState(s => ({ ...s, messages: [...s.messages, msg] }));
|
|
100
105
|
}, []);
|
|
106
|
+
// ── Coding session helpers (chat-to-runner bridge) ──────────────
|
|
107
|
+
/**
|
|
108
|
+
* Append streamed CLI output to the transcript, merging into the last
|
|
109
|
+
* coding message so a burst of chunks doesn't explode into hundreds of
|
|
110
|
+
* transcript entries. Caps a single coding message before rolling over.
|
|
111
|
+
*/
|
|
112
|
+
const appendCodingOutput = useCallback((text) => {
|
|
113
|
+
if (!text)
|
|
114
|
+
return;
|
|
115
|
+
setState(s => {
|
|
116
|
+
const msgs = [...s.messages];
|
|
117
|
+
const last = msgs[msgs.length - 1];
|
|
118
|
+
if (last && last.role === 'coding' && last.text.length < 4000) {
|
|
119
|
+
msgs[msgs.length - 1] = { ...last, text: last.text + text };
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
msgs.push({ role: 'coding', text });
|
|
123
|
+
}
|
|
124
|
+
return { ...s, messages: msgs };
|
|
125
|
+
});
|
|
126
|
+
}, []);
|
|
127
|
+
const startCoding = useCallback(async (choice, task) => {
|
|
128
|
+
setState(s => ({ ...s, loading: true, loadingLabel: `Starting ${choice.agentName} on ${choice.runnerName}` }));
|
|
129
|
+
try {
|
|
130
|
+
const session = await client.startCodingSession(choice.runnerId, {
|
|
131
|
+
agent: choice.agentId,
|
|
132
|
+
task,
|
|
133
|
+
});
|
|
134
|
+
setState(s => ({ ...s, loading: false }));
|
|
135
|
+
setCoding({
|
|
136
|
+
runnerId: choice.runnerId,
|
|
137
|
+
runnerName: choice.runnerName,
|
|
138
|
+
agent: choice.agentId,
|
|
139
|
+
sessionId: session.sessionId,
|
|
140
|
+
});
|
|
141
|
+
addMessage({
|
|
142
|
+
role: 'info',
|
|
143
|
+
text: `coding session started — ${choice.agentId}@${choice.runnerName} (cwd ${session.cwd ?? '~'})`,
|
|
144
|
+
});
|
|
145
|
+
// Background stream: coding.output chunks land in the transcript,
|
|
146
|
+
// coding.exit closes the mode. /esc aborts the stream client-side.
|
|
147
|
+
const ac = new AbortController();
|
|
148
|
+
codingAbortRef.current = ac;
|
|
149
|
+
void client.streamCodingEvents(choice.runnerId, session.sessionId, (event) => {
|
|
150
|
+
if (event.type === 'coding.output') {
|
|
151
|
+
const chunk = event.data.data;
|
|
152
|
+
if (chunk)
|
|
153
|
+
appendCodingOutput(String(chunk));
|
|
154
|
+
}
|
|
155
|
+
else if (event.type === 'coding.exit') {
|
|
156
|
+
const code = event.data.exitCode;
|
|
157
|
+
addMessage({ role: 'info', text: `coding session exited${code != null ? ` (exit ${code})` : ''}` });
|
|
158
|
+
codingAbortRef.current = null;
|
|
159
|
+
setCoding(null);
|
|
160
|
+
}
|
|
161
|
+
}, ac.signal).catch((err) => {
|
|
162
|
+
if (ac.signal.aborted)
|
|
163
|
+
return;
|
|
164
|
+
addMessage({ role: 'error', text: `coding stream lost: ${err.message}` });
|
|
165
|
+
codingAbortRef.current = null;
|
|
166
|
+
setCoding(null);
|
|
167
|
+
});
|
|
168
|
+
}
|
|
169
|
+
catch (err) {
|
|
170
|
+
setState(s => ({ ...s, loading: false }));
|
|
171
|
+
addMessage({ role: 'error', text: err.message });
|
|
172
|
+
}
|
|
173
|
+
}, [client, addMessage, appendCodingOutput]);
|
|
174
|
+
/** Detach from coding mode (abort the stream; the remote session is untouched). */
|
|
175
|
+
const leaveCodingMode = useCallback((note) => {
|
|
176
|
+
codingAbortRef.current?.abort();
|
|
177
|
+
codingAbortRef.current = null;
|
|
178
|
+
setCoding(null);
|
|
179
|
+
addMessage({ role: 'info', text: note });
|
|
180
|
+
}, [addMessage]);
|
|
101
181
|
const handleSubmit = useCallback(async (value) => {
|
|
102
182
|
const trimmed = value.trim();
|
|
103
183
|
if (!trimmed)
|
|
@@ -110,7 +190,7 @@ export function ChatApp({ client, initialAgent, gw, resumeConversationId }) {
|
|
|
110
190
|
const cmd = resolveSlash(raw);
|
|
111
191
|
if (!cmd) {
|
|
112
192
|
addMessage({ role: 'error', text: `Unknown command: /${raw}` });
|
|
113
|
-
addMessage({ role: 'info', text: `Commands: /help /agents /clear /quit` });
|
|
193
|
+
addMessage({ role: 'info', text: `Commands: /help /agents /runners /code /clear /quit` });
|
|
114
194
|
return;
|
|
115
195
|
}
|
|
116
196
|
if (cmd !== raw.toLowerCase() && !Object.entries(ALIASES).some(([k, v]) => k === raw.toLowerCase() && v === cmd)) {
|
|
@@ -128,11 +208,15 @@ export function ChatApp({ client, initialAgent, gw, resumeConversationId }) {
|
|
|
128
208
|
setState(s => ({ ...s, messages: [] }));
|
|
129
209
|
return;
|
|
130
210
|
case 'help':
|
|
131
|
-
addMessage({ role: 'info', text: '/agents
|
|
132
|
-
addMessage({ role: 'info', text: '/tools
|
|
133
|
-
addMessage({ role: 'info', text: '/
|
|
134
|
-
addMessage({ role: 'info', text: '/
|
|
135
|
-
addMessage({ role: 'info', text: '/
|
|
211
|
+
addMessage({ role: 'info', text: '/agents browse and switch agents' });
|
|
212
|
+
addMessage({ role: 'info', text: '/tools show available tools' });
|
|
213
|
+
addMessage({ role: 'info', text: '/runners list your runners + coding CLIs' });
|
|
214
|
+
addMessage({ role: 'info', text: '/code run a coding task on a runner' });
|
|
215
|
+
addMessage({ role: 'info', text: '/code-stop stop the active coding session' });
|
|
216
|
+
addMessage({ role: 'info', text: '/esc leave coding mode (session keeps running)' });
|
|
217
|
+
addMessage({ role: 'info', text: '/clear clear conversation' });
|
|
218
|
+
addMessage({ role: 'info', text: '/help show this help' });
|
|
219
|
+
addMessage({ role: 'info', text: '/quit exit' });
|
|
136
220
|
addMessage({ role: 'info', text: 'Tab to autocomplete commands.' });
|
|
137
221
|
return;
|
|
138
222
|
case 'tools': {
|
|
@@ -174,6 +258,91 @@ export function ChatApp({ client, initialAgent, gw, resumeConversationId }) {
|
|
|
174
258
|
setShowPicker(true);
|
|
175
259
|
return;
|
|
176
260
|
}
|
|
261
|
+
case 'runners': {
|
|
262
|
+
setState(s => ({ ...s, loading: true, loadingLabel: 'Loading runners' }));
|
|
263
|
+
try {
|
|
264
|
+
const runners = await client.listRunners();
|
|
265
|
+
setState(s => ({ ...s, loading: false }));
|
|
266
|
+
if (!runners.length) {
|
|
267
|
+
addMessage({ role: 'info', text: 'No runners registered. Start one: npx @almyty/runner start' });
|
|
268
|
+
return;
|
|
269
|
+
}
|
|
270
|
+
for (const r of runners) {
|
|
271
|
+
const clis = (r.codingAgents ?? []).map(a => a.id).join(', ') || 'no coding CLIs detected';
|
|
272
|
+
addMessage({ role: 'info', text: `${r.name} · ${r.state ?? 'unknown'} · ${clis}` });
|
|
273
|
+
}
|
|
274
|
+
}
|
|
275
|
+
catch (err) {
|
|
276
|
+
setState(s => ({ ...s, loading: false }));
|
|
277
|
+
addMessage({ role: 'error', text: err.message });
|
|
278
|
+
}
|
|
279
|
+
return;
|
|
280
|
+
}
|
|
281
|
+
case 'code': {
|
|
282
|
+
const task = args.join(' ').trim();
|
|
283
|
+
if (!task) {
|
|
284
|
+
addMessage({ role: 'error', text: 'Usage: /code <task>' });
|
|
285
|
+
return;
|
|
286
|
+
}
|
|
287
|
+
if (coding) {
|
|
288
|
+
addMessage({ role: 'error', text: 'A coding session is already active — /code-stop or /esc first.' });
|
|
289
|
+
return;
|
|
290
|
+
}
|
|
291
|
+
setState(s => ({ ...s, loading: true, loadingLabel: 'Finding runners' }));
|
|
292
|
+
try {
|
|
293
|
+
const runners = await client.listRunners();
|
|
294
|
+
setState(s => ({ ...s, loading: false }));
|
|
295
|
+
const choices = buildCodeChoices(runners);
|
|
296
|
+
if (!choices.length) {
|
|
297
|
+
addMessage({ role: 'error', text: 'No online runner with a detected coding CLI. Start one: npx @almyty/runner start' });
|
|
298
|
+
return;
|
|
299
|
+
}
|
|
300
|
+
if (choices.length === 1) {
|
|
301
|
+
await startCoding(choices[0], task);
|
|
302
|
+
return;
|
|
303
|
+
}
|
|
304
|
+
setCodeChoices({ choices, task });
|
|
305
|
+
}
|
|
306
|
+
catch (err) {
|
|
307
|
+
setState(s => ({ ...s, loading: false }));
|
|
308
|
+
addMessage({ role: 'error', text: err.message });
|
|
309
|
+
}
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
case 'code-stop': {
|
|
313
|
+
if (!coding) {
|
|
314
|
+
addMessage({ role: 'info', text: 'No active coding session.' });
|
|
315
|
+
return;
|
|
316
|
+
}
|
|
317
|
+
try {
|
|
318
|
+
await client.stopCodingSession(coding.runnerId, coding.sessionId);
|
|
319
|
+
}
|
|
320
|
+
catch (err) {
|
|
321
|
+
addMessage({ role: 'error', text: err.message });
|
|
322
|
+
}
|
|
323
|
+
leaveCodingMode('coding session stopped');
|
|
324
|
+
return;
|
|
325
|
+
}
|
|
326
|
+
case 'esc': {
|
|
327
|
+
if (!coding) {
|
|
328
|
+
addMessage({ role: 'info', text: 'Not in coding mode.' });
|
|
329
|
+
return;
|
|
330
|
+
}
|
|
331
|
+
leaveCodingMode(`left coding mode — session keeps running on ${coding.runnerName} (/code-stop to kill it)`);
|
|
332
|
+
return;
|
|
333
|
+
}
|
|
334
|
+
}
|
|
335
|
+
return;
|
|
336
|
+
}
|
|
337
|
+
// Coding mode: anything that isn't a slash command routes to the
|
|
338
|
+
// session's stdin, not to the chat agent.
|
|
339
|
+
if (classifyInput(trimmed, coding !== null) === 'coding' && coding) {
|
|
340
|
+
addMessage({ role: 'user', text: trimmed });
|
|
341
|
+
try {
|
|
342
|
+
await client.sendCodingInput(coding.runnerId, coding.sessionId, trimmed);
|
|
343
|
+
}
|
|
344
|
+
catch (err) {
|
|
345
|
+
addMessage({ role: 'error', text: err.message });
|
|
177
346
|
}
|
|
178
347
|
return;
|
|
179
348
|
}
|
|
@@ -283,7 +452,7 @@ export function ChatApp({ client, initialAgent, gw, resumeConversationId }) {
|
|
|
283
452
|
setState(s => ({ ...s, loading: false }));
|
|
284
453
|
addMessage({ role: 'error', text: err.message });
|
|
285
454
|
}
|
|
286
|
-
}, [state.agent, state.conversationId, state.pendingRunId, client, addMessage, exit]);
|
|
455
|
+
}, [state.agent, state.conversationId, state.pendingRunId, client, addMessage, exit, coding, startCoding, leaveCodingMode]);
|
|
287
456
|
const handlePickerSelect = useCallback((agent) => {
|
|
288
457
|
setShowPicker(false);
|
|
289
458
|
if (agent.id === state.agent.id)
|
|
@@ -300,13 +469,27 @@ export function ChatApp({ client, initialAgent, gw, resumeConversationId }) {
|
|
|
300
469
|
if (showPicker) {
|
|
301
470
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { agent: state.agent, conversationId: state.conversationId }), _jsx(AgentSelector, { agents: pickerAgents, onSelect: handlePickerSelect })] }));
|
|
302
471
|
}
|
|
472
|
+
if (codeChoices) {
|
|
473
|
+
const items = codeChoices.choices.map(c => ({
|
|
474
|
+
key: `${c.runnerId}:${c.agentId}`,
|
|
475
|
+
label: c.agentName,
|
|
476
|
+
hint: `on ${c.runnerName}`,
|
|
477
|
+
}));
|
|
478
|
+
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { agent: state.agent, conversationId: state.conversationId }), _jsx(ChoiceSelector, { title: "Pick a coding CLI + runner:", choices: items, onSelect: (picked) => {
|
|
479
|
+
const choice = codeChoices.choices.find(c => `${c.runnerId}:${c.agentId}` === picked.key);
|
|
480
|
+
const task = codeChoices.task;
|
|
481
|
+
setCodeChoices(null);
|
|
482
|
+
if (choice)
|
|
483
|
+
void startCoding(choice, task);
|
|
484
|
+
} })] }));
|
|
485
|
+
}
|
|
303
486
|
return (_jsxs(Box, { flexDirection: "column", children: [_jsx(Header, { agent: state.agent, conversationId: state.conversationId }), _jsxs(Box, { flexDirection: "column", flexGrow: 1, paddingRight: 2, overflow: "hidden", children: [state.messages.map((msg, i) => (_jsx(MessageView, { msg: msg }, i))), state.loading && _jsx(LoadingIndicator, { label: state.loadingLabel })] }), paletteOpen && (_jsx(Box, { flexDirection: "column", paddingLeft: 2, children: slashMatches.map((cmd, i) => {
|
|
304
487
|
const active = i === paletteCursor;
|
|
305
488
|
const padded = `/${cmd}`.padEnd(10);
|
|
306
489
|
const desc = COMMAND_DESCS[cmd] ?? '';
|
|
307
490
|
const line = `${active ? '❯' : ' '} ${padded} ${desc}`;
|
|
308
491
|
return _jsx(Text, { color: active ? '#8b5cf6' : undefined, bold: active, wrap: "truncate", children: line }, cmd);
|
|
309
|
-
}) })), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: '─'.repeat(Math.min(process.stdout.columns || 80, 120)) }) }), _jsxs(Box, { paddingX: 1, paddingY: 1, children: [_jsx(Text, { color:
|
|
492
|
+
}) })), coding && _jsx(CodingModeIndicator, { agent: coding.agent, runner: coding.runnerName }), _jsx(Box, { children: _jsx(Text, { dimColor: true, children: '─'.repeat(Math.min(process.stdout.columns || 80, 120)) }) }), _jsxs(Box, { paddingX: 1, paddingY: 1, children: [_jsx(Text, { color: coding ? '#22d3ee' : '#8b5cf6', children: "\u276F " }), _jsx(Box, { flexGrow: 1, children: _jsx(TextInput, { value: input, onChange: handleInputChange, onSubmit: (val) => {
|
|
310
493
|
if (paletteOpen && slashMatches.length > 0) {
|
|
311
494
|
const selected = `/${slashMatches[paletteCursor]}`;
|
|
312
495
|
setInput('');
|
|
@@ -315,5 +498,5 @@ export function ChatApp({ client, initialAgent, gw, resumeConversationId }) {
|
|
|
315
498
|
return;
|
|
316
499
|
}
|
|
317
500
|
handleSubmit(val);
|
|
318
|
-
}, placeholder:
|
|
501
|
+
}, placeholder: coding ? 'Type input for the coding session or / for commands' : 'Type a message or / for commands' }) })] }), _jsx(Box, { paddingX: 1, children: _jsxs(Text, { dimColor: true, children: [state.agent.name, state.agent.tools?.length ? ` · ${state.agent.tools.length} tools` : '', state.conversationId ? ` · ${state.conversationId.slice(0, 8)}` : '', coding ? ` · coding:${coding.agent}@${coding.runnerName}` : ''] }) })] }));
|
|
319
502
|
}
|
package/dist/commands.d.ts
CHANGED
|
@@ -1,5 +1,25 @@
|
|
|
1
|
-
|
|
1
|
+
import type { RunnerSummary } from '@almyty/client';
|
|
2
|
+
export declare const SLASH_COMMANDS: readonly ["agents", "tools", "runners", "code", "code-stop", "esc", "help", "clear", "quit"];
|
|
2
3
|
export declare const ALIASES: Record<string, string>;
|
|
3
4
|
export declare const COMMAND_DESCS: Record<string, string>;
|
|
4
5
|
export declare function resolveSlash(input: string): string | null;
|
|
5
6
|
export declare function getSuggestion(partial: string): string;
|
|
7
|
+
export type InputRoute = 'command' | 'coding' | 'chat';
|
|
8
|
+
/**
|
|
9
|
+
* Where a submitted line goes: slash commands are always commands; when a
|
|
10
|
+
* coding session is active, everything else routes to the session's stdin;
|
|
11
|
+
* otherwise it's a normal chat message.
|
|
12
|
+
*/
|
|
13
|
+
export declare function classifyInput(value: string, codingActive: boolean): InputRoute;
|
|
14
|
+
export interface CodeChoice {
|
|
15
|
+
runnerId: string;
|
|
16
|
+
runnerName: string;
|
|
17
|
+
agentId: string;
|
|
18
|
+
agentName: string;
|
|
19
|
+
}
|
|
20
|
+
/**
|
|
21
|
+
* Expand online runners x detected coding CLIs into a flat choice list for
|
|
22
|
+
* the selector. Offline/stale runners and runners without any detected
|
|
23
|
+
* coding CLI are excluded.
|
|
24
|
+
*/
|
|
25
|
+
export declare function buildCodeChoices(runners: RunnerSummary[]): CodeChoice[];
|
package/dist/commands.js
CHANGED
|
@@ -1,8 +1,13 @@
|
|
|
1
1
|
// ── Slash command resolution ────────────────────────────────────
|
|
2
|
-
export const SLASH_COMMANDS = [
|
|
2
|
+
export const SLASH_COMMANDS = [
|
|
3
|
+
'agents', 'tools', 'runners', 'code', 'code-stop', 'esc', 'help', 'clear', 'quit',
|
|
4
|
+
];
|
|
3
5
|
export const ALIASES = {
|
|
4
6
|
agent: 'agents', ag: 'agents', switch: 'agents', sw: 'agents',
|
|
5
7
|
tool: 'tools', t: 'tools',
|
|
8
|
+
runner: 'runners',
|
|
9
|
+
stop: 'code-stop',
|
|
10
|
+
detach: 'esc',
|
|
6
11
|
h: 'help', '?': 'help',
|
|
7
12
|
cls: 'clear', c: 'clear',
|
|
8
13
|
exit: 'quit', q: 'quit',
|
|
@@ -10,6 +15,10 @@ export const ALIASES = {
|
|
|
10
15
|
export const COMMAND_DESCS = {
|
|
11
16
|
agents: 'browse and switch agents',
|
|
12
17
|
tools: 'show available tools',
|
|
18
|
+
runners: 'list your runners + coding CLIs',
|
|
19
|
+
code: 'run a coding task on a runner',
|
|
20
|
+
'code-stop': 'stop the active coding session',
|
|
21
|
+
esc: 'leave coding mode (session keeps running)',
|
|
13
22
|
help: 'show commands',
|
|
14
23
|
clear: 'clear conversation',
|
|
15
24
|
quit: 'exit',
|
|
@@ -34,3 +43,37 @@ export function getSuggestion(partial) {
|
|
|
34
43
|
const match = SLASH_COMMANDS.find(c => c.startsWith(p) && c !== p);
|
|
35
44
|
return match ? `/${match}` : '';
|
|
36
45
|
}
|
|
46
|
+
/**
|
|
47
|
+
* Where a submitted line goes: slash commands are always commands; when a
|
|
48
|
+
* coding session is active, everything else routes to the session's stdin;
|
|
49
|
+
* otherwise it's a normal chat message.
|
|
50
|
+
*/
|
|
51
|
+
export function classifyInput(value, codingActive) {
|
|
52
|
+
if (value.trim().startsWith('/'))
|
|
53
|
+
return 'command';
|
|
54
|
+
return codingActive ? 'coding' : 'chat';
|
|
55
|
+
}
|
|
56
|
+
// ── /code target selection ──────────────────────────────────────
|
|
57
|
+
/** Runner states that can accept a coding dispatch. */
|
|
58
|
+
const DISPATCHABLE_STATES = new Set(['online', 'busy']);
|
|
59
|
+
/**
|
|
60
|
+
* Expand online runners x detected coding CLIs into a flat choice list for
|
|
61
|
+
* the selector. Offline/stale runners and runners without any detected
|
|
62
|
+
* coding CLI are excluded.
|
|
63
|
+
*/
|
|
64
|
+
export function buildCodeChoices(runners) {
|
|
65
|
+
const choices = [];
|
|
66
|
+
for (const runner of runners) {
|
|
67
|
+
if (!DISPATCHABLE_STATES.has(runner.state ?? ''))
|
|
68
|
+
continue;
|
|
69
|
+
for (const agent of runner.codingAgents ?? []) {
|
|
70
|
+
choices.push({
|
|
71
|
+
runnerId: runner.id,
|
|
72
|
+
runnerName: runner.name,
|
|
73
|
+
agentId: agent.id,
|
|
74
|
+
agentName: agent.displayName || agent.id,
|
|
75
|
+
});
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
return choices;
|
|
79
|
+
}
|
package/dist/components.d.ts
CHANGED
|
@@ -1,17 +1,17 @@
|
|
|
1
1
|
import React from 'react';
|
|
2
2
|
import type { AgentInfo } from '@almyty/client';
|
|
3
3
|
export interface Message {
|
|
4
|
-
role: 'user' | 'agent' | 'error' | 'info' | 'tool';
|
|
4
|
+
role: 'user' | 'agent' | 'error' | 'info' | 'tool' | 'coding';
|
|
5
5
|
text: string;
|
|
6
6
|
}
|
|
7
7
|
export declare function MarkdownText({ children, prefix, prefixColor }: {
|
|
8
8
|
children: string;
|
|
9
9
|
prefix?: string;
|
|
10
10
|
prefixColor?: string;
|
|
11
|
-
}):
|
|
11
|
+
}): React.JSX.Element;
|
|
12
12
|
export declare function InlineFormat({ text }: {
|
|
13
13
|
text: string;
|
|
14
|
-
}):
|
|
14
|
+
}): React.JSX.Element;
|
|
15
15
|
export declare function boldify(text: string): React.ReactElement;
|
|
16
16
|
export declare function estimateLines(msg: Message, cols: number): number;
|
|
17
17
|
export declare function MessageWindow({ messages, loading, loadingLabel, maxRows, scrollOffset }: {
|
|
@@ -20,18 +20,36 @@ export declare function MessageWindow({ messages, loading, loadingLabel, maxRows
|
|
|
20
20
|
loadingLabel: string;
|
|
21
21
|
maxRows: number;
|
|
22
22
|
scrollOffset?: number;
|
|
23
|
-
}):
|
|
23
|
+
}): React.JSX.Element;
|
|
24
24
|
export declare function Header({ agent, conversationId }: {
|
|
25
25
|
agent: AgentInfo;
|
|
26
26
|
conversationId?: string | null;
|
|
27
|
-
}):
|
|
27
|
+
}): React.JSX.Element;
|
|
28
28
|
export declare function MessageView({ msg }: {
|
|
29
29
|
msg: Message;
|
|
30
|
-
}):
|
|
30
|
+
}): React.JSX.Element | null;
|
|
31
31
|
export declare function LoadingIndicator({ label }: {
|
|
32
32
|
label: string;
|
|
33
|
-
}):
|
|
33
|
+
}): React.JSX.Element;
|
|
34
34
|
export declare function AgentSelector({ agents, onSelect }: {
|
|
35
35
|
agents: AgentInfo[];
|
|
36
36
|
onSelect: (a: AgentInfo) => void;
|
|
37
|
-
}):
|
|
37
|
+
}): React.JSX.Element;
|
|
38
|
+
/**
|
|
39
|
+
* Visible mode banner while a coding session is active: input routes to the
|
|
40
|
+
* session, /esc detaches, /code-stop kills it.
|
|
41
|
+
*/
|
|
42
|
+
export declare function CodingModeIndicator({ agent, runner }: {
|
|
43
|
+
agent: string;
|
|
44
|
+
runner: string;
|
|
45
|
+
}): React.JSX.Element;
|
|
46
|
+
export interface Choice {
|
|
47
|
+
key: string;
|
|
48
|
+
label: string;
|
|
49
|
+
hint?: string;
|
|
50
|
+
}
|
|
51
|
+
export declare function ChoiceSelector({ title, choices, onSelect }: {
|
|
52
|
+
title: string;
|
|
53
|
+
choices: Choice[];
|
|
54
|
+
onSelect: (c: Choice) => void;
|
|
55
|
+
}): React.JSX.Element;
|
package/dist/components.js
CHANGED
|
@@ -137,6 +137,10 @@ export function MessageView({ msg }) {
|
|
|
137
137
|
return (_jsx(Box, { marginTop: 1, paddingLeft: 2, children: _jsxs(Text, { color: "red", children: ["\u2502 ", msg.text] }) }));
|
|
138
138
|
case 'info':
|
|
139
139
|
return (_jsx(Box, { paddingLeft: 2, children: _jsxs(Text, { dimColor: true, children: ["\u2502 ", msg.text] }) }));
|
|
140
|
+
case 'coding':
|
|
141
|
+
// Coding-session output: distinct cyan gutter so streamed CLI output
|
|
142
|
+
// reads apart from agent/chat messages.
|
|
143
|
+
return (_jsx(Box, { paddingLeft: 2, flexDirection: "column", children: msg.text.replace(/\n+$/, '').split('\n').map((line, i) => (_jsxs(Text, { wrap: "truncate-end", children: [_jsx(Text, { color: "#22d3ee", children: "\u258D " }), _jsx(Text, { dimColor: true, children: line || ' ' })] }, i))) }));
|
|
140
144
|
default:
|
|
141
145
|
return null;
|
|
142
146
|
}
|
|
@@ -157,3 +161,23 @@ export function AgentSelector({ agents, onSelect }) {
|
|
|
157
161
|
});
|
|
158
162
|
return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [_jsx(Text, { dimColor: true, children: " Select an agent:" }), _jsx(Text, { children: " " }), agents.map((ag, i) => (_jsxs(Box, { paddingLeft: 2, children: [_jsxs(Text, { color: i === cursor ? '#8b5cf6' : undefined, children: [i === cursor ? '❯' : ' ', ' '] }), _jsx(Text, { bold: i === cursor, children: ag.name }), _jsxs(Text, { dimColor: true, children: [" ", ag.mode] })] }, ag.id))), agents[cursor]?.description && (_jsx(Box, { paddingLeft: 4, paddingTop: 1, children: _jsx(Text, { dimColor: true, children: agents[cursor].description }) }))] }));
|
|
159
163
|
}
|
|
164
|
+
// ── Coding mode ─────────────────────────────────────────────────
|
|
165
|
+
/**
|
|
166
|
+
* Visible mode banner while a coding session is active: input routes to the
|
|
167
|
+
* session, /esc detaches, /code-stop kills it.
|
|
168
|
+
*/
|
|
169
|
+
export function CodingModeIndicator({ agent, runner }) {
|
|
170
|
+
return (_jsxs(Box, { paddingX: 1, children: [_jsx(Text, { backgroundColor: "#0891B2", color: "black", children: " coding " }), _jsxs(Text, { color: "#22d3ee", children: [" ", agent, "@", runner] }), _jsx(Text, { dimColor: true, children: " input goes to the session \u00B7 /esc detach \u00B7 /code-stop stop" })] }));
|
|
171
|
+
}
|
|
172
|
+
export function ChoiceSelector({ title, choices, onSelect }) {
|
|
173
|
+
const [cursor, setCursor] = useState(0);
|
|
174
|
+
useInput((input, key) => {
|
|
175
|
+
if (key.upArrow)
|
|
176
|
+
setCursor(c => (c - 1 + choices.length) % choices.length);
|
|
177
|
+
if (key.downArrow)
|
|
178
|
+
setCursor(c => (c + 1) % choices.length);
|
|
179
|
+
if (key.return)
|
|
180
|
+
onSelect(choices[cursor]);
|
|
181
|
+
});
|
|
182
|
+
return (_jsxs(Box, { flexDirection: "column", paddingTop: 1, children: [_jsxs(Text, { dimColor: true, children: [" ", title] }), _jsx(Text, { children: " " }), choices.map((choice, i) => (_jsxs(Box, { paddingLeft: 2, children: [_jsxs(Text, { color: i === cursor ? '#8b5cf6' : undefined, children: [i === cursor ? '❯' : ' ', ' '] }), _jsx(Text, { bold: i === cursor, children: choice.label }), choice.hint && _jsxs(Text, { dimColor: true, children: [" ", choice.hint] })] }, choice.key)))] }));
|
|
183
|
+
}
|
package/package.json
CHANGED
|
@@ -1,7 +1,10 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@almyty/chat",
|
|
3
|
-
"version": "
|
|
4
|
-
"
|
|
3
|
+
"version": "1.2.0",
|
|
4
|
+
"publishConfig": {
|
|
5
|
+
"access": "public"
|
|
6
|
+
},
|
|
7
|
+
"description": "Chat with your almyty agents in an interactive terminal REPL \u2014 multi-turn and streaming, with tool calls and autonomous runs.",
|
|
5
8
|
"type": "module",
|
|
6
9
|
"main": "dist/index.js",
|
|
7
10
|
"bin": {
|
|
@@ -25,9 +28,9 @@
|
|
|
25
28
|
"cli"
|
|
26
29
|
],
|
|
27
30
|
"author": "almyty",
|
|
28
|
-
"license": "
|
|
31
|
+
"license": "Apache-2.0",
|
|
29
32
|
"dependencies": {
|
|
30
|
-
"@almyty/client": "^
|
|
33
|
+
"@almyty/client": "^1.2.0",
|
|
31
34
|
"@types/react": "^19.2.14",
|
|
32
35
|
"ink": "^7.0.1",
|
|
33
36
|
"ink-spinner": "^5.0.0",
|
|
@@ -40,5 +43,14 @@
|
|
|
40
43
|
"tsx": "^4.7.0",
|
|
41
44
|
"typescript": "^5.3.0",
|
|
42
45
|
"vitest": "^4.1.0"
|
|
46
|
+
},
|
|
47
|
+
"homepage": "https://almyty.com",
|
|
48
|
+
"repository": {
|
|
49
|
+
"type": "git",
|
|
50
|
+
"url": "git+https://github.com/almyty-inc/almyty.git",
|
|
51
|
+
"directory": "packages/chat-cli"
|
|
52
|
+
},
|
|
53
|
+
"bugs": {
|
|
54
|
+
"url": "https://github.com/almyty-inc/almyty/issues"
|
|
43
55
|
}
|
|
44
56
|
}
|