codeshark-cli 0.1.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 CodeShark contributors
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,260 @@
1
+ # CodeShark ๐Ÿฆˆ
2
+
3
+ An open-source **terminal coding agent** with a pixel-shark mascot โ€” five
4
+ frontier models (GPT-5.6 Sol, DeepSeek-V4 Flash, GLM 5.3 Flash Thinking,
5
+ Kimi-K3, Gemini 3.6 Flash) behind one OpenAI-compatible endpoint.
6
+
7
+ Run `codeshark banner --plain` to see the live mascot in your terminal: a dark
8
+ grey head with black eyes, white teeth, and a red mouth.
9
+
10
+ ## How it works
11
+
12
+ Everything routes through **UnoRouter** (`https://api.unorouter.com/v1`), one
13
+ OpenAI-compatible endpoint. CodeShark ships with a zero-setup **community
14
+ gateway**, so you can start without configuring anything; bring your own
15
+ UnoRouter key whenever you want your own rate limits and private prompts.
16
+
17
+ The gateway strictly serves only UnoRouter's `:free` lane identifiers โ€” any
18
+ other identifier is refused, so there is no paid path through the gateway.
19
+
20
+ ## Quick start
21
+
22
+ CodeShark is intentionally **project-folder-only**. You must select the folder
23
+ that the agent is allowed to work in with `--folder` (or `--cwd`). It refuses to
24
+ start chat, setup, model switching, key management, or a prompt without a real
25
+ project folder.
26
+
27
+ ```bash
28
+ # From a cloned checkout:
29
+ npm install
30
+ npm run build
31
+ npm install -g .
32
+
33
+ # Start CodeShark in the project folder:
34
+ codeshark --folder .
35
+ codeshark --folder . "explain this project"
36
+
37
+ # Or select another folder from anywhere:
38
+ codeshark --folder path/to/your-project
39
+ codeshark --cwd path/to/your-project "fix the tests"
40
+
41
+ # After the package is published:
42
+ npx codeshark-cli # run from any folder without a global install
43
+ npm install -g codeshark-cli
44
+ ```
45
+
46
+ Out of the box, CodeShark talks to the community gateway (which routes through
47
+ UnoRouter), so there is **nothing to configure**. If you installed from a
48
+ checkout and `codeshark` is not recognized yet, use `node dist/index.js
49
+ --folder .` or run `npm install -g .`. All file edits, searches, and commands
50
+ stay inside the selected project folder.
51
+
52
+ By using CodeShark you agree to the [Terms of Service](TERMS.md) โ€” the first
53
+ interactive start asks you to accept them once, and `codeshark terms` prints
54
+ them anytime.
55
+
56
+ ### Setup: shared gateway or your own key?
57
+
58
+ On first launch the CLI asks how you want to connect โ€” and you can re-run it
59
+ anytime with `codeshark setup`:
60
+
61
+ - **My own API key (recommended)** โ€” typed into the terminal **hidden (never
62
+ echoed)**, stored only on your computer in a locked config file (`0600`
63
+ permissions), and never printed by CodeShark. Your own key means your own
64
+ rate limits and private prompts. One UnoRouter key unlocks all 5 models.
65
+ - **Shared gateway** โ€” no key on your computer at all; the gateway's keys live
66
+ as server-side secrets. Shared free lanes are rate-limited, so the gateway
67
+ **queues** bursts (you wait a moment instead of erroring) and fails over
68
+ between UnoRouter โ†’ OpenRouter โ†’ NVIDIA automatically.
69
+ - **Local Ollama** โ€” fully offline on your own machine.
70
+
71
+ Your local keys are protected further by the password-locked key vault
72
+ (`codeshark keys`) โ€” scrypt-hashed password, session + CSRF tokens, and keys
73
+ never appear in the page's HTML or the terminal.
74
+
75
+ ## Usage
76
+
77
+ ```
78
+ codeshark --folder . interactive chat for this folder
79
+ codeshark --folder . "fix the tests" one-shot prompt for this folder
80
+ codeshark banner just the mascot (no folder needed)
81
+ codeshark --folder . setup providers / keys wizard
82
+ codeshark --folder . model list the catalog and active model
83
+ codeshark --folder . keys open the local key vault
84
+ ```
85
+
86
+ REPL commands: `/help` `/model` `/keys` `/key-status` `/setup` `/clear` `/quit`
87
+ (Ctrl+C also quits). Read the terms anytime with `codeshark terms`. The `/keys` page listens only on `127.0.0.1`, requires a
88
+ local password, and keeps key values hidden in password-style fields; the
89
+ terminal status command only shows masked values.
90
+
91
+ ## Tools the agent can use
92
+
93
+ - `read_file` / `write_file` / `edit_file` (strict: `oldString` must match **exactly once** or the edit is rejected)
94
+ - `list_directory` / `glob` (`**`, `*`, `?`, `{a,b}`)
95
+ - `code_search` (ripgrep when installed, dependency-free fallback otherwise)
96
+ - `run_command` (bash / cmd, 30s timeout, output capped) โ€” with a built-in
97
+ danger blocklist: `rm -rf`, `git push`, `sudo`, `curl | sh`, โ€ฆ are refused
98
+ unless you set `CODESHARK_ALLOW_DANGEROUS=1`
99
+ - `finish` โ€” the model signals completion with a summary
100
+
101
+ ## Providers & models
102
+
103
+ Everything runs through **UnoRouter** (`https://api.unorouter.com/v1`), one
104
+ OpenAI-compatible endpoint that routes to 45+ upstream providers โ€” all five
105
+ catalog models are served on its `:free` lanes. Switch anytime with
106
+ `/model <name>` in the REPL (or `codeshark model <name>`).
107
+
108
+ | Model | API identifier | Context | |
109
+ | --- | --- | --- | --- |
110
+ | Chat-GPT 5.6 Sol | `gpt-5.6-sol:free` | 400K | |
111
+ | DeepSeek-V4 Flash | `deepseek-v4-flash-0731:free` | 256K | |
112
+ | GLM 5.3 Flash Thinking | `glm-5.3-flash-thinking:free` | 1M | default |
113
+ | Kimi-K3 | `kimi-k3:free` | 256K | |
114
+ | Gemini 3.6 Flash | `gemini-3.6-flash:free` | 1M | |
115
+
116
+ \* All models run through the zero-setup gateway without any key โ€” nothing to
117
+ configure.
118
+
119
+ **Keys:**
120
+ - UnoRouter โ†’ https://unorouter.com/en/tokens (one key, 200+ models โ€” the only key you really need)
121
+ - Google AI Studio โ†’ https://aistudio.google.com/apikey (`AIzaโ€ฆ`)
122
+
123
+ Run `codeshark setup` to paste a key and pick a default model โ€” it tests the key
124
+ live before saving.
125
+
126
+ **Rate limits:** shared lanes throttle (~20 req/min, ~200/day). If you hit
127
+ a 429, the agent tells you โ€” wait a moment or switch models with `/model`.
128
+
129
+ ## Configuration
130
+
131
+ `~/.codeshark.json` (env vars override):
132
+
133
+ ```json
134
+ {
135
+ "provider": "unorouter",
136
+ "model": "unorouter/glm-5.3-flash-thinking",
137
+ "unorouterApiKey": "ur-โ€ฆ",
138
+ "openrouterApiKey": "sk-โ€ฆ",
139
+ "nvidiaApiKey": "nvapi-โ€ฆ",
140
+ "geminiApiKey": "AIzaโ€ฆ",
141
+ "gatewayUrl": "https://your-gateway.workers.dev",
142
+ "ollamaBaseUrl": "http://127.0.0.1:11434/v1",
143
+ "ollamaModel": "qwen3-coder:30b",
144
+ "maxIterations": 25
145
+ }
146
+ ```
147
+
148
+ Env vars: `CODESHARK_MODEL`, `UNOROUTER_API_KEY`, `OPENROUTER_API_KEY`,
149
+ `GEMINI_API_KEY`, `CODESHARK_GATEWAY_URL`, `CODESHARK_GATEWAY_KEY`,
150
+ `CODESHARK_NO_COLOR`, `CODESHARK_NO_BANNER`, `CODESHARK_NO_LOADING`,
151
+ `CODESHARK_ALLOW_DANGEROUS`.
152
+
153
+ ## Run your own community gateway
154
+
155
+ Anyone can fork the repo and deploy their own gateway โ€” the URL is configurable,
156
+ so your users hit *your* gateway:
157
+
158
+ ```bash
159
+ cd worker
160
+ npx wrangler secret put UNOROUTER_API_KEY # your UnoRouter key (primary), stays server-side
161
+ npx wrangler secret put OPENROUTER_API_KEY # optional: OpenRouter fallback key
162
+ npx wrangler secret put NVIDIA_API_KEY # optional: NVIDIA NIM fallback key (nvapi-โ€ฆ)
163
+ npx wrangler deploy
164
+ ```
165
+
166
+ Then point CodeShark at it:
167
+
168
+ ```bash
169
+ codeshark setup # or edit ~/.codeshark.json: "gatewayUrl": "https://<you>.workers.dev"
170
+ ```
171
+
172
+ The gateway serves only `:free` model identifiers โ€” it never routes anything
173
+ paid โ€” rate-limits per IP, adds CORS, and refuses any non-`:free` identifier.
174
+ When a provider rate-limits (429), the gateway **waits and retries**, then
175
+ hands off to the next configured provider (UnoRouter โ†’ OpenRouter โ†’ NVIDIA),
176
+ and per-IP bursts **queue** (wait for a slot) instead of failing. Your keys are
177
+ Worker secrets: never logged, never returned, never visible to users. Clients
178
+ also retry 429s with backoff, so shared lanes feel smooth.
179
+
180
+ ## Architecture
181
+
182
+ ```
183
+ โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” messages/tools โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ” OpenAI-compatible โ”Œโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”
184
+ โ”‚ terminal โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ agent loop โ”‚ โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ–ถ โ”‚ provider โ”‚
185
+ โ”‚ (repl/cli) โ”‚ โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ (tools, guard) โ”‚ โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€ โ”‚ chain โ”‚
186
+ โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ streamed text โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜ SSE stream โ””โ”€โ”€โ”€โ”€โ”€โ”€โ”ฌโ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
187
+ โ”‚
188
+ gateway (Cloudflare Worker, :free only) โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
189
+ unorouter (:free, your key) โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
190
+ gemini โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”ค
191
+ ollama (local) โ—€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”€โ”˜
192
+ ```
193
+
194
+ - `src/provider/` โ€” canonical `ChatClient` interface + adapters (OpenAI-compatible
195
+ shared core, UnoRouter, OpenRouter, NVIDIA NIM, Gemini conversion, Ollama)
196
+ - `src/agent.ts` โ€” the loop: model โ†’ tool calls โ†’ results โ†’ repeat (25-iteration guard)
197
+ - `src/tools/` โ€” file, search, and shell tools (zero dependencies)
198
+ - `src/banner.ts` โ€” the mascot, defined as a character grid
199
+ - `worker/` โ€” the Cloudflare Worker gateway
200
+
201
+ ## Share it with other computers
202
+
203
+ Other people run your CodeShark the exact same way you do โ€” through npm.
204
+ Publish once, and anyone with Node.js 18+ can run it from any computer.
205
+
206
+ The package name is `codeshark-cli` (npm's anti-squatting rules reserve the
207
+ bare `codeshark` name, which is too similar to the existing `code-shark`
208
+ package). The command you type stays `codeshark`:
209
+
210
+ ```bash
211
+ npm login # once per computer (create an npm account first)
212
+ npm run typecheck && npm test
213
+ npm pack --dry-run # preview exactly what ships: dist/, README, LICENSE
214
+ npm publish --access public # publish version 0.1.0
215
+ ```
216
+
217
+ Bump the version for every release: `npm version patch` (or `minor`/`major`),
218
+ then `npm publish` again.
219
+
220
+ From that moment, on **any computer** with Node.js 18+:
221
+
222
+ ```bash
223
+ npx codeshark-cli --folder . # run instantly โ€” no install needed
224
+ npm install -g codeshark-cli # or install it like a real CLI
225
+ codeshark --folder . # then use it anywhere
226
+ ```
227
+
228
+ The person on the other computer needs no API key and no account โ€” the model
229
+ provider stays server-side, so it works out of the box. The agent only works
230
+ inside the folder they open with `--folder`; it edits, searches, and runs
231
+ commands there and nowhere else.
232
+
233
+ Not ready to publish? `npm pack` produces a single `codeshark-<version>.tgz`
234
+ file โ€” send that one file and the receiver runs `npm install -g
235
+ ./codeshark-0.1.0.tgz`.
236
+
237
+ ## Development
238
+
239
+ ```bash
240
+ npm install
241
+ npm run typecheck # tsc --noEmit
242
+ npm test # build + node:test (agent loop, tools, parsers, banner)
243
+ npm run worker:build # typecheck the gateway worker
244
+ ```
245
+
246
+ ## Roadmap
247
+
248
+ - [x] Mascot banner, REPL, one-shot mode, setup wizard
249
+ - [x] UnoRouter provider: 5 frontier models, one key
250
+ - [x] Agent loop with streaming + tool calling (UnoRouter/Gemini/Ollama/gateway)
251
+ - [x] File, search, and shell tools with safety blocklist
252
+ - [x] Cloudflare Worker gateway (`:free`-only, UnoRouter-routed)
253
+ - [ ] Session memory / project context
254
+ - [x] npm package metadata and `npx codeshark-cli`/global-install support (publish manually)
255
+ - [ ] Approval prompts for sensitive commands
256
+ - [ ] More providers (Groq, Cerebras, โ€ฆ)
257
+
258
+ ## License
259
+
260
+ MIT. ๐Ÿฆˆ
package/TERMS.md ADDED
@@ -0,0 +1,72 @@
1
+ # CodeShark Terms of Service
2
+
3
+ *Last updated: September 4, 2026*
4
+
5
+ By installing, downloading, or using CodeShark (the "Software"), you agree to
6
+ these Terms of Service. If you do not agree, do not use the Software.
7
+
8
+ ## 1. What CodeShark is
9
+
10
+ CodeShark is an open-source terminal coding agent. It can read, create, and
11
+ edit files, run searches, and execute commands **inside the project folder you
12
+ explicitly open with `--folder`**. It is a tool you operate โ€” you are
13
+ responsible for choosing when and how it acts.
14
+
15
+ ## 2. No warranty
16
+
17
+ The Software and the shared gateway service are provided **"AS IS" and "AS
18
+ AVAILABLE"**, without warranty of any kind, express or implied, including
19
+ merchantability, fitness for a particular purpose, and non-infringement. Model
20
+ responses may be incorrect, incomplete, or harmful; verify all output before
21
+ relying on it. The shared gateway is a free, best-effort service โ€” uptime,
22
+ speed, and model availability are not guaranteed.
23
+
24
+ ## 3. Fair use of the shared gateway
25
+
26
+ When you use the shared gateway (the default "zero setup" path), requests are
27
+ routed through shared free-tier provider lanes. You agree to:
28
+
29
+ - **Not** resell, redistribute, or grant access to the gateway to third parties.
30
+ - **Not** scrape, stress-test, or otherwise abuse the gateway or its providers.
31
+ - **Not** use the Software for unlawful purposes, spam, or generating harmful
32
+ content.
33
+ - Accept that rate limits and queues apply to keep the service usable by
34
+ everyone. Requests may be delayed, queued, or refused to protect the shared
35
+ free tier.
36
+
37
+ ## 4. Your content
38
+
39
+ You retain all rights to the prompts you enter and the files you edit. You are
40
+ solely responsible for them. Do not paste passwords, API keys, or other secrets
41
+ into prompts โ€” they may pass through third-party providers. Prompts and
42
+ generated content are not private; treat them accordingly.
43
+
44
+ ## 5. Limitation of liability
45
+
46
+ To the maximum extent permitted by law, the authors and maintainers of
47
+ CodeShark are **not liable** for any damages arising from use of the Software,
48
+ including lost data, lost profits, corrupted files, or harm caused by generated
49
+ content or executed commands. You use the Software entirely at your own risk.
50
+
51
+ ## 6. API keys
52
+
53
+ If you paste your own provider key, it is stored locally on your computer in a
54
+ locked configuration file and is never printed by CodeShark. Keys configured on
55
+ the shared gateway are stored as server-side secrets and are never exposed,
56
+ logged, or returned to clients. You are responsible for protecting your own
57
+ keys.
58
+
59
+ ## 7. Changes
60
+
61
+ These Terms may be updated at any time. Continued use of the Software after
62
+ changes constitutes acceptance of the revised Terms.
63
+
64
+ ## 8. Termination
65
+
66
+ We may refuse or terminate access to the shared gateway at any time, with or
67
+ without notice, for any reason โ€” including violation of these Terms. The
68
+ open-source Software itself remains available under its MIT license.
69
+
70
+ ## 9. Contact
71
+
72
+ For questions about these Terms, open an issue in the CodeShark repository.
package/dist/agent.js ADDED
@@ -0,0 +1,77 @@
1
+ import { errorMessage } from "./provider/types.js";
2
+ import { defaultSystemPrompt } from "./system.js";
3
+ import { DEFAULT_MAX_ITERATIONS } from "./config.js";
4
+ /**
5
+ * Run the agent loop for one user prompt:
6
+ * model โ†’ tool calls โ†’ execute โ†’ feed results back โ†’ โ€ฆ until the model
7
+ * answers (or calls finish) without further tool calls.
8
+ */
9
+ export async function runAgent(input, opts, events) {
10
+ const maxIterations = opts.maxIterations ?? DEFAULT_MAX_ITERATIONS;
11
+ const messages = opts.initialMessages ? [...opts.initialMessages] : [];
12
+ const systemPrompt = opts.systemPrompt ?? defaultSystemPrompt(opts.cwd);
13
+ if (!messages.some((message) => message.role === "system")) {
14
+ messages.unshift({ role: "system", content: systemPrompt });
15
+ }
16
+ messages.push({ role: "user", content: input });
17
+ const ctx = { cwd: opts.cwd, log: opts.debug };
18
+ let lastError = null;
19
+ for (let i = 0; i < maxIterations; i++) {
20
+ let assistant = null;
21
+ let streamedText = "";
22
+ for (const client of opts.clients) {
23
+ let clientStreamedText = "";
24
+ const clientEvents = events
25
+ ? {
26
+ onText: (delta) => {
27
+ clientStreamedText += delta;
28
+ events.onText?.(delta);
29
+ },
30
+ onToolCall: events.onToolCall,
31
+ onDebug: events.onDebug,
32
+ }
33
+ : undefined;
34
+ try {
35
+ assistant = await client.chat(messages, opts.registry.schemas(), clientEvents);
36
+ streamedText = clientStreamedText;
37
+ break;
38
+ }
39
+ catch (e) {
40
+ lastError = e instanceof Error ? e : new Error(String(e));
41
+ opts.debug?.(`[provider] ${client.provider} failed: ${lastError.message}`);
42
+ }
43
+ }
44
+ if (!assistant) {
45
+ throw lastError ?? new Error("No provider available.");
46
+ }
47
+ const assistantMsg = assistant;
48
+ messages.push(assistantMsg);
49
+ const calls = assistantMsg.toolCalls ?? [];
50
+ if (!calls.length) {
51
+ return {
52
+ text: assistantMsg.content,
53
+ iterations: i + 1,
54
+ streamedText: streamedText || undefined,
55
+ history: messages,
56
+ };
57
+ }
58
+ for (const call of calls) {
59
+ if (call.name === "finish") {
60
+ const summary = typeof call.args.summary === "string" && call.args.summary.trim() ? call.args.summary.trim() : "Task complete.";
61
+ return { text: summary, iterations: i + 1, history: messages };
62
+ }
63
+ opts.debug?.(`[tool] ${call.name}`);
64
+ events?.onToolCall?.(call);
65
+ const result = await opts.registry.execute(call.name, call.args, ctx);
66
+ messages.push({
67
+ role: "tool",
68
+ content: result.content,
69
+ toolCallId: call.id,
70
+ toolName: call.name,
71
+ isError: result.isError,
72
+ });
73
+ }
74
+ }
75
+ throw new Error(`Agent exceeded ${maxIterations} iterations without finishing. The model may be looping on tool calls โ€” try rephrasing, or set a higher maxIterations in ~/.codeshark.json.`);
76
+ }
77
+ export { errorMessage };
package/dist/ansi.js ADDED
@@ -0,0 +1,52 @@
1
+ /**
2
+ * Minimal ANSI escape-code helpers โ€” zero dependencies.
3
+ */
4
+ export const ESC = "\u001b[";
5
+ export const RESET = `${ESC}0m`;
6
+ /** True when the current terminal is likely to support color. */
7
+ export function supportsColor() {
8
+ if (process.env.NO_COLOR !== undefined)
9
+ return false;
10
+ if (process.env.CODESHARK_NO_COLOR !== undefined)
11
+ return false;
12
+ if (process.env.TERM === "dumb")
13
+ return false;
14
+ if (process.platform === "win32" && !process.env.TERM)
15
+ return false;
16
+ return true;
17
+ }
18
+ /** Style `text` with a foreground RGB color: hex("#d96b43", "x"). */
19
+ export function hex(hexColor, text) {
20
+ const m = /^#?([0-9a-f]{6})$/i.exec(hexColor.trim());
21
+ if (!m)
22
+ throw new Error(`Invalid hex color: ${hexColor}`);
23
+ const n = parseInt(m[1], 16);
24
+ const r = (n >> 16) & 0xff;
25
+ const g = (n >> 8) & 0xff;
26
+ const b = n & 0xff;
27
+ return `${ESC}38;2;${r};${g};${b}m${text}${RESET}`;
28
+ }
29
+ /** Style `text` with a background RGB color. */
30
+ export function hexBg(hexColor, text) {
31
+ const m = /^#?([0-9a-f]{6})$/i.exec(hexColor.trim());
32
+ if (!m)
33
+ throw new Error(`Invalid hex color: ${hexColor}`);
34
+ const n = parseInt(m[1], 16);
35
+ const r = (n >> 16) & 0xff;
36
+ const g = (n >> 8) & 0xff;
37
+ const b = n & 0xff;
38
+ return `${ESC}48;2;${r};${g};${b}m${text}${RESET}`;
39
+ }
40
+ export function bold(text) {
41
+ return `${ESC}1m${text}${RESET}`;
42
+ }
43
+ export function dim(text) {
44
+ return `${ESC}2m${text}${RESET}`;
45
+ }
46
+ export function underline(text) {
47
+ return `${ESC}4m${text}${RESET}`;
48
+ }
49
+ /** Remove ANSI escape sequences (useful for tests / piping). */
50
+ export function stripAnsi(text) {
51
+ return text.replace(/\u001b\[[0-9;]*m/g, "");
52
+ }
package/dist/banner.js ADDED
@@ -0,0 +1,94 @@
1
+ import { hex, bold, dim, stripAnsi } from "./ansi.js";
2
+ import { modelLabel, loadConfig, activeModelId } from "./config.js";
3
+ import { findModel } from "./models.js";
4
+ /**
5
+ * A front-facing pixel shark head, reproduced from the 16ร—16 mascot artwork:
6
+ * a rounded dome, two eyes, and one open mouth with pointy zigzag teeth โ€”
7
+ * white triangular points separated by red gaps, over a white lower jaw.
8
+ * There is no badge background; the sprite floats directly on the terminal's
9
+ * own background.
10
+ */
11
+ const SPRITE_WIDTH = 16;
12
+ function spriteRow(value) {
13
+ if (value.length !== SPRITE_WIDTH) {
14
+ throw new Error(`Mascot row is ${value.length} cells; expected ${SPRITE_WIDTH}`);
15
+ }
16
+ return value;
17
+ }
18
+ /**
19
+ * Character grid:
20
+ * '.' transparent (terminal background)
21
+ * 'D' head
22
+ * 'E' eye
23
+ * 'W' teeth
24
+ * 'R' mouth interior
25
+ */
26
+ export const SPRITE = [
27
+ spriteRow(".......D........"),
28
+ spriteRow("......DDDD......"),
29
+ spriteRow("....DDDDDDDD...."),
30
+ spriteRow("...DDDDDDDDDD..."),
31
+ spriteRow("...DDDDDDDDDD..."),
32
+ spriteRow("..DDDDDDDDDDDD.."),
33
+ spriteRow(".DDDDDDDDDDDDDD."),
34
+ spriteRow(".DDEDDWWWWDDEDD."),
35
+ spriteRow(".DDWDWWWWWWDWDD."),
36
+ spriteRow(".DWWDWRRRRWDWWD."),
37
+ spriteRow("DDWDWRRRRRRWDWDD"),
38
+ spriteRow("DDWDRRRRRRRRDWDD"),
39
+ spriteRow("DDWWWRWRWRWWWWDD"),
40
+ spriteRow("DDWWWRWWRWWRWWDD"),
41
+ spriteRow("DDWWWWWWWWWWWWDD"),
42
+ spriteRow("DDWWWWWWWWWWWWDD"),
43
+ ];
44
+ const COLORS = {
45
+ D: { fg: "#4a5a68" },
46
+ E: { fg: "#0b1220" },
47
+ W: { fg: "#f8fafc" },
48
+ R: { fg: "#e5484d" },
49
+ };
50
+ function renderSpriteCell(ch, plain) {
51
+ if (ch === ".")
52
+ return " ";
53
+ if (plain) {
54
+ if (ch === "E")
55
+ return "โ— ";
56
+ if (ch === "W")
57
+ return "โ–‘โ–‘";
58
+ if (ch === "R")
59
+ return "โ–’โ–’";
60
+ return "โ–ˆโ–ˆ";
61
+ }
62
+ return hex(COLORS[ch]?.fg ?? "#4a5a68", "โ–ˆโ–ˆ");
63
+ }
64
+ export function renderBanner(opts = {}) {
65
+ const plain = opts.plain ?? false;
66
+ const cfg = loadConfig();
67
+ const entry = findModel(activeModelId(cfg));
68
+ const baseLabel = modelLabel(cfg);
69
+ const modelLine = opts.modelLine ?? (entry ? `${baseLabel} ยท ${entry.context} context` : baseLabel);
70
+ const cwd = opts.cwd ?? process.cwd();
71
+ const title = plain ? "CodeShark" : bold("CodeShark");
72
+ const sub = plain ? modelLine : hex("#8ba3ba", modelLine);
73
+ const dir = plain ? cwd : dim(cwd);
74
+ const lines = [];
75
+ const textLines = [title, sub, dir];
76
+ for (let i = 0; i < SPRITE.length; i++) {
77
+ let line = " ";
78
+ for (const ch of SPRITE[i])
79
+ line += renderSpriteCell(ch, plain);
80
+ const text = textLines[i];
81
+ if (text !== undefined)
82
+ line += " " + text;
83
+ lines.push(line);
84
+ }
85
+ if (opts.footer)
86
+ lines.push("", opts.footer);
87
+ return lines.join("\n") + "\n";
88
+ }
89
+ export function printBanner(opts = {}) {
90
+ process.stdout.write(renderBanner(opts));
91
+ }
92
+ export function bannerHasTitle(text) {
93
+ return stripAnsi(text).includes("CodeShark");
94
+ }