@aitherium/shell-cli 1.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.
@@ -0,0 +1,12 @@
1
+ /**
2
+ * Product scaffolding and deployment CLI commands.
3
+ *
4
+ * Commands:
5
+ * aither products init --name=MyProduct --port=8902 --category=business_agent
6
+ * aither products deploy --name=myproduct --subdomain=myproduct
7
+ * aither products list
8
+ */
9
+ import type { CommandHandler } from './commands.js';
10
+ export declare const productsInit: CommandHandler;
11
+ export declare const productsDeploy: CommandHandler;
12
+ export declare const productsList: CommandHandler;
@@ -0,0 +1,159 @@
1
+ /**
2
+ * Product scaffolding and deployment CLI commands.
3
+ *
4
+ * Commands:
5
+ * aither products init --name=MyProduct --port=8902 --category=business_agent
6
+ * aither products deploy --name=myproduct --subdomain=myproduct
7
+ * aither products list
8
+ */
9
+ import { execSync } from 'node:child_process';
10
+ import { existsSync, readFileSync } from 'node:fs';
11
+ import { resolve } from 'node:path';
12
+ import chalk from 'chalk';
13
+ import ora from 'ora';
14
+ // Parse --key=value flags from args string
15
+ function parseFlags(args) {
16
+ const flags = {};
17
+ const matches = args.matchAll(/--(\w[\w-]*)=([^\s]+)/g);
18
+ for (const m of matches) {
19
+ flags[m[1]] = m[2];
20
+ }
21
+ // Also handle --flag value pattern
22
+ const parts = args.split(/\s+/);
23
+ for (let i = 0; i < parts.length; i++) {
24
+ if (parts[i].startsWith('--') && !parts[i].includes('=') && i + 1 < parts.length && !parts[i + 1].startsWith('--')) {
25
+ flags[parts[i].slice(2)] = parts[i + 1];
26
+ }
27
+ }
28
+ return flags;
29
+ }
30
+ function findAitherOSRoot() {
31
+ // Walk up from this file to find AitherOS root
32
+ let dir = resolve('.');
33
+ for (let i = 0; i < 10; i++) {
34
+ if (existsSync(resolve(dir, 'AitherOS', 'config', 'services.yaml')))
35
+ return dir;
36
+ if (existsSync(resolve(dir, 'config', 'services.yaml')))
37
+ return resolve(dir, '..');
38
+ dir = resolve(dir, '..');
39
+ }
40
+ return resolve('.');
41
+ }
42
+ export const productsInit = async (_client, args, _config) => {
43
+ const flags = parseFlags(args);
44
+ const name = flags['name'];
45
+ const port = flags['port'] || '8900';
46
+ const category = flags['category'] || 'business_agent';
47
+ const description = flags['description'] || '';
48
+ if (!name) {
49
+ console.log(chalk.red('Usage: aither products init --name=MyProduct --port=8902 --category=business_agent'));
50
+ console.log(chalk.dim('Categories: business_agent, knowledge_agent, creative_agent'));
51
+ return;
52
+ }
53
+ const spinner = ora(`Scaffolding product "${name}"...`).start();
54
+ try {
55
+ const root = findAitherOSRoot();
56
+ const pythonCmd = process.platform === 'win32' ? 'python' : 'python3';
57
+ const script = `
58
+ import sys
59
+ sys.path.insert(0, "${resolve(root, 'AitherOS').replace(/\\/g, '/')}")
60
+ from lib.products.scaffold import scaffold_product
61
+ result = scaffold_product(
62
+ name="${name}",
63
+ category="${category}",
64
+ port=${port},
65
+ description="${description}",
66
+ )
67
+ print(str(result))
68
+ `;
69
+ const result = execSync(`${pythonCmd} -c "${script.replace(/"/g, '\\"').replace(/\n/g, ';')}"`, {
70
+ cwd: root,
71
+ encoding: 'utf-8',
72
+ timeout: 30000,
73
+ }).trim();
74
+ spinner.succeed(chalk.green(`Product scaffolded at: ${result}`));
75
+ console.log(chalk.dim(`\nNext steps:`));
76
+ console.log(chalk.dim(` 1. cd ${result}`));
77
+ console.log(chalk.dim(` 2. Edit .env with your config`));
78
+ console.log(chalk.dim(` 3. docker compose -f docker-compose.yml -f docker-compose.aitheros.yml up -d --build`));
79
+ console.log(chalk.dim(` 4. aither products deploy --name=${name.toLowerCase()} --subdomain=${name.toLowerCase()}`));
80
+ }
81
+ catch (err) {
82
+ spinner.fail(chalk.red(`Scaffold failed: ${err.message}`));
83
+ }
84
+ };
85
+ export const productsDeploy = async (client, args, _config) => {
86
+ const flags = parseFlags(args);
87
+ const name = flags['name'];
88
+ const subdomain = flags['subdomain'] || name;
89
+ if (!name) {
90
+ console.log(chalk.red('Usage: aither products deploy --name=myproduct --subdomain=myproduct'));
91
+ return;
92
+ }
93
+ const spinner = ora(`Deploying ${name} to ${subdomain}.aitherium.com...`).start();
94
+ try {
95
+ // Call Genesis product deploy endpoint
96
+ const resp = await client.post('/products/deploy', {
97
+ product_name: name,
98
+ subdomain: subdomain,
99
+ });
100
+ if (resp.status === 'ok' || resp.deployed) {
101
+ spinner.succeed(chalk.green(`Deployed ${name} at https://${subdomain}.aitherium.com`));
102
+ }
103
+ else {
104
+ spinner.warn(chalk.yellow(`Deploy returned: ${JSON.stringify(resp)}`));
105
+ }
106
+ }
107
+ catch (err) {
108
+ spinner.fail(chalk.red(`Deploy failed: ${err.message}`));
109
+ console.log(chalk.dim('Ensure Genesis is running and the product containers are up.'));
110
+ }
111
+ };
112
+ export const productsList = async (_client, _args, _config) => {
113
+ const root = findAitherOSRoot();
114
+ const templatesPath = resolve(root, 'AitherOS', 'config', 'product_templates.yaml');
115
+ if (!existsSync(templatesPath)) {
116
+ console.log(chalk.dim('No products registered yet. Use `aither products init` to create one.'));
117
+ return;
118
+ }
119
+ try {
120
+ const content = readFileSync(templatesPath, 'utf-8');
121
+ // Simple YAML parsing for the products list
122
+ const lines = content.split('\n');
123
+ const products = [];
124
+ let current = null;
125
+ for (const line of lines) {
126
+ if (line.match(/^\s*- name:/)) {
127
+ if (current)
128
+ products.push(current);
129
+ current = { name: line.split(':')[1]?.trim() || '', slug: '', port: '', category: '' };
130
+ }
131
+ else if (current && line.match(/^\s+slug:/)) {
132
+ current.slug = line.split(':')[1]?.trim() || '';
133
+ }
134
+ else if (current && line.match(/^\s+port:/)) {
135
+ current.port = line.split(':')[1]?.trim() || '';
136
+ }
137
+ else if (current && line.match(/^\s+category:/)) {
138
+ current.category = line.split(':')[1]?.trim() || '';
139
+ }
140
+ }
141
+ if (current)
142
+ products.push(current);
143
+ if (products.length === 0) {
144
+ console.log(chalk.dim('No products registered.'));
145
+ return;
146
+ }
147
+ console.log(chalk.bold('\nRegistered Products:\n'));
148
+ console.log(chalk.dim(' Name'.padEnd(20) + 'Port'.padEnd(8) + 'Category'.padEnd(20) + 'URL'));
149
+ console.log(chalk.dim(' ' + '-'.repeat(70)));
150
+ for (const p of products) {
151
+ const url = `https://${p.slug}.aitherium.com`;
152
+ console.log(` ${chalk.cyan(p.name.padEnd(18))}${p.port.padEnd(8)}${chalk.dim(p.category.padEnd(20))}${chalk.underline(url)}`);
153
+ }
154
+ console.log('');
155
+ }
156
+ catch (err) {
157
+ console.log(chalk.red(`Failed to read product templates: ${err.message}`));
158
+ }
159
+ };
@@ -0,0 +1,104 @@
1
+ /**
2
+ * Terminal renderer — banners, streaming tokens, markdown, tables.
3
+ */
4
+ import type { SSEEvent } from './client.js';
5
+ export declare function renderBanner(info: {
6
+ genesis: string;
7
+ genesisOnline?: boolean;
8
+ services?: number;
9
+ agents?: number;
10
+ llm?: string;
11
+ user?: string;
12
+ serviceLines?: {
13
+ name: string;
14
+ up: boolean;
15
+ }[];
16
+ backendType?: string;
17
+ backendName?: string;
18
+ }): void;
19
+ /**
20
+ * Resolve `/api/files?path=...` image references to real file paths
21
+ * and render as clickable OSC 8 terminal hyperlinks.
22
+ */
23
+ export declare function resolveImagePath(relativePath: string): string;
24
+ export declare function renderMarkdown(text: string): string;
25
+ export interface StreamRenderer {
26
+ onEvent(event: SSEEvent): void;
27
+ getContent(): string;
28
+ finish(): void;
29
+ /** Full trace of all events received during this session prompt. */
30
+ getTrace(): SSEEvent[];
31
+ /** Session profile for persistence and future reference. */
32
+ getSessionProfile(): SessionProfile;
33
+ }
34
+ export interface SessionProfile {
35
+ session_id: string;
36
+ prompt: string;
37
+ started_at: string;
38
+ duration_ms: number;
39
+ event_count: number;
40
+ model: string;
41
+ agent: string;
42
+ events: SSEEvent[];
43
+ tool_calls: Array<{
44
+ name: string;
45
+ args: any;
46
+ timestamp: number;
47
+ }>;
48
+ thinking_traces: string[];
49
+ context_sources: Record<string, any>;
50
+ errors: string[];
51
+ }
52
+ export interface SessionArtifact {
53
+ filename: string;
54
+ path: string;
55
+ size: number;
56
+ language: string;
57
+ retrieve_cmd: string;
58
+ timestamp: string;
59
+ }
60
+ export declare function getSessionArtifacts(): SessionArtifact[];
61
+ export declare function clearSessionArtifacts(): void;
62
+ export declare function createStreamRenderer(sessionId?: string, prompt?: string, steeringBar?: SteeringBar): StreamRenderer;
63
+ export declare function formatTable(headers: string[], rows: string[][]): string;
64
+ /**
65
+ * Keeps a visible input line at the bottom of the terminal while
66
+ * stream output prints above. Works on Windows Terminal (no ANSI
67
+ * scroll regions).
68
+ *
69
+ * How it works:
70
+ * 1. Intercepts process.stdout.write
71
+ * 2. Before each write: erase the bar line so output doesn't collide
72
+ * 3. After each write: redraw the bar on a fresh line below output
73
+ * 4. User keystrokes update the bar's displayed text in real time
74
+ * 5. On Enter, the bar clears and the repl routes to /chat/steer
75
+ *
76
+ * The bar line looks like:
77
+ * ── [steer] > what the user is typing_
78
+ */
79
+ export declare class SteeringBar {
80
+ private _active;
81
+ private _inputText;
82
+ private _statusText;
83
+ private _origWrite;
84
+ private _barDrawn;
85
+ private _debounceTimer;
86
+ private _spinFrame;
87
+ private _spinTimer;
88
+ private static readonly _SPIN_CHARS;
89
+ get active(): boolean;
90
+ get inputText(): string;
91
+ activate(): void;
92
+ deactivate(): void;
93
+ /** Schedule a single debounced redraw. All state changes route here. */
94
+ private _scheduleRedraw;
95
+ /** Show spinner + status text in the bar (replaces ora when bar is active). */
96
+ setStatus(text: string): void;
97
+ /** Clear status text. */
98
+ clearStatus(): void;
99
+ /** Update displayed input text (called from readline _ttyWrite hook). */
100
+ setInput(text: string): void;
101
+ /** Clear input after steer send. */
102
+ clearInput(): void;
103
+ private _drawBar;
104
+ }