@arcforge/axon 0.7.2
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/LICENSE +26 -0
- package/README.md +52 -0
- package/app/index.ts +204 -0
- package/dist/axon.js +424 -0
- package/dist/cpufeatures-tnar8e9z.node +0 -0
- package/dist/sshcrypto-j88rng6w.node +0 -0
- package/package.json +50 -0
package/LICENSE
ADDED
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
Copyright (c) 2025 ArcForge
|
|
2
|
+
|
|
3
|
+
All rights reserved.
|
|
4
|
+
|
|
5
|
+
This software and associated documentation files (the "Software") are proprietary
|
|
6
|
+
and confidential to ArcForge.
|
|
7
|
+
|
|
8
|
+
The Software is licensed, not sold. This license grants you the following rights:
|
|
9
|
+
|
|
10
|
+
1. You may install and use the Software solely for your internal business purposes.
|
|
11
|
+
|
|
12
|
+
2. You may not:
|
|
13
|
+
- Modify, adapt, translate, or create derivative works of the Software
|
|
14
|
+
- Reverse engineer, decompile, or disassemble the Software
|
|
15
|
+
- Remove or alter any proprietary notices or labels on the Software
|
|
16
|
+
- Distribute, sublicense, lease, rent, loan, or transfer the Software to any third party
|
|
17
|
+
- Use the Software for any purpose other than as expressly permitted herein
|
|
18
|
+
|
|
19
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
20
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
21
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL
|
|
22
|
+
ARCFORGE BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
|
|
23
|
+
ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
|
|
24
|
+
WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
|
|
25
|
+
|
|
26
|
+
For licensing inquiries, please contact: legal@arcforge.io
|
package/README.md
ADDED
|
@@ -0,0 +1,52 @@
|
|
|
1
|
+
# Axon
|
|
2
|
+
|
|
3
|
+
> High-performance CLI coding agent with Blessed terminal UI
|
|
4
|
+
|
|
5
|
+
Axon is a powerful terminal-based coding agent that combines LLM capabilities with a beautiful text-based user interface.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
npm install -g @arcforge/axon
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
**Requirements:**
|
|
14
|
+
- [Bun](https://bun.sh) runtime installed
|
|
15
|
+
|
|
16
|
+
## Quick Start
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
axon
|
|
20
|
+
```
|
|
21
|
+
|
|
22
|
+
This launches the interactive terminal UI where you can:
|
|
23
|
+
- Chat with the AI coding agent
|
|
24
|
+
- Manage multiple processes
|
|
25
|
+
- View and manage tasks in a Kanban board
|
|
26
|
+
- Customize themes and audio visualizations
|
|
27
|
+
|
|
28
|
+
## Features
|
|
29
|
+
|
|
30
|
+
- **🎨 Beautiful Terminal UI** - Built with Blessed for a responsive TUI experience
|
|
31
|
+
- **🤖 AI-Powered** - Integrated LLM agent for coding assistance
|
|
32
|
+
- **📋 Process Management** - Monitor and control running processes
|
|
33
|
+
- **🎯 Task Management** - Built-in Kanban board for organizing work
|
|
34
|
+
- **🎵 Audio Visualizer** - Real-time audio visualization in your terminal
|
|
35
|
+
- **🔐 Device Authentication** - Secure authentication flow
|
|
36
|
+
- **⚡ High Performance** - Built with Bun for speed
|
|
37
|
+
|
|
38
|
+
## Usage
|
|
39
|
+
|
|
40
|
+
Once launched, use keyboard shortcuts to navigate:
|
|
41
|
+
- Arrow keys to navigate menus
|
|
42
|
+
- Enter to select
|
|
43
|
+
- Esc to go back
|
|
44
|
+
- Tab to switch between panels
|
|
45
|
+
|
|
46
|
+
Visit the in-app profile page for more keyboard shortcuts and settings.
|
|
47
|
+
|
|
48
|
+
## License
|
|
49
|
+
|
|
50
|
+
Copyright © 2025 ArcForge. All rights reserved.
|
|
51
|
+
|
|
52
|
+
This is proprietary software. Unauthorized copying, modification, distribution, or use of this software is strictly prohibited.
|
package/app/index.ts
ADDED
|
@@ -0,0 +1,204 @@
|
|
|
1
|
+
import blessed from "blessed"
|
|
2
|
+
|
|
3
|
+
import {
|
|
4
|
+
useAudioVisualizer,
|
|
5
|
+
useScreen,
|
|
6
|
+
useCommands,
|
|
7
|
+
useCognos,
|
|
8
|
+
useStack,
|
|
9
|
+
useRouter,
|
|
10
|
+
useDeviceAuth,
|
|
11
|
+
} from "#composables"
|
|
12
|
+
|
|
13
|
+
import {
|
|
14
|
+
ChatPage,
|
|
15
|
+
KanbanPage,
|
|
16
|
+
LoginPage,
|
|
17
|
+
ProcessesPage,
|
|
18
|
+
ProcessDetailPage,
|
|
19
|
+
ProfilePage,
|
|
20
|
+
ThemePage,
|
|
21
|
+
} from "#pages"
|
|
22
|
+
import { builtinCommands, processCommands, kanbanCommands, parseArgs, printHelp, executePrompt, redirectConsoleToFile, restoreConsole, getLogFilePath } from "#lib"
|
|
23
|
+
import { Footer } from "#components"
|
|
24
|
+
|
|
25
|
+
|
|
26
|
+
/**
|
|
27
|
+
* Interactive mode: Full blessed TUI
|
|
28
|
+
*
|
|
29
|
+
* The screen consists of two regions:
|
|
30
|
+
*
|
|
31
|
+
* - The current page (chat, login, etc.), containing page-specific content.
|
|
32
|
+
* - The footer, containing chat input and menus.
|
|
33
|
+
*
|
|
34
|
+
*/
|
|
35
|
+
async function mainInteractive(debug: boolean = false) {
|
|
36
|
+
// Redirect console output to prevent TUI corruption (unless debug mode)
|
|
37
|
+
if (!debug) {
|
|
38
|
+
redirectConsoleToFile()
|
|
39
|
+
}
|
|
40
|
+
|
|
41
|
+
const screen = useScreen()
|
|
42
|
+
const commands = useCommands()
|
|
43
|
+
const stackComposable = useStack()
|
|
44
|
+
const cognos = useCognos()
|
|
45
|
+
const router = useRouter()
|
|
46
|
+
const deviceAuth = useDeviceAuth()
|
|
47
|
+
|
|
48
|
+
// Register builtin commands
|
|
49
|
+
builtinCommands.forEach(cmd => commands.register(cmd))
|
|
50
|
+
|
|
51
|
+
// Register process management commands
|
|
52
|
+
processCommands.forEach(cmd => commands.register(cmd))
|
|
53
|
+
|
|
54
|
+
// Register kanban commands
|
|
55
|
+
kanbanCommands.forEach(cmd => commands.register(cmd))
|
|
56
|
+
|
|
57
|
+
|
|
58
|
+
// Navigate to chat immediately if at root
|
|
59
|
+
if (router.current().path === "/") {
|
|
60
|
+
router.navigate("/chat")
|
|
61
|
+
}
|
|
62
|
+
|
|
63
|
+
// Start auth check in background (non-blocking)
|
|
64
|
+
// The result will be awaited later when the user sends their first message
|
|
65
|
+
deviceAuth.checkAuth().then(isAuthenticated => {
|
|
66
|
+
if (isAuthenticated) {
|
|
67
|
+
// Only connect to websocket after successful auth
|
|
68
|
+
cognos.connect().catch(error => {
|
|
69
|
+
console.error("Failed to connect to websocket:", error)
|
|
70
|
+
})
|
|
71
|
+
} else if (router.current().path !== "/login") {
|
|
72
|
+
// Redirect to login if auth fails
|
|
73
|
+
router.navigate("/login")
|
|
74
|
+
}
|
|
75
|
+
})
|
|
76
|
+
|
|
77
|
+
const container = blessed.box({
|
|
78
|
+
width: "100%",
|
|
79
|
+
height: "100%",
|
|
80
|
+
layout: "vertical",
|
|
81
|
+
})
|
|
82
|
+
|
|
83
|
+
const footer = Footer()
|
|
84
|
+
|
|
85
|
+
// Create page mapping with route patterns
|
|
86
|
+
const routes = [
|
|
87
|
+
{ pattern: "/login", component: () => LoginPage() },
|
|
88
|
+
{ pattern: "/chat", component: () => ChatPage() },
|
|
89
|
+
{ pattern: "/kanban", component: () => KanbanPage() },
|
|
90
|
+
{ pattern: "/processes", component: () => ProcessesPage() },
|
|
91
|
+
{ pattern: "/processes/:refId", component: () => ProcessDetailPage() },
|
|
92
|
+
{ pattern: "/profile", component: () => ProfilePage() },
|
|
93
|
+
{ pattern: "/theme", component: () => ThemePage() },
|
|
94
|
+
]
|
|
95
|
+
|
|
96
|
+
// Helper to find matching route
|
|
97
|
+
function findMatchingRoute(path: string) {
|
|
98
|
+
for (const route of routes) {
|
|
99
|
+
const match = router.match(route.pattern)
|
|
100
|
+
if (match) {
|
|
101
|
+
return route
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
// Default to login if no match
|
|
105
|
+
return routes[0]
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
// Initialize with the current page
|
|
109
|
+
const initialRoute = findMatchingRoute(router.current().path)
|
|
110
|
+
let currentPageNode = initialRoute.component().node
|
|
111
|
+
|
|
112
|
+
container.append(footer.node)
|
|
113
|
+
container.append(currentPageNode)
|
|
114
|
+
|
|
115
|
+
screen.append(container)
|
|
116
|
+
screen.append(footer.paletteNode) // Append command palette to screen so it floats above footer
|
|
117
|
+
screen.append(footer.pagePaletteNode) // Append page palette to screen so it floats above footer
|
|
118
|
+
screen.render()
|
|
119
|
+
|
|
120
|
+
// Focus the input initially
|
|
121
|
+
footer.focus()
|
|
122
|
+
|
|
123
|
+
// Handle page navigation
|
|
124
|
+
router.onNavigate((route) => {
|
|
125
|
+
// Remove current page
|
|
126
|
+
container.remove(currentPageNode)
|
|
127
|
+
|
|
128
|
+
// Find matching route and create page
|
|
129
|
+
const matchedRoute = findMatchingRoute(route.path)
|
|
130
|
+
currentPageNode = matchedRoute.component().node
|
|
131
|
+
container.append(currentPageNode)
|
|
132
|
+
|
|
133
|
+
// Keep footer on top
|
|
134
|
+
container.remove(footer.node)
|
|
135
|
+
container.append(footer.node)
|
|
136
|
+
|
|
137
|
+
screen.render()
|
|
138
|
+
})
|
|
139
|
+
|
|
140
|
+
// Initialize audio visualizer (will activate in voice mode)
|
|
141
|
+
const audioVisualizer = useAudioVisualizer()
|
|
142
|
+
audioVisualizer.init()
|
|
143
|
+
|
|
144
|
+
// Show Axon logo and stats on boot
|
|
145
|
+
const axonCmd = builtinCommands.find(cmd => cmd.name === "axon")
|
|
146
|
+
if (axonCmd) {
|
|
147
|
+
axonCmd.execute()
|
|
148
|
+
}
|
|
149
|
+
|
|
150
|
+
stackComposable.onUpdate(() => {
|
|
151
|
+
// Put the footer back at the top (z-index).
|
|
152
|
+
container.remove(footer.node)
|
|
153
|
+
container.append(footer.node)
|
|
154
|
+
screen.render()
|
|
155
|
+
})
|
|
156
|
+
|
|
157
|
+
// Setup cleanup handlers for websocket and capsule
|
|
158
|
+
const cleanup = async () => {
|
|
159
|
+
cognos.disconnect()
|
|
160
|
+
if (!debug) {
|
|
161
|
+
restoreConsole()
|
|
162
|
+
}
|
|
163
|
+
}
|
|
164
|
+
|
|
165
|
+
process.on("SIGINT", async () => {
|
|
166
|
+
await cleanup()
|
|
167
|
+
process.exit(0)
|
|
168
|
+
})
|
|
169
|
+
|
|
170
|
+
process.on("exit", async () => {
|
|
171
|
+
await cleanup()
|
|
172
|
+
})
|
|
173
|
+
|
|
174
|
+
screen.key(["C-c"], async () => {
|
|
175
|
+
await cleanup()
|
|
176
|
+
process.exit(0)
|
|
177
|
+
})
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
/**
|
|
181
|
+
* Main entry point: Parse args and route to appropriate mode
|
|
182
|
+
*/
|
|
183
|
+
async function main() {
|
|
184
|
+
const args = parseArgs(process.argv.slice(2))
|
|
185
|
+
|
|
186
|
+
if (args.help) {
|
|
187
|
+
printHelp()
|
|
188
|
+
process.exit(0)
|
|
189
|
+
}
|
|
190
|
+
|
|
191
|
+
if (args.mode === 'prompt') {
|
|
192
|
+
if (!args.prompt) {
|
|
193
|
+
console.error("Error: --prompt requires a value")
|
|
194
|
+
process.exit(1)
|
|
195
|
+
}
|
|
196
|
+
await executePrompt(args.prompt)
|
|
197
|
+
process.exit(0)
|
|
198
|
+
}
|
|
199
|
+
|
|
200
|
+
// Interactive mode (default)
|
|
201
|
+
await mainInteractive(args.debug || false)
|
|
202
|
+
}
|
|
203
|
+
|
|
204
|
+
await main()
|