junecoder 1.0.0 → 1.0.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/README.md +1 -2
- package/agent.mjs +3 -3
- package/checkpoint.mjs +2 -2
- package/cli.js +16 -16
- package/config.mjs +3 -3
- package/distill.mjs +1 -1
- package/executor.mjs +2 -2
- package/mcp.mjs +1 -1
- package/memory.mjs +1 -1
- package/metaTools.mjs +1 -1
- package/package.json +2 -2
- package/session.mjs +2 -2
- package/skills.mjs +4 -4
- package/tools/websearch.mjs +1 -1
- package/tui.mjs +3 -3
package/README.md
CHANGED
|
@@ -1,2 +1 @@
|
|
|
1
|
-
#
|
|
2
|
-
# junecode
|
|
1
|
+
# junecoder
|
package/agent.mjs
CHANGED
|
@@ -199,13 +199,13 @@ export function collectGitContext(cwd) {
|
|
|
199
199
|
|
|
200
200
|
/**
|
|
201
201
|
* Load project instructions from AGENTS.md, PROJECT_RULES.md etc.
|
|
202
|
-
* Reads from both ~/.
|
|
202
|
+
* Reads from both ~/.junecoder/ and the local project directory.
|
|
203
203
|
* @param {string} cwd
|
|
204
204
|
* @returns {string} combined instructions
|
|
205
205
|
*/
|
|
206
206
|
export function loadProjectInstructions(cwd) {
|
|
207
207
|
const sources = [
|
|
208
|
-
join(homedir(), '.
|
|
208
|
+
join(homedir(), '.junecoder', 'AGENTS.md'),
|
|
209
209
|
join(cwd, 'AGENTS.md'),
|
|
210
210
|
join(cwd, 'PROJECT_RULES.md'),
|
|
211
211
|
join(cwd, '.cursorrules'),
|
|
@@ -239,7 +239,7 @@ export function loadProjectInstructions(cwd) {
|
|
|
239
239
|
|
|
240
240
|
// ─── System Prompt ────────────────────────────────────────────────────────────
|
|
241
241
|
|
|
242
|
-
export const DEFAULT_SYSTEM_PROMPT = `You are
|
|
242
|
+
export const DEFAULT_SYSTEM_PROMPT = `You are JuneCoder, a coding agent. You are a terse, precise engineer who cuts straight to the point—no fluff, no showing off, no filler. You write the most minimal, elegant code that solves the problem, and you say things in as few words as the truth allows.
|
|
243
243
|
|
|
244
244
|
Rules:
|
|
245
245
|
- Prefer tool calls over guessing. Read files before modifying them.
|
package/checkpoint.mjs
CHANGED
|
@@ -11,13 +11,13 @@ export async function createCheckpoint(cwd) {
|
|
|
11
11
|
// Bail if there are pre-existing uncommitted changes — we must not hide user's work
|
|
12
12
|
const status = execSync('git status --porcelain', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
|
13
13
|
if (status) return;
|
|
14
|
-
execSync('git stash push -m "
|
|
14
|
+
execSync('git stash push -m "junecoder-checkpoint"', { cwd, stdio: 'ignore' });
|
|
15
15
|
} catch {
|
|
16
16
|
// Not a git repo or no changes to stash
|
|
17
17
|
}
|
|
18
18
|
}
|
|
19
19
|
|
|
20
|
-
/** List available
|
|
20
|
+
/** List available junecoder checkpoints. */
|
|
21
21
|
export async function listCheckpoints(cwd) {
|
|
22
22
|
try {
|
|
23
23
|
const out = execSync('git stash list', { cwd, encoding: 'utf-8', stdio: ['ignore', 'pipe', 'ignore'] }).trim();
|
package/cli.js
CHANGED
|
@@ -1,18 +1,18 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
/**
|
|
3
|
-
*
|
|
3
|
+
* junecoder CLI entry point — starts the TUI or single-shot execution.
|
|
4
4
|
*
|
|
5
5
|
* Usage:
|
|
6
|
-
*
|
|
7
|
-
*
|
|
8
|
-
*
|
|
9
|
-
*
|
|
6
|
+
* junecoder [directory] # Start TUI in directory (default: .)
|
|
7
|
+
* junecoder open [directory] # Start TUI in directory (default: .)
|
|
8
|
+
* junecoder -d <dir> [prompt] # Specify working directory
|
|
9
|
+
* junecoder "write a hello world" # Single-shot (non-TUI)
|
|
10
10
|
*
|
|
11
11
|
* API keys can be provided via:
|
|
12
12
|
* 1. .env file in project directory (DEEPSEEK_API_KEY=sk-...)
|
|
13
|
-
* 2. ~/.
|
|
13
|
+
* 2. ~/.junecoder/.env
|
|
14
14
|
* 3. DEEPSEEK_API_KEY environment variable
|
|
15
|
-
* 4. ~/.
|
|
15
|
+
* 4. ~/.junecoder/config.json
|
|
16
16
|
*/
|
|
17
17
|
import { existsSync, statSync, readFileSync } from 'node:fs';
|
|
18
18
|
import { resolve, join } from 'node:path';
|
|
@@ -41,14 +41,14 @@ if (rawArgs.length > 0) {
|
|
|
41
41
|
const first = rawArgs[0];
|
|
42
42
|
|
|
43
43
|
if (first === '-h' || first === '--help' || first === 'help') {
|
|
44
|
-
console.log(`
|
|
44
|
+
console.log(`junecoder - AI coding agent
|
|
45
45
|
|
|
46
46
|
Usage:
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
47
|
+
junecoder [directory] Start TUI in directory (default: .)
|
|
48
|
+
junecoder open [directory] Start TUI in directory (default: .)
|
|
49
|
+
junecoder --dir <path> [prompt] Set working directory
|
|
50
|
+
junecoder -p, --prompt <prompt> Run single-shot prompt (non-TUI)
|
|
51
|
+
junecoder "your prompt here" Run single-shot prompt (non-TUI)
|
|
52
52
|
|
|
53
53
|
Options:
|
|
54
54
|
-d, --dir <path> Working directory
|
|
@@ -63,9 +63,9 @@ Options:
|
|
|
63
63
|
if (first === '-v' || first === '--version') {
|
|
64
64
|
try {
|
|
65
65
|
const pkg = JSON.parse(readFileSync(new URL('./package.json', import.meta.url), 'utf-8'));
|
|
66
|
-
console.log(`
|
|
66
|
+
console.log(`junecoder v${pkg.version}`);
|
|
67
67
|
} catch {
|
|
68
|
-
console.log('
|
|
68
|
+
console.log('junecoder v1.0.0');
|
|
69
69
|
}
|
|
70
70
|
process.exit(0);
|
|
71
71
|
}
|
|
@@ -154,7 +154,7 @@ const config = loadConfig();
|
|
|
154
154
|
const apiKey = config.provider.apiKey || process.env.DEEPSEEK_API_KEY;
|
|
155
155
|
|
|
156
156
|
if (!apiKey) {
|
|
157
|
-
console.error('Error: No API key found. Set DEEPSEEK_API_KEY in .env or in ~/.
|
|
157
|
+
console.error('Error: No API key found. Set DEEPSEEK_API_KEY in .env or in ~/.junecoder/config.json.');
|
|
158
158
|
process.exit(1);
|
|
159
159
|
}
|
|
160
160
|
|
package/config.mjs
CHANGED
|
@@ -11,7 +11,7 @@ import { existsSync, readFileSync } from 'node:fs';
|
|
|
11
11
|
// Load .env files before any code reads process.env.
|
|
12
12
|
// Must happen here (not cli.mjs) because ESM static imports are hoisted —
|
|
13
13
|
// this module's body runs before cli.mjs's body.
|
|
14
|
-
for (const envPath of ['.env', join(homedir(), '.
|
|
14
|
+
for (const envPath of ['.env', join(homedir(), '.junecoder', '.env')]) {
|
|
15
15
|
try {
|
|
16
16
|
if (existsSync(envPath)) {
|
|
17
17
|
const content = readFileSync(envPath, 'utf-8');
|
|
@@ -32,8 +32,8 @@ for (const envPath of ['.env', join(homedir(), '.junecode', '.env')]) {
|
|
|
32
32
|
} catch { /* ignore missing/unreadable .env */ }
|
|
33
33
|
}
|
|
34
34
|
|
|
35
|
-
/** Default config directory: ~/.
|
|
36
|
-
export const configDir = join(homedir(), '.
|
|
35
|
+
/** Default config directory: ~/.junecoder */
|
|
36
|
+
export const configDir = join(homedir(), '.junecoder');
|
|
37
37
|
|
|
38
38
|
/** Default configuration object. Callers can spread/override with user settings. */
|
|
39
39
|
export function defaultConfig() {
|
package/distill.mjs
CHANGED
|
@@ -165,7 +165,7 @@ export async function saveCandidate(memory, candidate, opts = {}) {
|
|
|
165
165
|
const { mkdirSync, writeFileSync } = await import('node:fs');
|
|
166
166
|
const { homedir } = await import('node:os');
|
|
167
167
|
|
|
168
|
-
const dir = memory.dir || join(homedir(), '.
|
|
168
|
+
const dir = memory.dir || join(homedir(), '.junecoder', 'memory');
|
|
169
169
|
try { mkdirSync(dir, { recursive: true }); } catch { /* dir exists */ }
|
|
170
170
|
|
|
171
171
|
writeFileSync(join(dir, entry.id + '.json'), JSON.stringify(entry, null, 2), 'utf-8');
|
package/executor.mjs
CHANGED
|
@@ -187,10 +187,10 @@ async function runOne(agent, item, callbacks, signal) {
|
|
|
187
187
|
|
|
188
188
|
/**
|
|
189
189
|
* Write long tool results to disk, return a preview + file path.
|
|
190
|
-
* Directory: ~/.
|
|
190
|
+
* Directory: ~/.junecoder/tool-results/
|
|
191
191
|
*/
|
|
192
192
|
function offloadToolResult(toolName, fullResult) {
|
|
193
|
-
const dir = join(homedir(), '.
|
|
193
|
+
const dir = join(homedir(), '.junecoder', 'tool-results');
|
|
194
194
|
if (!existsSync(dir)) {
|
|
195
195
|
mkdirSync(dir, { recursive: true });
|
|
196
196
|
}
|
package/mcp.mjs
CHANGED
|
@@ -137,7 +137,7 @@ export async function connectMcpServer(srv) {
|
|
|
137
137
|
const initResult = await jsonRpc(proc, 'initialize', {
|
|
138
138
|
protocolVersion: '2024-11-05',
|
|
139
139
|
capabilities: { tools: {} },
|
|
140
|
-
clientInfo: { name: '
|
|
140
|
+
clientInfo: { name: 'junecoder', version: '1.0.0' },
|
|
141
141
|
});
|
|
142
142
|
|
|
143
143
|
// Step 2: Send initialized notification
|
package/memory.mjs
CHANGED
package/metaTools.mjs
CHANGED
|
@@ -123,7 +123,7 @@ export const planTool = {
|
|
|
123
123
|
export const skillTool = {
|
|
124
124
|
name: 'skill',
|
|
125
125
|
description:
|
|
126
|
-
'Manage project skills. Skills are reusable workflows stored in .
|
|
126
|
+
'Manage project skills. Skills are reusable workflows stored in .junecoder/skills/. ' +
|
|
127
127
|
'Use skill=list to see available skills, skill=load to activate one.',
|
|
128
128
|
parameters: {
|
|
129
129
|
type: 'object',
|
package/package.json
CHANGED
|
@@ -1,11 +1,11 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "junecoder",
|
|
3
|
-
"version": "1.0.
|
|
3
|
+
"version": "1.0.2",
|
|
4
4
|
"description": "Zero Npm Dependencies Agent Framework",
|
|
5
5
|
"main": "agent.mjs",
|
|
6
6
|
"type": "module",
|
|
7
7
|
"bin": {
|
|
8
|
-
"
|
|
8
|
+
"junecoder": "cli.js"
|
|
9
9
|
},
|
|
10
10
|
"files": [
|
|
11
11
|
"*.mjs",
|
package/session.mjs
CHANGED
|
@@ -8,9 +8,9 @@ import { join } from 'node:path';
|
|
|
8
8
|
import { existsSync, mkdirSync, readFileSync, writeFileSync, readdirSync, renameSync, unlinkSync } from 'node:fs';
|
|
9
9
|
import { homedir } from 'node:os';
|
|
10
10
|
|
|
11
|
-
/** Directory under ~/.
|
|
11
|
+
/** Directory under ~/.junecoder/ for session storage. */
|
|
12
12
|
function sessionsDir(cwd) {
|
|
13
|
-
const dir = join(homedir(), '.
|
|
13
|
+
const dir = join(homedir(), '.junecoder', 'sessions');
|
|
14
14
|
if (!existsSync(dir)) mkdirSync(dir, { recursive: true });
|
|
15
15
|
return dir;
|
|
16
16
|
}
|
package/skills.mjs
CHANGED
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* Skills module — loads and formats project skills from .
|
|
2
|
+
* Skills module — loads and formats project skills from .junecoder/skills/.
|
|
3
3
|
*
|
|
4
4
|
* Skill files are markdown (.md) with optional YAML-like frontmatter.
|
|
5
5
|
* Frontmatter keys: name (string), description (string).
|
|
@@ -9,12 +9,12 @@ import { join, basename, extname } from 'node:path';
|
|
|
9
9
|
import { existsSync, readdirSync, readFileSync } from 'node:fs';
|
|
10
10
|
|
|
11
11
|
/**
|
|
12
|
-
* Load skills from cwd/.
|
|
12
|
+
* Load skills from cwd/.junecoder/skills/.
|
|
13
13
|
* @param {string} cwd
|
|
14
14
|
* @returns {{ name: string, description: string, path: string }[]}
|
|
15
15
|
*/
|
|
16
16
|
export function loadSkills(cwd) {
|
|
17
|
-
const skillsDir = join(cwd, '.
|
|
17
|
+
const skillsDir = join(cwd, '.junecoder', 'skills');
|
|
18
18
|
if (!existsSync(skillsDir)) return [];
|
|
19
19
|
|
|
20
20
|
let entries;
|
|
@@ -61,7 +61,7 @@ export function formatSkillListing(skills) {
|
|
|
61
61
|
* @returns {string|null} skill content or null if not found
|
|
62
62
|
*/
|
|
63
63
|
export function readSkill(cwd, name) {
|
|
64
|
-
const skillsDir = join(cwd, '.
|
|
64
|
+
const skillsDir = join(cwd, '.junecoder', 'skills');
|
|
65
65
|
if (!existsSync(skillsDir)) return null;
|
|
66
66
|
|
|
67
67
|
// Try exact name match (with/without .md extension)
|
package/tools/websearch.mjs
CHANGED
|
@@ -21,7 +21,7 @@ export const websearchTool = {
|
|
|
21
21
|
const url = `https://www.bing.com/search?q=${encodeURIComponent(args.query)}`;
|
|
22
22
|
const response = await fetch(url, {
|
|
23
23
|
headers: {
|
|
24
|
-
'User-Agent': 'Mozilla/5.0 (compatible;
|
|
24
|
+
'User-Agent': 'Mozilla/5.0 (compatible; junecoder/1.0)',
|
|
25
25
|
'Accept': 'text/html',
|
|
26
26
|
},
|
|
27
27
|
signal: AbortSignal.timeout(15_000),
|
package/tui.mjs
CHANGED
|
@@ -152,7 +152,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
152
152
|
};
|
|
153
153
|
let assistantLabeled = false;
|
|
154
154
|
const ensureAssistantLabel = () => {
|
|
155
|
-
if (!assistantLabeled) { assistantLabeled = true; pushLabel("\u276f
|
|
155
|
+
if (!assistantLabeled) { assistantLabeled = true; pushLabel("\u276f JuneCoder:", ansi.bold + C.assistant); }
|
|
156
156
|
};
|
|
157
157
|
|
|
158
158
|
let lastFrame = "", renderTimer = null;
|
|
@@ -204,7 +204,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
204
204
|
state.scroll = Math.min(state.scroll, maxScroll);
|
|
205
205
|
const end = convLines.length - state.scroll;
|
|
206
206
|
const visible = convLines.slice(Math.max(0, end - convH), end);
|
|
207
|
-
const out = [`${ansi.home}${ansi.bold}${C.tool}
|
|
207
|
+
const out = [`${ansi.home}${ansi.bold}${C.tool}JuneCoder${ansi.reset}${ansi.dim} \u2502 ${sliceByWidth(agent.provider.model || "?", 30)} \u2502 ${sliceByWidth(basename(agent.cwd), Math.max(10, cols - 50))}${ansi.reset}${ansi.clearLine}`];
|
|
208
208
|
|
|
209
209
|
const pad = convH - visible.length;
|
|
210
210
|
for (let i = 0; i < pad; i++) out.push(ansi.clearLine);
|
|
@@ -429,7 +429,7 @@ export async function startTUI(agent, opts = {}) {
|
|
|
429
429
|
state.status = "Session restored";
|
|
430
430
|
} else {
|
|
431
431
|
pushLine("", C.dim);
|
|
432
|
-
pushLine("
|
|
432
|
+
pushLine("JuneCoder TUI \u2014 " + (agent.provider.model || ""), ansi.bold + C.tool);
|
|
433
433
|
pushLine("Type /help for commands, Ctrl+C to quit.", C.dim);
|
|
434
434
|
pushLine("", C.dim);
|
|
435
435
|
}
|