chels-code 0.5.1 → 0.5.3

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/dist/src/cli.js CHANGED
@@ -4,6 +4,7 @@ import path from "node:path";
4
4
  import { realpathSync } from "node:fs";
5
5
  import { writeFile } from "node:fs/promises";
6
6
  import { createInterface } from "node:readline";
7
+ import { exec } from "node:child_process";
7
8
  import { stdin, stdout } from "node:process";
8
9
  import { fileURLToPath } from "node:url";
9
10
  import { render } from "ink";
@@ -108,6 +109,36 @@ async function ensureApiKeys() {
108
109
  rl.close();
109
110
  }
110
111
  }
112
+ async function checkAndPromptUpdate() {
113
+ try {
114
+ const response = await fetch("https://registry.npmjs.org/chelscode/latest");
115
+ if (!response.ok)
116
+ return;
117
+ const data = (await response.json());
118
+ const latest = data.version;
119
+ if (!latest || latest === VERSION)
120
+ return;
121
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
122
+ const answer = await new Promise((resolve) => {
123
+ rl.question(`A new version of ChelsCode is available (${latest}). Update now? (y/n): `, (res) => resolve(res.trim()));
124
+ });
125
+ rl.close();
126
+ if (answer.toLowerCase() === "y" || answer.toLowerCase() === "yes") {
127
+ await new Promise((resolve, reject) => {
128
+ exec(`npm install -g chelscode@${latest}`, { cwd: process.cwd() }, (error, _stdout, stderr) => {
129
+ if (error)
130
+ reject(error);
131
+ else
132
+ resolve();
133
+ });
134
+ });
135
+ process.exit(0);
136
+ }
137
+ }
138
+ catch {
139
+ // Ignore update-check failures.
140
+ }
141
+ }
111
142
  async function main() {
112
143
  const options = parseOptions(process.argv.slice(2));
113
144
  if (options.help) {
@@ -157,6 +188,7 @@ async function main() {
157
188
  saveSession(session);
158
189
  const restoreTitle = installTerminalTitle("ChelsCode");
159
190
  try {
191
+ await checkAndPromptUpdate();
160
192
  const instance = render(_jsx(App, { apiKeys: apiKeys, workspace: workspace, version: VERSION, sessionId: session.id, resume: resumed, missingKeys: keysMissing }), {
161
193
  exitOnCtrlC: true,
162
194
  kittyKeyboard: { mode: "auto" },
@@ -87,8 +87,8 @@ const EFFORT_GUIDANCE = {
87
87
  low: "Keep analysis and responses brief and direct.",
88
88
  medium: "Stay clear and practical without unnecessary detail.",
89
89
  high: "Check assumptions and explain the important details.",
90
- max: "Be thorough, verify edge cases, and avoid ambiguity.",
91
- ultra: "Be exhaustive, double-check each step, and call out risks and alternatives.",
90
+ max: "Be thorough, verify edge cases, and avoid ambiguity. Act decisively and code fast while verifying.",
91
+ ultra: "Be exhaustive, double-check each step, and call out risks and alternatives. Prioritize a fast implementation while verifying as you go.",
92
92
  };
93
93
  function withEffort(messages, effort) {
94
94
  const guidance = `Work at ${effort} effort. ${EFFORT_GUIDANCE[effort]}`;
@@ -218,18 +218,19 @@ async function runToolLoop({ provider, apiKey, model, system, user, tools, maxRo
218
218
  ];
219
219
  const allowed = new Set(tools.map((tool) => tool.function.name));
220
220
  const made = [];
221
+ let roundStart = Date.now();
221
222
  const context = {
222
223
  workspace,
223
224
  confirm: async (action) => {
224
225
  const approved = await hooks.confirmAction(action);
225
226
  if (approved)
226
- hooks.onStatus(`${PROVIDERS[provider].label} is working…`);
227
+ hooks.onStatus(statusWithElapsed(`${PROVIDERS[provider].label} is working…`, roundStart));
227
228
  return approved;
228
229
  },
229
230
  onMade: (item) => appendMade(made, item),
230
231
  };
231
232
  for (let toolRound = 0; toolRound < maxRounds; toolRound += 1) {
232
- const roundStart = Date.now();
233
+ roundStart = Date.now();
233
234
  hooks.onStatus(statusWithElapsed(`${PROVIDERS[provider].label} is thinking…`, roundStart));
234
235
  let assistantMessage;
235
236
  try {
@@ -260,7 +261,7 @@ async function runToolLoop({ provider, apiKey, model, system, user, tools, maxRo
260
261
  result = `Tool error: ${message}.`;
261
262
  }
262
263
  else {
263
- hooks.onStatus(`${PROVIDERS[provider].label} is working…`);
264
+ hooks.onStatus(statusWithElapsed(`${PROVIDERS[provider].label} is working…`, roundStart));
264
265
  try {
265
266
  result = await executeTool(name, toolCall.function?.arguments ?? "{}", context);
266
267
  }
@@ -296,7 +297,8 @@ async function runAgentTask({ provider, apiKey, model, request, task, workspace,
296
297
  }
297
298
  async function answerQuestion({ request, apiKeys, models, workspace, hooks, effort, }) {
298
299
  const provider = "openrouter";
299
- hooks.onStatus(`${PROVIDERS[provider].label} is answering…`);
300
+ const answerStart = Date.now();
301
+ hooks.onStatus(statusWithElapsed(`${PROVIDERS[provider].label} is answering…`, answerStart));
300
302
  try {
301
303
  const { text, made } = await runToolLoop({
302
304
  provider,
@@ -385,7 +387,8 @@ export function createEngine({ apiKeys, models, workspace, hooks, effort }) {
385
387
  throw new Error(`All three AIs must analyze the task before it can continue.\n${failures.join("\n")}`);
386
388
  }
387
389
  hooks.onStatus(null);
388
- hooks.onStatus("OpenRouter is combining the proposals…");
390
+ const combineStart = Date.now();
391
+ hooks.onStatus(statusWithElapsed("OpenRouter is combining the proposals…", combineStart));
389
392
  let tasks;
390
393
  try {
391
394
  tasks = await coordinatePlan({
@@ -431,10 +434,35 @@ export function createEngine({ apiKeys, models, workspace, hooks, effort }) {
431
434
  title: `${PROVIDERS[provider].label} subtask failed`,
432
435
  message: errorMessage(error),
433
436
  });
437
+ // OpenRouter is the preferred coder. If it is having a hard time,
438
+ // divide the task across the other available AIs instead of stopping.
439
+ if (provider === "openrouter") {
440
+ const fallbacks = ["nvidia", "groq"];
441
+ for (const fallbackProvider of fallbacks) {
442
+ try {
443
+ const fallbackResult = await runAgentTask({
444
+ provider: fallbackProvider,
445
+ apiKey: apiKeys[fallbackProvider],
446
+ model: models[fallbackProvider].id,
447
+ request,
448
+ task,
449
+ workspace,
450
+ hooks,
451
+ effort,
452
+ });
453
+ outcomes.push({ title: task.title, provider: fallbackProvider, result: fallbackResult.text, failed: false });
454
+ break;
455
+ }
456
+ catch (fallbackError) {
457
+ outcomes.push({ title: task.title, provider: fallbackProvider, result: errorMessage(fallbackError), failed: true });
458
+ }
459
+ }
460
+ }
434
461
  break;
435
462
  }
436
463
  }
437
- hooks.onStatus("OpenRouter is writing the final answer…");
464
+ const finalStart = Date.now();
465
+ hooks.onStatus(statusWithElapsed("OpenRouter is writing the final answer…", finalStart));
438
466
  try {
439
467
  const finalText = await askForFinalReport({
440
468
  apiKey: apiKeys.openrouter,
@@ -1 +1 @@
1
- export const VERSION = "0.5.0";
1
+ export const VERSION = "0.5.3";
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "chels-code",
3
- "version": "0.5.1",
3
+ "version": "0.5.3",
4
4
  "description": "A TypeScript + Ink terminal coding assistant for OpenRouter, NVIDIA, and Groq",
5
5
  "type": "module",
6
6
  "bin": {