@almyty/chat 0.1.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +42 -0
- package/dist/client.d.ts +35 -0
- package/dist/client.js +107 -0
- package/dist/credentials.d.ts +15 -0
- package/dist/credentials.js +33 -0
- package/dist/index.d.ts +19 -0
- package/dist/index.js +290 -0
- package/package.json +35 -0
package/README.md
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
# @almyty/chat
|
|
2
|
+
|
|
3
|
+
Interactive chat REPL for almyty agents. Supports both workflow and autonomous agent modes.
|
|
4
|
+
|
|
5
|
+
## Quick start
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
$ npx @almyty/auth login
|
|
9
|
+
$ npx @almyty/chat
|
|
10
|
+
$ npx @almyty/chat my-research-bot
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
Run without arguments to pick an agent from an interactive menu, or pass a name or ID to connect directly.
|
|
16
|
+
|
|
17
|
+
```bash
|
|
18
|
+
$ npx @almyty/chat # interactive agent picker
|
|
19
|
+
$ npx @almyty/chat <name|id> # connect to a specific agent
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
## REPL commands
|
|
23
|
+
|
|
24
|
+
| Command | Description |
|
|
25
|
+
|---------|-------------|
|
|
26
|
+
| `/switch <agent>` | Switch to a different agent |
|
|
27
|
+
| `/agents` | List agents (current one marked) |
|
|
28
|
+
| `/clear` | Clear the screen |
|
|
29
|
+
| `/help` | Show available commands |
|
|
30
|
+
| `/quit` | Exit the REPL |
|
|
31
|
+
|
|
32
|
+
## Authentication
|
|
33
|
+
|
|
34
|
+
Requires `npx @almyty/auth login` first. Reads credentials from `~/.almyty/credentials.json`. Override with `ALMYTY_TOKEN` and `ALMYTY_URL` environment variables.
|
|
35
|
+
|
|
36
|
+
## Docs
|
|
37
|
+
|
|
38
|
+
https://almyty.com/docs
|
|
39
|
+
|
|
40
|
+
## License
|
|
41
|
+
|
|
42
|
+
BSL-1.1
|
package/dist/client.d.ts
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny HTTP client used by @almyty/chat. Subset of @almyty/agents'
|
|
3
|
+
* client — kept inline so this package has zero workspace deps.
|
|
4
|
+
*/
|
|
5
|
+
export interface AgentInfo {
|
|
6
|
+
id: string;
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
mode?: 'workflow' | 'autonomous';
|
|
10
|
+
status?: string;
|
|
11
|
+
}
|
|
12
|
+
export interface AgentRun {
|
|
13
|
+
id: string;
|
|
14
|
+
status: string;
|
|
15
|
+
output?: any;
|
|
16
|
+
error?: string;
|
|
17
|
+
steps?: any[];
|
|
18
|
+
}
|
|
19
|
+
export declare class AlmytyClient {
|
|
20
|
+
private readonly baseUrl;
|
|
21
|
+
private readonly token;
|
|
22
|
+
constructor(baseUrl: string, token: string);
|
|
23
|
+
private request;
|
|
24
|
+
listAgents(): Promise<AgentInfo[]>;
|
|
25
|
+
findAgentByNameOrId(nameOrId: string): Promise<AgentInfo | null>;
|
|
26
|
+
invokeAgent(agentId: string, input: Record<string, any>): Promise<any>;
|
|
27
|
+
startAgentRun(agentId: string, input: any): Promise<AgentRun>;
|
|
28
|
+
getRun(agentId: string, runId: string): Promise<AgentRun>;
|
|
29
|
+
sendRunInput(agentId: string, runId: string, input: string): Promise<void>;
|
|
30
|
+
pollRun(agentId: string, runId: string, options?: {
|
|
31
|
+
intervalMs?: number;
|
|
32
|
+
timeoutMs?: number;
|
|
33
|
+
onStep?: (run: AgentRun) => void;
|
|
34
|
+
}): Promise<AgentRun>;
|
|
35
|
+
}
|
package/dist/client.js
ADDED
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Tiny HTTP client used by @almyty/chat. Subset of @almyty/agents'
|
|
3
|
+
* client — kept inline so this package has zero workspace deps.
|
|
4
|
+
*/
|
|
5
|
+
const TERMINAL_STATUSES = new Set(['completed', 'failed', 'cancelled', 'timeout']);
|
|
6
|
+
export class AlmytyClient {
|
|
7
|
+
baseUrl;
|
|
8
|
+
token;
|
|
9
|
+
constructor(baseUrl, token) {
|
|
10
|
+
this.baseUrl = baseUrl.replace(/\/$/, '');
|
|
11
|
+
this.token = token;
|
|
12
|
+
}
|
|
13
|
+
async request(path, init = {}) {
|
|
14
|
+
const url = `${this.baseUrl}${path}`;
|
|
15
|
+
const headers = {
|
|
16
|
+
Authorization: `Bearer ${this.token}`,
|
|
17
|
+
'Content-Type': 'application/json',
|
|
18
|
+
...(init.headers || {}),
|
|
19
|
+
};
|
|
20
|
+
const res = await fetch(url, { ...init, headers });
|
|
21
|
+
if (!res.ok) {
|
|
22
|
+
if (res.status === 401) {
|
|
23
|
+
throw new Error('Authentication failed. Run: npx @almyty/auth login');
|
|
24
|
+
}
|
|
25
|
+
const text = await res.text().catch(() => '');
|
|
26
|
+
throw new Error(`API error ${res.status}: ${text}`);
|
|
27
|
+
}
|
|
28
|
+
if (res.status === 204)
|
|
29
|
+
return null;
|
|
30
|
+
return res.json();
|
|
31
|
+
}
|
|
32
|
+
async listAgents() {
|
|
33
|
+
const data = await this.request('/agents');
|
|
34
|
+
const agents = data?.data?.data || data?.data || data || [];
|
|
35
|
+
return agents.map((a) => ({
|
|
36
|
+
id: a.id,
|
|
37
|
+
name: a.name,
|
|
38
|
+
description: a.description,
|
|
39
|
+
mode: a.mode,
|
|
40
|
+
status: a.status,
|
|
41
|
+
}));
|
|
42
|
+
}
|
|
43
|
+
async findAgentByNameOrId(nameOrId) {
|
|
44
|
+
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
45
|
+
if (uuidRegex.test(nameOrId)) {
|
|
46
|
+
try {
|
|
47
|
+
const data = await this.request(`/agents/${nameOrId}`);
|
|
48
|
+
const a = data?.data ?? data;
|
|
49
|
+
if (a?.id) {
|
|
50
|
+
return { id: a.id, name: a.name, description: a.description, mode: a.mode, status: a.status };
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
catch {
|
|
54
|
+
// fall through
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
const all = await this.listAgents();
|
|
58
|
+
const lower = nameOrId.toLowerCase();
|
|
59
|
+
return (all.find((a) => a.name === nameOrId) ||
|
|
60
|
+
all.find((a) => a.name.toLowerCase() === lower) ||
|
|
61
|
+
all.find((a) => a.name.toLowerCase().replace(/\s+/g, '-') === lower) ||
|
|
62
|
+
null);
|
|
63
|
+
}
|
|
64
|
+
async invokeAgent(agentId, input) {
|
|
65
|
+
const data = await this.request(`/agents/${agentId}/invoke`, {
|
|
66
|
+
method: 'POST',
|
|
67
|
+
body: JSON.stringify({ input }),
|
|
68
|
+
});
|
|
69
|
+
return data?.data ?? data;
|
|
70
|
+
}
|
|
71
|
+
async startAgentRun(agentId, input) {
|
|
72
|
+
const data = await this.request(`/agents/${agentId}/runs`, {
|
|
73
|
+
method: 'POST',
|
|
74
|
+
body: JSON.stringify({ input }),
|
|
75
|
+
});
|
|
76
|
+
const run = data?.data ?? data;
|
|
77
|
+
return { id: run.id, status: run.status };
|
|
78
|
+
}
|
|
79
|
+
async getRun(agentId, runId) {
|
|
80
|
+
const data = await this.request(`/agents/${agentId}/runs/${runId}`);
|
|
81
|
+
return (data?.data ?? data);
|
|
82
|
+
}
|
|
83
|
+
async sendRunInput(agentId, runId, input) {
|
|
84
|
+
await this.request(`/agents/${agentId}/runs/${runId}/input`, {
|
|
85
|
+
method: 'POST',
|
|
86
|
+
body: JSON.stringify({ input }),
|
|
87
|
+
});
|
|
88
|
+
}
|
|
89
|
+
async pollRun(agentId, runId, options = {}) {
|
|
90
|
+
const intervalMs = options.intervalMs ?? 1500;
|
|
91
|
+
const timeoutMs = options.timeoutMs ?? 5 * 60_000;
|
|
92
|
+
const deadline = Date.now() + timeoutMs;
|
|
93
|
+
let lastStepCount = -1;
|
|
94
|
+
while (Date.now() < deadline) {
|
|
95
|
+
const run = await this.getRun(agentId, runId);
|
|
96
|
+
if (Array.isArray(run.steps) && run.steps.length !== lastStepCount) {
|
|
97
|
+
lastStepCount = run.steps.length;
|
|
98
|
+
options.onStep?.(run);
|
|
99
|
+
}
|
|
100
|
+
if (run.status && (TERMINAL_STATUSES.has(run.status) || run.status === 'waiting_input')) {
|
|
101
|
+
return run;
|
|
102
|
+
}
|
|
103
|
+
await new Promise((r) => setTimeout(r, intervalMs));
|
|
104
|
+
}
|
|
105
|
+
throw new Error(`Run ${runId} did not finish within ${Math.round(timeoutMs / 1000)}s`);
|
|
106
|
+
}
|
|
107
|
+
}
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read shared almyty credentials from ~/.almyty/credentials.json.
|
|
3
|
+
*
|
|
4
|
+
* Identical to the helper in @almyty/auth and @almyty/agents — kept
|
|
5
|
+
* inline to keep each CLI package self-contained.
|
|
6
|
+
*/
|
|
7
|
+
export declare const CREDENTIALS_FILE: string;
|
|
8
|
+
export interface StoredCredentials {
|
|
9
|
+
url: string;
|
|
10
|
+
token: string;
|
|
11
|
+
email?: string;
|
|
12
|
+
frontendUrl?: string;
|
|
13
|
+
}
|
|
14
|
+
export declare function loadCredentials(): StoredCredentials | null;
|
|
15
|
+
export declare function resolveCredentialsOrExit(): StoredCredentials;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Read shared almyty credentials from ~/.almyty/credentials.json.
|
|
3
|
+
*
|
|
4
|
+
* Identical to the helper in @almyty/auth and @almyty/agents — kept
|
|
5
|
+
* inline to keep each CLI package self-contained.
|
|
6
|
+
*/
|
|
7
|
+
import { readFileSync, existsSync } from 'fs';
|
|
8
|
+
import { homedir } from 'os';
|
|
9
|
+
import { join } from 'path';
|
|
10
|
+
export const CREDENTIALS_FILE = join(homedir(), '.almyty', 'credentials.json');
|
|
11
|
+
export function loadCredentials() {
|
|
12
|
+
try {
|
|
13
|
+
if (!existsSync(CREDENTIALS_FILE))
|
|
14
|
+
return null;
|
|
15
|
+
return JSON.parse(readFileSync(CREDENTIALS_FILE, 'utf-8'));
|
|
16
|
+
}
|
|
17
|
+
catch {
|
|
18
|
+
return null;
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
export function resolveCredentialsOrExit() {
|
|
22
|
+
const envToken = process.env.ALMYTY_TOKEN;
|
|
23
|
+
const envUrl = process.env.ALMYTY_URL || 'https://api.almyty.com';
|
|
24
|
+
if (envToken)
|
|
25
|
+
return { url: envUrl, token: envToken };
|
|
26
|
+
const stored = loadCredentials();
|
|
27
|
+
if (stored?.token)
|
|
28
|
+
return stored;
|
|
29
|
+
console.error('Not authenticated. Run one of:');
|
|
30
|
+
console.error(' npx @almyty/auth login');
|
|
31
|
+
console.error(' export ALMYTY_TOKEN=<your-token>');
|
|
32
|
+
process.exit(1);
|
|
33
|
+
}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,19 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @almyty/chat — interactive REPL with almyty agents.
|
|
4
|
+
*
|
|
5
|
+
* npx @almyty/chat # pick from a menu
|
|
6
|
+
* npx @almyty/chat <agent> # start chatting with that agent
|
|
7
|
+
*
|
|
8
|
+
* Reads credentials from ~/.almyty/credentials.json (created by
|
|
9
|
+
* `npx @almyty/auth login`).
|
|
10
|
+
*
|
|
11
|
+
* Slash commands inside the REPL:
|
|
12
|
+
*
|
|
13
|
+
* /switch <agent> switch to a different agent
|
|
14
|
+
* /agents list agents in your organization
|
|
15
|
+
* /clear clear the screen
|
|
16
|
+
* /help show command list
|
|
17
|
+
* /quit, /exit leave the REPL
|
|
18
|
+
*/
|
|
19
|
+
export {};
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,290 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
/**
|
|
3
|
+
* @almyty/chat — interactive REPL with almyty agents.
|
|
4
|
+
*
|
|
5
|
+
* npx @almyty/chat # pick from a menu
|
|
6
|
+
* npx @almyty/chat <agent> # start chatting with that agent
|
|
7
|
+
*
|
|
8
|
+
* Reads credentials from ~/.almyty/credentials.json (created by
|
|
9
|
+
* `npx @almyty/auth login`).
|
|
10
|
+
*
|
|
11
|
+
* Slash commands inside the REPL:
|
|
12
|
+
*
|
|
13
|
+
* /switch <agent> switch to a different agent
|
|
14
|
+
* /agents list agents in your organization
|
|
15
|
+
* /clear clear the screen
|
|
16
|
+
* /help show command list
|
|
17
|
+
* /quit, /exit leave the REPL
|
|
18
|
+
*/
|
|
19
|
+
import { createInterface } from 'readline';
|
|
20
|
+
import { AlmytyClient } from './client.js';
|
|
21
|
+
import { resolveCredentialsOrExit } from './credentials.js';
|
|
22
|
+
const VERSION = '0.1.0';
|
|
23
|
+
function printHelp() {
|
|
24
|
+
console.log(`
|
|
25
|
+
@almyty/chat v${VERSION}
|
|
26
|
+
|
|
27
|
+
Usage:
|
|
28
|
+
npx @almyty/chat Pick an agent from a menu
|
|
29
|
+
npx @almyty/chat <name|id> Start chatting with that agent
|
|
30
|
+
|
|
31
|
+
Inside the REPL:
|
|
32
|
+
/switch <agent> switch to a different agent
|
|
33
|
+
/agents list agents in your organization
|
|
34
|
+
/clear clear the screen
|
|
35
|
+
/help show command list
|
|
36
|
+
/quit, /exit leave the REPL
|
|
37
|
+
|
|
38
|
+
Login:
|
|
39
|
+
npx @almyty/auth login Browser-based login
|
|
40
|
+
`);
|
|
41
|
+
}
|
|
42
|
+
function printAgentBanner(agent) {
|
|
43
|
+
console.log('');
|
|
44
|
+
console.log(` 💬 ${agent.name}${agent.mode ? ` [${agent.mode}]` : ''}`);
|
|
45
|
+
if (agent.description)
|
|
46
|
+
console.log(` ${agent.description}`);
|
|
47
|
+
console.log('');
|
|
48
|
+
console.log(' Type your message, or /help for commands. /quit to exit.');
|
|
49
|
+
console.log('');
|
|
50
|
+
}
|
|
51
|
+
function formatStep(step) {
|
|
52
|
+
if (!step)
|
|
53
|
+
return null;
|
|
54
|
+
switch (step.type) {
|
|
55
|
+
case 'llm_call':
|
|
56
|
+
if (step.output?.content)
|
|
57
|
+
return ` ${step.output.content}`;
|
|
58
|
+
return ` · llm_call (${step.tokens?.input ?? 0}↓ / ${step.tokens?.output ?? 0}↑)`;
|
|
59
|
+
case 'tool_call':
|
|
60
|
+
return ` · tool_call → ${step.input?.tool ?? '?'}`;
|
|
61
|
+
case 'sub_agent_call':
|
|
62
|
+
return ` · sub_agent_call → ${step.input?.agentId ?? '?'}`;
|
|
63
|
+
case 'error':
|
|
64
|
+
return ` ! error: ${step.error}`;
|
|
65
|
+
default:
|
|
66
|
+
return null;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
async function pickAgent(client) {
|
|
70
|
+
const agents = await client.listAgents();
|
|
71
|
+
if (agents.length === 0) {
|
|
72
|
+
console.error('No agents in your organization. Create one at https://app.almyty.com/agents');
|
|
73
|
+
return null;
|
|
74
|
+
}
|
|
75
|
+
if (agents.length === 1) {
|
|
76
|
+
return agents[0];
|
|
77
|
+
}
|
|
78
|
+
console.log('');
|
|
79
|
+
console.log(' Pick an agent:');
|
|
80
|
+
agents.forEach((a, i) => {
|
|
81
|
+
const mode = a.mode ? ` [${a.mode}]` : '';
|
|
82
|
+
console.log(` ${i + 1}. ${a.name}${mode}`);
|
|
83
|
+
});
|
|
84
|
+
console.log('');
|
|
85
|
+
const rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
86
|
+
const answer = await new Promise((resolve) => {
|
|
87
|
+
rl.question(' Enter number: ', (input) => {
|
|
88
|
+
rl.close();
|
|
89
|
+
resolve(input.trim());
|
|
90
|
+
});
|
|
91
|
+
});
|
|
92
|
+
const idx = parseInt(answer, 10) - 1;
|
|
93
|
+
if (Number.isNaN(idx) || idx < 0 || idx >= agents.length) {
|
|
94
|
+
console.error('Invalid selection.');
|
|
95
|
+
return null;
|
|
96
|
+
}
|
|
97
|
+
return agents[idx];
|
|
98
|
+
}
|
|
99
|
+
async function handleSlashCommand(command, state) {
|
|
100
|
+
const [cmd, ...rest] = command.slice(1).trim().split(/\s+/);
|
|
101
|
+
switch (cmd) {
|
|
102
|
+
case 'quit':
|
|
103
|
+
case 'exit':
|
|
104
|
+
return { exit: true };
|
|
105
|
+
case 'help':
|
|
106
|
+
console.log('');
|
|
107
|
+
console.log(' /switch <agent> switch to a different agent');
|
|
108
|
+
console.log(' /agents list agents in your organization');
|
|
109
|
+
console.log(' /clear clear the screen');
|
|
110
|
+
console.log(' /quit, /exit leave the REPL');
|
|
111
|
+
console.log(' /help this help');
|
|
112
|
+
console.log('');
|
|
113
|
+
return {};
|
|
114
|
+
case 'clear':
|
|
115
|
+
process.stdout.write('\x1b[2J\x1b[H');
|
|
116
|
+
printAgentBanner(state.agent);
|
|
117
|
+
return {};
|
|
118
|
+
case 'agents': {
|
|
119
|
+
const agents = await state.client.listAgents();
|
|
120
|
+
if (agents.length === 0) {
|
|
121
|
+
console.log(' (no agents in this organization)');
|
|
122
|
+
return {};
|
|
123
|
+
}
|
|
124
|
+
console.log('');
|
|
125
|
+
for (const a of agents) {
|
|
126
|
+
const marker = a.id === state.agent.id ? '→' : ' ';
|
|
127
|
+
const mode = a.mode ? ` [${a.mode}]` : '';
|
|
128
|
+
console.log(` ${marker} ${a.name}${mode}${a.description ? ' — ' + a.description : ''}`);
|
|
129
|
+
}
|
|
130
|
+
console.log('');
|
|
131
|
+
return {};
|
|
132
|
+
}
|
|
133
|
+
case 'switch': {
|
|
134
|
+
const target = rest.join(' ').trim();
|
|
135
|
+
if (!target) {
|
|
136
|
+
console.log(' Usage: /switch <agent name or id>');
|
|
137
|
+
return {};
|
|
138
|
+
}
|
|
139
|
+
const next = await state.client.findAgentByNameOrId(target);
|
|
140
|
+
if (!next) {
|
|
141
|
+
console.log(` Agent "${target}" not found.`);
|
|
142
|
+
return {};
|
|
143
|
+
}
|
|
144
|
+
state.agent = next;
|
|
145
|
+
state.pendingRunId = null;
|
|
146
|
+
printAgentBanner(state.agent);
|
|
147
|
+
return {};
|
|
148
|
+
}
|
|
149
|
+
default:
|
|
150
|
+
console.log(` Unknown command: /${cmd}. Type /help for available commands.`);
|
|
151
|
+
return {};
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
async function handleAutonomousTurn(state, message) {
|
|
155
|
+
let runId;
|
|
156
|
+
if (state.pendingRunId) {
|
|
157
|
+
await state.client.sendRunInput(state.agent.id, state.pendingRunId, message);
|
|
158
|
+
runId = state.pendingRunId;
|
|
159
|
+
state.pendingRunId = null;
|
|
160
|
+
}
|
|
161
|
+
else {
|
|
162
|
+
const run = await state.client.startAgentRun(state.agent.id, message);
|
|
163
|
+
runId = run.id;
|
|
164
|
+
}
|
|
165
|
+
let printed = 0;
|
|
166
|
+
const final = await state.client.pollRun(state.agent.id, runId, {
|
|
167
|
+
onStep: (run) => {
|
|
168
|
+
const newSteps = (run.steps ?? []).slice(printed);
|
|
169
|
+
for (const step of newSteps) {
|
|
170
|
+
const line = formatStep(step);
|
|
171
|
+
if (line)
|
|
172
|
+
console.log(line);
|
|
173
|
+
}
|
|
174
|
+
printed = run.steps?.length ?? printed;
|
|
175
|
+
},
|
|
176
|
+
});
|
|
177
|
+
if (final.status === 'waiting_input') {
|
|
178
|
+
state.pendingRunId = runId;
|
|
179
|
+
console.log('');
|
|
180
|
+
console.log(' (agent is waiting for your input — answer below)');
|
|
181
|
+
console.log('');
|
|
182
|
+
return;
|
|
183
|
+
}
|
|
184
|
+
if (final.status === 'completed') {
|
|
185
|
+
if (final.output != null) {
|
|
186
|
+
console.log('');
|
|
187
|
+
console.log(typeof final.output === 'string' ? ` ${final.output}` : JSON.stringify(final.output, null, 2));
|
|
188
|
+
}
|
|
189
|
+
return;
|
|
190
|
+
}
|
|
191
|
+
console.log('');
|
|
192
|
+
console.log(` Run ${final.status}${final.error ? `: ${final.error}` : ''}`);
|
|
193
|
+
}
|
|
194
|
+
async function handleWorkflowTurn(state, message) {
|
|
195
|
+
const result = await state.client.invokeAgent(state.agent.id, { message });
|
|
196
|
+
const output = result?.output ?? result?.data?.output ?? result;
|
|
197
|
+
if (output == null) {
|
|
198
|
+
console.log(' (no output)');
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
console.log('');
|
|
202
|
+
if (typeof output === 'string') {
|
|
203
|
+
console.log(` ${output}`);
|
|
204
|
+
}
|
|
205
|
+
else {
|
|
206
|
+
console.log(JSON.stringify(output, null, 2).split('\n').map((l) => ' ' + l).join('\n'));
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
async function handleUserTurn(state, message) {
|
|
210
|
+
if (state.agent.mode === 'autonomous') {
|
|
211
|
+
return handleAutonomousTurn(state, message);
|
|
212
|
+
}
|
|
213
|
+
return handleWorkflowTurn(state, message);
|
|
214
|
+
}
|
|
215
|
+
async function startChat(client, agent) {
|
|
216
|
+
const state = { client, agent, pendingRunId: null };
|
|
217
|
+
printAgentBanner(state.agent);
|
|
218
|
+
const rl = createInterface({
|
|
219
|
+
input: process.stdin,
|
|
220
|
+
output: process.stdout,
|
|
221
|
+
prompt: '> ',
|
|
222
|
+
});
|
|
223
|
+
rl.prompt();
|
|
224
|
+
rl.on('line', async (raw) => {
|
|
225
|
+
const line = raw.trim();
|
|
226
|
+
if (!line) {
|
|
227
|
+
rl.prompt();
|
|
228
|
+
return;
|
|
229
|
+
}
|
|
230
|
+
if (line.startsWith('/')) {
|
|
231
|
+
try {
|
|
232
|
+
const { exit } = await handleSlashCommand(line, state);
|
|
233
|
+
if (exit) {
|
|
234
|
+
rl.close();
|
|
235
|
+
return;
|
|
236
|
+
}
|
|
237
|
+
}
|
|
238
|
+
catch (err) {
|
|
239
|
+
console.error(` Error: ${err.message}`);
|
|
240
|
+
}
|
|
241
|
+
rl.prompt();
|
|
242
|
+
return;
|
|
243
|
+
}
|
|
244
|
+
try {
|
|
245
|
+
await handleUserTurn(state, line);
|
|
246
|
+
}
|
|
247
|
+
catch (err) {
|
|
248
|
+
console.error(` Error: ${err.message}`);
|
|
249
|
+
}
|
|
250
|
+
console.log('');
|
|
251
|
+
rl.prompt();
|
|
252
|
+
});
|
|
253
|
+
rl.on('close', () => {
|
|
254
|
+
console.log('');
|
|
255
|
+
console.log('Chat ended.');
|
|
256
|
+
process.exit(0);
|
|
257
|
+
});
|
|
258
|
+
}
|
|
259
|
+
async function main() {
|
|
260
|
+
const argv = process.argv.slice(2);
|
|
261
|
+
if (argv.includes('--version') || argv.includes('-v')) {
|
|
262
|
+
console.log(VERSION);
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
if (argv.includes('--help') || argv.includes('-h')) {
|
|
266
|
+
printHelp();
|
|
267
|
+
return;
|
|
268
|
+
}
|
|
269
|
+
const creds = resolveCredentialsOrExit();
|
|
270
|
+
const client = new AlmytyClient(creds.url, creds.token);
|
|
271
|
+
let agent = null;
|
|
272
|
+
const ref = argv.find((a) => !a.startsWith('-'));
|
|
273
|
+
if (ref) {
|
|
274
|
+
agent = await client.findAgentByNameOrId(ref);
|
|
275
|
+
if (!agent) {
|
|
276
|
+
console.error(`Agent not found: ${ref}`);
|
|
277
|
+
process.exit(1);
|
|
278
|
+
}
|
|
279
|
+
}
|
|
280
|
+
else {
|
|
281
|
+
agent = await pickAgent(client);
|
|
282
|
+
if (!agent)
|
|
283
|
+
process.exit(1);
|
|
284
|
+
}
|
|
285
|
+
await startChat(client, agent);
|
|
286
|
+
}
|
|
287
|
+
main().catch((err) => {
|
|
288
|
+
console.error(`Error: ${err.message}`);
|
|
289
|
+
process.exit(1);
|
|
290
|
+
});
|
package/package.json
ADDED
|
@@ -0,0 +1,35 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@almyty/chat",
|
|
3
|
+
"version": "0.1.0",
|
|
4
|
+
"description": "Interactive chat REPL with almyty agents",
|
|
5
|
+
"type": "module",
|
|
6
|
+
"main": "dist/index.js",
|
|
7
|
+
"bin": {
|
|
8
|
+
"almyty-chat": "dist/index.js"
|
|
9
|
+
},
|
|
10
|
+
"files": [
|
|
11
|
+
"dist"
|
|
12
|
+
],
|
|
13
|
+
"scripts": {
|
|
14
|
+
"build": "tsc",
|
|
15
|
+
"dev": "tsx src/index.ts",
|
|
16
|
+
"test": "vitest run",
|
|
17
|
+
"test:watch": "vitest",
|
|
18
|
+
"prepublishOnly": "npm run build"
|
|
19
|
+
},
|
|
20
|
+
"keywords": [
|
|
21
|
+
"almyty",
|
|
22
|
+
"agents",
|
|
23
|
+
"chat",
|
|
24
|
+
"repl",
|
|
25
|
+
"cli"
|
|
26
|
+
],
|
|
27
|
+
"author": "almyty",
|
|
28
|
+
"license": "BSL-1.1",
|
|
29
|
+
"devDependencies": {
|
|
30
|
+
"@types/node": "^25.4.0",
|
|
31
|
+
"tsx": "^4.7.0",
|
|
32
|
+
"typescript": "^5.3.0",
|
|
33
|
+
"vitest": "^4.1.0"
|
|
34
|
+
}
|
|
35
|
+
}
|