@axplusb/kepler 1.0.5 → 1.0.9

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/KEPLER-README.md CHANGED
@@ -26,9 +26,42 @@ kepler dashboard Open Kepler Pulse analytics dashboard
26
26
  kepler sessions List recent local sessions
27
27
  kepler stats Aggregate session stats (tokens, cost, tools)
28
28
  kepler history Recent prompt history
29
+ kepler skills list List discovered project and global skills
30
+ kepler skills view NAME View a skill procedure or bundled resource
31
+ kepler skills install SRC Install skills from a directory or Git repository
32
+ kepler skills update NAME Update a locked installed skill
33
+ kepler skills remove NAME Remove an installed skill
29
34
  kepler version Show version
30
35
  ```
31
36
 
37
+ ## Agent Skills
38
+
39
+ Kepler supports portable `SKILL.md` bundles and Claude-compatible skill
40
+ directories. Skill metadata is included in the cached agent context; full
41
+ instructions and references are loaded only when the agent calls
42
+ `skill_view`.
43
+
44
+ Discovery precedence:
45
+
46
+ 1. `<project>/.kepler/skills`
47
+ 2. `<project>/.claude/skills`
48
+ 3. `~/.kepler/skills`
49
+ 4. `~/.claude/skills`
50
+
51
+ Install a local bundle or a repository containing one or more skills:
52
+
53
+ ```bash
54
+ kepler skills install ./my-skill
55
+ kepler skills install github:owner/skills-repository
56
+ kepler skills install https://github.com/owner/skills.git
57
+ kepler skills install ./team-skills --project
58
+ ```
59
+
60
+ Global installations are recorded in `~/.kepler/skills.lock.json`. Project
61
+ installations use `<project>/.kepler/skills.lock.json`. Kepler copies skill
62
+ files as data, rejects symlinks, and does not execute bundled scripts during
63
+ installation.
64
+
32
65
  ## REPL Commands
33
66
 
34
67
  ```
@@ -84,6 +117,7 @@ Platform default included free. Bring your own API key for unlimited.
84
117
 
85
118
  | Model | Score | Cost |
86
119
  |-------|-------|------|
120
+ | DeepSeek V4 Flash + stagnation | 47.3% (142/300) | $0.046/instance |
87
121
  | DeepSeek V4 Flash | 39.7% (119/300) | $0.046/instance |
88
122
  | DeepSeek V4 Flash (baseline) | 30.7% (92/300) | $0.03/instance |
89
123
  | DeepSeek V4 Pro | 50% (14/28 sample) | $0.48/instance |
