@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 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
- ## Docs
43
+ ## About almyty
44
44
 
45
- https://almyty.com/docs
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
- ## License
49
+ - Website — https://almyty.com
50
+ - Docs — https://docs.almyty.com
51
+ - Source — https://github.com/almyty-inc/almyty
48
52
 
49
- BSL-1.1
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
- }): import("react/jsx-runtime").JSX.Element;
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 browse and switch agents' });
132
- addMessage({ role: 'info', text: '/tools show available tools' });
133
- addMessage({ role: 'info', text: '/clear clear conversation' });
134
- addMessage({ role: 'info', text: '/help show this help' });
135
- addMessage({ role: 'info', text: '/quit exit' });
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: "#8b5cf6", children: "\u276F " }), _jsx(Box, { flexGrow: 1, children: _jsx(TextInput, { value: input, onChange: handleInputChange, onSubmit: (val) => {
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: "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)}` : ''] }) })] }));
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
  }
@@ -1,5 +1,25 @@
1
- export declare const SLASH_COMMANDS: readonly ["agents", "tools", "help", "clear", "quit"];
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 = ['agents', 'tools', 'help', 'clear', 'quit'];
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
+ }
@@ -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
- }): import("react/jsx-runtime").JSX.Element;
11
+ }): React.JSX.Element;
12
12
  export declare function InlineFormat({ text }: {
13
13
  text: string;
14
- }): import("react/jsx-runtime").JSX.Element;
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
- }): import("react/jsx-runtime").JSX.Element;
23
+ }): React.JSX.Element;
24
24
  export declare function Header({ agent, conversationId }: {
25
25
  agent: AgentInfo;
26
26
  conversationId?: string | null;
27
- }): import("react/jsx-runtime").JSX.Element;
27
+ }): React.JSX.Element;
28
28
  export declare function MessageView({ msg }: {
29
29
  msg: Message;
30
- }): import("react/jsx-runtime").JSX.Element | null;
30
+ }): React.JSX.Element | null;
31
31
  export declare function LoadingIndicator({ label }: {
32
32
  label: string;
33
- }): import("react/jsx-runtime").JSX.Element;
33
+ }): React.JSX.Element;
34
34
  export declare function AgentSelector({ agents, onSelect }: {
35
35
  agents: AgentInfo[];
36
36
  onSelect: (a: AgentInfo) => void;
37
- }): import("react/jsx-runtime").JSX.Element;
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;
@@ -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": "0.2.0",
4
- "description": "Interactive chat REPL with almyty agents",
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": "BSL-1.1",
31
+ "license": "Apache-2.0",
29
32
  "dependencies": {
30
- "@almyty/client": "^0.1.0",
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
  }