jeo-code 0.1.0 → 0.4.5
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.ja.md +160 -0
- package/README.ko.md +160 -0
- package/README.md +115 -297
- package/README.zh.md +160 -0
- package/package.json +11 -6
- package/scripts/install.sh +28 -28
- package/scripts/uninstall.sh +17 -15
- package/src/AGENTS.md +50 -0
- package/src/agent/AGENTS.md +49 -0
- package/src/agent/bash-fixups.ts +103 -0
- package/src/agent/compaction.ts +410 -19
- package/src/agent/config-schema.ts +119 -5
- package/src/agent/context-files.ts +314 -17
- package/src/agent/dev/AGENTS.md +36 -0
- package/src/agent/dev/advanced-analyzer.ts +12 -0
- package/src/agent/dev/evolution-bridge.ts +82 -0
- package/src/agent/dev/evolution-logger.ts +41 -0
- package/src/agent/dev/self-analysis.ts +64 -0
- package/src/agent/dev/self-improve.ts +24 -0
- package/src/agent/dev/spec-automation.ts +49 -0
- package/src/agent/engine.ts +808 -54
- package/src/agent/hooks.ts +273 -0
- package/src/agent/loop.ts +21 -1
- package/src/agent/memory.ts +201 -0
- package/src/agent/model-recency.ts +32 -0
- package/src/agent/output-minimizer.ts +108 -0
- package/src/agent/output-util.ts +64 -0
- package/src/agent/plan.ts +187 -0
- package/src/agent/seed.ts +52 -0
- package/src/agent/session.ts +235 -21
- package/src/agent/state.ts +286 -39
- package/src/agent/step-budget.ts +232 -0
- package/src/agent/subagents.ts +223 -26
- package/src/agent/task-tool.ts +272 -0
- package/src/agent/todo-tool.ts +87 -0
- package/src/agent/tokenizer.ts +117 -0
- package/src/agent/tool-registry.ts +54 -0
- package/src/agent/tools.ts +624 -103
- package/src/agent/web-search.ts +538 -0
- package/src/ai/AGENTS.md +44 -0
- package/src/ai/index.ts +1 -0
- package/src/ai/model-catalog-compat.ts +3 -1
- package/src/ai/model-catalog.ts +74 -9
- package/src/ai/model-discovery.ts +215 -17
- package/src/ai/model-manager.ts +346 -32
- package/src/ai/model-picker.ts +1 -1
- package/src/ai/model-registry.ts +4 -2
- package/src/ai/pricing.ts +84 -0
- package/src/ai/provider-registry.ts +23 -0
- package/src/ai/provider-status.ts +60 -16
- package/src/ai/providers/AGENTS.md +42 -0
- package/src/ai/providers/anthropic.ts +250 -31
- package/src/ai/providers/antigravity.ts +219 -0
- package/src/ai/providers/errors.ts +15 -1
- package/src/ai/providers/gemini.ts +196 -13
- package/src/ai/providers/ollama.ts +37 -7
- package/src/ai/providers/openai-responses.ts +173 -0
- package/src/ai/providers/openai.ts +64 -12
- package/src/ai/sse.ts +4 -1
- package/src/ai/types.ts +18 -1
- package/src/auth/AGENTS.md +41 -0
- package/src/auth/callback-server.ts +6 -1
- package/src/auth/flows/AGENTS.md +32 -0
- package/src/auth/flows/antigravity.ts +151 -0
- package/src/auth/flows/google-project.ts +190 -0
- package/src/auth/flows/google.ts +39 -18
- package/src/auth/flows/index.ts +15 -5
- package/src/auth/flows/openai.ts +2 -2
- package/src/auth/oauth.ts +8 -0
- package/src/auth/refresh.ts +44 -27
- package/src/auth/storage.ts +149 -26
- package/src/auth/types.ts +1 -1
- package/src/autopilot.ts +362 -0
- package/src/bun-imports.d.ts +4 -0
- package/src/cli/AGENTS.md +39 -0
- package/src/cli/runner.ts +148 -14
- package/src/cli.ts +13 -4
- package/src/commands/AGENTS.md +40 -0
- package/src/commands/approve.ts +62 -3
- package/src/commands/auth.ts +167 -25
- package/src/commands/chat.ts +37 -8
- package/src/commands/deep-interview.ts +633 -175
- package/src/commands/doctor.ts +84 -37
- package/src/commands/evolve-core.ts +18 -0
- package/src/commands/evolve.ts +2 -1
- package/src/commands/export.ts +176 -0
- package/src/commands/gjc.ts +52 -0
- package/src/commands/launch.ts +3549 -240
- package/src/commands/mcp.ts +3 -3
- package/src/commands/ooo-seed.ts +19 -0
- package/src/commands/ralplan.ts +253 -35
- package/src/commands/resume.ts +1 -1
- package/src/commands/session.ts +183 -0
- package/src/commands/setup-helpers.ts +10 -3
- package/src/commands/setup.ts +57 -16
- package/src/commands/skills.ts +78 -18
- package/src/commands/state.ts +198 -0
- package/src/commands/status.ts +84 -0
- package/src/commands/team.ts +340 -212
- package/src/commands/ultragoal.ts +122 -61
- package/src/commands/update.ts +244 -0
- package/src/ledger.ts +270 -0
- package/src/mcp/AGENTS.md +38 -0
- package/src/mcp/server.ts +115 -14
- package/src/mcp/tools.ts +42 -22
- package/src/md-modules.d.ts +4 -0
- package/src/prompts/AGENTS.md +41 -0
- package/src/prompts/agents/AGENTS.md +35 -0
- package/src/prompts/agents/architect.md +35 -0
- package/src/prompts/agents/critic.md +37 -0
- package/src/prompts/agents/executor.md +36 -0
- package/src/prompts/agents/planner.md +37 -0
- package/src/prompts/skills/AGENTS.md +36 -0
- package/src/prompts/skills/deep-dive/AGENTS.md +31 -0
- package/src/prompts/skills/deep-dive/SKILL.md +13 -0
- package/src/prompts/skills/deep-interview/AGENTS.md +31 -0
- package/src/prompts/skills/deep-interview/SKILL.md +12 -0
- package/src/prompts/skills/gjc/AGENTS.md +31 -0
- package/src/prompts/skills/gjc/SKILL.md +15 -0
- package/src/prompts/skills/ralplan/AGENTS.md +31 -0
- package/src/prompts/skills/ralplan/SKILL.md +11 -0
- package/src/prompts/skills/team/AGENTS.md +31 -0
- package/src/prompts/skills/team/SKILL.md +11 -0
- package/src/prompts/skills/ultragoal/AGENTS.md +31 -0
- package/src/prompts/skills/ultragoal/SKILL.md +11 -0
- package/src/skills/AGENTS.md +38 -0
- package/src/skills/catalog.ts +565 -31
- package/src/tui/AGENTS.md +43 -0
- package/src/tui/app.ts +1181 -92
- package/src/tui/components/AGENTS.md +42 -0
- package/src/tui/components/ascii-art.ts +257 -15
- package/src/tui/components/autocomplete.ts +98 -16
- package/src/tui/components/autopilot-status.ts +65 -0
- package/src/tui/components/category-index.ts +49 -0
- package/src/tui/components/code-view.ts +54 -11
- package/src/tui/components/color.ts +171 -2
- package/src/tui/components/config-panel.ts +82 -15
- package/src/tui/components/duration.ts +38 -0
- package/src/tui/components/evolution.ts +3 -3
- package/src/tui/components/footer.ts +91 -42
- package/src/tui/components/forge.ts +426 -31
- package/src/tui/components/hints.ts +54 -0
- package/src/tui/components/hud.ts +73 -0
- package/src/tui/components/index.ts +4 -0
- package/src/tui/components/input-box.ts +150 -0
- package/src/tui/components/layout.ts +11 -3
- package/src/tui/components/live-model-picker.ts +108 -0
- package/src/tui/components/markdown-table.ts +140 -0
- package/src/tui/components/markdown-text.ts +97 -0
- package/src/tui/components/meter.ts +4 -1
- package/src/tui/components/model-picker.ts +3 -2
- package/src/tui/components/provider-picker.ts +3 -2
- package/src/tui/components/section.ts +70 -0
- package/src/tui/components/select-list.ts +40 -10
- package/src/tui/components/skill-picker.ts +25 -0
- package/src/tui/components/slash.ts +244 -21
- package/src/tui/components/status.ts +272 -11
- package/src/tui/components/step-timeline.ts +218 -0
- package/src/tui/components/stream.ts +26 -9
- package/src/tui/components/themes.ts +212 -6
- package/src/tui/components/todo-card.ts +47 -0
- package/src/tui/components/tool-list.ts +58 -12
- package/src/tui/components/transcript.ts +120 -0
- package/src/tui/components/update-box.ts +31 -0
- package/src/tui/components/welcome.ts +162 -0
- package/src/tui/components/width.ts +163 -0
- package/src/tui/monitoring/AGENTS.md +31 -0
- package/src/tui/monitoring/hud-view.ts +55 -0
- package/src/tui/renderer.ts +112 -3
- package/src/tui/terminal.ts +40 -33
- package/src/util/AGENTS.md +39 -0
- package/src/util/clipboard-image.ts +118 -0
- package/src/util/env.ts +12 -0
- package/src/util/provider-error.ts +78 -0
- package/src/util/retry.ts +91 -6
- package/src/util/update-check.ts +64 -0
- package/src/commands/models.ts +0 -104
|
@@ -0,0 +1,73 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
export type JeoPhase = "thinking" | "planning" | "executing" | "reporting" | "done";
|
|
4
|
+
|
|
5
|
+
export interface HudOptions {
|
|
6
|
+
unicode?: boolean;
|
|
7
|
+
color?: boolean;
|
|
8
|
+
}
|
|
9
|
+
|
|
10
|
+
export function renderHud(phase: JeoPhase, opts: HudOptions = {}): string {
|
|
11
|
+
const unicode = opts.unicode !== false;
|
|
12
|
+
const color = opts.color !== false;
|
|
13
|
+
|
|
14
|
+
const phases: JeoPhase[] = ["thinking", "planning", "executing", "reporting", "done"];
|
|
15
|
+
const activeIndex = phases.indexOf(phase);
|
|
16
|
+
|
|
17
|
+
const arrow = unicode ? " → " : " -> ";
|
|
18
|
+
const renderedArrow = color ? chalk.dim(arrow) : arrow;
|
|
19
|
+
|
|
20
|
+
const parts = phases.map((p, i) => {
|
|
21
|
+
let glyph = "";
|
|
22
|
+
let formatted = "";
|
|
23
|
+
|
|
24
|
+
if (i < activeIndex) {
|
|
25
|
+
// Completed
|
|
26
|
+
glyph = unicode ? "✔" : "v";
|
|
27
|
+
formatted = `${glyph} ${p}`;
|
|
28
|
+
if (color) {
|
|
29
|
+
formatted = chalk.green(formatted);
|
|
30
|
+
}
|
|
31
|
+
} else if (i === activeIndex) {
|
|
32
|
+
// Active
|
|
33
|
+
glyph = unicode ? "●" : "*";
|
|
34
|
+
formatted = `${glyph} ${p}`;
|
|
35
|
+
if (color) {
|
|
36
|
+
formatted = chalk.cyan.bold(formatted);
|
|
37
|
+
}
|
|
38
|
+
} else {
|
|
39
|
+
// Future
|
|
40
|
+
glyph = unicode ? "○" : "o";
|
|
41
|
+
formatted = `${glyph} ${p}`;
|
|
42
|
+
if (color) {
|
|
43
|
+
formatted = chalk.dim(formatted);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
return formatted;
|
|
47
|
+
});
|
|
48
|
+
|
|
49
|
+
return parts.join(renderedArrow);
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
export interface DerivePhaseInput {
|
|
53
|
+
thinking: boolean;
|
|
54
|
+
runningTool: boolean;
|
|
55
|
+
todosActive: boolean;
|
|
56
|
+
finished: boolean;
|
|
57
|
+
}
|
|
58
|
+
|
|
59
|
+
export function derivePhase(input: DerivePhaseInput): JeoPhase {
|
|
60
|
+
if (input.finished) {
|
|
61
|
+
return "done";
|
|
62
|
+
}
|
|
63
|
+
if (input.runningTool) {
|
|
64
|
+
return "executing";
|
|
65
|
+
}
|
|
66
|
+
if (input.thinking) {
|
|
67
|
+
return "thinking";
|
|
68
|
+
}
|
|
69
|
+
if (input.todosActive) {
|
|
70
|
+
return "planning";
|
|
71
|
+
}
|
|
72
|
+
return "reporting";
|
|
73
|
+
}
|
|
@@ -0,0 +1,150 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { BOX_ASCII, BOX_UNICODE, boxBlock } from "./layout";
|
|
3
|
+
import { visibleWidth } from "./width";
|
|
4
|
+
|
|
5
|
+
export interface InputBoxOptions {
|
|
6
|
+
cols?: number;
|
|
7
|
+
color?: boolean;
|
|
8
|
+
unicode?: boolean;
|
|
9
|
+
cwdLabel?: string;
|
|
10
|
+
/** Pending clipboard-image attachments hint (e.g. "⧉ 1 image attached — ctrl+v"). */
|
|
11
|
+
attachmentLabel?: string;
|
|
12
|
+
placeholder?: string;
|
|
13
|
+
maxBodyRows?: number;
|
|
14
|
+
/** Caret offset in CHARACTERS into `line` (readline's rl.cursor). Defaults to end. */
|
|
15
|
+
cursor?: number;
|
|
16
|
+
/** Accent painter for the border + `>` prompt mark (theme accent); default red/blue. */
|
|
17
|
+
accent?: (s: string) => string;
|
|
18
|
+
/** Shadow painter for the bottom/right "shaded" edges; defaults to a dim accent.
|
|
19
|
+
* The lit-vs-shaded two-tone contrast gives the box visible depth. */
|
|
20
|
+
accentShadow?: (s: string) => string;
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
export interface InputFrame {
|
|
24
|
+
lines: string[];
|
|
25
|
+
/** Caret row relative to the box's FIRST line (0 = top border, 1 = first body row). */
|
|
26
|
+
cursorRow: number;
|
|
27
|
+
/** 1-based terminal column of the caret cell. */
|
|
28
|
+
cursorCol: number;
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
/**
|
|
32
|
+
* Wrap plain input text (readline lines carry no ANSI) by DISPLAY width and map the
|
|
33
|
+
* caret's character offset to its (row, columnWidth) cell in the same pass — so the
|
|
34
|
+
* box prompt can place the real terminal cursor exactly where the next glyph lands,
|
|
35
|
+
* and arrow-key movement (readline updates rl.cursor) repositions it visibly.
|
|
36
|
+
*/
|
|
37
|
+
function wrapWithCursor(
|
|
38
|
+
text: string,
|
|
39
|
+
cursor: number,
|
|
40
|
+
width: number,
|
|
41
|
+
): { rows: string[]; row: number; col: number } {
|
|
42
|
+
const rows: string[] = [];
|
|
43
|
+
let cur = "";
|
|
44
|
+
let curW = 0;
|
|
45
|
+
let row = 0;
|
|
46
|
+
let col = 0;
|
|
47
|
+
const chars = Array.from(text.replace(/\r/g, ""));
|
|
48
|
+
const pos = Math.max(0, Math.min(cursor, chars.length));
|
|
49
|
+
for (let i = 0; i <= chars.length; i++) {
|
|
50
|
+
const ch = i < chars.length ? chars[i]! : "";
|
|
51
|
+
const rendered = ch === "\t" ? " " : ch;
|
|
52
|
+
const w = ch === "" || ch === "\n" ? 0 : ch === "\t" ? 2 : visibleWidth(ch);
|
|
53
|
+
// Wrap BEFORE recording the caret so a caret on a wrapping char follows it down.
|
|
54
|
+
if (w > 0 && curW + w > width && curW > 0) {
|
|
55
|
+
rows.push(cur);
|
|
56
|
+
cur = "";
|
|
57
|
+
curW = 0;
|
|
58
|
+
}
|
|
59
|
+
if (i === pos) {
|
|
60
|
+
row = rows.length;
|
|
61
|
+
col = curW;
|
|
62
|
+
}
|
|
63
|
+
if (ch === "\n") {
|
|
64
|
+
rows.push(cur);
|
|
65
|
+
cur = "";
|
|
66
|
+
curW = 0;
|
|
67
|
+
continue;
|
|
68
|
+
}
|
|
69
|
+
if (ch !== "") {
|
|
70
|
+
cur += rendered;
|
|
71
|
+
curW += w;
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
rows.push(cur);
|
|
75
|
+
return { rows, row, col };
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/**
|
|
79
|
+
* Boxed input prompt (gjc-style): a `>` marker leads the first body row, the typed
|
|
80
|
+
* text (or a dim placeholder) follows, and the caret cell is reported so the caller
|
|
81
|
+
* can park the REAL terminal cursor right after `>` — moving with the arrow keys.
|
|
82
|
+
*/
|
|
83
|
+
export function renderInputFrame(line: string, opts: InputBoxOptions = {}): InputFrame {
|
|
84
|
+
const cols = Math.max(24, Math.trunc(opts.cols ?? 80));
|
|
85
|
+
const useColor = opts.color !== false;
|
|
86
|
+
const placeholder = opts.placeholder ?? "Type your message...";
|
|
87
|
+
const bodyWidth = Math.max(1, cols - 4);
|
|
88
|
+
const textWidth = Math.max(1, bodyWidth - 2); // "> " / " " prefix columns
|
|
89
|
+
|
|
90
|
+
let rows: string[];
|
|
91
|
+
let crow = 0;
|
|
92
|
+
let ccol = 0;
|
|
93
|
+
let placeholderRow = false;
|
|
94
|
+
if (line.length === 0) {
|
|
95
|
+
rows = [placeholder];
|
|
96
|
+
placeholderRow = true;
|
|
97
|
+
} else {
|
|
98
|
+
const wrapped = wrapWithCursor(line, opts.cursor ?? line.length, textWidth);
|
|
99
|
+
rows = wrapped.rows;
|
|
100
|
+
crow = wrapped.row;
|
|
101
|
+
ccol = wrapped.col;
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Tail-truncate to maxBodyRows (caret usually edits near the end); the first
|
|
105
|
+
// visible row carries an `…` marker when earlier rows are hidden.
|
|
106
|
+
const maxBodyRows = Math.max(1, Math.trunc(opts.maxBodyRows ?? rows.length));
|
|
107
|
+
let hidden = 0;
|
|
108
|
+
if (rows.length > maxBodyRows) {
|
|
109
|
+
hidden = rows.length - maxBodyRows;
|
|
110
|
+
rows = rows.slice(hidden);
|
|
111
|
+
rows[0] = `…${rows[0] ?? ""}`.slice(0, textWidth);
|
|
112
|
+
}
|
|
113
|
+
let visRow = Math.max(0, Math.min(crow - hidden, rows.length - 1));
|
|
114
|
+
if (hidden > 0 && crow - hidden === 0) ccol += 1; // shifted by the `…` marker
|
|
115
|
+
if (crow - hidden < 0) { visRow = 0; ccol = 0; }
|
|
116
|
+
|
|
117
|
+
const promptMark = "> ";
|
|
118
|
+
const paintPrompt = useColor ? (opts.accent ?? chalk.red) : (s: string) => s;
|
|
119
|
+
const paintGhost = useColor ? chalk.dim : (s: string) => s;
|
|
120
|
+
const body = rows.map((r, i) => {
|
|
121
|
+
const content = placeholderRow ? paintGhost(r) : r;
|
|
122
|
+
return i === 0 ? paintPrompt(promptMark) + content : " " + r;
|
|
123
|
+
});
|
|
124
|
+
|
|
125
|
+
const content = [...body];
|
|
126
|
+
if (opts.attachmentLabel) {
|
|
127
|
+
content.push(useColor ? chalk.cyan(opts.attachmentLabel) : opts.attachmentLabel);
|
|
128
|
+
}
|
|
129
|
+
if (opts.cwdLabel) {
|
|
130
|
+
content.push(useColor ? chalk.gray(opts.cwdLabel) : opts.cwdLabel);
|
|
131
|
+
}
|
|
132
|
+
const glyphs = opts.unicode === false ? BOX_ASCII : BOX_UNICODE;
|
|
133
|
+
// Depth cue: lit top/left edge (bright accent) vs shaded bottom/right edge (dim).
|
|
134
|
+
const paint = useColor ? (opts.accent ?? chalk.blueBright) : (s: string) => s;
|
|
135
|
+
const paintShadow = useColor ? (opts.accentShadow ?? ((s: string) => chalk.blue.dim(s))) : (s: string) => s;
|
|
136
|
+
const lines = boxBlock(content, cols, { glyphs, paint, paintShadow, align: "left" });
|
|
137
|
+
|
|
138
|
+
// Terminal columns: border at col 1, content starts col 2, text after "> " at col 4.
|
|
139
|
+
const cursorRow = 1 + visRow;
|
|
140
|
+
const cursorCol = 4 + (placeholderRow ? 0 : ccol);
|
|
141
|
+
return { lines, cursorRow, cursorCol };
|
|
142
|
+
}
|
|
143
|
+
|
|
144
|
+
/**
|
|
145
|
+
* Renders a boxed input box enclosing either the current typed text or a placeholder.
|
|
146
|
+
* If opts.cwdLabel is provided, a dim gray label line is appended after the text inside the box.
|
|
147
|
+
*/
|
|
148
|
+
export function renderInputBox(line: string, opts: InputBoxOptions = {}): string[] {
|
|
149
|
+
return renderInputFrame(line, opts).lines;
|
|
150
|
+
}
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
* pure functions over `string[]` blocks, injectable `cols`/`rows` for tests.
|
|
7
7
|
*/
|
|
8
8
|
import { visibleWidth } from "./color";
|
|
9
|
+
import { truncate } from "../terminal";
|
|
9
10
|
|
|
10
11
|
export type HAlign = "left" | "center" | "right";
|
|
11
12
|
export type VAlign = "top" | "center" | "bottom";
|
|
@@ -82,24 +83,31 @@ export interface BoxGlyphs {
|
|
|
82
83
|
export const BOX_UNICODE: BoxGlyphs = { tl: "\u256d", tr: "\u256e", bl: "\u2570", br: "\u256f", h: "\u2500", v: "\u2502" };
|
|
83
84
|
export const BOX_ASCII: BoxGlyphs = { tl: "+", tr: "+", bl: "+", br: "+", h: "-", v: "|" };
|
|
84
85
|
|
|
86
|
+
/**
|
|
87
|
+
* `paintShadow` (optional) paints the bottom border and right vertical edge —
|
|
88
|
+
* the "shaded" edges — while `paint` keeps the top border and left vertical
|
|
89
|
+
* "lit". The two-tone contrast gives flat boxes a pseudo-3D depth cue.
|
|
90
|
+
*/
|
|
85
91
|
export function boxBlock(
|
|
86
92
|
lines: string[],
|
|
87
93
|
width: number,
|
|
88
|
-
opts: { glyphs?: BoxGlyphs; paint?: (s: string) => string; align?: HAlign } = {},
|
|
94
|
+
opts: { glyphs?: BoxGlyphs; paint?: (s: string) => string; paintShadow?: (s: string) => string; align?: HAlign } = {},
|
|
89
95
|
): string[] {
|
|
90
96
|
const g = opts.glyphs ?? BOX_UNICODE;
|
|
91
97
|
const paint = opts.paint ?? ((s: string) => s);
|
|
98
|
+
const shadow = opts.paintShadow ?? paint;
|
|
92
99
|
const inner = Math.max(0, width - 2);
|
|
93
100
|
const top = paint(g.tl + g.h.repeat(inner) + g.tr);
|
|
94
|
-
const bottom =
|
|
101
|
+
const bottom = shadow(g.bl + g.h.repeat(inner) + g.br);
|
|
95
102
|
const mid = lines.map(l => {
|
|
96
103
|
if (l === "DIVIDER") {
|
|
97
104
|
const leftChar = g.tl === "+" ? "+" : "├";
|
|
98
105
|
const rightChar = g.tr === "+" ? "+" : "┤";
|
|
99
106
|
return paint(leftChar + g.h.repeat(inner) + rightChar);
|
|
100
107
|
}
|
|
108
|
+
const truncated = truncate(l, inner);
|
|
101
109
|
const align = opts.align ?? "left";
|
|
102
|
-
return paint(g.v) + padLineTo(
|
|
110
|
+
return paint(g.v) + padLineTo(truncated, inner, align) + shadow(g.v);
|
|
103
111
|
});
|
|
104
112
|
return [top, ...mid, bottom];
|
|
105
113
|
}
|
|
@@ -0,0 +1,108 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { PickEntry } from "../../ai/model-picker";
|
|
3
|
+
import type { ProviderName } from "../../ai/types";
|
|
4
|
+
import { catalogMetadata, formatTokens } from "../../ai/model-catalog";
|
|
5
|
+
import { SelectList, renderSelectList, type RenderSelectOptions, type SelectItem } from "./select-list";
|
|
6
|
+
|
|
7
|
+
function liveModelHint(model: string, current?: string): string {
|
|
8
|
+
const meta = catalogMetadata(model);
|
|
9
|
+
const parts: string[] = [];
|
|
10
|
+
if (meta) {
|
|
11
|
+
parts.push(`${formatTokens(meta.contextTokens)} ctx`);
|
|
12
|
+
parts.push(`${formatTokens(meta.maxOutputTokens)} out`);
|
|
13
|
+
parts.push(meta.thinking.length ? meta.thinking.join(",") : "-");
|
|
14
|
+
parts.push(meta.images ? "img" : "text");
|
|
15
|
+
} else {
|
|
16
|
+
parts.push("unknown caps");
|
|
17
|
+
}
|
|
18
|
+
if (current === model) parts.push("current");
|
|
19
|
+
return parts.join(" · ");
|
|
20
|
+
}
|
|
21
|
+
|
|
22
|
+
export interface ModelAssignmentBadge {
|
|
23
|
+
/** Stable target id: "default" or a subagent role id. */
|
|
24
|
+
role: string;
|
|
25
|
+
/** Short badge text shown beside a matching model (DEFAULT, EXECUTOR, ...). */
|
|
26
|
+
label: string;
|
|
27
|
+
/** Configured model id for this target, qualified (`provider/model`) when possible. */
|
|
28
|
+
model: string;
|
|
29
|
+
/** Reasoning budget shown after the badge. Omit for no suffix; use "inherit" for inherited role thinking. */
|
|
30
|
+
thinking?: string;
|
|
31
|
+
/** Visual role color. Unknown values fall back to a neutral badge. */
|
|
32
|
+
color?: "default" | "executor" | "architect" | "planner" | "critic" | string;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface LiveModelPickerOptions {
|
|
36
|
+
current?: string;
|
|
37
|
+
/** Role/default assignments to render as badges on matching models. */
|
|
38
|
+
assignments?: readonly ModelAssignmentBadge[];
|
|
39
|
+
/** Disable ANSI badge styling for deterministic tests or plain terminals. */
|
|
40
|
+
color?: boolean;
|
|
41
|
+
/** Providers visible for context but not selectable because they cannot serve a turn. */
|
|
42
|
+
disabledProviders?: readonly ProviderName[];
|
|
43
|
+
disabledHint?: string;
|
|
44
|
+
}
|
|
45
|
+
function qualifiedModelId(entry: PickEntry): string {
|
|
46
|
+
return entry.model.includes("/") ? entry.model : `${entry.provider}/${entry.model}`;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function modelMatchesAssignment(entry: PickEntry, model: string): boolean {
|
|
50
|
+
const assigned = model.trim().toLowerCase();
|
|
51
|
+
if (!assigned) return false;
|
|
52
|
+
const bare = entry.model.toLowerCase();
|
|
53
|
+
const qualified = qualifiedModelId(entry).toLowerCase();
|
|
54
|
+
return assigned === bare || assigned === qualified;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
function roleBadgeColor(label: string, color: ModelAssignmentBadge["color"]): string {
|
|
58
|
+
switch (color) {
|
|
59
|
+
case "default":
|
|
60
|
+
return chalk.bgGreen.black(` ${label} `);
|
|
61
|
+
case "executor":
|
|
62
|
+
return chalk.bgRedBright.black(` ${label} `);
|
|
63
|
+
case "architect":
|
|
64
|
+
return chalk.bgHex("#e7c7bd").black(` ${label} `);
|
|
65
|
+
case "planner":
|
|
66
|
+
return chalk.bgYellow.black(` ${label} `);
|
|
67
|
+
case "critic":
|
|
68
|
+
return chalk.bgMagenta.black(` ${label} `);
|
|
69
|
+
default:
|
|
70
|
+
return chalk.bgGray.black(` ${label} `);
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
function assignmentBadges(entry: PickEntry, opts: LiveModelPickerOptions): string[] {
|
|
75
|
+
return (opts.assignments ?? [])
|
|
76
|
+
.filter(a => modelMatchesAssignment(entry, a.model))
|
|
77
|
+
.map(a => {
|
|
78
|
+
const label = a.label.trim().toUpperCase();
|
|
79
|
+
const think = a.thinking ? ` (${a.thinking})` : "";
|
|
80
|
+
return opts.color === false ? `${label}${think}` : `${roleBadgeColor(label, a.color)}${chalk.dim(think)}`;
|
|
81
|
+
});
|
|
82
|
+
}
|
|
83
|
+
|
|
84
|
+
|
|
85
|
+
export function buildLiveModelChoices(entries: PickEntry[], opts: LiveModelPickerOptions = {}): SelectItem<PickEntry>[] {
|
|
86
|
+
const disabled = new Set(opts.disabledProviders ?? []);
|
|
87
|
+
return entries.map(entry => {
|
|
88
|
+
const blocked = disabled.has(entry.provider);
|
|
89
|
+
const badges = assignmentBadges(entry, opts);
|
|
90
|
+
const caps = blocked ? `${liveModelHint(entry.model, opts.current)} · ${opts.disabledHint ?? "provider not ready"}` : liveModelHint(entry.model, opts.current);
|
|
91
|
+
return {
|
|
92
|
+
value: entry,
|
|
93
|
+
label: `#${entry.index} ${qualifiedModelId(entry)}`,
|
|
94
|
+
group: blocked ? `${entry.provider} (not ready)` : entry.provider,
|
|
95
|
+
hint: badges.length ? `${badges.join(" ")} ${opts.color === false ? "· " : chalk.dim("· ")}${caps}` : caps,
|
|
96
|
+
hintRaw: badges.length > 0 && opts.color !== false,
|
|
97
|
+
disabled: blocked,
|
|
98
|
+
};
|
|
99
|
+
});
|
|
100
|
+
}
|
|
101
|
+
|
|
102
|
+
export function liveModelPicker(entries: PickEntry[], opts: LiveModelPickerOptions = {}): SelectList<PickEntry> {
|
|
103
|
+
return new SelectList(buildLiveModelChoices(entries, opts));
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
export function renderLiveModelPicker(list: SelectList<PickEntry>, opts: RenderSelectOptions = {}): string[] {
|
|
107
|
+
return renderSelectList(list, { title: "Select a live model", rows: 12, ...opts });
|
|
108
|
+
}
|
|
@@ -0,0 +1,140 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Render GFM markdown tables in assistant text as box-drawn tables (consensus-seed
|
|
3
|
+
* P2.B8). Pure-TS, width-aware (uses ./width so CJK/emoji columns align). Non-table
|
|
4
|
+
* text passes through untouched; a block is only treated as a table when it has a
|
|
5
|
+
* header row, a `|---|---|` delimiter row, and at least one body row.
|
|
6
|
+
*/
|
|
7
|
+
import { visibleWidth, truncateToWidth } from "./width";
|
|
8
|
+
|
|
9
|
+
export interface TableRenderOptions {
|
|
10
|
+
unicode?: boolean;
|
|
11
|
+
/** Hard cap on a column's display width before truncation; keeps wide tables on screen. */
|
|
12
|
+
maxColWidth?: number;
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
/** A line is a table row when it has at least one unescaped `|` and trims to start/contain cells. */
|
|
16
|
+
function isTableRow(line: string): boolean {
|
|
17
|
+
const t = line.trim();
|
|
18
|
+
return t.includes("|") && /\|/.test(t);
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
/** A line is the GFM delimiter row, e.g. `| --- | :--: | ---: |`. */
|
|
22
|
+
function isDelimiterRow(line: string): boolean {
|
|
23
|
+
const t = line.trim().replace(/^\||\|$/g, "");
|
|
24
|
+
if (!t.includes("-")) return false;
|
|
25
|
+
return t.split("|").every(cell => /^\s*:?-{1,}:?\s*$/.test(cell));
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
/** Split a markdown table row into trimmed cell strings (drop the outer pipes). */
|
|
29
|
+
function splitCells(line: string): string[] {
|
|
30
|
+
let t = line.trim();
|
|
31
|
+
if (t.startsWith("|")) t = t.slice(1);
|
|
32
|
+
if (t.endsWith("|")) t = t.slice(0, -1);
|
|
33
|
+
return t.split("|").map(c => c.trim());
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
type Align = "left" | "center" | "right";
|
|
37
|
+
|
|
38
|
+
function alignmentsFrom(delim: string): Align[] {
|
|
39
|
+
return splitCells(delim).map(cell => {
|
|
40
|
+
const c = cell.trim();
|
|
41
|
+
const left = c.startsWith(":");
|
|
42
|
+
const right = c.endsWith(":");
|
|
43
|
+
if (left && right) return "center";
|
|
44
|
+
if (right) return "right";
|
|
45
|
+
return "left";
|
|
46
|
+
});
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
function padCell(text: string, width: number, align: Align): string {
|
|
50
|
+
const w = visibleWidth(text);
|
|
51
|
+
const pad = Math.max(0, width - w);
|
|
52
|
+
if (align === "right") return " ".repeat(pad) + text;
|
|
53
|
+
if (align === "center") {
|
|
54
|
+
const l = Math.floor(pad / 2);
|
|
55
|
+
return " ".repeat(l) + text + " ".repeat(pad - l);
|
|
56
|
+
}
|
|
57
|
+
return text + " ".repeat(pad);
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
/** Full box glyph set incl. T-junctions for grid rules (layout.BoxGlyphs has corners only). */
|
|
61
|
+
interface TableGlyphs {
|
|
62
|
+
h: string; v: string;
|
|
63
|
+
tl: string; tt: string; tr: string;
|
|
64
|
+
ml: string; mm: string; mr: string;
|
|
65
|
+
bl: string; bt: string; br: string;
|
|
66
|
+
}
|
|
67
|
+
const TABLE_UNICODE: TableGlyphs = {
|
|
68
|
+
h: "─", v: "│",
|
|
69
|
+
tl: "┌", tt: "┬", tr: "┐",
|
|
70
|
+
ml: "├", mm: "┼", mr: "┤",
|
|
71
|
+
bl: "└", bt: "┴", br: "┘",
|
|
72
|
+
};
|
|
73
|
+
const TABLE_ASCII: TableGlyphs = {
|
|
74
|
+
h: "-", v: "|",
|
|
75
|
+
tl: "+", tt: "+", tr: "+",
|
|
76
|
+
ml: "+", mm: "+", mr: "+",
|
|
77
|
+
bl: "+", bt: "+", br: "+",
|
|
78
|
+
};
|
|
79
|
+
|
|
80
|
+
/** Render one parsed table (header + rows) to box-drawn lines. */
|
|
81
|
+
function drawTable(header: string[], rows: string[][], aligns: Align[], opts: TableRenderOptions): string[] {
|
|
82
|
+
const g = opts.unicode === false ? TABLE_ASCII : TABLE_UNICODE;
|
|
83
|
+
const cap = Math.max(4, opts.maxColWidth ?? 40);
|
|
84
|
+
const cols = Math.max(header.length, ...rows.map(r => r.length));
|
|
85
|
+
const norm = (r: string[]): string[] => Array.from({ length: cols }, (_, i) => truncateToWidth(r[i] ?? "", cap));
|
|
86
|
+
const h = norm(header);
|
|
87
|
+
const body = rows.map(norm);
|
|
88
|
+
const aligned = Array.from({ length: cols }, (_, i) => aligns[i] ?? "left");
|
|
89
|
+
// Column width = max display width of header + body cells in that column.
|
|
90
|
+
const widths = Array.from({ length: cols }, (_, i) =>
|
|
91
|
+
Math.max(visibleWidth(h[i]!), ...body.map(r => visibleWidth(r[i]!)), 1),
|
|
92
|
+
);
|
|
93
|
+
const rule = (l: string, mid: string, r: string): string =>
|
|
94
|
+
l + widths.map(w => g.h.repeat(w + 2)).join(mid) + r;
|
|
95
|
+
const rowLine = (cells: string[]): string =>
|
|
96
|
+
g.v + cells.map((c, i) => " " + padCell(c, widths[i]!, aligned[i]!) + " ").join(g.v) + g.v;
|
|
97
|
+
return [
|
|
98
|
+
rule(g.tl, g.tt, g.tr),
|
|
99
|
+
rowLine(h),
|
|
100
|
+
rule(g.ml, g.mm, g.mr),
|
|
101
|
+
...body.map(rowLine),
|
|
102
|
+
rule(g.bl, g.bt, g.br),
|
|
103
|
+
];
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
/**
|
|
107
|
+
* Replace every GFM table block in `text` with a box-drawn table. Returns the text
|
|
108
|
+
* with tables rendered; all non-table lines are preserved verbatim.
|
|
109
|
+
*/
|
|
110
|
+
export function renderMarkdownTables(text: string, opts: TableRenderOptions = {}): string {
|
|
111
|
+
if (!text.includes("|")) return text;
|
|
112
|
+
const lines = text.split("\n");
|
|
113
|
+
const out: string[] = [];
|
|
114
|
+
let i = 0;
|
|
115
|
+
while (i < lines.length) {
|
|
116
|
+
// A table needs: header row, delimiter row, ≥1 body row.
|
|
117
|
+
if (
|
|
118
|
+
i + 1 < lines.length &&
|
|
119
|
+
isTableRow(lines[i]!) &&
|
|
120
|
+
isDelimiterRow(lines[i + 1]!) &&
|
|
121
|
+
i + 2 < lines.length &&
|
|
122
|
+
isTableRow(lines[i + 2]!)
|
|
123
|
+
) {
|
|
124
|
+
const header = splitCells(lines[i]!);
|
|
125
|
+
const aligns = alignmentsFrom(lines[i + 1]!);
|
|
126
|
+
const rows: string[][] = [];
|
|
127
|
+
let j = i + 2;
|
|
128
|
+
while (j < lines.length && isTableRow(lines[j]!) && !isDelimiterRow(lines[j]!)) {
|
|
129
|
+
rows.push(splitCells(lines[j]!));
|
|
130
|
+
j++;
|
|
131
|
+
}
|
|
132
|
+
for (const line of drawTable(header, rows, aligns, opts)) out.push(line);
|
|
133
|
+
i = j;
|
|
134
|
+
continue;
|
|
135
|
+
}
|
|
136
|
+
out.push(lines[i]!);
|
|
137
|
+
i++;
|
|
138
|
+
}
|
|
139
|
+
return out.join("\n");
|
|
140
|
+
}
|
|
@@ -0,0 +1,97 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
/**
|
|
3
|
+
* Strip markdown formatting from text, returning plain text.
|
|
4
|
+
*/
|
|
5
|
+
export function stripMarkdown(text: string): string {
|
|
6
|
+
if (!text) return "";
|
|
7
|
+
|
|
8
|
+
let out = text;
|
|
9
|
+
|
|
10
|
+
// 1. Remove code block fences (e.g. ```ts or ```)
|
|
11
|
+
out = out.replace(/^```[a-zA-Z0-9_-]*\s*$/gm, "");
|
|
12
|
+
|
|
13
|
+
// 2. Remove inline code backticks
|
|
14
|
+
out = out.replace(/`([^`]+)`/g, "$1");
|
|
15
|
+
|
|
16
|
+
// 3. Remove headers (e.g. # Header -> Header)
|
|
17
|
+
out = out.replace(/^#+\s+(.+)$/gm, "$1");
|
|
18
|
+
|
|
19
|
+
// 4. Remove bold/italic markers
|
|
20
|
+
out = out.replace(/\*\*\*([^\*]+)\*\*\*/g, "$1");
|
|
21
|
+
out = out.replace(/\*\*([^\*]+)\*\*/g, "$1");
|
|
22
|
+
out = out.replace(/\*([^\*]+)\*/g, "$1");
|
|
23
|
+
out = out.replace(/___([^\_]+)___/g, "$1");
|
|
24
|
+
out = out.replace(/__([^\_]+)__/g, "$1");
|
|
25
|
+
out = out.replace(/_([^\_]+)_/g, "$1");
|
|
26
|
+
|
|
27
|
+
// 5. Convert links [text](url) -> text (url)
|
|
28
|
+
out = out.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, "$1 ($2)");
|
|
29
|
+
|
|
30
|
+
// 6. Convert images  -> alt
|
|
31
|
+
out = out.replace(/!\[([^\]]*)\]\(([^\)]+)\)/g, "$1");
|
|
32
|
+
|
|
33
|
+
// 7. Remove blockquote markers (> text -> text)
|
|
34
|
+
out = out.replace(/^>\s+(.+)$/gm, "$1");
|
|
35
|
+
|
|
36
|
+
// 8. Remove horizontal rules
|
|
37
|
+
out = out.replace(/^[-\*_]{3,}\s*$/gm, "");
|
|
38
|
+
|
|
39
|
+
return out.trim();
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
export interface MarkdownAnsiOptions {
|
|
43
|
+
/** Heading painter (theme accent + bold); default chalk.bold. */
|
|
44
|
+
accent?: (s: string) => string;
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/**
|
|
48
|
+
* Render markdown as styled ANSI text (jeo-ref final-report format): headings
|
|
49
|
+
* become painted section titles, **bold** / `inline code` keep visual weight,
|
|
50
|
+
* fences/links/quotes degrade exactly like stripMarkdown. Fenced code BODIES are
|
|
51
|
+
* passed through untouched (only the ``` fence rows are dropped) so code samples
|
|
52
|
+
* are never reformatted. Pure + stateless — safe for the one-shot finish path.
|
|
53
|
+
*/
|
|
54
|
+
export function renderMarkdownAnsi(text: string, opts: MarkdownAnsiOptions = {}): string {
|
|
55
|
+
if (!text) return "";
|
|
56
|
+
const accent = opts.accent ?? ((s: string) => chalk.bold(s));
|
|
57
|
+
|
|
58
|
+
// Links/images FIRST: bold/code styling injects ANSI escapes whose `[` would
|
|
59
|
+
// otherwise be swallowed by the `[label](url)` matcher (corrupting both).
|
|
60
|
+
const styleInline = (line: string): string =>
|
|
61
|
+
line
|
|
62
|
+
.replace(/!\[([^\]]*)\]\(([^\)]+)\)/g, "$1")
|
|
63
|
+
.replace(/\[([^\]]+)\]\(([^\)]+)\)/g, (_m, label: string, url: string) => `${label} ${chalk.dim(`(${url})`)}`)
|
|
64
|
+
.replace(/`([^`]+)`/g, (_m, code: string) => chalk.cyan(code))
|
|
65
|
+
.replace(/\*\*\*([^\*]+)\*\*\*/g, (_m, t: string) => chalk.bold.italic(t))
|
|
66
|
+
.replace(/\*\*([^\*]+)\*\*/g, (_m, t: string) => chalk.bold(t))
|
|
67
|
+
.replace(/__([^\_]+)__/g, (_m, t: string) => chalk.bold(t));
|
|
68
|
+
|
|
69
|
+
const out: string[] = [];
|
|
70
|
+
let inFence = false;
|
|
71
|
+
for (const line of text.split("\n")) {
|
|
72
|
+
if (/^```/.test(line.trim())) {
|
|
73
|
+
inFence = !inFence;
|
|
74
|
+
continue; // drop the fence row itself (stripMarkdown parity)
|
|
75
|
+
}
|
|
76
|
+
if (inFence) {
|
|
77
|
+
out.push(line); // code bodies verbatim — never styled or reflowed
|
|
78
|
+
continue;
|
|
79
|
+
}
|
|
80
|
+
const heading = line.match(/^(#{1,6})\s+(.+)$/);
|
|
81
|
+
if (heading) {
|
|
82
|
+
out.push(accent(styleInline(heading[2]!)));
|
|
83
|
+
continue;
|
|
84
|
+
}
|
|
85
|
+
const quote = line.match(/^>\s+(.+)$/);
|
|
86
|
+
if (quote) {
|
|
87
|
+
out.push(chalk.dim(`▎ ${styleInline(quote[1]!)}`));
|
|
88
|
+
continue;
|
|
89
|
+
}
|
|
90
|
+
if (/^[-\*_]{3,}\s*$/.test(line)) {
|
|
91
|
+
out.push(chalk.dim("─".repeat(24)));
|
|
92
|
+
continue;
|
|
93
|
+
}
|
|
94
|
+
out.push(styleInline(line));
|
|
95
|
+
}
|
|
96
|
+
return out.join("\n").trim();
|
|
97
|
+
}
|
|
@@ -4,6 +4,8 @@ import { size } from "../terminal";
|
|
|
4
4
|
export interface MeterOptions {
|
|
5
5
|
/** Use ASCII-only glyphs for terminals without unicode (default true = unicode). */
|
|
6
6
|
unicode?: boolean;
|
|
7
|
+
/** Whether to render color (default true). */
|
|
8
|
+
color?: boolean;
|
|
7
9
|
}
|
|
8
10
|
|
|
9
11
|
/** Horizontal evolutionary percent/progress meter for pipeline + doctor TUI views. */
|
|
@@ -19,7 +21,8 @@ export function meter(value: number, max = 1, width?: number, opts: MeterOptions
|
|
|
19
21
|
const cells = width !== undefined ? Math.max(0, Math.trunc(width)) : Math.max(10, Math.min(40, size().cols - 30));
|
|
20
22
|
const filledCount = Math.round(ratio * cells);
|
|
21
23
|
const { fill, empty, color } = meterGlyphsFor(stageIndexForRatio(ratio), opts.unicode !== false);
|
|
22
|
-
const
|
|
24
|
+
const paint = opts.color !== false ? color : (s: string) => s;
|
|
25
|
+
const bar = paint(fill.repeat(filledCount)) + empty.repeat(cells - filledCount);
|
|
23
26
|
return `[${bar}] ${Math.round(ratio * 100)}%`;
|
|
24
27
|
}
|
|
25
28
|
|
|
@@ -6,6 +6,7 @@
|
|
|
6
6
|
*/
|
|
7
7
|
import { SelectList, renderSelectList, type SelectItem, type RenderSelectOptions } from "./select-list";
|
|
8
8
|
import { catalogForProvider, type ModelCatalogEntry } from "../../ai/model-catalog-compat";
|
|
9
|
+
import { companyLabel } from "../../ai/model-catalog";
|
|
9
10
|
import { PROVIDER_NAMES, type ProviderStatus } from "../../ai/provider-status";
|
|
10
11
|
import type { ProviderName } from "../../ai/types";
|
|
11
12
|
|
|
@@ -58,11 +59,11 @@ export function buildModelChoices(statuses: ProviderStatus[], opts: ModelPickerO
|
|
|
58
59
|
for (const provider of providers) {
|
|
59
60
|
const ready = !!readyOf.get(provider);
|
|
60
61
|
if (!ready && !includeUnready) continue;
|
|
61
|
-
const group = `${provider}${ready ? "" : " (no credential)"}`;
|
|
62
|
+
const group = `${provider} — ${companyLabel(provider)}${ready ? "" : " (no credential)"}`;
|
|
62
63
|
for (const entry of catalogForProvider(provider)) {
|
|
63
64
|
items.push({
|
|
64
65
|
value: entry.id,
|
|
65
|
-
label: entry.id,
|
|
66
|
+
label: `${entry.id} (${companyLabel(provider, entry)})`,
|
|
66
67
|
group,
|
|
67
68
|
hint: modelHint(entry, ready, unicode),
|
|
68
69
|
});
|