@astra-code/astra-ai 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.
@@ -0,0 +1,158 @@
1
+ import { spawn, spawnSync } from "child_process";
2
+ import { randomUUID } from "crypto";
3
+ import { basename, join } from "path";
4
+ import { tmpdir } from "os";
5
+ import { readFile, rm } from "fs/promises";
6
+ const VOICE_TEXT_LIMIT = 600;
7
+ const DEFAULT_STT_MODEL = process.env.ASTRA_STT_MODEL?.trim() || "whisper-1";
8
+ const DEFAULT_CHUNK_SECONDS = Number(process.env.ASTRA_STT_CHUNK_SECONDS ?? "2.5");
9
+ const safeText = (text) => text.replace(/\s+/g, " ").trim().slice(0, VOICE_TEXT_LIMIT);
10
+ const runShell = async (command) => new Promise((resolve, reject) => {
11
+ const child = spawn(command, { shell: true, stdio: "ignore" });
12
+ child.on("error", reject);
13
+ child.on("exit", (code) => {
14
+ if (code === 0) {
15
+ resolve();
16
+ }
17
+ else {
18
+ reject(new Error(`Command failed (${code}): ${command}`));
19
+ }
20
+ });
21
+ });
22
+ const captureAudioChunk = async (seconds) => {
23
+ const outPath = join(tmpdir(), `astra-stt-${randomUUID()}.wav`);
24
+ const custom = process.env.ASTRA_STT_CAPTURE_COMMAND?.trim();
25
+ let cmd = custom ?? "";
26
+ if (!cmd) {
27
+ if (process.platform === "darwin") {
28
+ cmd = `sox -q -d -r 16000 -c 1 -b 16 "${outPath}" trim 0 ${seconds}`;
29
+ }
30
+ else if (process.platform === "linux") {
31
+ cmd = `arecord -q -f S16_LE -r 16000 -c 1 -d ${Math.ceil(seconds)} "${outPath}"`;
32
+ }
33
+ else {
34
+ throw new Error("No default audio capture command for this platform. Set ASTRA_STT_CAPTURE_COMMAND.");
35
+ }
36
+ }
37
+ else {
38
+ cmd = cmd.replaceAll("{output}", outPath).replaceAll("{seconds}", String(seconds));
39
+ }
40
+ await runShell(cmd);
41
+ return outPath;
42
+ };
43
+ const transcribeAudioFile = async (filePath) => {
44
+ const apiKey = process.env.ASTRA_OPENAI_API_KEY?.trim() || process.env.OPENAI_API_KEY?.trim();
45
+ if (!apiKey) {
46
+ throw new Error("Missing OPENAI_API_KEY (or ASTRA_OPENAI_API_KEY) for Whisper STT.");
47
+ }
48
+ const bytes = await readFile(filePath);
49
+ const file = new File([bytes], basename(filePath), { type: "audio/wav" });
50
+ const form = new FormData();
51
+ form.append("file", file);
52
+ form.append("model", DEFAULT_STT_MODEL);
53
+ form.append("response_format", "json");
54
+ const response = await fetch("https://api.openai.com/v1/audio/transcriptions", {
55
+ method: "POST",
56
+ headers: { Authorization: `Bearer ${apiKey}` },
57
+ body: form
58
+ });
59
+ if (!response.ok) {
60
+ const detail = (await response.text()).slice(0, 400);
61
+ throw new Error(`Whisper STT failed ${response.status}: ${detail}`);
62
+ }
63
+ const data = (await response.json());
64
+ return String(data.text ?? "").trim();
65
+ };
66
+ /**
67
+ * Speak assistant text using either:
68
+ * - ASTRA_TTS_COMMAND (shell command, receives text as final arg), or
69
+ * - macOS `say` by default.
70
+ */
71
+ export const speakText = (text) => {
72
+ const clean = safeText(text);
73
+ if (!clean) {
74
+ return;
75
+ }
76
+ const custom = process.env.ASTRA_TTS_COMMAND?.trim();
77
+ if (custom) {
78
+ spawn(custom, [clean], { shell: true, detached: true, stdio: "ignore" }).unref();
79
+ return;
80
+ }
81
+ if (process.platform === "darwin") {
82
+ spawn("say", [clean], { detached: true, stdio: "ignore" }).unref();
83
+ }
84
+ };
85
+ /**
86
+ * Run one-shot STT. Uses custom ASTRA_STT_COMMAND if provided;
87
+ * otherwise records a short chunk and transcribes via OpenAI Whisper.
88
+ */
89
+ export const transcribeOnce = async () => {
90
+ const sttCommand = process.env.ASTRA_STT_COMMAND?.trim();
91
+ if (sttCommand) {
92
+ const result = spawnSync(sttCommand, { shell: true, encoding: "utf-8" });
93
+ if (result.error || result.status !== 0) {
94
+ return null;
95
+ }
96
+ const out = String(result.stdout ?? "").trim();
97
+ return out || null;
98
+ }
99
+ let audioPath = null;
100
+ try {
101
+ audioPath = await captureAudioChunk(DEFAULT_CHUNK_SECONDS);
102
+ const text = await transcribeAudioFile(audioPath);
103
+ return text || null;
104
+ }
105
+ catch {
106
+ return null;
107
+ }
108
+ finally {
109
+ if (audioPath) {
110
+ await rm(audioPath, { force: true });
111
+ }
112
+ }
113
+ };
114
+ export const startLiveTranscription = (handlers) => {
115
+ let running = true;
116
+ let stopping = false;
117
+ let transcript = "";
118
+ let finishedResolve = null;
119
+ const finished = new Promise((resolve) => {
120
+ finishedResolve = resolve;
121
+ });
122
+ const loop = async () => {
123
+ while (running) {
124
+ let audioPath = null;
125
+ try {
126
+ audioPath = await captureAudioChunk(DEFAULT_CHUNK_SECONDS);
127
+ const piece = await transcribeAudioFile(audioPath);
128
+ if (piece) {
129
+ transcript = transcript ? `${transcript} ${piece}` : piece;
130
+ handlers.onPartial(transcript);
131
+ }
132
+ }
133
+ catch (error) {
134
+ handlers.onError(error instanceof Error ? error : new Error(String(error)));
135
+ }
136
+ finally {
137
+ if (audioPath) {
138
+ await rm(audioPath, { force: true });
139
+ }
140
+ }
141
+ }
142
+ handlers.onFinal(transcript.trim());
143
+ finishedResolve?.();
144
+ };
145
+ void loop();
146
+ return {
147
+ stop: async () => {
148
+ if (stopping) {
149
+ await finished;
150
+ return;
151
+ }
152
+ stopping = true;
153
+ running = false;
154
+ await finished;
155
+ }
156
+ };
157
+ };
158
+ //# sourceMappingURL=voice.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"voice.js","sourceRoot":"","sources":["../../src/lib/voice.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,158 @@
1
+ /**
2
+ * Scans a local workspace directory to produce:
3
+ * workspaceTree — relative paths the backend uses for list_directory / search_files
4
+ * workspaceFiles — file contents the backend populates into VirtualFS
5
+ * so view_file / edit_file work without a local disk on the backend
6
+ */
7
+ import { existsSync, readdirSync, readFileSync, statSync } from "fs";
8
+ import { join, relative } from "path";
9
+ const SKIP_DIRS = new Set([
10
+ "node_modules",
11
+ ".git",
12
+ ".svn",
13
+ ".hg",
14
+ "dist",
15
+ "build",
16
+ ".next",
17
+ ".nuxt",
18
+ "__pycache__",
19
+ ".cache",
20
+ "coverage",
21
+ ".nyc_output",
22
+ "vendor",
23
+ ".venv",
24
+ "venv",
25
+ "env",
26
+ ".tox",
27
+ "target",
28
+ ".gradle",
29
+ ".idea",
30
+ ".pytest_cache",
31
+ ".mypy_cache",
32
+ ".ruff_cache",
33
+ ]);
34
+ const BINARY_EXTENSIONS = new Set([
35
+ ".png", ".jpg", ".jpeg", ".gif", ".webp", ".ico", ".bmp",
36
+ ".woff", ".woff2", ".ttf", ".eot", ".otf",
37
+ ".mp3", ".mp4", ".wav", ".webm", ".ogg", ".flac",
38
+ ".zip", ".tar", ".gz", ".br", ".zst", ".bz2", ".7z", ".rar",
39
+ ".pyc", ".pyo", ".class", ".o", ".so", ".dll", ".dylib",
40
+ ".exe", ".bin", ".pdf", ".sqlite", ".db",
41
+ ]);
42
+ const EXT_TO_LANG = {
43
+ ".py": "python",
44
+ ".js": "javascript",
45
+ ".ts": "typescript",
46
+ ".tsx": "typescriptreact",
47
+ ".jsx": "javascriptreact",
48
+ ".json": "json",
49
+ ".md": "markdown",
50
+ ".css": "css",
51
+ ".scss": "scss",
52
+ ".less": "less",
53
+ ".html": "html",
54
+ ".xml": "xml",
55
+ ".rs": "rust",
56
+ ".go": "go",
57
+ ".java": "java",
58
+ ".c": "c",
59
+ ".cpp": "cpp",
60
+ ".h": "c",
61
+ ".rb": "ruby",
62
+ ".sh": "shellscript",
63
+ ".bash": "shellscript",
64
+ ".zsh": "shellscript",
65
+ ".yaml": "yaml",
66
+ ".yml": "yaml",
67
+ ".toml": "toml",
68
+ ".sql": "sql",
69
+ ".swift": "swift",
70
+ ".kt": "kotlin",
71
+ ".php": "php",
72
+ ".graphql": "graphql",
73
+ ".vue": "vue",
74
+ ".svelte": "svelte",
75
+ ".env": "plaintext",
76
+ ".txt": "plaintext",
77
+ ".csv": "plaintext",
78
+ ".lock": "plaintext",
79
+ };
80
+ const MAX_TREE_ENTRIES = 2000;
81
+ const MAX_CONTENT_FILES = 200;
82
+ const MAX_FILE_BYTES = 100 * 1024; // 100 KB per file
83
+ const MAX_TOTAL_BYTES = 2 * 1024 * 1024; // 2 MB total content
84
+ function detectLanguage(relPath) {
85
+ const m = relPath.toLowerCase().match(/\.[^./\\]+$/);
86
+ return m ? (EXT_TO_LANG[m[0]] ?? "plaintext") : "plaintext";
87
+ }
88
+ function isBinary(relPath) {
89
+ const m = relPath.toLowerCase().match(/\.[^./\\]+$/);
90
+ return m ? BINARY_EXTENSIONS.has(m[0]) : false;
91
+ }
92
+ /**
93
+ * Scan `rootDir` and return both the path tree and file contents.
94
+ *
95
+ * Called once per `streamChat` request so the backend VirtualFS is always
96
+ * populated with the current state of the local workspace.
97
+ */
98
+ export function scanWorkspace(rootDir) {
99
+ if (!existsSync(rootDir)) {
100
+ return { workspaceTree: [], workspaceFiles: [] };
101
+ }
102
+ const workspaceTree = [];
103
+ const workspaceFiles = [];
104
+ let totalBytes = 0;
105
+ function walk(dir) {
106
+ if (workspaceTree.length >= MAX_TREE_ENTRIES)
107
+ return;
108
+ let entries;
109
+ try {
110
+ entries = readdirSync(dir).sort();
111
+ }
112
+ catch {
113
+ return;
114
+ }
115
+ for (const entry of entries) {
116
+ if (workspaceTree.length >= MAX_TREE_ENTRIES)
117
+ break;
118
+ const fullPath = join(dir, entry);
119
+ const relPath = relative(rootDir, fullPath).replace(/\\/g, "/");
120
+ let st;
121
+ try {
122
+ st = statSync(fullPath);
123
+ }
124
+ catch {
125
+ continue;
126
+ }
127
+ if (st.isDirectory()) {
128
+ if (SKIP_DIRS.has(entry) || entry.startsWith("."))
129
+ continue;
130
+ workspaceTree.push(relPath + "/");
131
+ walk(fullPath);
132
+ }
133
+ else if (st.isFile()) {
134
+ workspaceTree.push(relPath);
135
+ if (workspaceFiles.length < MAX_CONTENT_FILES &&
136
+ totalBytes < MAX_TOTAL_BYTES &&
137
+ !isBinary(relPath) &&
138
+ st.size < MAX_FILE_BYTES) {
139
+ try {
140
+ const content = readFileSync(fullPath, "utf-8");
141
+ workspaceFiles.push({
142
+ path: relPath,
143
+ content,
144
+ language: detectLanguage(relPath),
145
+ });
146
+ totalBytes += st.size;
147
+ }
148
+ catch {
149
+ // Unreadable file — include in tree but skip content.
150
+ }
151
+ }
152
+ }
153
+ }
154
+ }
155
+ walk(rootDir);
156
+ return { workspaceTree, workspaceFiles };
157
+ }
158
+ //# sourceMappingURL=workspaceScanner.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"workspaceScanner.js","sourceRoot":"","sources":["../../src/lib/workspaceScanner.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,2 @@
1
+ export {};
2
+ //# sourceMappingURL=events.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"events.js","sourceRoot":"","sources":["../../src/types/events.ts"],"names":[],"mappings":""}
@@ -0,0 +1,45 @@
1
+ # Python to TypeScript parity checklist
2
+
3
+ This checklist tracks migration parity from `../astra-code/astra_code/app.py` and `../astra-code/astra_code/tui.py`.
4
+
5
+ ## Core runtime parity
6
+
7
+ - [x] Env config defaults: `ASTRA_BACKEND_URL`, `ASTRA_MODEL`, `ASTRA_CLIENT_ID`
8
+ - [x] Session persistence in `~/.astra-code/session.json`
9
+ - [x] Health check gate on startup (`/healthz`)
10
+ - [x] Session validation via `/api/user/profile`
11
+ - [x] Login/signup flow via `/api/auth/sign-in` and `/api/auth/sign-up`
12
+ - [x] Session creation via `/api/sessions`
13
+ - [x] Chat stream via `/api/agent/chat/stream` (`data:` SSE + `[DONE]`)
14
+
15
+ ## UI and event behavior parity
16
+
17
+ - [x] Login screen with email/password
18
+ - [x] Chat screen input + message stream
19
+ - [x] Thinking indicator while stream active
20
+ - [x] Slash commands: `/help`, `/new`, `/logout`, `/exit`
21
+ - [x] `tool_start` rendering
22
+ - [x] `tool_result` rendering
23
+ - [x] `credits_update` rendering
24
+ - [x] `credits_exhausted` rendering
25
+ - [x] `error` rendering
26
+
27
+ ## Terminal bridge parity
28
+
29
+ - [x] Handle `run_in_terminal` events
30
+ - [x] Run local shell command in workspace context
31
+ - [x] Post completion to `/api/agent/terminal-result`
32
+
33
+ ## Command mode parity
34
+
35
+ - [x] `whoami`
36
+ - [x] `sessions`
37
+ - [x] `skills`
38
+ - [x] `skills --suggest`
39
+ - [x] `session-messages --session`
40
+ - [x] `logout`
41
+
42
+ ## Known deltas (intentional for first cut)
43
+
44
+ - Textual-specific layout/styling and widget system is replaced by Ink components.
45
+ - Some Python-only utility commands (for workspace sandbox inspection) are not yet ported in this first TypeScript cut and can be added in follow-up milestones.
package/install.sh ADDED
@@ -0,0 +1,32 @@
1
+ #!/usr/bin/env bash
2
+ set -euo pipefail
3
+
4
+ SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)"
5
+ cd "$SCRIPT_DIR"
6
+
7
+ if ! command -v node >/dev/null 2>&1; then
8
+ echo "Node.js is required (20+ recommended)." >&2
9
+ exit 1
10
+ fi
11
+
12
+ if ! command -v npm >/dev/null 2>&1; then
13
+ echo "npm is required." >&2
14
+ exit 1
15
+ fi
16
+
17
+ echo "Installing dependencies..."
18
+ npm install
19
+
20
+ echo "Building astra-code..."
21
+ npm run build
22
+
23
+ echo "Installing global astra-code bin..."
24
+ npm install -g .
25
+
26
+ if [[ ! -f "$SCRIPT_DIR/.env" && -f "$SCRIPT_DIR/.env.example" ]]; then
27
+ cp "$SCRIPT_DIR/.env.example" "$SCRIPT_DIR/.env"
28
+ echo "Created .env from .env.example"
29
+ fi
30
+
31
+ echo ""
32
+ echo "Done. Run: astra-code"
package/package.json ADDED
@@ -0,0 +1,38 @@
1
+ {
2
+ "name": "@astra-code/astra-ai",
3
+ "version": "0.1.0",
4
+ "description": "Astra Code terminal app built by Sean Donovan",
5
+ "type": "module",
6
+ "main": "dist/index.js",
7
+ "bin": {
8
+ "astra": "dist/index.js",
9
+ "astra-code": "dist/index.js"
10
+ },
11
+ "scripts": {
12
+ "build": "tsc -p tsconfig.json",
13
+ "typecheck": "tsc -p tsconfig.json --noEmit",
14
+ "start": "tsx src/index.ts",
15
+ "dev": "tsx src/index.ts"
16
+ },
17
+ "keywords": [
18
+ "astra",
19
+ "terminal",
20
+ "ink",
21
+ "ai"
22
+ ],
23
+ "author": "Sean Donovan",
24
+ "license": "MIT",
25
+ "dependencies": {
26
+ "dotenv": "^17.3.1",
27
+ "ink": "^6.8.0",
28
+ "ink-spinner": "^5.0.0",
29
+ "ink-text-input": "^6.0.0",
30
+ "react": "^19.2.4"
31
+ },
32
+ "devDependencies": {
33
+ "@types/node": "^25.5.0",
34
+ "@types/react": "^19.2.14",
35
+ "tsx": "^4.21.0",
36
+ "typescript": "^5.9.3"
37
+ }
38
+ }