@orxataguy/tyr 1.0.30 → 1.0.32

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
@@ -173,6 +173,35 @@ tyr doc
173
173
  # → Open http://localhost:3000
174
174
  ```
175
175
 
176
+ ### `tyr chat [directory] [--port <n>] [--split <0-1>]`
177
+
178
+ Opens a local web app with a chat pane and a file browser for `directory` (defaults to the current directory). You can attach images to a message, drag the divider between the two panes to resize them, and click any file in the browser to preview it. Messages are answered by the configured AI vendor (see `AIVendorManager`, below) by default.
179
+
180
+ ```bash
181
+ tyr chat ./my-project --split 0.35
182
+ # → Chat ready at: http://localhost:4646
183
+ ```
184
+
185
+ Under the hood this is powered by `ChatManager`, injected into every command as `chat`. Write your own command to customise it:
186
+
187
+ ```typescript
188
+ export default ({ chat, aiVendor, logger }: TyrContext) => {
189
+ return async (args: string[]) => {
190
+ chat.onMessage(async ({ message, history, dir }) => {
191
+ // Decide how to answer — call AIVendorManager, a local model, a script, etc.
192
+ const result = await aiVendor.complete([{ role: 'user', content: message.text }]);
193
+ return result.content;
194
+ });
195
+
196
+ chat.on('message:send', ({ message }) => logger.info(`User: ${message.text}`));
197
+ chat.on('message:response', ({ message }) => logger.info(`Assistant: ${message.text}`));
198
+
199
+ const session = await chat.open(args[0] ?? process.cwd(), { splitRatio: 0.4 });
200
+ logger.success(`Chat ready at: ${session.url}`);
201
+ };
202
+ };
203
+ ```
204
+
176
205
  ---
177
206
 
178
207
  ## Context API Reference
@@ -252,6 +281,8 @@ tyr deploy --debug
252
281
  │ │ ├── Kernel.ts # Command router and execution engine
253
282
  │ │ ├── Container.ts # Dependency injection container
254
283
  │ │ ├── TyrError.ts # Structured error type
284
+ │ │ ├── util
285
+ │ │ │ └── getenv.ts # Helper: get environment variables
255
286
  │ │ └── sys/
256
287
  │ │ ├── gen.ts # Built-in: scaffold a command
257
288
  │ │ ├── rem.ts # Built-in: remove a command
@@ -259,16 +290,24 @@ tyr deploy --debug
259
290
  │ ├── commands/
260
291
  │ │ └── *.tyr.ts # Your custom commands go here
261
292
  │ └── lib/
262
- ├── ShellManager.ts
263
- ├── FileSystemManager.ts
264
- ├── PackageManager.ts
265
- ├── DockerManager.ts
266
- ├── GitManager.ts
267
- ├── SystemManager.ts
268
- ├── SQLManager.ts
269
- └── WebManager.ts
270
- ├── config/
271
- └── map.yml # Command registry
293
+ ├── AIContextManager.ts
294
+ ├── AIVendorManager.ts
295
+ ├── ChatManager.ts
296
+ ├── DockerManager.ts
297
+ ├── FileSystemManager.ts
298
+ ├── GitManager.ts
299
+ ├── JiraManager.ts
300
+ │ ├── MongoManager.ts
301
+ │ │ ├── PackageManager.ts
302
+ │ ├── PromptTemplateManager.ts
303
+ │ │ ├── SetupManager.ts
304
+ │ │ ├── ShellManager.ts
305
+ │ │ ├── SQLManager.ts
306
+ │ │ ├── SystemManager.ts
307
+ │ │ ├── TokenManager.ts
308
+ │ │ ├── WebManager.ts
309
+ │ │ └── WorkspaceManager.ts
310
+ | └── index.ts # Exports context
272
311
  └── tests/
273
312
  ├── setup.ts # Mock context factory
274
313
  └── test-runner.ts # Smoke test runner
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@orxataguy/tyr",
3
- "version": "1.0.30",
3
+ "version": "1.0.32",
4
4
  "type": "module",
5
5
  "bin": {
6
6
  "tyr": "./bin/tyr.js"
@@ -14,6 +14,7 @@ import { AIVendorManager } from '../lib/AIVendorManager.js';
14
14
  import { AIContextManager } from '../lib/AIContextManager.js';
15
15
  import { PromptTemplateManager } from '../lib/PromptTemplateManager.js';
16
16
  import { TokenManager } from '../lib/TokenManager.js';
17
+ import { ChatManager } from '../lib/ChatManager.js';
17
18
  import { Logger, createLogger } from './Logger.js';
18
19
 
19
20
  import path from 'path';
@@ -39,6 +40,7 @@ export interface ServiceContainer {
39
40
  aiContext: AIContextManager;
40
41
  prompts: PromptTemplateManager;
41
42
  tokens: TokenManager;
43
+ chat: ChatManager;
42
44
  }
43
45
 
44
46
  export class Container {
@@ -56,7 +58,7 @@ export class Container {
56
58
  const web = new WebManager(logger);
57
59
  const fs = new FileSystemManager(logger);
58
60
  const aiVendor = new AIVendorManager(logger);
59
- const aiContext = new AIContextManager(fs, aiVendor, logger);
61
+ const aiContext = new AIContextManager(fs, shell, aiVendor, logger);
60
62
 
61
63
  this.services = {
62
64
  logger,
@@ -77,6 +79,7 @@ export class Container {
77
79
  aiContext,
78
80
  prompts: new PromptTemplateManager(aiContext, logger),
79
81
  tokens: new TokenManager(logger),
82
+ chat: new ChatManager(fs, logger),
80
83
  };
81
84
  }
82
85
 
@@ -9,6 +9,7 @@ import { Container } from './Container';
9
9
  import gen from './sys/gen';
10
10
  import rem from './sys/rem';
11
11
  import doc from './sys/doc';
12
+ import chat from './sys/chat';
12
13
  import config from './sys/config';
13
14
  import help from './sys/help';
14
15
 
@@ -166,6 +167,7 @@ export class Kernel {
166
167
  gen,
167
168
  rem,
168
169
  doc,
170
+ chat,
169
171
  };
170
172
 
171
173
  if (systemCommands[commandName]) {
@@ -0,0 +1,73 @@
1
+ import path from 'path';
2
+ import { readFile } from 'fs/promises';
3
+ import { TyrContext } from '../Kernel';
4
+ import type { AIContentBlock, AIMessage } from '../../lib/AIVendorManager';
5
+ import type { ChatMessageContext } from '../../lib/ChatManager';
6
+
7
+ function parseFlag(args: string[], name: string): string | undefined {
8
+ const index = args.indexOf(name);
9
+ return index !== -1 ? args[index + 1] : undefined;
10
+ }
11
+
12
+ export default function chat({ logger, chat: chatManager, aiVendor, fail }: TyrContext) {
13
+ return async (args: string[]) => {
14
+ const positional = args.filter((a) => !a.startsWith('--'));
15
+ const dir = path.resolve(positional[0] ?? process.cwd());
16
+
17
+ const portArg = parseFlag(args, '--port');
18
+ const splitArg = parseFlag(args, '--split');
19
+ const port = portArg ? parseInt(portArg, 10) : undefined;
20
+ const splitRatio = splitArg ? parseFloat(splitArg) : undefined;
21
+
22
+ // Default responder: forwards the conversation (plus any attached images) to the
23
+ // configured AI vendor. Replace with your own chatManager.onMessage(...) in a custom
24
+ // command if you want different behaviour.
25
+ chatManager.onMessage(async ({ message, history, dir: chatDir }: ChatMessageContext) => {
26
+ const priorTurns: AIMessage[] = history.slice(0, -1).map((m) => ({
27
+ role: m.role === 'user' ? 'user' : 'assistant',
28
+ content: m.text,
29
+ }));
30
+
31
+ const contentBlocks: AIContentBlock[] = [];
32
+ if (message.text) contentBlocks.push({ type: 'text', text: message.text });
33
+
34
+ for (const attachment of message.attachments) {
35
+ try {
36
+ const fileBuffer = await readFile(attachment.path);
37
+ contentBlocks.push({ type: 'image', mediaType: attachment.mimeType, data: fileBuffer.toString('base64') });
38
+ } catch (e) {
39
+ logger.warn(`Could not read attachment '${attachment.filename}': ${(e as Error).message}`);
40
+ }
41
+ }
42
+
43
+ const messages: AIMessage[] = [
44
+ {
45
+ role: 'system',
46
+ content: `You are an assistant embedded in a chat UI browsing the directory: ${chatDir}. Answer helpfully and concisely, referencing its files when relevant.`,
47
+ },
48
+ ...priorTurns,
49
+ { role: 'user', content: contentBlocks.length > 0 ? contentBlocks : message.text },
50
+ ];
51
+
52
+ const result = await aiVendor.complete(messages);
53
+ return result.content;
54
+ });
55
+
56
+ // Example hooks — side effects around the conversation, independent from onMessage.
57
+ chatManager.on('message:send', ({ message }: { message: { text: string } }) => {
58
+ logger.info(`[chat] user: ${message.text}`);
59
+ });
60
+ chatManager.on('message:error', ({ error }: { error: Error }) => {
61
+ logger.warn(`[chat] handler failed: ${error.message}`);
62
+ });
63
+
64
+ try {
65
+ const session = await chatManager.open(dir, { port, splitRatio });
66
+ logger.success(`Chat ready at: ${session.url}`);
67
+ logger.info(`Browsing: ${session.dir}`);
68
+ logger.info('Press Ctrl+C to stop.');
69
+ } catch (e: any) {
70
+ fail(`Could not start chat: ${e.message}`, 'Check that the directory exists and the port is free.');
71
+ }
72
+ };
73
+ }
@@ -76,6 +76,7 @@ export default function help({ userRoot }: TyrContext) {
76
76
  { name: '--upgrade', description: 'Upgrades the tyr npm package.', usage: 'tyr --upgrade' },
77
77
  { name: 'gen', description: 'Generates a new command from a description using AI.', usage: 'tyr gen <name> "<description>"' },
78
78
  { name: 'doc', description: 'Opens the framework documentation in the browser.', usage: 'tyr doc' },
79
+ { name: 'chat', description: 'Opens an AI chat + file browser for a directory.', usage: 'tyr chat [directory] [--port <n>] [--split <0-1>]' },
79
80
  ];
80
81
 
81
82
  console.log(` ${bold}${yellow}Framework${reset}`);
package/src/index.ts CHANGED
@@ -3,3 +3,4 @@
3
3
  // import type { TyrContext } from '@orxataguy/tyr';
4
4
 
5
5
  export type { TyrContext } from './core/Kernel.js';
6
+ export type { TaskPriority } from './lib/AIVendorManager.js';