@axplusb/kepler 1.0.5 → 1.0.10
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 +34 -0
- package/package.json +4 -4
- package/src/core/headless.mjs +68 -24
- package/src/core/pricing.mjs +23 -1
- package/src/core/project-artifacts.mjs +37 -0
- package/src/core/tool-executor.mjs +192 -57
- package/src/skills/installer.mjs +188 -0
- package/src/skills/loader.mjs +217 -112
- package/src/terminal/ansi.mjs +3 -5
- package/src/terminal/main.mjs +18 -0
- package/src/terminal/repl.mjs +38 -60
- package/src/terminal/skills.mjs +54 -0
- package/src/tools/bash.mjs +5 -2
- package/src/tools/project-overview.mjs +418 -0
- package/src/tools/registry.mjs +0 -16
package/src/terminal/repl.mjs
CHANGED
|
@@ -17,17 +17,17 @@
|
|
|
17
17
|
|
|
18
18
|
import * as readline from 'node:readline';
|
|
19
19
|
import * as path from 'node:path';
|
|
20
|
+
import * as fs from 'node:fs';
|
|
20
21
|
import { c, progressBar, spinner, inPlace, renderMarkdown, renderDiff, formatElapsed, formatCost, stripAnsi } from './ansi.mjs';
|
|
21
|
-
import { calculateCost, formatCostValue, formatTokens } from '../core/pricing.mjs';
|
|
22
|
+
import { calculateCost, formatCostValue, formatTokens, costToCredits, formatCredits } from '../core/pricing.mjs';
|
|
22
23
|
import { TarangStreamClient, EVENT_TYPES } from '../core/stream-client.mjs';
|
|
23
24
|
import { JsonlWriter } from '../core/jsonl-writer.mjs';
|
|
24
25
|
import { createToolExecutor } from '../core/tool-executor.mjs';
|
|
26
|
+
import { persistProjectArtifacts } from '../core/project-artifacts.mjs';
|
|
25
27
|
import { TarangAuth } from '../auth/tarang-auth.mjs';
|
|
26
28
|
import { ApprovalManager } from '../core/approval.mjs';
|
|
27
29
|
import { resolveBackendUrl } from '../core/backend-url.mjs';
|
|
28
30
|
import { BUILTIN_AGENTS, runAgent } from './agents.mjs';
|
|
29
|
-
import { ContextRetriever } from '../context/retriever.mjs';
|
|
30
|
-
import { buildProjectSkeleton } from '../context/skeleton.mjs';
|
|
31
31
|
import { SessionManager } from '../core/session-manager.mjs';
|
|
32
32
|
import { parseArgs } from '../config/cli-args.mjs';
|
|
33
33
|
import { formatShellCommand, toolDisplayLabel, toolDisplaySummary } from './tool-display.mjs';
|
|
@@ -163,13 +163,12 @@ function printBanner(auth) {
|
|
|
163
163
|
*/
|
|
164
164
|
function buildContextStrip() {
|
|
165
165
|
const totalTokens = session.inputTokens + session.outputTokens;
|
|
166
|
-
const
|
|
166
|
+
const credits = formatCredits(costToCredits(session.totalCost));
|
|
167
167
|
const elapsed = formatElapsed(session.startTime);
|
|
168
168
|
|
|
169
|
-
// Right side — always shown
|
|
170
169
|
const right = [
|
|
171
170
|
c.dim(`${formatTokens(totalTokens)} tok`),
|
|
172
|
-
c.dim(
|
|
171
|
+
c.dim(credits),
|
|
173
172
|
c.dim(elapsed),
|
|
174
173
|
].join(c.dim(' · '));
|
|
175
174
|
|
|
@@ -200,7 +199,7 @@ function printTurnSummary(toolCount, durationS, turnCost) {
|
|
|
200
199
|
const parts = [];
|
|
201
200
|
if (toolCount > 0) parts.push(`${toolCount} tools`);
|
|
202
201
|
if (durationS) parts.push(`${Number(durationS).toFixed(1)}s`);
|
|
203
|
-
if (turnCost > 0) parts.push(
|
|
202
|
+
if (turnCost > 0) parts.push(formatCredits(costToCredits(turnCost)));
|
|
204
203
|
if (parts.length > 0) {
|
|
205
204
|
process.stderr.write(`\n ${c.green('✓')} ${c.dim(parts.join(' · '))}\n`);
|
|
206
205
|
}
|
|
@@ -621,6 +620,16 @@ function renderEvent(event) {
|
|
|
621
620
|
break;
|
|
622
621
|
}
|
|
623
622
|
|
|
623
|
+
case 'plan_created': {
|
|
624
|
+
process.stderr.write(` ${c.dim('project plan prepared')}\n`);
|
|
625
|
+
break;
|
|
626
|
+
}
|
|
627
|
+
|
|
628
|
+
case 'goal_created': {
|
|
629
|
+
process.stderr.write(` ${c.dim('project goal prepared')}\n`);
|
|
630
|
+
break;
|
|
631
|
+
}
|
|
632
|
+
|
|
624
633
|
case 'session_info': {
|
|
625
634
|
if (data?.session_id) {
|
|
626
635
|
session.id = data.session_id;
|
|
@@ -773,7 +782,7 @@ async function handleCommand(input, ctx) {
|
|
|
773
782
|
process.stderr.write(` ${c.dim('Turns')} ${session.turns}\n`);
|
|
774
783
|
process.stderr.write(` ${c.dim('Tools')} ${session.totalToolCalls} total, ${session.toolCalls} last turn\n`);
|
|
775
784
|
process.stderr.write(` ${c.dim('Duration')} ${formatElapsed(session.startTime)}\n`);
|
|
776
|
-
process.stderr.write(` ${c.dim('
|
|
785
|
+
process.stderr.write(` ${c.dim('Credits')} ${formatCredits(costToCredits(session.totalCost))}${session.costAccurate ? '' : c.dim(' (est)')}\n`);
|
|
777
786
|
process.stderr.write(` ${c.dim('CWD')} ${safeCwd()}\n`);
|
|
778
787
|
|
|
779
788
|
// Permissions
|
|
@@ -841,29 +850,29 @@ async function handleCommand(input, ctx) {
|
|
|
841
850
|
process.stderr.write(` ${c.gray('Turns:')} ${session.turns}\n`);
|
|
842
851
|
process.stderr.write(` ${c.gray('Tools:')} ${session.toolCalls}\n`);
|
|
843
852
|
process.stderr.write(` ${c.gray('Blocked:')} ${session.blockedOps}\n`);
|
|
844
|
-
process.stderr.write(` ${c.gray('
|
|
853
|
+
process.stderr.write(` ${c.gray('Credits:')} ${formatCredits(costToCredits(session.totalCost))}${session.costAccurate ? '' : c.dim(' (est)')}\n`);
|
|
845
854
|
process.stderr.write(` ${c.gray('Elapsed:')} ${formatElapsed(session.startTime)}\n\n`);
|
|
846
855
|
return;
|
|
847
856
|
}
|
|
848
857
|
|
|
849
858
|
case '/cost': {
|
|
850
|
-
process.stderr.write(`\n ${c.bold('Session
|
|
859
|
+
process.stderr.write(`\n ${c.bold('Session Credits')} ${c.brand(formatCredits(costToCredits(session.totalCost)))}`);
|
|
851
860
|
if (!session.costAccurate) {
|
|
852
|
-
process.stderr.write(` ${c.yellow('(estimated
|
|
861
|
+
process.stderr.write(` ${c.yellow('(estimated)')}`);
|
|
853
862
|
}
|
|
854
863
|
process.stderr.write('\n');
|
|
855
864
|
process.stderr.write(` ${c.dim('─'.repeat(70))}\n`);
|
|
856
865
|
|
|
857
866
|
if (session.costBreakdown.length > 0) {
|
|
858
867
|
// Header
|
|
859
|
-
process.stderr.write(` ${c.dim('Model'.padEnd(36))}${c.dim('Input'.padStart(10))}${c.dim('Output'.padStart(10))}${c.dim('Cache'.padStart(10))}${c.dim('
|
|
868
|
+
process.stderr.write(` ${c.dim('Model'.padEnd(36))}${c.dim('Input'.padStart(10))}${c.dim('Output'.padStart(10))}${c.dim('Cache'.padStart(10))}${c.dim('Credits'.padStart(10))}\n`);
|
|
860
869
|
process.stderr.write(` ${c.dim('─'.repeat(70))}\n`);
|
|
861
870
|
|
|
862
871
|
for (const b of session.costBreakdown) {
|
|
863
872
|
const modelLabel = b.model === 'unknown' ? c.yellow('unknown model') : b.model;
|
|
864
873
|
const roleTag = b.role && b.role !== 'unknown' ? ` ${c.dim(`(${b.role})`)}` : '';
|
|
865
874
|
const cacheTokens = (b.cache_read_tokens || 0) + (b.cache_creation_tokens || 0);
|
|
866
|
-
const costStr = b.free ? c.green('free') :
|
|
875
|
+
const costStr = b.free ? c.green('free') : formatCredits(costToCredits(b.cost));
|
|
867
876
|
|
|
868
877
|
process.stderr.write(
|
|
869
878
|
` ${(modelLabel + roleTag).padEnd(36)}` +
|
|
@@ -882,9 +891,9 @@ async function handleCommand(input, ctx) {
|
|
|
882
891
|
`${formatTokens(session.inputTokens).padStart(10)}` +
|
|
883
892
|
`${formatTokens(session.outputTokens).padStart(10)}` +
|
|
884
893
|
`${''.padStart(10)}` +
|
|
885
|
-
`${
|
|
894
|
+
`${formatCredits(costToCredits(session.totalCost)).padStart(10)}\n`
|
|
886
895
|
);
|
|
887
|
-
process.stderr.write(` ${c.dim(`Turns: ${session.turns} Duration: ${formatElapsed(session.startTime)}`)}\n\n`);
|
|
896
|
+
process.stderr.write(` ${c.dim(`Turns: ${session.turns} Duration: ${formatElapsed(session.startTime)} Provider: ${formatCostValue(session.totalCost)}`)}\n\n`);
|
|
888
897
|
return;
|
|
889
898
|
}
|
|
890
899
|
|
|
@@ -1122,9 +1131,8 @@ export async function startTerminalRepl() {
|
|
|
1122
1131
|
const cliArgs = parseArgs(process.argv.slice(2));
|
|
1123
1132
|
const auth = new TarangAuth();
|
|
1124
1133
|
|
|
1125
|
-
//
|
|
1126
|
-
const
|
|
1127
|
-
const toolExecutor = createToolExecutor({ retriever });
|
|
1134
|
+
// Projects are registered and indexed on demand through get_project_overview.
|
|
1135
|
+
const toolExecutor = createToolExecutor();
|
|
1128
1136
|
const skipPerms = cliArgs.freeswim;
|
|
1129
1137
|
const approval = new ApprovalManager({ autoApprove: skipPerms });
|
|
1130
1138
|
|
|
@@ -1142,51 +1150,13 @@ export async function startTerminalRepl() {
|
|
|
1142
1150
|
|
|
1143
1151
|
printBanner(auth);
|
|
1144
1152
|
|
|
1145
|
-
// ── Initialization
|
|
1146
|
-
// BM25 indexing is CPU-bound and blocks the event loop, so setInterval
|
|
1147
|
-
// spinners won't tick during it. Instead, show a static "Initializing..."
|
|
1148
|
-
// message, then yield to the event loop between phases so the spinner runs.
|
|
1149
|
-
let projectSkeleton = '';
|
|
1150
|
-
|
|
1151
|
-
// Phase 1: Show immediate feedback
|
|
1153
|
+
// ── Initialization ──
|
|
1152
1154
|
process.stderr.write(` ${c.brand('⠋')} ${c.dim('Initializing...')}\r`);
|
|
1153
|
-
|
|
1154
|
-
// Fetch user in parallel (network I/O, won't block event loop)
|
|
1155
|
-
const userPromise = fetchUser(ctx);
|
|
1156
|
-
|
|
1157
|
-
// Phase 2: BM25 index — CPU-bound, blocks event loop.
|
|
1158
|
-
// Wrap in a microtask break so the initial message renders first.
|
|
1159
|
-
const indexResult = await new Promise((resolve) => {
|
|
1160
|
-
// Let the event loop flush stderr before blocking
|
|
1161
|
-
setImmediate(async () => {
|
|
1162
|
-
try {
|
|
1163
|
-
process.stderr.write(`\r ${c.brand('⠹')} ${c.dim('Indexing project files...')}${' '.repeat(20)}\r`);
|
|
1164
|
-
const result = await retriever.buildIndex();
|
|
1165
|
-
resolve(result);
|
|
1166
|
-
} catch {
|
|
1167
|
-
resolve({ fileCount: 0, chunkCount: 0 });
|
|
1168
|
-
}
|
|
1169
|
-
});
|
|
1170
|
-
});
|
|
1171
|
-
|
|
1172
|
-
// Phase 3: Build skeleton (fast, synchronous)
|
|
1173
|
-
process.stderr.write(`\r ${c.brand('⠼')} ${c.dim('Building project skeleton...')}${' '.repeat(20)}\r`);
|
|
1174
|
-
await new Promise(r => setImmediate(r)); // yield so message renders
|
|
1175
|
-
projectSkeleton = buildProjectSkeleton(safeCwd());
|
|
1176
|
-
|
|
1177
|
-
// Wait for user fetch
|
|
1178
|
-
await userPromise;
|
|
1155
|
+
await fetchUser(ctx);
|
|
1179
1156
|
|
|
1180
1157
|
// Clear the spinner line
|
|
1181
1158
|
process.stderr.write(`\r${' '.repeat(60)}\r`);
|
|
1182
|
-
|
|
1183
|
-
// Show init summary
|
|
1184
|
-
if (indexResult.fileCount > 0) {
|
|
1185
|
-
process.stderr.write(` ${c.green('✓')} ${c.dim(`Indexed ${indexResult.fileCount} files (${indexResult.chunkCount} chunks)`)}\n`);
|
|
1186
|
-
}
|
|
1187
|
-
if (projectSkeleton) {
|
|
1188
|
-
process.stderr.write(` ${c.green('✓')} ${c.dim('Project skeleton ready')}\n`);
|
|
1189
|
-
}
|
|
1159
|
+
process.stderr.write(` ${c.green('✓')} ${c.dim('Ready; projects will be indexed on demand')}\n`);
|
|
1190
1160
|
if (session.user) {
|
|
1191
1161
|
process.stderr.write(` ${c.green('✓')} ${c.dim(`Logged in as ${session.user.github_username || session.user.email || 'user'}`)}\n`);
|
|
1192
1162
|
}
|
|
@@ -1367,9 +1337,17 @@ export async function startTerminalRepl() {
|
|
|
1367
1337
|
|
|
1368
1338
|
const execContext = { cwd: safeCwd() };
|
|
1369
1339
|
if (skipPerms) execContext.freeswim = true;
|
|
1370
|
-
|
|
1340
|
+
execContext.project_resources = toolExecutor.getProjectResources();
|
|
1341
|
+
execContext.agent_context = toolExecutor.getAgentContext();
|
|
1371
1342
|
|
|
1372
1343
|
for await (const event of client.execute(input, execContext, session.history)) {
|
|
1344
|
+
if (event.type === 'plan_created' || event.type === 'goal_created') {
|
|
1345
|
+
persistProjectArtifacts(
|
|
1346
|
+
event.data,
|
|
1347
|
+
toolExecutor.getProjectResources(),
|
|
1348
|
+
message => process.stderr.write(` ${c.dim(message)}\n`),
|
|
1349
|
+
);
|
|
1350
|
+
}
|
|
1373
1351
|
renderEvent(event);
|
|
1374
1352
|
|
|
1375
1353
|
if (event.type === 'content_partial') {
|
|
@@ -0,0 +1,54 @@
|
|
|
1
|
+
import { SkillInstaller } from '../skills/installer.mjs';
|
|
2
|
+
import { SkillsLoader } from '../skills/loader.mjs';
|
|
3
|
+
|
|
4
|
+
function has(args, flag) {
|
|
5
|
+
return args.includes(flag);
|
|
6
|
+
}
|
|
7
|
+
|
|
8
|
+
function scopeFrom(args) {
|
|
9
|
+
return has(args, '--project') ? 'project' : 'global';
|
|
10
|
+
}
|
|
11
|
+
|
|
12
|
+
function print(value) {
|
|
13
|
+
process.stdout.write(typeof value === 'string' ? `${value}\n` : `${JSON.stringify(value, null, 2)}\n`);
|
|
14
|
+
}
|
|
15
|
+
|
|
16
|
+
export async function runSkillsCommand(args, { cwd = process.cwd() } = {}) {
|
|
17
|
+
const action = args[0] || 'list';
|
|
18
|
+
const rest = args.slice(1);
|
|
19
|
+
const scope = scopeFrom(rest);
|
|
20
|
+
const installer = new SkillInstaller({ cwd });
|
|
21
|
+
const loader = new SkillsLoader().load(cwd);
|
|
22
|
+
|
|
23
|
+
if (action === 'list') {
|
|
24
|
+
const rows = loader.list({ scope: has(rest, '--all') ? '' : scope });
|
|
25
|
+
if (has(rest, '--json')) print(rows);
|
|
26
|
+
else if (!rows.length) print('No skills found.');
|
|
27
|
+
else for (const row of rows) print(`${row.name}\t${row.scope}\t${row.source}\t${row.description}`);
|
|
28
|
+
return;
|
|
29
|
+
}
|
|
30
|
+
if (action === 'view') {
|
|
31
|
+
const name = rest.find(arg => !arg.startsWith('--'));
|
|
32
|
+
if (!name) throw new Error('Usage: kepler skills view <name> [resource-path]');
|
|
33
|
+
const nameIndex = rest.indexOf(name);
|
|
34
|
+
const resource = rest.slice(nameIndex + 1).find(arg => !arg.startsWith('--')) || null;
|
|
35
|
+
print(loader.view(name, resource));
|
|
36
|
+
return;
|
|
37
|
+
}
|
|
38
|
+
if (action === 'install') {
|
|
39
|
+
const source = rest.find(arg => !arg.startsWith('--'));
|
|
40
|
+
print(installer.install(source, { scope, force: has(rest, '--force') }));
|
|
41
|
+
return;
|
|
42
|
+
}
|
|
43
|
+
if (action === 'remove') {
|
|
44
|
+
const name = rest.find(arg => !arg.startsWith('--'));
|
|
45
|
+
print(installer.remove(name, { scope }));
|
|
46
|
+
return;
|
|
47
|
+
}
|
|
48
|
+
if (action === 'update') {
|
|
49
|
+
const name = rest.find(arg => !arg.startsWith('--'));
|
|
50
|
+
print(installer.update(name, { scope }));
|
|
51
|
+
return;
|
|
52
|
+
}
|
|
53
|
+
throw new Error(`Unknown skills command: ${action}`);
|
|
54
|
+
}
|
package/src/tools/bash.mjs
CHANGED
|
@@ -27,6 +27,7 @@ export const BashTool = {
|
|
|
27
27
|
command: { type: 'string', description: 'The command to execute' },
|
|
28
28
|
timeout: { type: 'number', description: 'Timeout in ms (max 600000)', default: 120000 },
|
|
29
29
|
description: { type: 'string', description: 'Description of what this command does' },
|
|
30
|
+
cwd: { type: 'string', description: 'Working directory for the command' },
|
|
30
31
|
run_in_background: { type: 'boolean', description: 'Run in background', default: false },
|
|
31
32
|
},
|
|
32
33
|
required: ['command'],
|
|
@@ -40,7 +41,7 @@ export const BashTool = {
|
|
|
40
41
|
const timeout = Math.min(input.timeout || 120000, 600000);
|
|
41
42
|
|
|
42
43
|
if (input.run_in_background) {
|
|
43
|
-
return runBackground(input.command);
|
|
44
|
+
return runBackground(input.command, input.cwd);
|
|
44
45
|
}
|
|
45
46
|
|
|
46
47
|
return new Promise((resolve) => {
|
|
@@ -50,6 +51,7 @@ export const BashTool = {
|
|
|
50
51
|
let exitCode = null;
|
|
51
52
|
|
|
52
53
|
const proc = spawn('bash', ['-c', input.command], {
|
|
54
|
+
cwd: input.cwd,
|
|
53
55
|
env: { ...process.env },
|
|
54
56
|
stdio: ['pipe', 'pipe', 'pipe'],
|
|
55
57
|
timeout: 0, // we handle timeout ourselves
|
|
@@ -120,9 +122,10 @@ export const BashTool = {
|
|
|
120
122
|
const backgroundJobs = new Map();
|
|
121
123
|
let bgJobId = 0;
|
|
122
124
|
|
|
123
|
-
function runBackground(command) {
|
|
125
|
+
function runBackground(command, cwd) {
|
|
124
126
|
const id = ++bgJobId;
|
|
125
127
|
const proc = spawn('bash', ['-c', command], {
|
|
128
|
+
cwd,
|
|
126
129
|
detached: true,
|
|
127
130
|
stdio: ['ignore', 'pipe', 'pipe'],
|
|
128
131
|
});
|