@axplusb/kepler 1.0.9 → 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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@axplusb/kepler",
3
- "version": "1.0.9",
3
+ "version": "1.0.10",
4
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": {
@@ -271,7 +271,7 @@ export function calculateCost(usage) {
271
271
  // ── Formatting ───────────────────────────────────────────────
272
272
 
273
273
  /**
274
- * Format a cost value as a dollar string.
274
+ * Format a cost value as a dollar string (diagnostic/telemetry only).
275
275
  * @param {number} cost
276
276
  * @returns {string}
277
277
  */
@@ -281,6 +281,28 @@ export function formatCostValue(cost) {
281
281
  return `$${cost.toFixed(2)}`;
282
282
  }
283
283
 
284
+ /**
285
+ * Convert provider cost to Kepler credits.
286
+ * 100 credits = $1 of retail model usage, 2x multiplier.
287
+ * @param {number} costUsd
288
+ * @returns {number}
289
+ */
290
+ export function costToCredits(costUsd) {
291
+ if (costUsd <= 0) return 0;
292
+ return Math.max(1, Math.ceil(costUsd * 2.0 * 100));
293
+ }
294
+
295
+ /**
296
+ * Format credits for display (e.g. "5 cr", "1.2k cr").
297
+ * @param {number} credits
298
+ * @returns {string}
299
+ */
300
+ export function formatCredits(credits) {
301
+ if (credits === 0) return '0 cr';
302
+ if (credits >= 1000) return `${(credits / 1000).toFixed(1)}k cr`;
303
+ return `${credits} cr`;
304
+ }
305
+
284
306
  /**
285
307
  * Format a token count for display (e.g. 42100 → '42.1k').
286
308
  * @param {number} tokens
@@ -113,6 +113,25 @@ export function createToolExecutor({
113
113
  }
114
114
  }
115
115
 
116
+ // ── Post-edit verification hint ──────────────────────────────
117
+ // Appended to edit_file/write_file results so the model knows
118
+ // exactly how to verify. Uses detected project commands.
119
+
120
+ function verificationHint(filePath) {
121
+ const project = projectRegistry.projectForPath(filePath);
122
+ const commands = project?.resource?.commands || {};
123
+ const parts = [];
124
+ if (commands.test) {
125
+ parts.push(`Run tests: ${commands.test}`);
126
+ }
127
+ if (parts.length === 0) {
128
+ const ext = path.extname(filePath);
129
+ if (ext === '.py') parts.push('Run tests: python -m pytest');
130
+ else if (['.js', '.ts', '.tsx', '.mjs'].includes(ext)) parts.push('Run tests: npm test');
131
+ }
132
+ return parts.length ? `\n--- Verify ---\n${parts.join('\n')}` : '';
133
+ }
134
+
116
135
  // ── Tool mapping table ──────────────────────────────────────
117
136
 
118
137
  const toolMap = {
@@ -275,10 +294,14 @@ export function createToolExecutor({
275
294
  // Auto-lint the written file
276
295
  const lintOutput = autoLint(filePath);
277
296
  if (lintOutput) {
278
- wrapped.output += `\n\n--- Lint result ---\n${lintOutput}`;
297
+ wrapped.output += `\n\n--- Lint ---\n${lintOutput}`;
279
298
  wrapped.lint = lintOutput;
280
299
  }
281
300
 
301
+ // Nudge: tell the model how to verify
302
+ const hint = verificationHint(filePath);
303
+ if (hint) wrapped.output += hint;
304
+
282
305
  return wrapped;
283
306
  },
284
307
 
@@ -399,10 +422,14 @@ print('OK: replaced')
399
422
  // Auto-lint the edited file
400
423
  const lintOutput = autoLint(filePath);
401
424
  if (lintOutput) {
402
- wrapped.output += `\n\n--- Lint result ---\n${lintOutput}`;
425
+ wrapped.output += `\n\n--- Lint ---\n${lintOutput}`;
403
426
  wrapped.lint = lintOutput;
404
427
  }
405
428
 
429
+ // Nudge: tell the model how to verify
430
+ const hint = verificationHint(filePath);
431
+ if (hint) wrapped.output += hint;
432
+
406
433
  return wrapped;
407
434
  },
408
435
 
@@ -399,7 +399,7 @@ export function formatElapsed(startMs) {
399
399
 
400
400
  // ── Format Cost ──
401
401
 
402
- import { calculateCost, formatCostValue } from '../core/pricing.mjs';
402
+ import { calculateCost, formatCostValue, costToCredits, formatCredits } from '../core/pricing.mjs';
403
403
 
404
404
  /**
405
405
  * Format cost from token counts.
@@ -407,15 +407,13 @@ import { calculateCost, formatCostValue } from '../core/pricing.mjs';
407
407
  * or a single usage object with optional per-model breakdown.
408
408
  */
409
409
  export function formatCost(inputOrUsage, outputTokens) {
410
- // New API: pass a usage object directly
411
410
  if (typeof inputOrUsage === 'object' && inputOrUsage !== null) {
412
411
  const { total } = calculateCost(inputOrUsage);
413
- return formatCostValue(total);
412
+ return formatCredits(costToCredits(total));
414
413
  }
415
- // Legacy API: flat input/output token counts, default pricing
416
414
  const { total } = calculateCost({
417
415
  input_tokens: inputOrUsage || 0,
418
416
  output_tokens: outputTokens || 0,
419
417
  });
420
- return formatCostValue(total);
418
+ return formatCredits(costToCredits(total));
421
419
  }
@@ -19,7 +19,7 @@ import * as readline from 'node:readline';
19
19
  import * as path from 'node:path';
20
20
  import * as fs from 'node:fs';
21
21
  import { c, progressBar, spinner, inPlace, renderMarkdown, renderDiff, formatElapsed, formatCost, stripAnsi } from './ansi.mjs';
22
- import { calculateCost, formatCostValue, formatTokens } from '../core/pricing.mjs';
22
+ import { calculateCost, formatCostValue, formatTokens, costToCredits, formatCredits } from '../core/pricing.mjs';
23
23
  import { TarangStreamClient, EVENT_TYPES } from '../core/stream-client.mjs';
24
24
  import { JsonlWriter } from '../core/jsonl-writer.mjs';
25
25
  import { createToolExecutor } from '../core/tool-executor.mjs';
@@ -163,13 +163,12 @@ function printBanner(auth) {
163
163
  */
164
164
  function buildContextStrip() {
165
165
  const totalTokens = session.inputTokens + session.outputTokens;
166
- const cost = formatCostValue(session.totalCost);
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(cost),
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(formatCostValue(turnCost));
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
  }
@@ -783,7 +782,7 @@ async function handleCommand(input, ctx) {
783
782
  process.stderr.write(` ${c.dim('Turns')} ${session.turns}\n`);
784
783
  process.stderr.write(` ${c.dim('Tools')} ${session.totalToolCalls} total, ${session.toolCalls} last turn\n`);
785
784
  process.stderr.write(` ${c.dim('Duration')} ${formatElapsed(session.startTime)}\n`);
786
- process.stderr.write(` ${c.dim('Cost')} ${formatCostValue(session.totalCost)}${session.costAccurate ? '' : c.dim(' (est)')}\n`);
785
+ process.stderr.write(` ${c.dim('Credits')} ${formatCredits(costToCredits(session.totalCost))}${session.costAccurate ? '' : c.dim(' (est)')}\n`);
787
786
  process.stderr.write(` ${c.dim('CWD')} ${safeCwd()}\n`);
788
787
 
789
788
  // Permissions
@@ -851,29 +850,29 @@ async function handleCommand(input, ctx) {
851
850
  process.stderr.write(` ${c.gray('Turns:')} ${session.turns}\n`);
852
851
  process.stderr.write(` ${c.gray('Tools:')} ${session.toolCalls}\n`);
853
852
  process.stderr.write(` ${c.gray('Blocked:')} ${session.blockedOps}\n`);
854
- process.stderr.write(` ${c.gray('Cost:')} ${formatCostValue(session.totalCost)}${session.costAccurate ? '' : c.dim(' (est)')}\n`);
853
+ process.stderr.write(` ${c.gray('Credits:')} ${formatCredits(costToCredits(session.totalCost))}${session.costAccurate ? '' : c.dim(' (est)')}\n`);
855
854
  process.stderr.write(` ${c.gray('Elapsed:')} ${formatElapsed(session.startTime)}\n\n`);
856
855
  return;
857
856
  }
858
857
 
859
858
  case '/cost': {
860
- process.stderr.write(`\n ${c.bold('Session Cost')}`);
859
+ process.stderr.write(`\n ${c.bold('Session Credits')} ${c.brand(formatCredits(costToCredits(session.totalCost)))}`);
861
860
  if (!session.costAccurate) {
862
- process.stderr.write(` ${c.yellow('(estimated — backend not sending model breakdown)')}`);
861
+ process.stderr.write(` ${c.yellow('(estimated)')}`);
863
862
  }
864
863
  process.stderr.write('\n');
865
864
  process.stderr.write(` ${c.dim('─'.repeat(70))}\n`);
866
865
 
867
866
  if (session.costBreakdown.length > 0) {
868
867
  // Header
869
- 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('Cost'.padStart(10))}\n`);
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`);
870
869
  process.stderr.write(` ${c.dim('─'.repeat(70))}\n`);
871
870
 
872
871
  for (const b of session.costBreakdown) {
873
872
  const modelLabel = b.model === 'unknown' ? c.yellow('unknown model') : b.model;
874
873
  const roleTag = b.role && b.role !== 'unknown' ? ` ${c.dim(`(${b.role})`)}` : '';
875
874
  const cacheTokens = (b.cache_read_tokens || 0) + (b.cache_creation_tokens || 0);
876
- const costStr = b.free ? c.green('free') : formatCostValue(b.cost);
875
+ const costStr = b.free ? c.green('free') : formatCredits(costToCredits(b.cost));
877
876
 
878
877
  process.stderr.write(
879
878
  ` ${(modelLabel + roleTag).padEnd(36)}` +
@@ -892,9 +891,9 @@ async function handleCommand(input, ctx) {
892
891
  `${formatTokens(session.inputTokens).padStart(10)}` +
893
892
  `${formatTokens(session.outputTokens).padStart(10)}` +
894
893
  `${''.padStart(10)}` +
895
- `${formatCostValue(session.totalCost).padStart(10)}\n`
894
+ `${formatCredits(costToCredits(session.totalCost)).padStart(10)}\n`
896
895
  );
897
- 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`);
898
897
  return;
899
898
  }
900
899