junecoder 1.0.4 → 1.0.6

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.
Files changed (4) hide show
  1. package/agent.mjs +5 -0
  2. package/cli.js +7 -11
  3. package/package.json +8 -2
  4. package/tui.mjs +43 -3
package/agent.mjs CHANGED
@@ -533,6 +533,11 @@ export async function runAgent(agent, input, callbacks = {}, options = {}) {
533
533
 
534
534
  // Report result
535
535
  try { cb.onToolResult(r.name, r.output, r.error); } catch { /* ignore */ }
536
+
537
+ // Sync task list to TUI after task tool
538
+ if (r.name === 'task' && !r.error && !r.denied) {
539
+ try { cb.onTaskUpdate(agent.tasks); } catch { /* ignore */ }
540
+ }
536
541
  }
537
542
 
538
543
  // Step 8: Inject pending reminders
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. .env file in project directory (DEEPSEEK_API_KEY=sk-...)
13
- * 2. ~/.junecoder/.env
14
- * 3. DEEPSEEK_API_KEY environment variable
15
- * 4. ~/.junecoder/config.json
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
- if (!apiKey) {
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.4",
3
+ "version": "1.0.6",
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": ["agent", "react", "llm", "coding", "ai"],
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,47 @@ 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
+ state.status = "Paste your DeepSeek API key and press Enter (https://platform.deepseek.com/api_keys)";
335
+ }
336
+
329
337
  async function submit() {
330
338
  const text = state.input.join("").trim();
331
339
  if (!text || state.processing) return;
340
+
341
+ // ─── Setup mode: first input is the API key ──────────────────────────────
342
+ if (setupMode) {
343
+ setupMode = false;
344
+ state.input = []; state.cursor = 0;
345
+ const trimmed = text.trim();
346
+
347
+ if (!trimmed) {
348
+ pushLine("No API key provided. Exiting.", C.error);
349
+ render();
350
+ await new Promise(r => setTimeout(r, 1500));
351
+ cleanup();
352
+ process.exit(1);
353
+ }
354
+
355
+ if (!trimmed.startsWith("sk-")) {
356
+ pushLine("Warning: API key doesn't start with 'sk-'. It may not work.", C.warn);
357
+ }
358
+
359
+ const configDir = join(homedir(), '.junecoder');
360
+ if (!existsSync(configDir)) mkdirSync(configDir, { recursive: true });
361
+ writeFileSync(join(configDir, '.env'), `DEEPSEEK_API_KEY=${trimmed}\n`, 'utf-8');
362
+ process.env.DEEPSEEK_API_KEY = trimmed;
363
+ agent.provider.apiKey = trimmed;
364
+
365
+ pushLine("API key saved to ~/.junecoder/.env", C.tool);
366
+ pushLine("", C.dim);
367
+ state.status = "Ready";
368
+ render();
369
+ return;
370
+ }
371
+
332
372
  state.input = []; state.cursor = 0; state.history.push(text); state.historyIndex = -1; state.scroll = 0;
333
373
  if (text.startsWith("/")) { await handleSlash(text); return; }
334
374
  pushLabel("\u276f You:", ansi.bold + C.user); pushLine(text, C.text);
@@ -359,7 +399,7 @@ export async function startTUI(agent, opts = {}) {
359
399
  }
360
400
  }
361
401
  clearInterval(ticker); state.processing = false; state.controller = null; state.status = "Ready";
362
- if (state.tasks.length > 0 && state.tasks.every(t => t.status === "done")) state.tasks = [];
402
+ if (state.tasks.length > 0 && state.tasks.every(t => t.status === "done")) { state.tasks = []; agent.tasks = []; }
363
403
  try { saveSession(agent, state.lines); } catch {} render();
364
404
  }
365
405
  function flushStream() { if (state.reasoning) { pushLine(state.reasoning, C.reason); state.reasoning = ""; } if (state.streaming) { pushLine(state.streaming, C.text); state.streaming = ""; } }
@@ -424,7 +464,7 @@ export async function startTUI(agent, opts = {}) {
424
464
  if (typeof raw === "string") state.lines.push({ text: raw, color: C.text });
425
465
  else if (raw && typeof raw.text === "string") state.lines.push(raw);
426
466
  }
427
- if (restored.tasks) state.tasks = restored.tasks;
467
+ if (restored.tasks) { state.tasks = restored.tasks; agent.tasks = restored.tasks; }
428
468
  if (restored.planMode !== undefined) agent.planMode = restored.planMode;
429
469
  state.status = "Session restored";
430
470
  } else {