@nghyane/arcane 0.1.10 → 0.1.12
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/CHANGELOG.md +11 -0
- package/package.json +4 -4
- package/src/extensibility/plugins/installer.ts +6 -21
- package/src/extensibility/plugins/manager.ts +8 -29
- package/src/main.ts +3 -0
- package/src/modes/components/model-selector.ts +1 -1
- package/src/modes/components/status-line.ts +15 -40
- package/src/modes/components/user-message.ts +2 -0
- package/src/modes/components/welcome.ts +9 -10
- package/src/modes/controllers/event-controller.ts +51 -12
- package/src/modes/interactive-mode.ts +2 -0
- package/src/modes/theme/dark.json +56 -59
- package/src/modes/theme/defaults/dark-catppuccin.json +47 -56
- package/src/modes/theme/defaults/dark-dracula.json +24 -32
- package/src/modes/theme/defaults/dark-gruvbox.json +53 -74
- package/src/modes/theme/defaults/dark-solarized.json +33 -35
- package/src/modes/theme/defaults/dark-tokyo-night.json +57 -67
- package/src/modes/theme/defaults/index.ts +3 -179
- package/src/modes/theme/defaults/light-catppuccin.json +42 -50
- package/src/modes/theme/defaults/light-github.json +68 -94
- package/src/modes/theme/defaults/light-solarized.json +41 -49
- package/src/modes/theme/light.json +14 -12
- package/src/modes/theme/theme-schema.json +4 -0
- package/src/modes/theme/theme.ts +89 -6
- package/src/patch/index.ts +1 -1
- package/src/stt/downloader.ts +1 -4
- package/src/stt/setup.ts +2 -4
- package/src/tui/output-block.ts +2 -12
- package/src/utils/open.ts +2 -1
- package/src/modes/theme/defaults/alabaster.json +0 -93
- package/src/modes/theme/defaults/amethyst.json +0 -96
- package/src/modes/theme/defaults/anthracite.json +0 -93
- package/src/modes/theme/defaults/basalt.json +0 -91
- package/src/modes/theme/defaults/birch.json +0 -95
- package/src/modes/theme/defaults/dark-abyss.json +0 -91
- package/src/modes/theme/defaults/dark-arctic.json +0 -104
- package/src/modes/theme/defaults/dark-aurora.json +0 -95
- package/src/modes/theme/defaults/dark-cavern.json +0 -91
- package/src/modes/theme/defaults/dark-copper.json +0 -95
- package/src/modes/theme/defaults/dark-cosmos.json +0 -90
- package/src/modes/theme/defaults/dark-cyberpunk.json +0 -102
- package/src/modes/theme/defaults/dark-eclipse.json +0 -91
- package/src/modes/theme/defaults/dark-ember.json +0 -95
- package/src/modes/theme/defaults/dark-equinox.json +0 -90
- package/src/modes/theme/defaults/dark-forest.json +0 -96
- package/src/modes/theme/defaults/dark-github.json +0 -105
- package/src/modes/theme/defaults/dark-lavender.json +0 -95
- package/src/modes/theme/defaults/dark-lunar.json +0 -89
- package/src/modes/theme/defaults/dark-midnight.json +0 -95
- package/src/modes/theme/defaults/dark-monochrome.json +0 -94
- package/src/modes/theme/defaults/dark-monokai.json +0 -98
- package/src/modes/theme/defaults/dark-nebula.json +0 -90
- package/src/modes/theme/defaults/dark-nord.json +0 -97
- package/src/modes/theme/defaults/dark-ocean.json +0 -101
- package/src/modes/theme/defaults/dark-one.json +0 -100
- package/src/modes/theme/defaults/dark-rainforest.json +0 -91
- package/src/modes/theme/defaults/dark-reef.json +0 -91
- package/src/modes/theme/defaults/dark-retro.json +0 -92
- package/src/modes/theme/defaults/dark-rose-pine.json +0 -96
- package/src/modes/theme/defaults/dark-sakura.json +0 -95
- package/src/modes/theme/defaults/dark-slate.json +0 -95
- package/src/modes/theme/defaults/dark-solstice.json +0 -90
- package/src/modes/theme/defaults/dark-starfall.json +0 -91
- package/src/modes/theme/defaults/dark-sunset.json +0 -99
- package/src/modes/theme/defaults/dark-swamp.json +0 -90
- package/src/modes/theme/defaults/dark-synthwave.json +0 -103
- package/src/modes/theme/defaults/dark-taiga.json +0 -91
- package/src/modes/theme/defaults/dark-terminal.json +0 -95
- package/src/modes/theme/defaults/dark-tundra.json +0 -91
- package/src/modes/theme/defaults/dark-twilight.json +0 -91
- package/src/modes/theme/defaults/dark-volcanic.json +0 -91
- package/src/modes/theme/defaults/graphite.json +0 -92
- package/src/modes/theme/defaults/light-arctic.json +0 -107
- package/src/modes/theme/defaults/light-aurora-day.json +0 -91
- package/src/modes/theme/defaults/light-canyon.json +0 -91
- package/src/modes/theme/defaults/light-cirrus.json +0 -90
- package/src/modes/theme/defaults/light-coral.json +0 -95
- package/src/modes/theme/defaults/light-cyberpunk.json +0 -96
- package/src/modes/theme/defaults/light-dawn.json +0 -90
- package/src/modes/theme/defaults/light-dunes.json +0 -91
- package/src/modes/theme/defaults/light-eucalyptus.json +0 -95
- package/src/modes/theme/defaults/light-forest.json +0 -100
- package/src/modes/theme/defaults/light-frost.json +0 -95
- package/src/modes/theme/defaults/light-glacier.json +0 -91
- package/src/modes/theme/defaults/light-gruvbox.json +0 -108
- package/src/modes/theme/defaults/light-haze.json +0 -90
- package/src/modes/theme/defaults/light-honeycomb.json +0 -95
- package/src/modes/theme/defaults/light-lagoon.json +0 -91
- package/src/modes/theme/defaults/light-lavender.json +0 -95
- package/src/modes/theme/defaults/light-meadow.json +0 -91
- package/src/modes/theme/defaults/light-mint.json +0 -95
- package/src/modes/theme/defaults/light-monochrome.json +0 -101
- package/src/modes/theme/defaults/light-ocean.json +0 -99
- package/src/modes/theme/defaults/light-one.json +0 -99
- package/src/modes/theme/defaults/light-opal.json +0 -91
- package/src/modes/theme/defaults/light-orchard.json +0 -91
- package/src/modes/theme/defaults/light-paper.json +0 -95
- package/src/modes/theme/defaults/light-prism.json +0 -90
- package/src/modes/theme/defaults/light-retro.json +0 -98
- package/src/modes/theme/defaults/light-sand.json +0 -95
- package/src/modes/theme/defaults/light-savanna.json +0 -91
- package/src/modes/theme/defaults/light-soleil.json +0 -90
- package/src/modes/theme/defaults/light-sunset.json +0 -99
- package/src/modes/theme/defaults/light-synthwave.json +0 -98
- package/src/modes/theme/defaults/light-tokyo-night.json +0 -111
- package/src/modes/theme/defaults/light-wetland.json +0 -91
- package/src/modes/theme/defaults/light-zenith.json +0 -89
- package/src/modes/theme/defaults/limestone.json +0 -94
- package/src/modes/theme/defaults/mahogany.json +0 -97
- package/src/modes/theme/defaults/marble.json +0 -93
- package/src/modes/theme/defaults/obsidian.json +0 -91
- package/src/modes/theme/defaults/onyx.json +0 -91
- package/src/modes/theme/defaults/pearl.json +0 -93
- package/src/modes/theme/defaults/porcelain.json +0 -91
- package/src/modes/theme/defaults/quartz.json +0 -96
- package/src/modes/theme/defaults/sandstone.json +0 -95
- package/src/modes/theme/defaults/titanium.json +0 -90
package/CHANGELOG.md
CHANGED
|
@@ -2,6 +2,17 @@
|
|
|
2
2
|
|
|
3
3
|
## [Unreleased]
|
|
4
4
|
|
|
5
|
+
## [0.1.12] - 2026-02-24
|
|
6
|
+
|
|
7
|
+
### Fixed
|
|
8
|
+
|
|
9
|
+
- Preserve single blank line content in edit tool — `hashlineParseContent` no longer strips the only line when it is empty
|
|
10
|
+
|
|
11
|
+
### Changed
|
|
12
|
+
|
|
13
|
+
- Stream codemode intent immediately during LLM generation instead of waiting for execution start
|
|
14
|
+
- Hide loader spinner when codemode group is active to avoid duplicate status indicators
|
|
15
|
+
|
|
5
16
|
## [0.1.8] - 2026-02-22
|
|
6
17
|
|
|
7
18
|
### Changed
|
package/package.json
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
{
|
|
2
2
|
"type": "module",
|
|
3
3
|
"name": "@nghyane/arcane",
|
|
4
|
-
"version": "0.1.
|
|
4
|
+
"version": "0.1.12",
|
|
5
5
|
"description": "Coding agent CLI with read, bash, edit, write tools and session management",
|
|
6
6
|
"homepage": "https://github.com/nghyane/arcane",
|
|
7
7
|
"author": "Can Bölük",
|
|
@@ -45,11 +45,11 @@
|
|
|
45
45
|
"dependencies": {
|
|
46
46
|
"@mozilla/readability": "0.6.0",
|
|
47
47
|
"@nghyane/arcane-stats": "^0.1.8",
|
|
48
|
-
"@nghyane/arcane-agent": "^0.1.
|
|
49
|
-
"@nghyane/arcane-codemode": "^0.1.
|
|
48
|
+
"@nghyane/arcane-agent": "^0.1.10",
|
|
49
|
+
"@nghyane/arcane-codemode": "^0.1.11",
|
|
50
50
|
"@nghyane/arcane-ai": "^0.1.8",
|
|
51
51
|
"@nghyane/arcane-natives": "^0.1.7",
|
|
52
|
-
"@nghyane/arcane-tui": "^0.1.
|
|
52
|
+
"@nghyane/arcane-tui": "^0.1.9",
|
|
53
53
|
"@nghyane/arcane-utils": "^0.1.6",
|
|
54
54
|
"@sinclair/typebox": "^0.34.48",
|
|
55
55
|
"@xterm/headless": "^6.0.0",
|
|
@@ -2,6 +2,7 @@ import * as fs from "node:fs/promises";
|
|
|
2
2
|
import * as path from "node:path";
|
|
3
3
|
import { isEnoent } from "@nghyane/arcane-utils";
|
|
4
4
|
import { getAgentDir, getProjectDir } from "@nghyane/arcane-utils/dirs";
|
|
5
|
+
import { $ } from "bun";
|
|
5
6
|
import type { InstalledPlugin } from "./types";
|
|
6
7
|
|
|
7
8
|
const PLUGINS_DIR = path.join(getAgentDir(), "plugins");
|
|
@@ -45,17 +46,9 @@ export async function installPlugin(packageName: string): Promise<InstalledPlugi
|
|
|
45
46
|
}
|
|
46
47
|
|
|
47
48
|
// Run npm install in plugins directory
|
|
48
|
-
const
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
stdout: "pipe",
|
|
52
|
-
stderr: "pipe",
|
|
53
|
-
windowsHide: true,
|
|
54
|
-
});
|
|
55
|
-
|
|
56
|
-
const exitCode = await proc.exited;
|
|
57
|
-
if (exitCode !== 0) {
|
|
58
|
-
const stderr = await new Response(proc.stderr).text();
|
|
49
|
+
const result = await $`bun install ${packageName}`.cwd(PLUGINS_DIR).quiet().nothrow();
|
|
50
|
+
if (result.exitCode !== 0) {
|
|
51
|
+
const stderr = result.stderr.toString().trim();
|
|
59
52
|
throw new Error(`Failed to install ${packageName}: ${stderr}`);
|
|
60
53
|
}
|
|
61
54
|
|
|
@@ -87,16 +80,8 @@ export async function uninstallPlugin(name: string): Promise<void> {
|
|
|
87
80
|
|
|
88
81
|
await ensurePluginsDir();
|
|
89
82
|
|
|
90
|
-
const
|
|
91
|
-
|
|
92
|
-
stdin: "ignore",
|
|
93
|
-
stdout: "pipe",
|
|
94
|
-
stderr: "pipe",
|
|
95
|
-
windowsHide: true,
|
|
96
|
-
});
|
|
97
|
-
|
|
98
|
-
const exitCode = await proc.exited;
|
|
99
|
-
if (exitCode !== 0) {
|
|
83
|
+
const result = await $`bun uninstall ${name}`.cwd(PLUGINS_DIR).quiet().nothrow();
|
|
84
|
+
if (result.exitCode !== 0) {
|
|
100
85
|
throw new Error(`Failed to uninstall ${name}`);
|
|
101
86
|
}
|
|
102
87
|
}
|
|
@@ -9,6 +9,7 @@ import {
|
|
|
9
9
|
getProjectDir,
|
|
10
10
|
getProjectPluginOverridesPath,
|
|
11
11
|
} from "@nghyane/arcane-utils/dirs";
|
|
12
|
+
import { $ } from "bun";
|
|
12
13
|
import { extractPackageName, parsePluginSpec } from "./parser";
|
|
13
14
|
import type {
|
|
14
15
|
DoctorCheck,
|
|
@@ -155,17 +156,9 @@ export class PluginManager {
|
|
|
155
156
|
}
|
|
156
157
|
|
|
157
158
|
// Run npm install
|
|
158
|
-
const
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
stdout: "pipe",
|
|
162
|
-
stderr: "pipe",
|
|
163
|
-
windowsHide: true,
|
|
164
|
-
});
|
|
165
|
-
|
|
166
|
-
const exitCode = await proc.exited;
|
|
167
|
-
if (exitCode !== 0) {
|
|
168
|
-
const stderr = await new Response(proc.stderr).text();
|
|
159
|
+
const result = await $`bun install ${spec.packageName}`.cwd(getPluginsDir()).quiet().nothrow();
|
|
160
|
+
if (result.exitCode !== 0) {
|
|
161
|
+
const stderr = result.stderr.toString().trim();
|
|
169
162
|
throw new Error(`npm install failed: ${stderr}`);
|
|
170
163
|
}
|
|
171
164
|
|
|
@@ -236,16 +229,8 @@ export class PluginManager {
|
|
|
236
229
|
validatePackageName(name);
|
|
237
230
|
await this.#ensurePackageJson();
|
|
238
231
|
|
|
239
|
-
const
|
|
240
|
-
|
|
241
|
-
stdin: "ignore",
|
|
242
|
-
stdout: "pipe",
|
|
243
|
-
stderr: "pipe",
|
|
244
|
-
windowsHide: true,
|
|
245
|
-
});
|
|
246
|
-
|
|
247
|
-
const exitCode = await proc.exited;
|
|
248
|
-
if (exitCode !== 0) {
|
|
232
|
+
const result = await $`bun uninstall ${name}`.cwd(getPluginsDir()).quiet().nothrow();
|
|
233
|
+
if (result.exitCode !== 0) {
|
|
249
234
|
throw new Error(`npm uninstall failed for ${name}`);
|
|
250
235
|
}
|
|
251
236
|
|
|
@@ -619,14 +604,8 @@ export class PluginManager {
|
|
|
619
604
|
|
|
620
605
|
async #fixMissingPlugin(): Promise<boolean> {
|
|
621
606
|
try {
|
|
622
|
-
const
|
|
623
|
-
|
|
624
|
-
stdin: "ignore",
|
|
625
|
-
stdout: "pipe",
|
|
626
|
-
stderr: "pipe",
|
|
627
|
-
windowsHide: true,
|
|
628
|
-
});
|
|
629
|
-
return (await proc.exited) === 0;
|
|
607
|
+
const result = await $`bun install`.cwd(getPluginsDir()).quiet().nothrow();
|
|
608
|
+
return result.exitCode === 0;
|
|
630
609
|
} catch {
|
|
631
610
|
return false;
|
|
632
611
|
}
|
package/src/main.ts
CHANGED
|
@@ -11,6 +11,7 @@ import * as os from "node:os";
|
|
|
11
11
|
import * as path from "node:path";
|
|
12
12
|
import { createInterface } from "node:readline/promises";
|
|
13
13
|
import { type ImageContent, supportsXhigh } from "@nghyane/arcane-ai";
|
|
14
|
+
import { queryTerminalBackground } from "@nghyane/arcane-tui";
|
|
14
15
|
import { $env, postmortem } from "@nghyane/arcane-utils";
|
|
15
16
|
import { getProjectDir, setProjectDir } from "@nghyane/arcane-utils/dirs";
|
|
16
17
|
import chalk from "chalk";
|
|
@@ -591,12 +592,14 @@ export async function runRootCommand(parsed: Args, rawArgs: string[]): Promise<v
|
|
|
591
592
|
});
|
|
592
593
|
}
|
|
593
594
|
|
|
595
|
+
const terminalBg = isInteractive ? await queryTerminalBackground() : null;
|
|
594
596
|
await initTheme(
|
|
595
597
|
isInteractive,
|
|
596
598
|
settings.get("symbolPreset"),
|
|
597
599
|
settings.get("colorBlindMode"),
|
|
598
600
|
settings.get("theme.dark"),
|
|
599
601
|
settings.get("theme.light"),
|
|
602
|
+
terminalBg ?? undefined,
|
|
600
603
|
);
|
|
601
604
|
time("initTheme:final");
|
|
602
605
|
|
|
@@ -21,7 +21,7 @@ import { DynamicBorder } from "./dynamic-border";
|
|
|
21
21
|
function makeInvertedBadge(label: string, color: ThemeColor): string {
|
|
22
22
|
const fgAnsi = theme.getFgAnsi(color);
|
|
23
23
|
const bgAnsi = fgAnsi.replace(/\x1b\[38;/g, "\x1b[48;");
|
|
24
|
-
return `${bgAnsi}\x1b[30m ${label} \x1b[39m
|
|
24
|
+
return `${bgAnsi}\x1b[30m ${label} \x1b[39m${theme.getAppBgAnsi()}`;
|
|
25
25
|
}
|
|
26
26
|
|
|
27
27
|
interface ModelItem {
|
|
@@ -309,78 +309,53 @@ export class StatusLineComponent implements Component {
|
|
|
309
309
|
const ctx = this.#buildSegmentContext(width);
|
|
310
310
|
const effectiveSettings = this.#resolveSettings();
|
|
311
311
|
const separatorDef = getSeparator(effectiveSettings.separator ?? "powerline-thin", theme);
|
|
312
|
-
|
|
313
|
-
const bgAnsi = theme.getBgAnsi("statusLineBg");
|
|
314
312
|
const fgAnsi = theme.getFgAnsi("text");
|
|
315
313
|
const sepAnsi = theme.getFgAnsi("statusLineSep");
|
|
314
|
+
const leftSepWidth = visibleWidth(separatorDef.left);
|
|
315
|
+
const rightSepWidth = visibleWidth(separatorDef.right);
|
|
316
316
|
|
|
317
|
-
// Collect visible segment contents
|
|
318
317
|
const leftParts: string[] = [];
|
|
319
|
-
for (const segId of
|
|
318
|
+
for (const segId of this.#resolveSettings().leftSegments) {
|
|
320
319
|
const rendered = renderSegment(segId, ctx);
|
|
321
|
-
if (rendered.visible && rendered.content)
|
|
322
|
-
leftParts.push(rendered.content);
|
|
323
|
-
}
|
|
320
|
+
if (rendered.visible && rendered.content) leftParts.push(rendered.content);
|
|
324
321
|
}
|
|
325
322
|
|
|
326
323
|
const rightParts: string[] = [];
|
|
327
|
-
for (const segId of
|
|
324
|
+
for (const segId of this.#resolveSettings().rightSegments) {
|
|
328
325
|
const rendered = renderSegment(segId, ctx);
|
|
329
|
-
if (rendered.visible && rendered.content)
|
|
330
|
-
rightParts.push(rendered.content);
|
|
331
|
-
}
|
|
326
|
+
if (rendered.visible && rendered.content) rightParts.push(rendered.content);
|
|
332
327
|
}
|
|
333
328
|
|
|
334
329
|
const topFillWidth = width > 0 ? Math.max(0, width - 4) : 0;
|
|
335
330
|
const left = [...leftParts];
|
|
336
331
|
const right = [...rightParts];
|
|
337
332
|
|
|
338
|
-
const
|
|
339
|
-
const rightSepWidth = visibleWidth(separatorDef.right);
|
|
340
|
-
const leftCapWidth = separatorDef.endCaps ? visibleWidth(separatorDef.endCaps.right) : 0;
|
|
341
|
-
const rightCapWidth = separatorDef.endCaps ? visibleWidth(separatorDef.endCaps.left) : 0;
|
|
342
|
-
|
|
343
|
-
const groupWidth = (parts: string[], capWidth: number, sepWidth: number): number => {
|
|
333
|
+
const groupWidth = (parts: string[], sepWidth: number): number => {
|
|
344
334
|
if (parts.length === 0) return 0;
|
|
345
335
|
const partsWidth = parts.reduce((sum, part) => sum + visibleWidth(part), 0);
|
|
346
336
|
const sepTotal = Math.max(0, parts.length - 1) * (sepWidth + 2);
|
|
347
|
-
return partsWidth + sepTotal + 2
|
|
337
|
+
return partsWidth + sepTotal + 2;
|
|
348
338
|
};
|
|
349
339
|
|
|
350
|
-
let leftWidth = groupWidth(left,
|
|
351
|
-
let rightWidth = groupWidth(right,
|
|
340
|
+
let leftWidth = groupWidth(left, leftSepWidth);
|
|
341
|
+
let rightWidth = groupWidth(right, rightSepWidth);
|
|
352
342
|
const totalWidth = () => leftWidth + rightWidth + (left.length > 0 && right.length > 0 ? 1 : 0);
|
|
353
343
|
|
|
354
344
|
if (topFillWidth > 0) {
|
|
355
345
|
while (totalWidth() > topFillWidth && right.length > 0) {
|
|
356
346
|
right.pop();
|
|
357
|
-
rightWidth = groupWidth(right,
|
|
347
|
+
rightWidth = groupWidth(right, rightSepWidth);
|
|
358
348
|
}
|
|
359
349
|
while (totalWidth() > topFillWidth && left.length > 0) {
|
|
360
350
|
left.pop();
|
|
361
|
-
leftWidth = groupWidth(left,
|
|
351
|
+
leftWidth = groupWidth(left, leftSepWidth);
|
|
362
352
|
}
|
|
363
353
|
}
|
|
364
354
|
|
|
365
355
|
const renderGroup = (parts: string[], direction: "left" | "right"): string => {
|
|
366
356
|
if (parts.length === 0) return "";
|
|
367
357
|
const sep = direction === "left" ? separatorDef.left : separatorDef.right;
|
|
368
|
-
|
|
369
|
-
? direction === "left"
|
|
370
|
-
? separatorDef.endCaps.right
|
|
371
|
-
: separatorDef.endCaps.left
|
|
372
|
-
: "";
|
|
373
|
-
const capPrefix = separatorDef.endCaps?.useBgAsFg ? bgAnsi.replace("\x1b[48;", "\x1b[38;") : bgAnsi + sepAnsi;
|
|
374
|
-
const capText = cap ? `${capPrefix}${cap}\x1b[0m` : "";
|
|
375
|
-
|
|
376
|
-
let content = bgAnsi + fgAnsi;
|
|
377
|
-
content += ` ${parts.join(` ${sepAnsi}${sep}${fgAnsi} `)} `;
|
|
378
|
-
content += "\x1b[0m";
|
|
379
|
-
|
|
380
|
-
if (capText) {
|
|
381
|
-
return direction === "right" ? capText + content : content + capText;
|
|
382
|
-
}
|
|
383
|
-
return content;
|
|
358
|
+
return `${fgAnsi} ${parts.join(` ${sepAnsi}${sep}${fgAnsi} `)} \x1b[0m`;
|
|
384
359
|
};
|
|
385
360
|
|
|
386
361
|
const leftGroup = renderGroup(left, "left");
|
|
@@ -391,8 +366,8 @@ export class StatusLineComponent implements Component {
|
|
|
391
366
|
return leftGroup + (leftGroup && rightGroup ? " " : "") + rightGroup;
|
|
392
367
|
}
|
|
393
368
|
|
|
394
|
-
leftWidth = groupWidth(left,
|
|
395
|
-
rightWidth = groupWidth(right,
|
|
369
|
+
leftWidth = groupWidth(left, leftSepWidth);
|
|
370
|
+
rightWidth = groupWidth(right, rightSepWidth);
|
|
396
371
|
const gapWidth = Math.max(1, topFillWidth - leftWidth - rightWidth);
|
|
397
372
|
return leftGroup + padding(gapWidth) + rightGroup;
|
|
398
373
|
}
|
|
@@ -8,6 +8,7 @@ export class UserMessageComponent extends Container {
|
|
|
8
8
|
constructor(text: string, synthetic = false) {
|
|
9
9
|
super();
|
|
10
10
|
const bgColor = (value: string) => theme.bg("userMessageBg", value);
|
|
11
|
+
const leftBorder = theme.fg("accent", "▎");
|
|
11
12
|
const color = synthetic
|
|
12
13
|
? (value: string) => theme.fg("dim", value)
|
|
13
14
|
: (value: string) => theme.fg("userMessageText", value);
|
|
@@ -16,6 +17,7 @@ export class UserMessageComponent extends Container {
|
|
|
16
17
|
new Markdown(text, 1, 1, getMarkdownTheme(), {
|
|
17
18
|
bgColor,
|
|
18
19
|
color,
|
|
20
|
+
leftBorder,
|
|
19
21
|
}),
|
|
20
22
|
);
|
|
21
23
|
}
|
|
@@ -67,9 +67,9 @@ export class WelcomeComponent implements Component {
|
|
|
67
67
|
const leftCol = showRightColumn ? dualLeftCol : boxWidth - 2;
|
|
68
68
|
const rightCol = showRightColumn ? dualRightCol : 0;
|
|
69
69
|
|
|
70
|
-
// Block-based
|
|
70
|
+
// Block-based ARC logo (gradient: blue → cyan → green / Nord Frost)
|
|
71
71
|
// biome-ignore format: preserve ASCII art layout
|
|
72
|
-
const piLogo = ["
|
|
72
|
+
const piLogo = ["╭━━━╮╭━━━╮╭━━━╮", "┃╭━╮┃┃╭━╮┃┃╭━━╯", "┃╰━╯┃┃╰━╯┃┃┃ ", "┃┃ ┃┃┃╭╮╭╯┃╰━━╮", "╰╯ ╰╯╰╯╰╯ ╰━━━╯"];
|
|
73
73
|
|
|
74
74
|
// Apply gradient to logo
|
|
75
75
|
const logoColored = piLogo.map(line => this.#gradientLine(line));
|
|
@@ -82,7 +82,7 @@ export class WelcomeComponent implements Component {
|
|
|
82
82
|
...logoColored.map(l => this.#centerText(l, leftCol)),
|
|
83
83
|
"",
|
|
84
84
|
this.#centerText(theme.fg("muted", this.modelName), leftCol),
|
|
85
|
-
this.#centerText(theme.fg("
|
|
85
|
+
this.#centerText(theme.fg("dim", this.providerName), leftCol),
|
|
86
86
|
];
|
|
87
87
|
|
|
88
88
|
// Right column separator
|
|
@@ -190,15 +190,14 @@ export class WelcomeComponent implements Component {
|
|
|
190
190
|
return padding(leftPad) + text + padding(rightPad);
|
|
191
191
|
}
|
|
192
192
|
|
|
193
|
-
/** Apply
|
|
193
|
+
/** Apply Nord Frost gradient (blue → cyan → green) to a string */
|
|
194
194
|
#gradientLine(line: string): string {
|
|
195
195
|
const colors = [
|
|
196
|
-
"\x1b[38;
|
|
197
|
-
"\x1b[38;
|
|
198
|
-
"\x1b[38;
|
|
199
|
-
"\x1b[38;
|
|
200
|
-
"\x1b[38;
|
|
201
|
-
"\x1b[38;5;51m", // bright cyan
|
|
196
|
+
"\x1b[38;2;136;192;208m", // #88c0d0 blue
|
|
197
|
+
"\x1b[38;2;141;200;200m", // blend
|
|
198
|
+
"\x1b[38;2;143;188;187m", // #8fbcbb cyan
|
|
199
|
+
"\x1b[38;2;153;189;170m", // blend
|
|
200
|
+
"\x1b[38;2;163;190;140m", // #a3be8c green
|
|
202
201
|
];
|
|
203
202
|
const reset = "\x1b[0m";
|
|
204
203
|
|
|
@@ -42,6 +42,39 @@ export class EventController {
|
|
|
42
42
|
this.ctx.setWorkingMessage(`${trimmed} (esc to interrupt)`);
|
|
43
43
|
}
|
|
44
44
|
|
|
45
|
+
#ensureCodemodeGroup(id: string): CodeModeGroupComponent {
|
|
46
|
+
let group = this.#codemodeGroups.get(id);
|
|
47
|
+
if (!group) {
|
|
48
|
+
this.#resetReadGroup();
|
|
49
|
+
group = new CodeModeGroupComponent(this.ctx.ui);
|
|
50
|
+
group.setExpanded(this.ctx.toolOutputExpanded);
|
|
51
|
+
this.ctx.chatContainer.addChild(group);
|
|
52
|
+
this.#codemodeGroups.set(id, group);
|
|
53
|
+
this.ctx.pendingTools.set(id, group);
|
|
54
|
+
this.#hideLoader();
|
|
55
|
+
}
|
|
56
|
+
return group;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
#hideLoader(): void {
|
|
60
|
+
if (!this.ctx.loadingAnimation) return;
|
|
61
|
+
this.ctx.loadingAnimation.stop();
|
|
62
|
+
this.ctx.statusContainer.clear();
|
|
63
|
+
this.ctx.loadingAnimation = undefined;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
#restoreLoader(): void {
|
|
67
|
+
if (this.ctx.loadingAnimation || this.#codemodeGroups.size > 0) return;
|
|
68
|
+
this.ctx.loadingAnimation = new Loader(
|
|
69
|
+
this.ctx.ui,
|
|
70
|
+
spinner => theme.fg("accent", spinner),
|
|
71
|
+
text => theme.fg("muted", text),
|
|
72
|
+
`Working\u2026 (esc to interrupt)`,
|
|
73
|
+
getSymbolTheme().spinnerFrames,
|
|
74
|
+
);
|
|
75
|
+
this.ctx.statusContainer.addChild(this.ctx.loadingAnimation);
|
|
76
|
+
}
|
|
77
|
+
|
|
45
78
|
subscribeToAgent(): void {
|
|
46
79
|
this.ctx.unsubscribe = this.ctx.session.subscribe(async (event: AgentSessionEvent) => {
|
|
47
80
|
await this.handleEvent(event);
|
|
@@ -132,8 +165,16 @@ export class EventController {
|
|
|
132
165
|
|
|
133
166
|
for (const content of this.ctx.streamingMessage.content) {
|
|
134
167
|
if (content.type !== "toolCall") continue;
|
|
135
|
-
// Code Mode:
|
|
136
|
-
if (content.name === "code")
|
|
168
|
+
// Code Mode: create group component early during streaming for intent display
|
|
169
|
+
if (content.name === "code") {
|
|
170
|
+
const group = this.#ensureCodemodeGroup(content.id);
|
|
171
|
+
const args = content.arguments;
|
|
172
|
+
if (args && typeof args === "object" && INTENT_FIELD in args) {
|
|
173
|
+
const intent = (args[INTENT_FIELD] as string | undefined)?.trim();
|
|
174
|
+
if (intent) group.setIntent(intent);
|
|
175
|
+
}
|
|
176
|
+
continue;
|
|
177
|
+
}
|
|
137
178
|
|
|
138
179
|
if (!this.ctx.pendingTools.has(content.id)) {
|
|
139
180
|
if (content.name === "read") {
|
|
@@ -169,9 +210,10 @@ export class EventController {
|
|
|
169
210
|
}
|
|
170
211
|
}
|
|
171
212
|
|
|
172
|
-
// Update working message with intent
|
|
213
|
+
// Update working message with intent — skip for code tools that already have a visible group
|
|
173
214
|
for (const content of this.ctx.streamingMessage.content) {
|
|
174
215
|
if (content.type !== "toolCall") continue;
|
|
216
|
+
if (this.#codemodeGroups.has(content.id)) continue;
|
|
175
217
|
const args = content.arguments;
|
|
176
218
|
if (!args || typeof args !== "object" || !(INTENT_FIELD in args)) continue;
|
|
177
219
|
this.#updateWorkingMessageFromIntent(args[INTENT_FIELD] as string | undefined);
|
|
@@ -218,19 +260,15 @@ export class EventController {
|
|
|
218
260
|
break;
|
|
219
261
|
|
|
220
262
|
case "tool_execution_start": {
|
|
221
|
-
this.#updateWorkingMessageFromIntent(event.intent);
|
|
222
|
-
// Code Mode: create a group component for the "code" tool
|
|
263
|
+
if (!this.#codemodeGroups.has(event.toolCallId)) this.#updateWorkingMessageFromIntent(event.intent);
|
|
223
264
|
if (event.toolName === "code") {
|
|
224
|
-
this.#
|
|
225
|
-
const
|
|
226
|
-
|
|
265
|
+
const group = this.#ensureCodemodeGroup(event.toolCallId);
|
|
266
|
+
const intent = (event.intent ?? (event.args as Record<string, unknown>)?.agent__intent) as
|
|
267
|
+
| string
|
|
268
|
+
| undefined;
|
|
227
269
|
if (typeof intent === "string" && intent.trim()) {
|
|
228
270
|
group.setIntent(intent.trim());
|
|
229
271
|
}
|
|
230
|
-
group.setExpanded(this.ctx.toolOutputExpanded);
|
|
231
|
-
this.ctx.chatContainer.addChild(group);
|
|
232
|
-
this.#codemodeGroups.set(event.toolCallId, group);
|
|
233
|
-
this.ctx.pendingTools.set(event.toolCallId, group);
|
|
234
272
|
this.ctx.ui.requestRender();
|
|
235
273
|
break;
|
|
236
274
|
}
|
|
@@ -316,6 +354,7 @@ export class EventController {
|
|
|
316
354
|
group.setDone();
|
|
317
355
|
this.#codemodeGroups.delete(event.toolCallId);
|
|
318
356
|
}
|
|
357
|
+
this.#restoreLoader();
|
|
319
358
|
}
|
|
320
359
|
// Update todo display when todo_write tool completes
|
|
321
360
|
if (event.toolName === "todo_write" && !event.isError) {
|
|
@@ -175,6 +175,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
175
175
|
|
|
176
176
|
this.ui = new TUI(new ProcessTerminal(), settings.get("showHardwareCursor"));
|
|
177
177
|
this.ui.setClearOnShrink(settings.get("clearOnShrink"));
|
|
178
|
+
this.ui.setAppBg(theme.getAppBgPackedRgb());
|
|
178
179
|
setMermaidRenderCallback(() => this.ui.requestRender());
|
|
179
180
|
this.chatContainer = new Container();
|
|
180
181
|
this.pendingMessagesContainer = new Container();
|
|
@@ -344,6 +345,7 @@ export class InteractiveMode implements InteractiveModeContext {
|
|
|
344
345
|
// Set up theme file watcher
|
|
345
346
|
onThemeChange(() => {
|
|
346
347
|
this.ui.invalidate();
|
|
348
|
+
this.ui.setAppBg(theme.getAppBgPackedRgb());
|
|
347
349
|
this.updateEditorBorderColor();
|
|
348
350
|
this.ui.requestRender();
|
|
349
351
|
});
|
|
@@ -2,26 +2,22 @@
|
|
|
2
2
|
"$schema": "https://raw.githubusercontent.com/nghyane/arcane/main/packages/coding-agent/theme-schema.json",
|
|
3
3
|
"name": "dark",
|
|
4
4
|
"vars": {
|
|
5
|
-
"cyan": "#
|
|
6
|
-
"blue": "#
|
|
7
|
-
"
|
|
8
|
-
"
|
|
9
|
-
"
|
|
10
|
-
"
|
|
11
|
-
"
|
|
12
|
-
"
|
|
13
|
-
"
|
|
14
|
-
"
|
|
15
|
-
"
|
|
16
|
-
"toolPendingBg": "#1d2129",
|
|
17
|
-
"toolSuccessBg": "#161a1f",
|
|
18
|
-
"toolErrorBg": "#291d1d",
|
|
19
|
-
"customMsgBg": "#2a2530"
|
|
5
|
+
"cyan": "#8fbcbb",
|
|
6
|
+
"blue": "#88c0d0",
|
|
7
|
+
"lightBlue": "#81a1c1",
|
|
8
|
+
"green": "#a3be8c",
|
|
9
|
+
"red": "#d06050",
|
|
10
|
+
"yellow": "#ebcb8b",
|
|
11
|
+
"gray": "#7b88a1",
|
|
12
|
+
"dimGray": "#4c566a",
|
|
13
|
+
"darkGray": "#434c5e",
|
|
14
|
+
"accent": "#d08770",
|
|
15
|
+
"darkGrayBg": "#434c5e"
|
|
20
16
|
},
|
|
21
17
|
"colors": {
|
|
22
18
|
"accent": "accent",
|
|
23
19
|
"border": "blue",
|
|
24
|
-
"borderAccent": "
|
|
20
|
+
"borderAccent": "accent",
|
|
25
21
|
"borderMuted": "darkGray",
|
|
26
22
|
"success": "green",
|
|
27
23
|
"error": "red",
|
|
@@ -30,22 +26,22 @@
|
|
|
30
26
|
"dim": "dimGray",
|
|
31
27
|
"text": "",
|
|
32
28
|
"thinkingText": "gray",
|
|
33
|
-
"selectedBg": "
|
|
34
|
-
"userMessageBg": "
|
|
29
|
+
"selectedBg": "^2",
|
|
30
|
+
"userMessageBg": "^2",
|
|
35
31
|
"userMessageText": "",
|
|
36
|
-
"customMessageBg": "
|
|
32
|
+
"customMessageBg": "^2",
|
|
37
33
|
"customMessageText": "",
|
|
38
|
-
"customMessageLabel": "
|
|
39
|
-
"toolPendingBg": "
|
|
40
|
-
"toolSuccessBg": "
|
|
41
|
-
"toolErrorBg": "
|
|
34
|
+
"customMessageLabel": "accent",
|
|
35
|
+
"toolPendingBg": "^3",
|
|
36
|
+
"toolSuccessBg": "^2",
|
|
37
|
+
"toolErrorBg": "^3",
|
|
42
38
|
"toolTitle": "",
|
|
43
39
|
"toolOutput": "gray",
|
|
44
|
-
"mdHeading": "
|
|
45
|
-
"mdLink": "
|
|
40
|
+
"mdHeading": "accent",
|
|
41
|
+
"mdLink": "blue",
|
|
46
42
|
"mdLinkUrl": "dimGray",
|
|
47
|
-
"mdCode": "
|
|
48
|
-
"mdCodeBlock": "
|
|
43
|
+
"mdCode": "yellow",
|
|
44
|
+
"mdCodeBlock": "green",
|
|
49
45
|
"mdCodeBlockBorder": "darkGray",
|
|
50
46
|
"mdQuote": "gray",
|
|
51
47
|
"mdQuoteBorder": "darkGray",
|
|
@@ -54,42 +50,43 @@
|
|
|
54
50
|
"toolDiffAdded": "green",
|
|
55
51
|
"toolDiffRemoved": "red",
|
|
56
52
|
"toolDiffContext": "gray",
|
|
57
|
-
"link": "
|
|
58
|
-
"syntaxComment": "
|
|
59
|
-
"syntaxKeyword": "
|
|
60
|
-
"syntaxFunction": "
|
|
61
|
-
"syntaxVariable": "
|
|
62
|
-
"syntaxString": "
|
|
63
|
-
"syntaxNumber": "#
|
|
64
|
-
"syntaxType": "
|
|
65
|
-
"syntaxOperator": "
|
|
66
|
-
"syntaxPunctuation": "
|
|
53
|
+
"link": "blue",
|
|
54
|
+
"syntaxComment": "gray",
|
|
55
|
+
"syntaxKeyword": "lightBlue",
|
|
56
|
+
"syntaxFunction": "blue",
|
|
57
|
+
"syntaxVariable": "cyan",
|
|
58
|
+
"syntaxString": "green",
|
|
59
|
+
"syntaxNumber": "#b48ead",
|
|
60
|
+
"syntaxType": "yellow",
|
|
61
|
+
"syntaxOperator": "gray",
|
|
62
|
+
"syntaxPunctuation": "gray",
|
|
67
63
|
"thinkingOff": "darkGray",
|
|
68
64
|
"thinkingMinimal": "dimGray",
|
|
69
|
-
"thinkingLow": "
|
|
70
|
-
"thinkingMedium": "
|
|
71
|
-
"thinkingHigh": "
|
|
72
|
-
"thinkingXhigh": "#
|
|
73
|
-
"bashMode": "
|
|
74
|
-
"statusLineBg": "
|
|
75
|
-
"statusLineSep":
|
|
76
|
-
"statusLineModel": "
|
|
77
|
-
"statusLinePath": "
|
|
78
|
-
"statusLineGitClean": "
|
|
79
|
-
"statusLineGitDirty": "
|
|
80
|
-
"statusLineContext": "
|
|
81
|
-
"statusLineSpend": "
|
|
82
|
-
"statusLineStaged":
|
|
83
|
-
"statusLineDirty":
|
|
84
|
-
"statusLineUntracked":
|
|
85
|
-
"statusLineOutput":
|
|
86
|
-
"statusLineCost":
|
|
65
|
+
"thinkingLow": "blue",
|
|
66
|
+
"thinkingMedium": "cyan",
|
|
67
|
+
"thinkingHigh": "accent",
|
|
68
|
+
"thinkingXhigh": "#c87558",
|
|
69
|
+
"bashMode": "accent",
|
|
70
|
+
"statusLineBg": "^-2",
|
|
71
|
+
"statusLineSep": "dimGray",
|
|
72
|
+
"statusLineModel": "accent",
|
|
73
|
+
"statusLinePath": "blue",
|
|
74
|
+
"statusLineGitClean": "green",
|
|
75
|
+
"statusLineGitDirty": "yellow",
|
|
76
|
+
"statusLineContext": "gray",
|
|
77
|
+
"statusLineSpend": "cyan",
|
|
78
|
+
"statusLineStaged": 179,
|
|
79
|
+
"statusLineDirty": 173,
|
|
80
|
+
"statusLineUntracked": 180,
|
|
81
|
+
"statusLineOutput": 180,
|
|
82
|
+
"statusLineCost": 173,
|
|
87
83
|
"statusLineSubagents": "accent",
|
|
88
|
-
"pythonMode": "yellow"
|
|
84
|
+
"pythonMode": "yellow",
|
|
85
|
+
"appBg": "^0"
|
|
89
86
|
},
|
|
90
87
|
"export": {
|
|
91
|
-
"pageBg": "#
|
|
92
|
-
"cardBg": "#
|
|
93
|
-
"infoBg": "#
|
|
88
|
+
"pageBg": "#242933",
|
|
89
|
+
"cardBg": "#323846",
|
|
90
|
+
"infoBg": "#2d333b"
|
|
94
91
|
}
|
|
95
92
|
}
|