package/package.json CHANGED
@@ -1,7 +1,7 @@
1
1
  {
2
2
  "name": "@axplusb/kepler",
3
- "version": "1.0.5",
4
- "description": "Kepler — AI coding agent that plans, builds, tests, and ships. 30.7% on SWE-bench Lite.",
3
+ "version": "1.0.9",
4
+ "description": "Kepler — AI coding agent with operating brief, preflight planning, and sub-agents. SWE-bench Lite evaluated.",
5
5
  "type": "module",
6
6
  "bin": {
7
7
  "kepler": "src/terminal/main.mjs"
@@ -14,7 +14,7 @@
14
14
  "readme": "KEPLER-README.md",
15
15
  "scripts": {
16
16
  "start": "node src/terminal/main.mjs",
17
- "test": "node test/test-sse-client.mjs && node test/test-tool-executor.mjs && node test/test-callback.mjs && node test/test-formatter.mjs && node test/test-terminal-rendering.mjs && node test/test-slash-commands.mjs && node test/test-approval.mjs && node test/test-session-manager.mjs && node test/test-safety.mjs && node test/test-jsonl-writer.mjs && node test/test-analytics.mjs && node test/test-stagnation.mjs"
17
+ "test": "node test/test-sse-client.mjs && node test/test-tool-executor.mjs && node test/test-project-artifacts.mjs && node test/test-skills.mjs && node test/test-callback.mjs && node test/test-formatter.mjs && node test/test-terminal-rendering.mjs && node test/test-slash-commands.mjs && node test/test-approval.mjs && node test/test-session-manager.mjs && node test/test-safety.mjs && node test/test-jsonl-writer.mjs && node test/test-analytics.mjs && node test/test-stagnation.mjs"
18
18
  },
19
19
  "engines": {
20
20
  "node": ">=18.0.0"
@@ -38,7 +38,7 @@
38
38
  "homepage": "https://codekepler.ai",
39
39
  "repository": {
40
40
  "type": "git",
41
- "url": "git+https://github.com/raviakasapu/codekepler-web.git"
41
+ "url": "git+https://github.com/raviakasapu/codekepler-npm.git"
42
42
  },
43
43
  "dependencies": {}
44
44
  }
@@ -13,10 +13,9 @@
13
13
 
14
14
  import { TarangStreamClient } from './stream-client.mjs';
15
15
  import { createToolExecutor } from './tool-executor.mjs';
16
+ import { persistProjectArtifacts } from './project-artifacts.mjs';
16
17
  import { TarangAuth } from '../auth/tarang-auth.mjs';
17
18
  import { ApprovalManager } from './approval.mjs';
18
- import { ContextRetriever } from '../context/retriever.mjs';
19
- import { buildProjectSkeleton } from '../context/skeleton.mjs';
20
19
 
21
20
  /**
22
21
  * Run a single instruction in headless mode.
@@ -46,22 +45,8 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
46
45
  process.exit(1);
47
46
  }
48
47
 
49
- // ── Index project (with timeout large repos can take minutes) ──
50
- log('Indexing project...');
51
- const retriever = new ContextRetriever(process.cwd());
52
- try {
53
- const indexPromise = retriever.buildIndex();
54
- const indexTimeout = new Promise((_, reject) =>
55
- setTimeout(() => reject(new Error('Index timeout')), 15000)
56
- );
57
- await Promise.race([indexPromise, indexTimeout]);
58
- log('Index ready');
59
- } catch (e) {
60
- log(`Index skipped: ${e.message || 'failed'}`);
61
- }
62
-
63
- const skeleton = buildProjectSkeleton(process.cwd());
64
- const toolExecutor = createToolExecutor({ retriever });
48
+ // Projects are registered and indexed only when the agent requests an overview.
49
+ const toolExecutor = createToolExecutor();
65
50
 
66
51
  // Auto-approve everything — no prompts
67
52
  const approval = new ApprovalManager({ autoApprove: true });
@@ -85,14 +70,24 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
85
70
  // ── Execute ──
86
71
  emit({ type: 'start', timestamp: Date.now(), instruction, model: model || 'default', cwd: process.cwd() });
87
72
 
88
- const execContext = { cwd: process.cwd(), freeswim: true };
89
- if (skeleton) execContext.project_skeleton = skeleton;
73
+ const execContext = {
74
+ cwd: process.cwd(),
75
+ freeswim: true,
76
+ project_resources: toolExecutor.getProjectResources(),
77
+ agent_context: toolExecutor.getAgentContext(),
78
+ };
90
79
  if (model) execContext.model_override = model;
91
80
 
92
81
  let toolCount = 0;
93
82
  let finalContent = '';
94
83
  let totalCost = 0;
95
84
 
85
+ // ── Telemetry collectors ──
86
+ const toolCalls = []; // { tool, duration_ms, success }
87
+ const subAgents = []; // { type, model, duration_s, tool_calls, success }
88
+ let stagnationCount = 0;
89
+ let usage = {}; // { input_tokens, output_tokens, cache_read, cache_write }
90
+
96
91
  try {
97
92
  for await (const event of client.execute(instruction, execContext)) {
98
93
  const { type, data } = event;
@@ -101,6 +96,7 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
101
96
  toolCount++;
102
97
  const toolName = data?.tool || 'unknown';
103
98
  const args = data?.args || {};
99
+ toolCalls.push({ tool: toolName, success: null, duration_ms: 0 });
104
100
  emit({ type: 'tool_call', tool: toolName, args, approved: true });
105
101
  log(`Tool: ${toolName}`);
106
102
  }
@@ -108,9 +104,42 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
108
104
  if (type === 'tool_result' || type === 'tool_done') {
109
105
  const success = data?.success !== false;
110
106
  const duration = data?.duration_s || 0;
107
+ // Update last tool call with result
108
+ const last = toolCalls.findLast(t => t.tool === (data?.tool || ''));
109
+ if (last) { last.success = success; last.duration_ms = Math.round(duration * 1000); }
111
110
  emit({ type: 'tool_result', tool: data?.tool || '', success, duration_ms: Math.round(duration * 1000) });
112
111
  }
113
112
 
113
+ if (type === 'sub_agent_start') {
114
+ log(`SubAgent: ${data?.type} (${data?.model})`);
115
+ }
116
+
117
+ if (type === 'sub_agent_complete') {
118
+ subAgents.push({
119
+ type: data?.type || '',
120
+ model: data?.model || '',
121
+ duration_s: data?.duration_s || 0,
122
+ tool_calls: data?.tool_calls || 0,
123
+ success: data?.success !== false,
124
+ });
125
+ emit({ type: 'sub_agent', ...data });
126
+ log(`SubAgent done: ${data?.type} (${data?.tool_calls} tools, ${data?.duration_s}s)`);
127
+ }
128
+
129
+ if (type === 'plan_created' || type === 'goal_created') {
130
+ persistProjectArtifacts(
131
+ data,
132
+ toolExecutor.getProjectResources(),
133
+ log,
134
+ );
135
+ }
136
+
137
+ if (type === 'stagnation' || type === 'stagnation_detected') {
138
+ stagnationCount++;
139
+ emit({ type: 'stagnation', reason: data?.reason || '', strategy: data?.recovery_strategy || '' });
140
+ log(`Stagnation: ${data?.reason || ''}`);
141
+ }
142
+
114
143
  if (type === 'content') {
115
144
  finalContent = data?.text || '';
116
145
  }
@@ -122,10 +151,15 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
122
151
 
123
152
  if (type === 'complete') {
124
153
  totalCost = data?.cost || data?.total_cost || 0;
125
-
126
- // Extract cost from usage breakdown if available
127
- if (data?.usage?.total_cost) {
128
- totalCost = data.usage.total_cost;
154
+ if (data?.usage?.total_cost) totalCost = data.usage.total_cost;
155
+ // Capture token usage
156
+ if (data?.usage) {
157
+ usage = {
158
+ input_tokens: data.usage.total_input_tokens || data.usage.input_tokens || 0,
159
+ output_tokens: data.usage.total_output_tokens || data.usage.output_tokens || 0,
160
+ cache_read: data.usage.cache_read_input_tokens || data.usage.cache_read || 0,
161
+ cache_write: data.usage.cache_creation_input_tokens || data.usage.cache_write || 0,
162
+ };
129
163
  }
130
164
  }
131
165
 
@@ -148,9 +182,19 @@ export async function runHeadless({ instruction, model, timeout = 300, maxCost,
148
182
 
149
183
  const durationS = (Date.now() - startTime) / 1000;
150
184
 
185
+ // ── Tool breakdown ──
186
+ const toolBreakdown = {};
187
+ for (const t of toolCalls) {
188
+ toolBreakdown[t.tool] = (toolBreakdown[t.tool] || 0) + 1;
189
+ }
190
+
151
191
  emit({
152
192
  type: 'complete',
153
193
  tools: toolCount,
194
+ tool_breakdown: toolBreakdown,
195
+ sub_agents: subAgents,
196
+ stagnation_triggers: stagnationCount,
197
+ usage,
154
198
  duration_s: Math.round(durationS * 10) / 10,
155
199
  cost_usd: totalCost,
156
200
  model: model || 'default',
@@ -0,0 +1,37 @@
1
+ import * as fs from 'node:fs';
2
+ import * as path from 'node:path';
3
+
4
+ /**
5
+ * Persist generated session artifacts only beneath roots registered by the CLI.
6
+ */
7
+ export function persistProjectArtifacts(data, resources, log = () => {}) {
8
+ const projectIds = new Set(
9
+ Array.isArray(data?.project_ids) ? data.project_ids.filter(Boolean) : []
10
+ );
11
+ const registered = Array.isArray(resources) ? resources : [];
12
+ const targets = projectIds.size > 0
13
+ ? registered.filter(resource => projectIds.has(resource.project_id))
14
+ : registered.length === 1 ? registered : [];
15
+ const artifacts = [
16
+ ['goal', 'goal.md'],
17
+ ['plan', 'plan.md'],
18
+ ].filter(([field]) => typeof data?.[field] === 'string' && data[field].trim());
19
+ const written = [];
20
+
21
+ for (const resource of targets) {
22
+ for (const [field, filename] of artifacts) {
23
+ try {
24
+ const keplerDir = path.join(resource.root, '.kepler');
25
+ fs.mkdirSync(keplerDir, { recursive: true });
26
+ const artifactPath = path.join(keplerDir, filename);
27
+ fs.writeFileSync(artifactPath, data[field], 'utf-8');
28
+ resource[field] = data[field];
29
+ written.push(artifactPath);
30
+ log(`${field} saved -> ${artifactPath}`);
31
+ } catch {
32
+ // Persistence is helpful but must not stop the agent.
33
+ }
34
+ }
35
+ }
36
+ return written;
37
+ }