icopilot 2.2.0
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 +250 -0
- package/LICENSE +21 -0
- package/README.md +214 -0
- package/bin/icopilot.js +6 -0
- package/dist/acp/router.js +123 -0
- package/dist/acp/schema.js +53 -0
- package/dist/agents/aggregator.js +187 -0
- package/dist/agents/custom-agents.js +97 -0
- package/dist/agents/goal-driven.js +411 -0
- package/dist/agents/multi-repo.js +350 -0
- package/dist/agents/parallel-runner.js +181 -0
- package/dist/agents/router.js +144 -0
- package/dist/agents/self-heal.js +481 -0
- package/dist/agents/tdd-agent.js +278 -0
- package/dist/api/github-models.js +158 -0
- package/dist/bridge/ide-bridge.js +479 -0
- package/dist/cloud/routine-executor.js +34 -0
- package/dist/cloud/routine-scheduler.js +67 -0
- package/dist/cloud/routine-storage.js +297 -0
- package/dist/commands/acp-cmd.js +143 -0
- package/dist/commands/actions-cmd.js +624 -0
- package/dist/commands/agent-cmd.js +144 -0
- package/dist/commands/alias-cmd.js +132 -0
- package/dist/commands/bookmark-cmd.js +77 -0
- package/dist/commands/changelog-cmd.js +99 -0
- package/dist/commands/changes-cmd.js +120 -0
- package/dist/commands/clipboard-cmd.js +217 -0
- package/dist/commands/cloud-routine-cmd.js +265 -0
- package/dist/commands/codegen-cmd.js +544 -0
- package/dist/commands/compare-cmd.js +116 -0
- package/dist/commands/context-cmd.js +247 -0
- package/dist/commands/context-viz-cmd.js +43 -0
- package/dist/commands/conventions-cmd.js +116 -0
- package/dist/commands/cost-cmd.js +51 -0
- package/dist/commands/deps-cmd.js +294 -0
- package/dist/commands/diagram-cmd.js +658 -0
- package/dist/commands/diff-review-cmd.js +92 -0
- package/dist/commands/doc-cmd.js +412 -0
- package/dist/commands/doctor-cmd.js +152 -0
- package/dist/commands/editor-cmd.js +49 -0
- package/dist/commands/env-cmd.js +86 -0
- package/dist/commands/explain-cmd.js +78 -0
- package/dist/commands/explain-shell-cmd.js +22 -0
- package/dist/commands/explore-cmd.js +231 -0
- package/dist/commands/feedback-cmd.js +98 -0
- package/dist/commands/fix-cmd.js +17 -0
- package/dist/commands/generate-cmd.js +38 -0
- package/dist/commands/git-extra.js +197 -0
- package/dist/commands/git-log-cmd.js +98 -0
- package/dist/commands/git-undo-cmd.js +137 -0
- package/dist/commands/git.js +155 -0
- package/dist/commands/history-cmd.js +122 -0
- package/dist/commands/index-cmd.js +65 -0
- package/dist/commands/init-cmd.js +73 -0
- package/dist/commands/lint-cmd.js +133 -0
- package/dist/commands/memory-cmd.js +98 -0
- package/dist/commands/metrics-cmd.js +97 -0
- package/dist/commands/mode-prefix.js +30 -0
- package/dist/commands/multi-cmd.js +44 -0
- package/dist/commands/notify-cmd.js +204 -0
- package/dist/commands/profile-cmd.js +101 -0
- package/dist/commands/prompts.js +17 -0
- package/dist/commands/rag-cmd.js +60 -0
- package/dist/commands/readme-cmd.js +564 -0
- package/dist/commands/reasoning-cmd.js +34 -0
- package/dist/commands/refactor-cmd.js +96 -0
- package/dist/commands/release-cmd.js +450 -0
- package/dist/commands/repo-cmd.js +195 -0
- package/dist/commands/route-cmd.js +21 -0
- package/dist/commands/schedule-cmd.js +109 -0
- package/dist/commands/search-cmd.js +47 -0
- package/dist/commands/security-cmd.js +156 -0
- package/dist/commands/settings-cmd.js +238 -0
- package/dist/commands/skill-cmd.js +338 -0
- package/dist/commands/slash.js +2721 -0
- package/dist/commands/snippets-cmd.js +83 -0
- package/dist/commands/space-cmd.js +92 -0
- package/dist/commands/stash-cmd.js +156 -0
- package/dist/commands/stats-cmd.js +36 -0
- package/dist/commands/style-cmd.js +85 -0
- package/dist/commands/suggest-cmd.js +40 -0
- package/dist/commands/summary-cmd.js +138 -0
- package/dist/commands/task-cmd.js +58 -0
- package/dist/commands/team-memory-cmd.js +97 -0
- package/dist/commands/template-cmd.js +475 -0
- package/dist/commands/test-cmd.js +146 -0
- package/dist/commands/todo-cmd.js +172 -0
- package/dist/commands/tokens-cmd.js +277 -0
- package/dist/commands/trigger-cmd.js +147 -0
- package/dist/commands/undo-cmd.js +18 -0
- package/dist/commands/voice-cmd.js +89 -0
- package/dist/commands/watch-cmd.js +110 -0
- package/dist/commands/web-cmd.js +183 -0
- package/dist/commands/worktree-cmd.js +119 -0
- package/dist/config-profile.js +66 -0
- package/dist/config.js +288 -0
- package/dist/context/compactor.js +53 -0
- package/dist/context/dep-context.js +329 -0
- package/dist/context/file-refs.js +54 -0
- package/dist/context/git-context.js +229 -0
- package/dist/context/image-input.js +66 -0
- package/dist/context/memory.js +55 -0
- package/dist/context/persistent-memory.js +104 -0
- package/dist/context/pinned.js +96 -0
- package/dist/context/priority.js +150 -0
- package/dist/context/read-only.js +48 -0
- package/dist/context/smart-files.js +286 -0
- package/dist/context/team-memory.js +156 -0
- package/dist/extensions/loader.js +149 -0
- package/dist/extensions/marketplace.js +49 -0
- package/dist/extensions/slack-provider.js +181 -0
- package/dist/extensions/team.js +56 -0
- package/dist/extensions/teams-provider.js +222 -0
- package/dist/extensions/voice.js +18 -0
- package/dist/hooks/lifecycle.js +215 -0
- package/dist/hooks/precommit.js +463 -0
- package/dist/index/embeddings.js +23 -0
- package/dist/index/indexer.js +86 -0
- package/dist/index/retrieve.js +20 -0
- package/dist/index/store.js +95 -0
- package/dist/index.js +286 -0
- package/dist/intelligence/dead-code.js +457 -0
- package/dist/intelligence/error-watch.js +263 -0
- package/dist/intelligence/navigation.js +141 -0
- package/dist/intelligence/stack-trace.js +210 -0
- package/dist/intelligence/symbol-index.js +410 -0
- package/dist/knowledge/auto-memory.js +412 -0
- package/dist/knowledge/conventions.js +475 -0
- package/dist/knowledge/corrections.js +213 -0
- package/dist/knowledge/rag.js +450 -0
- package/dist/knowledge/style-learner.js +324 -0
- package/dist/logger.js +35 -0
- package/dist/mcp/client.js +144 -0
- package/dist/mcp/config.js +24 -0
- package/dist/mcp/index.js +89 -0
- package/dist/modes/auto-compact.js +20 -0
- package/dist/modes/autopilot.js +157 -0
- package/dist/modes/background.js +82 -0
- package/dist/modes/interactive.js +187 -0
- package/dist/modes/oneshot.js +36 -0
- package/dist/modes/tui.js +265 -0
- package/dist/modes/turn.js +342 -0
- package/dist/notifications/manager.js +107 -0
- package/dist/plugins/marketplace.js +244 -0
- package/dist/providers/custom-provider.js +298 -0
- package/dist/providers/local-model.js +121 -0
- package/dist/routing/profiles.js +44 -0
- package/dist/routing/router.js +18 -0
- package/dist/sandbox/container.js +151 -0
- package/dist/security/audit.js +237 -0
- package/dist/security/content-filter.js +449 -0
- package/dist/security/proxy.js +301 -0
- package/dist/security/retention.js +281 -0
- package/dist/security/roles.js +252 -0
- package/dist/server/api-server.js +679 -0
- package/dist/session/bookmarks.js +72 -0
- package/dist/session/cloud-session.js +291 -0
- package/dist/session/handoff.js +405 -0
- package/dist/session/manager.js +35 -0
- package/dist/session/session.js +296 -0
- package/dist/session/share.js +313 -0
- package/dist/session/undo-journal.js +91 -0
- package/dist/snippets/store.js +60 -0
- package/dist/spaces/space-config.js +156 -0
- package/dist/spaces/space.js +220 -0
- package/dist/stats/store.js +101 -0
- package/dist/tools/apply-patch.js +134 -0
- package/dist/tools/auto-check.js +218 -0
- package/dist/tools/diff-edit.js +150 -0
- package/dist/tools/diff-prompt.js +36 -0
- package/dist/tools/edit-file.js +66 -0
- package/dist/tools/file-ops.js +205 -0
- package/dist/tools/glob.js +17 -0
- package/dist/tools/grep.js +56 -0
- package/dist/tools/image.js +194 -0
- package/dist/tools/list-directory.js +228 -0
- package/dist/tools/memory.js +17 -0
- package/dist/tools/multi-edit.js +299 -0
- package/dist/tools/policy.js +95 -0
- package/dist/tools/registry.js +484 -0
- package/dist/tools/retry.js +74 -0
- package/dist/tools/run-in-terminal.js +162 -0
- package/dist/tools/safety.js +64 -0
- package/dist/tools/sandbox.js +15 -0
- package/dist/tools/search-symbols.js +212 -0
- package/dist/tools/shell.js +118 -0
- package/dist/tools/web.js +167 -0
- package/dist/ui/prompt.js +37 -0
- package/dist/ui/render.js +96 -0
- package/dist/ui/screen.js +13 -0
- package/dist/ui/theme.js +56 -0
- package/dist/util/browser.js +34 -0
- package/dist/util/completion.js +350 -0
- package/dist/util/cost.js +28 -0
- package/dist/util/keybindings.js +113 -0
- package/dist/util/lazy.js +26 -0
- package/dist/util/perf.js +25 -0
- package/dist/util/token-worker.js +11 -0
- package/dist/util/tokens.js +50 -0
- package/dist/workflows/builtins.js +128 -0
- package/dist/workflows/engine.js +496 -0
- package/dist/workflows/file-trigger.js +197 -0
- package/package.json +79 -0
|
@@ -0,0 +1,167 @@
|
|
|
1
|
+
import fs from 'node:fs';
|
|
2
|
+
import os from 'node:os';
|
|
3
|
+
import path from 'node:path';
|
|
4
|
+
const DEFAULT_POLICY = { allow: [], deny: [], defaultAllow: false };
|
|
5
|
+
const DEFAULT_MAX_BYTES = 200_000;
|
|
6
|
+
const TIMEOUT_MS = 10_000;
|
|
7
|
+
export function loadPolicy() {
|
|
8
|
+
const policyPath = path.join(os.homedir(), '.icopilot', 'web-policy.json');
|
|
9
|
+
if (!fs.existsSync(policyPath))
|
|
10
|
+
return { ...DEFAULT_POLICY };
|
|
11
|
+
try {
|
|
12
|
+
const parsed = JSON.parse(fs.readFileSync(policyPath, 'utf8'));
|
|
13
|
+
return {
|
|
14
|
+
allow: Array.isArray(parsed.allow) ? parsed.allow.filter(isString) : [],
|
|
15
|
+
deny: Array.isArray(parsed.deny) ? parsed.deny.filter(isString) : [],
|
|
16
|
+
defaultAllow: typeof parsed.defaultAllow === 'boolean' ? parsed.defaultAllow : false,
|
|
17
|
+
};
|
|
18
|
+
}
|
|
19
|
+
catch {
|
|
20
|
+
return { ...DEFAULT_POLICY };
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
export function hostAllowed(host, policy) {
|
|
24
|
+
const normalizedHost = normalizeHost(host);
|
|
25
|
+
if (!normalizedHost)
|
|
26
|
+
return false;
|
|
27
|
+
if (policy.deny.some((entry) => hostMatches(normalizedHost, entry)))
|
|
28
|
+
return false;
|
|
29
|
+
if (policy.allow.some((entry) => hostMatches(normalizedHost, entry)))
|
|
30
|
+
return true;
|
|
31
|
+
return policy.defaultAllow;
|
|
32
|
+
}
|
|
33
|
+
export async function webFetchTool(args) {
|
|
34
|
+
try {
|
|
35
|
+
const url = new URL(args.url);
|
|
36
|
+
if (url.protocol !== 'http:' && url.protocol !== 'https:') {
|
|
37
|
+
return JSON.stringify({ ok: false, error: `unsupported protocol: ${url.protocol}` });
|
|
38
|
+
}
|
|
39
|
+
const host = url.hostname;
|
|
40
|
+
if (!hostAllowed(host, loadPolicy())) {
|
|
41
|
+
return JSON.stringify({
|
|
42
|
+
ok: false,
|
|
43
|
+
error: `host not allowed by web fetch policy: ${host}`,
|
|
44
|
+
});
|
|
45
|
+
}
|
|
46
|
+
const maxBytes = normalizeMaxBytes(args.maxBytes);
|
|
47
|
+
const controller = new AbortController();
|
|
48
|
+
const timeout = setTimeout(() => controller.abort(), TIMEOUT_MS);
|
|
49
|
+
try {
|
|
50
|
+
const response = await fetch(url, {
|
|
51
|
+
headers: sanitizeHeaders(args.headers),
|
|
52
|
+
signal: controller.signal,
|
|
53
|
+
});
|
|
54
|
+
const { bytes, text } = await readText(response, maxBytes);
|
|
55
|
+
return JSON.stringify({
|
|
56
|
+
ok: true,
|
|
57
|
+
status: response.status,
|
|
58
|
+
contentType: getContentType(response),
|
|
59
|
+
bytes,
|
|
60
|
+
text,
|
|
61
|
+
});
|
|
62
|
+
}
|
|
63
|
+
finally {
|
|
64
|
+
clearTimeout(timeout);
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
catch (e) {
|
|
68
|
+
return JSON.stringify({ ok: false, error: e?.message || String(e) });
|
|
69
|
+
}
|
|
70
|
+
}
|
|
71
|
+
export const WEB_FETCH_SCHEMA = {
|
|
72
|
+
type: 'function',
|
|
73
|
+
function: {
|
|
74
|
+
name: 'web_fetch',
|
|
75
|
+
description: 'Fetch an HTTP(S) web page only when its host is allowed by the user-managed host allowlist.',
|
|
76
|
+
parameters: {
|
|
77
|
+
type: 'object',
|
|
78
|
+
properties: {
|
|
79
|
+
url: { type: 'string', description: 'HTTP or HTTPS URL to fetch.' },
|
|
80
|
+
maxBytes: {
|
|
81
|
+
type: 'number',
|
|
82
|
+
default: DEFAULT_MAX_BYTES,
|
|
83
|
+
description: 'Maximum response bytes to read before truncating.',
|
|
84
|
+
},
|
|
85
|
+
headers: {
|
|
86
|
+
type: 'object',
|
|
87
|
+
additionalProperties: { type: 'string' },
|
|
88
|
+
description: 'Optional request headers.',
|
|
89
|
+
},
|
|
90
|
+
},
|
|
91
|
+
required: ['url'],
|
|
92
|
+
},
|
|
93
|
+
},
|
|
94
|
+
};
|
|
95
|
+
function hostMatches(host, entry) {
|
|
96
|
+
const normalizedEntry = normalizeHost(entry);
|
|
97
|
+
if (!normalizedEntry)
|
|
98
|
+
return false;
|
|
99
|
+
if (normalizedEntry.startsWith('*.')) {
|
|
100
|
+
const suffix = normalizedEntry.slice(1);
|
|
101
|
+
return host.endsWith(suffix) && host.length > suffix.length;
|
|
102
|
+
}
|
|
103
|
+
return host === normalizedEntry;
|
|
104
|
+
}
|
|
105
|
+
function normalizeHost(host) {
|
|
106
|
+
return host.trim().toLowerCase().replace(/\.$/, '');
|
|
107
|
+
}
|
|
108
|
+
function normalizeMaxBytes(maxBytes) {
|
|
109
|
+
if (typeof maxBytes !== 'number' || !Number.isFinite(maxBytes))
|
|
110
|
+
return DEFAULT_MAX_BYTES;
|
|
111
|
+
return Math.max(0, Math.floor(maxBytes));
|
|
112
|
+
}
|
|
113
|
+
function sanitizeHeaders(headers) {
|
|
114
|
+
if (!headers)
|
|
115
|
+
return undefined;
|
|
116
|
+
return Object.fromEntries(Object.entries(headers).filter((entry) => isString(entry[1])));
|
|
117
|
+
}
|
|
118
|
+
async function readText(response, maxBytes) {
|
|
119
|
+
if (!response.body) {
|
|
120
|
+
const responseLike = response;
|
|
121
|
+
const buffer = typeof response.arrayBuffer === 'function'
|
|
122
|
+
? new Uint8Array(await response.arrayBuffer())
|
|
123
|
+
: new TextEncoder().encode(typeof responseLike.text === 'function' ? await responseLike.text() : '');
|
|
124
|
+
const slice = buffer.slice(0, maxBytes);
|
|
125
|
+
return { bytes: slice.byteLength, text: new TextDecoder().decode(slice) };
|
|
126
|
+
}
|
|
127
|
+
const reader = response.body.getReader();
|
|
128
|
+
const chunks = [];
|
|
129
|
+
let bytes = 0;
|
|
130
|
+
let reachedLimit = maxBytes === 0;
|
|
131
|
+
try {
|
|
132
|
+
while (bytes < maxBytes) {
|
|
133
|
+
const { done, value } = await reader.read();
|
|
134
|
+
if (done)
|
|
135
|
+
break;
|
|
136
|
+
const remaining = maxBytes - bytes;
|
|
137
|
+
const chunk = value.byteLength > remaining ? value.slice(0, remaining) : value;
|
|
138
|
+
chunks.push(chunk);
|
|
139
|
+
bytes += chunk.byteLength;
|
|
140
|
+
if (bytes >= maxBytes) {
|
|
141
|
+
reachedLimit = true;
|
|
142
|
+
break;
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
}
|
|
146
|
+
finally {
|
|
147
|
+
if (reachedLimit)
|
|
148
|
+
await reader.cancel();
|
|
149
|
+
reader.releaseLock();
|
|
150
|
+
}
|
|
151
|
+
return { bytes, text: new TextDecoder().decode(concatChunks(chunks, bytes)) };
|
|
152
|
+
}
|
|
153
|
+
function getContentType(response) {
|
|
154
|
+
return response.headers?.get?.('content-type') ?? '';
|
|
155
|
+
}
|
|
156
|
+
function concatChunks(chunks, bytes) {
|
|
157
|
+
const buffer = new Uint8Array(bytes);
|
|
158
|
+
let offset = 0;
|
|
159
|
+
for (const chunk of chunks) {
|
|
160
|
+
buffer.set(chunk, offset);
|
|
161
|
+
offset += chunk.byteLength;
|
|
162
|
+
}
|
|
163
|
+
return buffer;
|
|
164
|
+
}
|
|
165
|
+
function isString(value) {
|
|
166
|
+
return typeof value === 'string';
|
|
167
|
+
}
|
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
import readline from 'node:readline';
|
|
2
|
+
import { theme } from './theme.js';
|
|
3
|
+
import { attachKeybindings, applyKeybindingConfig, } from '../util/keybindings.js';
|
|
4
|
+
/** Minimal readline-based prompt (history-enabled, with optional keybindings). */
|
|
5
|
+
export function createPrompt(keybindingMode) {
|
|
6
|
+
const mode = keybindingMode ?? applyKeybindingConfig();
|
|
7
|
+
const rl = readline.createInterface({
|
|
8
|
+
input: process.stdin,
|
|
9
|
+
output: process.stdout,
|
|
10
|
+
terminal: true,
|
|
11
|
+
// Future DX: path completion can be added here without changing callers.
|
|
12
|
+
historySize: 500,
|
|
13
|
+
});
|
|
14
|
+
// Attach keybindings if configured
|
|
15
|
+
if (mode !== 'default') {
|
|
16
|
+
attachKeybindings(rl, mode);
|
|
17
|
+
}
|
|
18
|
+
return {
|
|
19
|
+
read(prompt) {
|
|
20
|
+
return new Promise((resolve) => {
|
|
21
|
+
rl.question(prompt, (answer) => resolve(answer));
|
|
22
|
+
});
|
|
23
|
+
},
|
|
24
|
+
close() {
|
|
25
|
+
rl.close();
|
|
26
|
+
},
|
|
27
|
+
getKeybindingMode() {
|
|
28
|
+
return mode;
|
|
29
|
+
},
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
export function prefix(mode) {
|
|
33
|
+
const safeUnicode = process.platform !== 'win32' || Boolean(process.env.WT_SESSION);
|
|
34
|
+
const label = mode === 'plan' ? 'Plan' : 'Copilot';
|
|
35
|
+
const tag = safeUnicode ? theme.badge(label) : `[${label}]`;
|
|
36
|
+
return `${tag} ${theme.user(safeUnicode ? '›' : '>')} `;
|
|
37
|
+
}
|
|
@@ -0,0 +1,96 @@
|
|
|
1
|
+
import { theme } from './theme.js';
|
|
2
|
+
import { lazy } from '../util/lazy.js';
|
|
3
|
+
let markdownConfigured = false;
|
|
4
|
+
const loadMarked = lazy(async () => import('marked'));
|
|
5
|
+
const loadTerminalRenderer = lazy(async () => (await import('marked-terminal')).default);
|
|
6
|
+
export async function ensureMarkdown() {
|
|
7
|
+
const [{ marked }, TerminalRenderer] = await Promise.all([loadMarked(), loadTerminalRenderer()]);
|
|
8
|
+
if (markdownConfigured) {
|
|
9
|
+
return { parse: (md) => marked.parse(md) };
|
|
10
|
+
}
|
|
11
|
+
marked.setOptions({
|
|
12
|
+
// @ts-expect-error — marked-terminal's renderer is compatible at runtime.
|
|
13
|
+
renderer: new TerminalRenderer({
|
|
14
|
+
reflowText: false,
|
|
15
|
+
showSectionPrefix: false,
|
|
16
|
+
tab: 2,
|
|
17
|
+
codespan: theme.hl,
|
|
18
|
+
}),
|
|
19
|
+
});
|
|
20
|
+
markdownConfigured = true;
|
|
21
|
+
return { parse: (md) => marked.parse(md) };
|
|
22
|
+
}
|
|
23
|
+
/** Streaming sink: prints raw tokens with mild styling for low-latency feel.
|
|
24
|
+
* Tracks fenced ``` code blocks across token boundaries and renders the
|
|
25
|
+
* inside of a fence in `theme.hl` so the user can visually distinguish code
|
|
26
|
+
* from prose during streaming, without waiting for the full markdown render. */
|
|
27
|
+
export class StreamSink {
|
|
28
|
+
buf = '';
|
|
29
|
+
inCode = false;
|
|
30
|
+
lineBuf = '';
|
|
31
|
+
write(token) {
|
|
32
|
+
this.buf += token;
|
|
33
|
+
for (const ch of token) {
|
|
34
|
+
this.lineBuf += ch;
|
|
35
|
+
if (ch === '\n') {
|
|
36
|
+
this.flushLine();
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
}
|
|
40
|
+
flushLine() {
|
|
41
|
+
const line = this.lineBuf;
|
|
42
|
+
this.lineBuf = '';
|
|
43
|
+
const trimmed = line.trimStart();
|
|
44
|
+
if (trimmed.startsWith('```')) {
|
|
45
|
+
// toggle on the fence boundary; print fence dimmed
|
|
46
|
+
this.inCode = !this.inCode;
|
|
47
|
+
process.stdout.write(theme.dim(line));
|
|
48
|
+
return;
|
|
49
|
+
}
|
|
50
|
+
if (this.inCode) {
|
|
51
|
+
process.stdout.write(theme.hl(line));
|
|
52
|
+
return;
|
|
53
|
+
}
|
|
54
|
+
process.stdout.write(line);
|
|
55
|
+
}
|
|
56
|
+
/** After completion, optionally re-render fenced markdown for a polished view. */
|
|
57
|
+
finalize() {
|
|
58
|
+
if (this.lineBuf) {
|
|
59
|
+
// flush any trailing partial line
|
|
60
|
+
const tail = this.lineBuf;
|
|
61
|
+
this.lineBuf = '';
|
|
62
|
+
if (this.inCode)
|
|
63
|
+
process.stdout.write(theme.hl(tail));
|
|
64
|
+
else
|
|
65
|
+
process.stdout.write(tail);
|
|
66
|
+
}
|
|
67
|
+
process.stdout.write('\n');
|
|
68
|
+
return this.buf;
|
|
69
|
+
}
|
|
70
|
+
/** Render captured markdown buffer as styled terminal output. */
|
|
71
|
+
async renderMarkdown() {
|
|
72
|
+
if (!this.buf.trim())
|
|
73
|
+
return;
|
|
74
|
+
try {
|
|
75
|
+
const markdown = await ensureMarkdown();
|
|
76
|
+
const rendered = markdown.parse(this.buf).trimEnd();
|
|
77
|
+
process.stdout.write(theme.dim('─'.repeat(60)) + '\n');
|
|
78
|
+
process.stdout.write(rendered + '\n');
|
|
79
|
+
}
|
|
80
|
+
catch {
|
|
81
|
+
/* fall back silently — raw stream already shown */
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
text() {
|
|
85
|
+
return this.buf;
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
export async function renderMarkdownString(md) {
|
|
89
|
+
try {
|
|
90
|
+
const markdown = await ensureMarkdown();
|
|
91
|
+
return markdown.parse(md).trimEnd();
|
|
92
|
+
}
|
|
93
|
+
catch {
|
|
94
|
+
return md;
|
|
95
|
+
}
|
|
96
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
export const ESC = '\x1b[';
|
|
2
|
+
export const altScreenEnter = () => process.stdout.write('\x1b[?1049h');
|
|
3
|
+
export const altScreenExit = () => process.stdout.write('\x1b[?1049l');
|
|
4
|
+
export const clear = () => process.stdout.write('\x1b[2J\x1b[H');
|
|
5
|
+
export const moveTo = (row, col) => process.stdout.write(`\x1b[${row};${col}H`);
|
|
6
|
+
export const hideCursor = () => process.stdout.write('\x1b[?25l');
|
|
7
|
+
export const showCursor = () => process.stdout.write('\x1b[?25h');
|
|
8
|
+
export function size() {
|
|
9
|
+
return {
|
|
10
|
+
rows: process.stdout.rows || 24,
|
|
11
|
+
cols: process.stdout.columns || 80,
|
|
12
|
+
};
|
|
13
|
+
}
|
package/dist/ui/theme.js
ADDED
|
@@ -0,0 +1,56 @@
|
|
|
1
|
+
import chalk, { Chalk, supportsColor } from 'chalk';
|
|
2
|
+
import { config } from '../config.js';
|
|
3
|
+
const plain = (text) => text;
|
|
4
|
+
function colorEnabled() {
|
|
5
|
+
if (config.theme === 'none')
|
|
6
|
+
return false;
|
|
7
|
+
if (process.env.FORCE_COLOR !== undefined)
|
|
8
|
+
return true;
|
|
9
|
+
if (process.env.NO_COLOR)
|
|
10
|
+
return false;
|
|
11
|
+
if (!process.stdout.isTTY)
|
|
12
|
+
return false;
|
|
13
|
+
if (process.env.CI)
|
|
14
|
+
return false;
|
|
15
|
+
return Boolean(supportsColor || chalk.level > 0);
|
|
16
|
+
}
|
|
17
|
+
function palette() {
|
|
18
|
+
const c = colorEnabled() ? chalk : new Chalk({ level: 0 });
|
|
19
|
+
return { c, name: config.theme === 'light' ? 'light' : 'dark' };
|
|
20
|
+
}
|
|
21
|
+
function style(fn) {
|
|
22
|
+
return (text) => {
|
|
23
|
+
if (!colorEnabled())
|
|
24
|
+
return text;
|
|
25
|
+
const p = palette();
|
|
26
|
+
return fn(p.c, p.name)(text);
|
|
27
|
+
};
|
|
28
|
+
}
|
|
29
|
+
export function selectTheme() {
|
|
30
|
+
return config.theme;
|
|
31
|
+
}
|
|
32
|
+
export const theme = {
|
|
33
|
+
brand: style((c, name) => (name === 'light' ? c.hex('#0F6CBD').bold : c.hex('#58A6FF').bold)),
|
|
34
|
+
user: style((c, name) => (name === 'light' ? c.hex('#0F6CBD').bold : c.hex('#58A6FF').bold)),
|
|
35
|
+
assistant: style((c, name) => (name === 'light' ? c.green.bold : c.green)),
|
|
36
|
+
system: style((c) => c.gray.italic),
|
|
37
|
+
warn: style((c, name) => (name === 'light' ? c.hex('#92400E') : c.yellow)),
|
|
38
|
+
err: style((c) => c.red.bold),
|
|
39
|
+
ok: style((c, name) => (name === 'light' ? c.hex('#166534').bold : c.green.bold)),
|
|
40
|
+
dim: style((c) => c.gray),
|
|
41
|
+
hl: style((c, name) => (name === 'light' ? c.hex('#0A5CA8') : c.hex('#79C0FF'))),
|
|
42
|
+
badge: (s) => {
|
|
43
|
+
if (!colorEnabled())
|
|
44
|
+
return ` ${s} `;
|
|
45
|
+
const p = palette();
|
|
46
|
+
return p.name === 'light'
|
|
47
|
+
? p.c.bgHex('#0F6CBD').white.bold(` ${s} `)
|
|
48
|
+
: p.c.bgHex('#1F6FEB').white.bold(` ${s} `);
|
|
49
|
+
},
|
|
50
|
+
};
|
|
51
|
+
export function banner(version, model) {
|
|
52
|
+
const title = theme.brand('GitHub Copilot');
|
|
53
|
+
const sep = colorEnabled() ? '•' : '-';
|
|
54
|
+
const sub = theme.dim(`v${version} ${sep} model: ${theme.hl(model)}`);
|
|
55
|
+
return `\n${title} ${sub}\n${theme.dim('Type /help for commands. @file to inject a file. Ctrl-C to interrupt.')}\n`;
|
|
56
|
+
}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
import { spawn } from 'node:child_process';
|
|
2
|
+
export async function openBrowser(url, spawnImpl = spawn) {
|
|
3
|
+
const platform = process.platform;
|
|
4
|
+
const { command, args } = resolveBrowserCommand(platform, url);
|
|
5
|
+
await new Promise((resolve, reject) => {
|
|
6
|
+
const child = spawnImpl(command, args, {
|
|
7
|
+
detached: true,
|
|
8
|
+
stdio: 'ignore',
|
|
9
|
+
windowsHide: true,
|
|
10
|
+
});
|
|
11
|
+
const proc = child;
|
|
12
|
+
proc.once('error', reject);
|
|
13
|
+
proc.unref?.();
|
|
14
|
+
resolve();
|
|
15
|
+
});
|
|
16
|
+
}
|
|
17
|
+
export function resolveBrowserCommand(platform, url) {
|
|
18
|
+
if (platform === 'win32') {
|
|
19
|
+
return {
|
|
20
|
+
command: 'cmd',
|
|
21
|
+
args: ['/c', 'start', '', url],
|
|
22
|
+
};
|
|
23
|
+
}
|
|
24
|
+
if (platform === 'darwin') {
|
|
25
|
+
return {
|
|
26
|
+
command: 'open',
|
|
27
|
+
args: [url],
|
|
28
|
+
};
|
|
29
|
+
}
|
|
30
|
+
return {
|
|
31
|
+
command: 'xdg-open',
|
|
32
|
+
args: [url],
|
|
33
|
+
};
|
|
34
|
+
}
|