junecoder 1.0.5 → 1.0.7
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/cli.js +7 -11
- package/package.json +8 -2
- package/tui.mjs +60 -3
package/cli.js
CHANGED
|
@@ -9,10 +9,11 @@
|
|
|
9
9
|
* junecoder "write a hello world" # Single-shot (non-TUI)
|
|
10
10
|
*
|
|
11
11
|
* API keys can be provided via:
|
|
12
|
-
* 1.
|
|
13
|
-
* 2.
|
|
14
|
-
* 3.
|
|
15
|
-
* 4.
|
|
12
|
+
* 1. Interactive setup on first run (TUI)
|
|
13
|
+
* 2. .env file in project directory (DEEPSEEK_API_KEY=sk-...)
|
|
14
|
+
* 3. ~/.junecoder/.env
|
|
15
|
+
* 4. DEEPSEEK_API_KEY environment variable
|
|
16
|
+
* 5. ~/.junecoder/config.json
|
|
16
17
|
*/
|
|
17
18
|
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
18
19
|
import { resolve, join } from 'node:path';
|
|
@@ -153,12 +154,7 @@ if (existsSync(targetDir) && statSync(targetDir).isDirectory()) {
|
|
|
153
154
|
const config = loadConfig();
|
|
154
155
|
const apiKey = config.provider.apiKey || process.env.DEEPSEEK_API_KEY;
|
|
155
156
|
|
|
156
|
-
|
|
157
|
-
console.error('Error: No API key found. Set DEEPSEEK_API_KEY in .env or in ~/.junecoder/config.json.');
|
|
158
|
-
process.exit(1);
|
|
159
|
-
}
|
|
160
|
-
|
|
161
|
-
config.provider.apiKey = apiKey;
|
|
157
|
+
config.provider.apiKey = apiKey || '';
|
|
162
158
|
|
|
163
159
|
// Create Agent instance with target cwd
|
|
164
160
|
const agent = createAgent({
|
|
@@ -196,4 +192,4 @@ if (!process.stdin.isTTY || !process.stdout.isTTY) {
|
|
|
196
192
|
}
|
|
197
193
|
|
|
198
194
|
const restored = loadSession(agent.cwd);
|
|
199
|
-
startTUI(agent, { projectDir: agent.cwd, restored });
|
|
195
|
+
startTUI(agent, { projectDir: agent.cwd, restored, needsSetup: !apiKey });
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "junecoder",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.7",
|
|
4
4
|
"description": "Zero Npm Dependencies Agent Framework",
|
|
5
5
|
"main": "agent.mjs",
|
|
6
6
|
"type": "module",
|
|
@@ -20,7 +20,13 @@
|
|
|
20
20
|
"engines": {
|
|
21
21
|
"node": ">=21.7"
|
|
22
22
|
},
|
|
23
|
-
"keywords": [
|
|
23
|
+
"keywords": [
|
|
24
|
+
"agent",
|
|
25
|
+
"react",
|
|
26
|
+
"llm",
|
|
27
|
+
"coding",
|
|
28
|
+
"ai"
|
|
29
|
+
],
|
|
24
30
|
"author": "",
|
|
25
31
|
"license": "ISC"
|
|
26
32
|
}
|
package/tui.mjs
CHANGED
|
@@ -8,7 +8,9 @@
|
|
|
8
8
|
*/
|
|
9
9
|
import { emitKeypressEvents } from "node:readline";
|
|
10
10
|
import { PassThrough } from "node:stream";
|
|
11
|
-
import { basename } from "node:path";
|
|
11
|
+
import { basename, join } from "node:path";
|
|
12
|
+
import { homedir } from "node:os";
|
|
13
|
+
import { existsSync, mkdirSync, writeFileSync } from "node:fs";
|
|
12
14
|
import { runAgent, ContinueError } from "./agent.mjs";
|
|
13
15
|
import { estimateTokens } from "./context.mjs";
|
|
14
16
|
import {
|
|
@@ -326,9 +328,57 @@ export async function startTUI(agent, opts = {}) {
|
|
|
326
328
|
if (str && str.length > 0 && !key.ctrl && !key.meta && str !== "\r" && str !== "\n") { for (const ch of str) { state.input.splice(state.cursor, 0, ch); state.cursor++; } }
|
|
327
329
|
}
|
|
328
330
|
|
|
331
|
+
// ─── First-run setup: capture API key from input box ──────────────────────────
|
|
332
|
+
let setupMode = opts.needsSetup;
|
|
333
|
+
if (setupMode) {
|
|
334
|
+
pushLine("", C.dim);
|
|
335
|
+
pushLine("\u250c\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2510", C.assistant);
|
|
336
|
+
pushLine("\u2502 Welcome to JuneCoder! \u2502", C.assistant);
|
|
337
|
+
pushLine("\u2502 \u2502", C.assistant);
|
|
338
|
+
pushLine("\u2502 Before you start, you need a DeepSeek API key. \u2502", C.assistant);
|
|
339
|
+
pushLine("\u2502 Get one at: https://platform.deepseek.com/api_keys \u2502", C.assistant);
|
|
340
|
+
pushLine("\u2502 \u2502", C.assistant);
|
|
341
|
+
pushLine("\u2502 Paste your key below and press Enter. \u2502", C.assistant);
|
|
342
|
+
pushLine("\u2514\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2500\u2518", C.assistant);
|
|
343
|
+
pushLine("", C.dim);
|
|
344
|
+
state.status = "Paste your DeepSeek API key and press Enter (https://platform.deepseek.com/api_keys)";
|
|
345
|
+
}
|
|
346
|
+
|
|
329
347
|
async function submit() {
|
|
330
348
|
const text = state.input.join("").trim();
|
|
331
349
|
if (!text || state.processing) return;
|
|
350
|
+
|
|
351
|
+
// ─── Setup mode: first input is the API key ──────────────────────────────
|
|
352
|
+
if (setupMode) {
|
|
353
|
+
setupMode = false;
|
|
354
|
+
state.input = []; state.cursor = 0;
|
|
355
|
+
const trimmed = text.trim();
|
|
356
|
+
|
|
357
|
+
if (!trimmed) {
|
|
358
|
+
pushLine("No API key provided. Exiting.", C.error);
|
|
359
|
+
render();
|
|
360
|
+
await new Promise(r => setTimeout(r, 1500));
|
|
361
|
+
cleanup();
|
|
362
|
+
process.exit(1);
|
|
363
|
+
}
|
|
364
|
+
|
|
365
|
+
if (!trimmed.startsWith("sk-")) {
|
|
366
|
+
pushLine("Warning: API key doesn't start with 'sk-'. It may not work.", C.warn);
|
|
367
|
+
}
|
|
368
|
+
|
|
369
|
+
const configDir = join(homedir(), '.junecoder');
|
|
370
|
+
if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
|
|
371
|
+
writeFileSync(join(configDir, '.env'), `DEEPSEEK_API_KEY=${trimmed}\n`, 'utf-8');
|
|
372
|
+
process.env.DEEPSEEK_API_KEY = trimmed;
|
|
373
|
+
agent.provider.apiKey = trimmed;
|
|
374
|
+
|
|
375
|
+
pushLine("API key saved to ~/.junecoder/.env", C.tool);
|
|
376
|
+
pushLine("", C.dim);
|
|
377
|
+
state.status = "Ready";
|
|
378
|
+
render();
|
|
379
|
+
return;
|
|
380
|
+
}
|
|
381
|
+
|
|
332
382
|
state.input = []; state.cursor = 0; state.history.push(text); state.historyIndex = -1; state.scroll = 0;
|
|
333
383
|
if (text.startsWith("/")) { await handleSlash(text); return; }
|
|
334
384
|
pushLabel("\u276f You:", ansi.bold + C.user); pushLine(text, C.text);
|
|
@@ -369,10 +419,17 @@ export async function startTUI(agent, opts = {}) {
|
|
|
369
419
|
async function handleSlash(text) {
|
|
370
420
|
const cmd = text.slice(1).split(/\s+/)[0].toLowerCase();
|
|
371
421
|
switch (cmd) {
|
|
372
|
-
case "help": for (const l of ["/help /plan /auto /model /session /clear /tasks /stats /new /distill /quit"]) pushLine(l, C.dim); break;
|
|
422
|
+
case "help": for (const l of ["/help /plan /auto /key /model /session /clear /tasks /stats /new /distill /quit"]) pushLine(l, C.dim); break;
|
|
373
423
|
case "plan": agent.planMode = !agent.planMode; pushLine(" Plan mode " + (agent.planMode ? "ON" : "OFF"), C.tool); break;
|
|
374
424
|
case "auto": agent.autoApprove = !agent.autoApprove; pushLine(" Auto-approve " + (agent.autoApprove ? "ON" : "OFF"), agent.autoApprove ? C.warn : C.tool); break;
|
|
375
425
|
case "model": pushLine(" Model: " + agent.provider.model + " | Provider: " + (agent.provider.type || "?"), C.dim); break;
|
|
426
|
+
case "key": {
|
|
427
|
+
pushLine(" Current key: " + (agent.provider.apiKey ? agent.provider.apiKey.slice(0, 8) + "..." : "(none)"), C.dim);
|
|
428
|
+
pushLine(" To change, paste your new API key and press Enter.", C.tool);
|
|
429
|
+
setupMode = true;
|
|
430
|
+
state.status = "Paste your DeepSeek API key and press Enter (https://platform.deepseek.com/api_keys)";
|
|
431
|
+
break;
|
|
432
|
+
}
|
|
376
433
|
case "session": { const slots = listSlots(agent.cwd); pushLine("Sessions:", C.tool); if (slots.length === 0) pushLine(" (none)", C.dim); else for (const s of slots) pushLine(" " + s.label, C.dim); break; }
|
|
377
434
|
case "clear": archiveCurrent(agent.cwd); agent.history = []; state.lines = []; state.streaming = ""; state.reasoning = ""; state.tasks = []; state.scroll = 0; pushLine("Cleared (archived).", C.warn); break;
|
|
378
435
|
case "new": archiveCurrent(agent.cwd); agent.history = []; state.lines = []; state.streaming = ""; state.reasoning = ""; state.toolStreams = {}; state.tasks = []; state.scroll = 0; state.tokens = { prompt: 0, completion: 0 }; pushLine("New session.", C.tool); break;
|
|
@@ -427,7 +484,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
427
484
|
if (restored.tasks) { state.tasks = restored.tasks; agent.tasks = restored.tasks; }
|
|
428
485
|
if (restored.planMode !== undefined) agent.planMode = restored.planMode;
|
|
429
486
|
state.status = "Session restored";
|
|
430
|
-
} else {
|
|
487
|
+
} else if (!setupMode) {
|
|
431
488
|
pushLine("", C.dim);
|
|
432
489
|
pushLine("JuneCoder TUI \u2014 " + (agent.provider.model || ""), ansi.bold + C.tool);
|
|
433
490
|
pushLine("Type /help for commands, Ctrl+C to quit.", C.dim);
|