chels-code 0.0.0-stage → 0.5.1

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 CHANGED
@@ -1,3 +1,124 @@
1
- # Temporary Holding Version
1
+ # Chels Code
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ Chels Code is a Windows-friendly terminal coding assistant powered by MystBloom V1.0, where OpenRouter, NVIDIA, and Groq models collaborate on one coding task.
4
+
5
+ **I like cats**
6
+
7
+ The interface is built with TypeScript, React, and [Ink](https://github.com/vadimdemedes/ink). It includes a real multi-line text area, a scrolling session transcript, an animated `Thinking` / `◆ Done` status, plan approval, and inline confirmation for every file write and PowerShell command.
8
+
9
+ ## Requirements
10
+
11
+ - Node.js 22 or newer
12
+ - API keys for OpenRouter, NVIDIA, and Groq
13
+ - Windows PowerShell for the `run_command` tool
14
+
15
+ Free model availability and rate limits vary by provider and can change. Chels Code automatically discovers the models available to each key, prefers models explicitly marked free, and selects a stable chat model without asking you to choose. It prefers tool-capable models when the provider reports that capability. If a provider doesn't identify free models, the selected model may be metered; check current provider pricing and limits.
16
+
17
+ ## Configure
18
+
19
+ In the project directory where you will run Chels Code, create a `.env` file:
20
+
21
+ ```dotenv
22
+ OPENROUTER_API_KEY=your-openrouter-key
23
+ NVIDIA_API_KEY=your-nvidia-key
24
+ GROQ_API_KEY=your-groq-key
25
+ ```
26
+
27
+ Chels Code reads the file without printing or saving the keys. `.env` is ignored by Git. Alternatively, set the three variables in PowerShell before starting.
28
+
29
+ ## Install and run
30
+
31
+ Install dependencies and build the CLI once from the Chels Code folder:
32
+
33
+ ```powershell
34
+ npm install
35
+ npm run build
36
+ npm link
37
+ ```
38
+
39
+ Then open a terminal in any project folder and type:
40
+
41
+ ```powershell
42
+ chelscode
43
+ ```
44
+
45
+ For local development, run the TypeScript entry directly without building:
46
+
47
+ ```powershell
48
+ npm run dev
49
+ ```
50
+
51
+ `npm start` builds and then runs the compiled CLI.
52
+
53
+ ### Install with Winget
54
+
55
+ To install the CLI as a global Windows command, use the Winget package:
56
+
57
+ ```powershell
58
+ winget install -g ChelsCode
59
+ ```
60
+
61
+ The package manifest lives in [`winget/ChelsCode.yaml`](winget/ChelsCode.yaml). Before it can be installed from the default Winget source, the manifest has to be submitted to the official community repository ([microsoft/winget-pkgs](https://github.com/microsoft/winget-pkgs)). After that, the installed CLI reads your API keys from the environment or from a `.env` file in the project folder (`OPENROUTER_API_KEY`, `NVIDIA_API_KEY`, `GROQ_API_KEY`) and starts answering questions, planning work, and editing code like the local build.
62
+
63
+ While ChelsCode is running, the PowerShell window and tab title show `ChelsCode`. The previous title is restored when the session ends.
64
+
65
+ ## Use
66
+
67
+ The start screen shows the big ChelsCode wordmark, the tagline, and the current workspace. The text area at the bottom is multi-line:
68
+
69
+ - `Enter` sends the request.
70
+ - `Shift+Enter` inserts a newline. In terminals that do not report modified Enter keys, `Ctrl+J` still works as a fallback.
71
+ - `Tab` or `Shift+Tab` switches between `Build` and `Plan` mode; the status line below the input updates to match. Some terminals send Shift+Tab as a plain Tab, so both keys work. `Ctrl+T` also toggles the mode.
72
+ - `Ctrl+P` opens the command menu: `Mode`, `Effort`, `Help`, or `Exit`. `Mode` switches between Build and Plan, and `Effort` lets you pick `low`, `medium`, `high`, `max`, or `ultra`; the default is `max`.
73
+ - Arrow keys move the cursor, including up and down across wrapped lines and newlines.
74
+ - `Home`/`End` or `Ctrl+A`/`Ctrl+E` jump to the start or end of a line.
75
+ - `Ctrl+U`/`Ctrl+K` delete to the start or end of a line, and `Ctrl+W` deletes the previous word.
76
+ - `/exit` or `/quit` ends the session.
77
+
78
+ A status line under the input shows `Build · MystBloom V1.0 Free ChelsCode · max`. The mode is blue, the model and plan name are white, and the effort value is color-coded: `low` green, `medium` cyan, `high` yellow, `max` orange, and `ultra` pink. Use `Ctrl+P` → `Effort` to change the level. The footer below it starts at `0 (0%)` and updates as the session uses the API keys. The number is cumulative session tokens; the percentage is the share of your OpenRouter credits used this session, until the credits run out. If OpenRouter does not expose a credit limit, the percentage falls back to the share of model context used. OpenRouter's free models do not consume credits, so the percentage can stay at 0% while the token count grows. The footer also shows `shift+tab mode` and `ctrl+p commands`, for example `109.6K (55%) shift+tab mode ctrl+p commands`.
79
+
80
+ ## Sessions
81
+
82
+ Every session is saved automatically under `%LOCALAPPDATA%\ChelsCode\sessions` on Windows, or `$XDG_STATE_HOME/chelscode/sessions` (usually `~/.local/state/chelscode/sessions`) elsewhere. When the session ends, ChelsCode prints:
83
+
84
+ ```text
85
+ Resume this session with:
86
+ chelscode --id 3f8a1c2e
87
+ ```
88
+
89
+ Run that command from the same folder to restore the transcript, mode, effort, and usage counter. `chelscode --id` without an ID resumes the most recent session in the current folder. Session files contain your prompts and the AI responses, so treat the sessions folder as private.
90
+
91
+ For each request, ChelsCode picks the lightest workflow:
92
+
93
+ - Questions and read-only requests get a direct answer from MystBloom V1.0. It can list and read workspace files and use built-in lookups for the current date and time, weather, web search, and news headlines, but it cannot write files or run commands.
94
+ - Requests that change files or run commands go to the three-AI team. The AIs independently propose a work breakdown, OpenRouter combines the proposals into subtasks, and you approve the plan before execution.
95
+ - After a task, MystBloom V1.0 writes the final answer summarizing what was actually completed.
96
+
97
+ ### Answering questions about the world
98
+
99
+ Ask anything that needs current information and the single-AI question workflow answers it:
100
+
101
+ - `what's the date today?` or `what time is it now?` → the built-in current date and time lookup.
102
+ - `what's the weather in Chicago?` or `how cold is it in Tokyo?` → the live weather lookup for the requested place.
103
+ - `what's the news today?` or `any updates on ...` → the latest news headlines.
104
+ - `search the web for ...` or `what do you know about ...` → a web search for current information.
105
+
106
+ These questions stay on the light path: one AI answers, no plan, no file writes, and no commands. If a lookup fails, the AI tells you what it could not confirm.
107
+
108
+ While an AI is searching, planning, editing, or running a command, the status shows an animated spinner with `Thinking`. When the request finishes, `◆ Done` shows the response and a `Made` list of the files written and commands that actually ran. Intermediate tool activity and provider/model headers stay hidden. ChelsCode asks for confirmation before every file write and PowerShell command. A declined write or command is reported back to the AI instead of being executed or counted as made.
109
+
110
+ Workspace file tools are restricted to the current project directory, exclude secret-like files such as `.env`, and do not follow links outside the project.
111
+
112
+ ## Tests
113
+
114
+ ```powershell
115
+ npm test
116
+ ```
117
+
118
+ The test suite covers model discovery, credit balance lookup, workspace confinement, the multi-line text area, confirmations, effort selection and Build/Plan switching in the command UI, and an end-to-end mocked session with plan approval and a file write.
119
+
120
+ To type-check and build the CLI:
121
+
122
+ ```powershell
123
+ npm run build
124
+ ```
@@ -0,0 +1,30 @@
1
+ export function parseOptions(argv) {
2
+ const options = {};
3
+ for (let index = 0; index < argv.length; index += 1) {
4
+ const arg = argv[index];
5
+ if (arg === "--help" || arg === "-h") {
6
+ options.help = true;
7
+ }
8
+ else if (arg === "--version" || arg === "-v") {
9
+ options.version = true;
10
+ }
11
+ else if (arg === "--id") {
12
+ const next = argv[index + 1];
13
+ if (next && !next.startsWith("-")) {
14
+ options.id = next;
15
+ index += 1;
16
+ }
17
+ else {
18
+ options.id = true;
19
+ }
20
+ }
21
+ else if (arg.startsWith("--id=")) {
22
+ const value = arg.slice("--id=".length).trim();
23
+ options.id = value || true;
24
+ }
25
+ else {
26
+ throw new Error(`Unknown option: ${arg}`);
27
+ }
28
+ }
29
+ return options;
30
+ }
@@ -0,0 +1,178 @@
1
+ #!/usr/bin/env node
2
+ import { jsx as _jsx } from "react/jsx-runtime";
3
+ import path from "node:path";
4
+ import { realpathSync } from "node:fs";
5
+ import { writeFile } from "node:fs/promises";
6
+ import { createInterface } from "node:readline";
7
+ import { stdin, stdout } from "node:process";
8
+ import { fileURLToPath } from "node:url";
9
+ import { render } from "ink";
10
+ import { parseOptions } from "./cli-options.js";
11
+ import { PROVIDERS, PROVIDER_IDS } from "./providers.js";
12
+ import { createSessionId, findLatestSession, loadSession, resumeHint, sameWorkspace, saveSession, } from "./session.js";
13
+ import { installTerminalTitle } from "./terminal-title.js";
14
+ import { DEFAULT_EFFORT, DEFAULT_MODE } from "./types.js";
15
+ import { App } from "./ui/App.js";
16
+ import { VERSION } from "./version.js";
17
+ const useColor = Boolean(stdout.isTTY && !process.env.NO_COLOR);
18
+ const paint = (code, text) => (useColor ? `\u001b[${code}m${text}\u001b[0m` : text);
19
+ const purple = (text) => paint("38;5;141", text);
20
+ const bold = (text) => paint("1", text);
21
+ const dim = (text) => paint("38;5;244", text);
22
+ function printHelp() {
23
+ console.log(`${purple("◆")} ${bold("ChelsCode")} ${dim(VERSION)}
24
+ ${dim("MystBloom V1.0")}
25
+
26
+ ${bold("Usage")} chelscode [--id [session]] [--help] [--version]
27
+
28
+ ${bold("Environment")}
29
+ ${dim("OPENROUTER_API_KEY")} OpenRouter key
30
+ ${dim("NVIDIA_API_KEY")} NVIDIA key
31
+ ${dim("GROQ_API_KEY")} Groq key
32
+
33
+ Keys can also live in a ${dim(".env")} file in the project directory.
34
+
35
+ ${bold("Input")}
36
+ ${dim("enter")} send
37
+ ${dim("shift+enter")} newline
38
+ ${dim("tab")} or ${dim("shift+tab")} build / plan mode
39
+ ${dim("ctrl+p")} commands, mode, and effort
40
+ ${dim("/exit")} or ${dim("/quit")} quit
41
+
42
+ ${bold("Sessions")}
43
+ ${dim("--id")} [session] resume a session, or the latest one in this folder
44
+ Every session is saved automatically, including its transcript and usage.
45
+
46
+ Effort choices are low, medium, high, max, and ultra; max is the default.
47
+ The PowerShell window and tab title show ChelsCode while the session runs.
48
+
49
+ MystBloom V1.0 answers questions with one AI. It can answer about the current date, weather, news, and web facts with built-in lookups and web search.
50
+ Requests that change files or run commands bring in all three AIs for a plan and
51
+ execution. It shows an animated Thinking spinner while working and ◆ Done with a
52
+ Made list when finished. File writes and PowerShell commands always ask first.`);
53
+ }
54
+ async function loadProjectEnvironment() {
55
+ try {
56
+ process.loadEnvFile(path.join(process.cwd(), ".env"));
57
+ }
58
+ catch (error) {
59
+ if (error.code !== "ENOENT")
60
+ throw error;
61
+ }
62
+ }
63
+ function getApiKeys() {
64
+ const keys = {};
65
+ const missing = [];
66
+ for (const provider of PROVIDER_IDS) {
67
+ const variable = PROVIDERS[provider].keyVariable;
68
+ const value = process.env[variable]?.trim();
69
+ if (value)
70
+ keys[provider] = value;
71
+ else
72
+ missing.push(variable);
73
+ }
74
+ if (missing.length) {
75
+ throw new Error(`Set all three API keys before starting: ${missing.join(", ")}.`);
76
+ }
77
+ return keys;
78
+ }
79
+ const REQUIRED_API_KEY_VARS = [
80
+ { variable: "OPENROUTER_API_KEY", label: "OpenRouter" },
81
+ { variable: "NVIDIA_API_KEY", label: "NVIDIA" },
82
+ { variable: "GROQ_API_KEY", label: "Groq" },
83
+ ];
84
+ async function ensureApiKeys() {
85
+ const missing = REQUIRED_API_KEY_VARS.filter((item) => !process.env[item.variable]?.trim());
86
+ if (missing.length === 0)
87
+ return;
88
+ const rl = createInterface({ input: process.stdin, output: process.stdout });
89
+ try {
90
+ const provided = {};
91
+ for (const item of missing) {
92
+ const answer = await new Promise((resolve) => {
93
+ rl.question(`\n${item.label} API key: `, (response) => resolve(response.trim()));
94
+ });
95
+ if (answer) {
96
+ process.env[item.variable] = answer;
97
+ provided[item.variable] = answer;
98
+ }
99
+ }
100
+ if (Object.keys(provided).length > 0) {
101
+ const content = Object.entries(provided)
102
+ .map(([key, value]) => `${key}=${value}`)
103
+ .join("\n") + "\n";
104
+ await writeFile(path.join(process.cwd(), ".env"), content, { encoding: "utf8" });
105
+ }
106
+ }
107
+ finally {
108
+ rl.close();
109
+ }
110
+ }
111
+ async function main() {
112
+ const options = parseOptions(process.argv.slice(2));
113
+ if (options.help) {
114
+ printHelp();
115
+ return;
116
+ }
117
+ if (options.version) {
118
+ stdout.write(`${VERSION}\n`);
119
+ return;
120
+ }
121
+ await loadProjectEnvironment();
122
+ if (!stdin.isTTY || !stdout.isTTY) {
123
+ throw new Error("ChelsCode needs an interactive terminal.");
124
+ }
125
+ const keysMissing = REQUIRED_API_KEY_VARS.some((item) => !process.env[item.variable]?.trim());
126
+ await ensureApiKeys();
127
+ const apiKeys = getApiKeys();
128
+ const workspace = process.cwd();
129
+ let resumed = null;
130
+ if (options.id === true) {
131
+ resumed = findLatestSession(workspace);
132
+ if (!resumed) {
133
+ throw new Error(`No saved sessions in ${workspace} yet. Start chelscode without --id.`);
134
+ }
135
+ }
136
+ else if (typeof options.id === "string") {
137
+ resumed = loadSession(options.id);
138
+ }
139
+ if (resumed && !sameWorkspace(resumed.workspace, workspace)) {
140
+ throw new Error(`Session ${resumed.id} belongs to ${resumed.workspace}. Run chelscode from that folder to resume it.`);
141
+ }
142
+ const now = new Date().toISOString();
143
+ const session = resumed ?? {
144
+ schema: 1,
145
+ id: createSessionId(),
146
+ workspace,
147
+ createdAt: now,
148
+ updatedAt: now,
149
+ mode: DEFAULT_MODE,
150
+ effort: DEFAULT_EFFORT,
151
+ tokensUsed: 0,
152
+ creditsUsed: 0,
153
+ creditsAvailable: null,
154
+ entries: [],
155
+ };
156
+ if (!resumed)
157
+ saveSession(session);
158
+ const restoreTitle = installTerminalTitle("ChelsCode");
159
+ try {
160
+ const instance = render(_jsx(App, { apiKeys: apiKeys, workspace: workspace, version: VERSION, sessionId: session.id, resume: resumed, missingKeys: keysMissing }), {
161
+ exitOnCtrlC: true,
162
+ kittyKeyboard: { mode: "auto" },
163
+ });
164
+ await instance.waitUntilExit();
165
+ }
166
+ finally {
167
+ restoreTitle();
168
+ stdout.write(`\n${resumeHint(session.id)}\n`);
169
+ }
170
+ }
171
+ if (process.argv[1]
172
+ && realpathSync(process.argv[1]) === realpathSync(fileURLToPath(import.meta.url))) {
173
+ main().catch((error) => {
174
+ const message = error instanceof Error ? error.message : String(error);
175
+ console.error(`ChelsCode: ${message}`);
176
+ process.exitCode = 1;
177
+ });
178
+ }