@iaforged/context-code 1.1.9 → 1.2.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +24 -0
- package/dist/src/commands/model/model.js +9 -5
- package/dist/src/commands/timeline/index.js +8 -0
- package/dist/src/commands/timeline/timeline.js +194 -0
- package/dist/src/commands.js +2 -0
- package/dist/src/components/AgentActivitySidebar.js +50 -0
- package/dist/src/components/AgentProgressLine.js +5 -5
- package/dist/src/components/ModelPicker.js +252 -441
- package/dist/src/components/PromptInput/PromptInputFooter.js +10 -29
- package/dist/src/components/Spinner/TeammateSpinnerLine.js +20 -62
- package/dist/src/components/Spinner/TeammateSpinnerTree.js +16 -258
- package/dist/src/components/Spinner/teammateSelectHint.js +1 -1
- package/dist/src/components/Spinner/utils.js +3 -6
- package/dist/src/components/ThemeBrowser.js +120 -0
- package/dist/src/components/ThemePicker.js +113 -321
- package/dist/src/components/design-system/ThemeProvider.js +3 -0
- package/dist/src/components/mcp/MCPListPanel.js +138 -444
- package/dist/src/components/permissions/SandboxPermissionRequest.js +5 -5
- package/dist/src/components/teams/TeamStatus.js +7 -71
- package/dist/src/constants/spinnerVerbs.js +80 -180
- package/dist/src/context/modalStackContext.js +12 -0
- package/dist/src/hooks/useTextInput.js +28 -18
- package/dist/src/main.js +12 -0
- package/dist/src/screens/REPL.js +386 -320
- package/dist/src/skills/loadSkillsDir.js +1 -0
- package/dist/src/tools/AgentTool/UI.js +8 -8
- package/dist/src/tools/BashTool/bashSecurity.js +1 -1
- package/dist/src/utils/handlePromptSubmit.js +12 -2
- package/dist/src/utils/processUserInput/processSlashCommand.js +9 -5
- package/dist/src/utils/sembleMcp/common.js +5 -0
- package/dist/src/utils/sembleMcp/setup.js +119 -0
- package/dist/src/utils/theme.js +24 -3
- package/dist/src/utils/themes/bootstrap.js +109 -0
- package/dist/src/utils/themes/builtin/opencode/_index.json +41 -0
- package/dist/src/utils/themes/builtin/opencode/amoled.json +49 -0
- package/dist/src/utils/themes/builtin/opencode/aura.json +51 -0
- package/dist/src/utils/themes/builtin/opencode/ayu.json +51 -0
- package/dist/src/utils/themes/builtin/opencode/carbonfox.json +53 -0
- package/dist/src/utils/themes/builtin/opencode/catppuccin-frappe.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/catppuccin-macchiato.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/catppuccin.json +45 -0
- package/dist/src/utils/themes/builtin/opencode/cobalt2.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/cursor.json +91 -0
- package/dist/src/utils/themes/builtin/opencode/dracula.json +49 -0
- package/dist/src/utils/themes/builtin/opencode/everforest.json +89 -0
- package/dist/src/utils/themes/builtin/opencode/flexoki.json +86 -0
- package/dist/src/utils/themes/builtin/opencode/github.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/gruvbox.json +45 -0
- package/dist/src/utils/themes/builtin/opencode/kanagawa.json +89 -0
- package/dist/src/utils/themes/builtin/opencode/lucent-orng.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/material.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/matrix.json +91 -0
- package/dist/src/utils/themes/builtin/opencode/mercury.json +86 -0
- package/dist/src/utils/themes/builtin/opencode/monokai.json +49 -0
- package/dist/src/utils/themes/builtin/opencode/nightowl.json +46 -0
- package/dist/src/utils/themes/builtin/opencode/nord.json +46 -0
- package/dist/src/utils/themes/builtin/opencode/oc-2.json +88 -0
- package/dist/src/utils/themes/builtin/opencode/one-dark.json +89 -0
- package/dist/src/utils/themes/builtin/opencode/onedarkpro.json +45 -0
- package/dist/src/utils/themes/builtin/opencode/opencode.json +89 -0
- package/dist/src/utils/themes/builtin/opencode/orng.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/osaka-jade.json +88 -0
- package/dist/src/utils/themes/builtin/opencode/palenight.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/rosepine.json +85 -0
- package/dist/src/utils/themes/builtin/opencode/shadesofpurple.json +51 -0
- package/dist/src/utils/themes/builtin/opencode/solarized.json +49 -0
- package/dist/src/utils/themes/builtin/opencode/synthwave84.json +87 -0
- package/dist/src/utils/themes/builtin/opencode/tokyonight.json +47 -0
- package/dist/src/utils/themes/builtin/opencode/vercel.json +90 -0
- package/dist/src/utils/themes/builtin/opencode/vesper.json +51 -0
- package/dist/src/utils/themes/builtin/opencode/zenburn.json +87 -0
- package/dist/src/utils/themes/index.js +4 -0
- package/dist/src/utils/themes/loader.js +147 -0
- package/dist/src/utils/themes/opencodeMapper.js +124 -0
- package/dist/src/utils/themes/resolver.js +66 -0
- package/dist/src/utils/themes/types.js +1 -0
- package/docs/MCP_SERVERS.md +27 -1
- package/package.json +1 -1
|
@@ -407,6 +407,7 @@ export const getSkillDirCommands = memoize(async (cwd) => {
|
|
|
407
407
|
const userSkillsDir = join(getClaudeConfigHomeDir(), 'skills');
|
|
408
408
|
const managedSkillsDir = join(getManagedFilePath(), '.context', 'skills');
|
|
409
409
|
const projectSkillsDirs = getProjectDirsUpToHome('skills', cwd, '.context');
|
|
410
|
+
const legacyProjectSkillsDirs = getProjectDirsUpToHome('skills', cwd, '.claude');
|
|
410
411
|
logForDebugging(`Loading skills from: managed=${managedSkillsDir}, user=${userSkillsDir}, project=[${projectSkillsDirs.join(', ')}]`);
|
|
411
412
|
// Load from additional directories (--add-dir)
|
|
412
413
|
const additionalDirs = getAdditionalDirectoriesForClaudeMd();
|
|
@@ -276,18 +276,18 @@ export function renderToolResultMessage(data, progressMessagesForMessage, { tool
|
|
|
276
276
|
// public schema. Narrow via the internal discriminant.
|
|
277
277
|
const internal = data;
|
|
278
278
|
if (internal.status === 'remote_launched') {
|
|
279
|
-
return _jsx(Box, { flexDirection: "column", children: _jsx(MessageResponse, { height: 1, children: _jsxs(Text, { children: ["
|
|
279
|
+
return _jsx(Box, { flexDirection: "column", children: _jsx(MessageResponse, { height: 1, children: _jsxs(Text, { children: ["Agente remoto iniciado", ' ', _jsxs(Text, { dimColor: true, children: ["\u00B7 ", internal.taskId, " \u00B7 ", internal.sessionUrl] })] }) }) });
|
|
280
280
|
}
|
|
281
281
|
if (data.status === 'async_launched') {
|
|
282
282
|
const { prompt } = data;
|
|
283
|
-
return _jsxs(Box, { flexDirection: "column", children: [_jsx(MessageResponse, { height: 1, children: _jsxs(Text, { children: ["
|
|
283
|
+
return _jsxs(Box, { flexDirection: "column", children: [_jsx(MessageResponse, { height: 1, children: _jsxs(Text, { children: ["Agente en segundo plano", !isTranscriptMode && _jsxs(Text, { dimColor: true, children: [' (', _jsxs(Byline, { children: [_jsx(KeyboardShortcutHint, { shortcut: "\u2193", action: "manage" }), prompt && _jsx(ConfigurableShortcutHint, { action: "app:toggleTranscript", context: "Global", fallback: "ctrl+o", description: "expand" })] }), ')'] })] }) }), isTranscriptMode && prompt && _jsx(MessageResponse, { children: _jsx(AgentPromptDisplay, { prompt: prompt, theme: theme }) })] });
|
|
284
284
|
}
|
|
285
285
|
if (data.status !== 'completed') {
|
|
286
286
|
return null;
|
|
287
287
|
}
|
|
288
288
|
const { agentId, totalDurationMs, totalToolUseCount, totalTokens, usage, content, prompt } = data;
|
|
289
|
-
const result = [totalToolUseCount === 1 ? '1
|
|
290
|
-
const completionMessage = `
|
|
289
|
+
const result = [totalToolUseCount === 1 ? '1 herramienta usada' : `${totalToolUseCount} herramientas usadas`, formatNumber(totalTokens) + ' tokens', formatDuration(totalDurationMs)];
|
|
290
|
+
const completionMessage = `Listo (${result.join(' · ')})`;
|
|
291
291
|
const finalAssistantMessage = createAssistantMessage({
|
|
292
292
|
content: completionMessage,
|
|
293
293
|
usage: {
|
|
@@ -319,7 +319,7 @@ export function renderToolUseTag(input) {
|
|
|
319
319
|
}
|
|
320
320
|
return _jsx(_Fragment, { children: tags });
|
|
321
321
|
}
|
|
322
|
-
const INITIALIZING_TEXT = '
|
|
322
|
+
const INITIALIZING_TEXT = 'Inicializando…';
|
|
323
323
|
export function renderToolUseProgressMessage(progressMessages, { tools, verbose, terminalSize, inProgressToolCallCount, isTranscriptMode = false }) {
|
|
324
324
|
if (!progressMessages.length) {
|
|
325
325
|
return _jsx(MessageResponse, { height: 1, children: _jsx(Text, { dimColor: true, children: INITIALIZING_TEXT }) });
|
|
@@ -349,7 +349,7 @@ export function renderToolUseProgressMessage(progressMessages, { tools, verbose,
|
|
|
349
349
|
};
|
|
350
350
|
if (shouldUseCondensedMode) {
|
|
351
351
|
const { toolUseCount, tokens } = getProgressStats();
|
|
352
|
-
return _jsx(MessageResponse, { height: 1, children: _jsxs(Text, { dimColor: true, children: ["
|
|
352
|
+
return _jsx(MessageResponse, { height: 1, children: _jsxs(Text, { dimColor: true, children: ["En progreso\u2026 \u00B7 ", _jsx(Text, { bold: true, children: toolUseCount }), " herramienta", ' ', toolUseCount === 1 ? 'usada' : 'usadas', tokens && ` · ${formatNumber(tokens)} tokens`, " \u00B7", ' ', _jsx(ConfigurableShortcutHint, { action: "app:toggleTranscript", context: "Global", fallback: "ctrl+o", description: "expand", parens: true })] }) });
|
|
353
353
|
}
|
|
354
354
|
// Process messages to group consecutive search/read operations into summaries (ants only)
|
|
355
355
|
// isAgentRunning=true since this is the progress view while the agent is still running
|
|
@@ -392,7 +392,7 @@ export function renderToolUseProgressMessage(progressMessages, { tools, verbose,
|
|
|
392
392
|
// doesn't leave a blank line. Tool call headers are single-line
|
|
393
393
|
// anyway so truncation isn't needed.
|
|
394
394
|
return _jsx(MessageComponent, { message: processed.message.data.message, lookups: subagentLookups, addMargin: false, tools: tools, commands: [], verbose: verbose, inProgressToolUseIDs: collapsedInProgressIDs, progressMessagesForMessage: [], shouldAnimate: false, shouldShowDot: false, style: "condensed", isTranscriptMode: false, isStatic: true }, processed.message.uuid);
|
|
395
|
-
})] }), hiddenToolUseCount > 0 && _jsxs(Text, { dimColor: true, children: ["+", hiddenToolUseCount, "
|
|
395
|
+
})] }), hiddenToolUseCount > 0 && _jsxs(Text, { dimColor: true, children: ["+", hiddenToolUseCount, " herramienta", hiddenToolUseCount === 1 ? '' : 's', " m\u00E1s ", _jsx(CtrlOToExpand, {})] })] }) });
|
|
396
396
|
}
|
|
397
397
|
export function renderToolUseRejectedMessage(_input, { progressMessagesForMessage, tools, verbose, isTranscriptMode }) {
|
|
398
398
|
// Get agentId from progress messages if available (agent was running before rejection)
|
|
@@ -490,7 +490,7 @@ export function renderGroupedAgentToolUse(toolUses, options) {
|
|
|
490
490
|
const commonType = allSameType && agentStats[0]?.agentType !== 'Agent' ? agentStats[0]?.agentType : null;
|
|
491
491
|
// Check if all resolved agents are async (background)
|
|
492
492
|
const allAsync = agentStats.every(stat => stat.isAsync);
|
|
493
|
-
return _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { shouldAnimate: shouldAnimate && anyUnresolved, isUnresolved: anyUnresolved, isError: anyError }), _jsxs(Text, { children: [allComplete ? allAsync ? _jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: toolUses.length }), "
|
|
493
|
+
return _jsxs(Box, { flexDirection: "column", marginTop: 1, children: [_jsxs(Box, { flexDirection: "row", children: [_jsx(ToolUseLoader, { shouldAnimate: shouldAnimate && anyUnresolved, isUnresolved: anyUnresolved, isError: anyError }), _jsxs(Text, { children: [allComplete ? allAsync ? _jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: toolUses.length }), " agentes en segundo plano iniciados", ' ', _jsx(Text, { dimColor: true, children: _jsx(KeyboardShortcutHint, { shortcut: "\u2193", action: "manage", parens: true }) })] }) : _jsxs(_Fragment, { children: [_jsx(Text, { bold: true, children: toolUses.length }), ' ', commonType ? `${commonType} agentes` : 'agentes', " finalizados"] }) : _jsxs(_Fragment, { children: ["Ejecutando ", _jsx(Text, { bold: true, children: toolUses.length }), ' ', commonType ? `${commonType} agentes` : 'agentes', "\u2026"] }), ' '] }), !allAsync && _jsx(CtrlOToExpand, {})] }), agentStats.map((stat, index) => _jsx(AgentProgressLine, { agentType: stat.agentType, description: stat.description, descriptionColor: stat.descriptionColor, taskDescription: stat.taskDescription, toolUseCount: stat.toolUseCount, tokens: stat.tokens, color: stat.color, isLast: index === agentStats.length - 1, isResolved: stat.isResolved, isError: stat.isError, isAsync: stat.isAsync, shouldAnimate: shouldAnimate, lastToolInfo: stat.lastToolInfo, hideType: allSameType, name: stat.name }, stat.id))] });
|
|
494
494
|
}
|
|
495
495
|
export function userFacingName(input) {
|
|
496
496
|
if (input?.subagent_type && input.subagent_type !== GENERAL_PURPOSE_AGENT.agentType) {
|
|
@@ -1796,7 +1796,7 @@ function validateCommentQuoteDesync(context) {
|
|
|
1796
1796
|
});
|
|
1797
1797
|
return {
|
|
1798
1798
|
behavior: 'ask',
|
|
1799
|
-
message: '
|
|
1799
|
+
message: 'El comando contiene comillas dentro de un comentario con #, lo que puede desincronizar el analisis de comillas',
|
|
1800
1800
|
};
|
|
1801
1801
|
}
|
|
1802
1802
|
// Skip to end of line (rest is comment)
|
|
@@ -16,7 +16,7 @@ function exit() {
|
|
|
16
16
|
gracefulShutdownSync(0);
|
|
17
17
|
}
|
|
18
18
|
export async function handlePromptSubmit(params) {
|
|
19
|
-
const { helpers, queryGuard, isExternalLoading = false, commands, onInputChange, setPastedContents, setToolJSX, getToolUseContext, messages, mainLoopModel, ideSelection, setUserInputOnProcessing, setAbortController, onQuery, setAppState, onBeforeQuery, canUseTool, queuedCommands, uuid, skipSlashCommands, } = params;
|
|
19
|
+
const { helpers, queryGuard, isExternalLoading = false, commands, onInputChange, setPastedContents, setToolJSX, getToolUseContext, messages, mainLoopModel, ideSelection, setUserInputOnProcessing, setAbortController, onQuery, setAppState, onBeforeQuery, canUseTool, queuedCommands, uuid, skipSlashCommands, onJumpToMessage, } = params;
|
|
20
20
|
const { setCursorOffset, clearBuffer, resetHistory } = helpers;
|
|
21
21
|
// Queue processor path: commands are pre-validated and ready to execute.
|
|
22
22
|
// Skip all input validation, reference parsing, and queuing logic.
|
|
@@ -40,6 +40,7 @@ export async function handlePromptSubmit(params) {
|
|
|
40
40
|
resetHistory,
|
|
41
41
|
canUseTool,
|
|
42
42
|
onInputChange,
|
|
43
|
+
onJumpToMessage: params.onJumpToMessage,
|
|
43
44
|
});
|
|
44
45
|
return;
|
|
45
46
|
}
|
|
@@ -131,6 +132,9 @@ export async function handlePromptSubmit(params) {
|
|
|
131
132
|
onInputChange(options.nextInput);
|
|
132
133
|
}
|
|
133
134
|
}
|
|
135
|
+
if (options?.jumpToMessageId) {
|
|
136
|
+
params.onJumpToMessage?.(options.jumpToMessageId);
|
|
137
|
+
}
|
|
134
138
|
};
|
|
135
139
|
const impl = await immediateCommand.load();
|
|
136
140
|
const jsx = await impl.call(onDone, context, commandArgs);
|
|
@@ -210,6 +214,7 @@ export async function handlePromptSubmit(params) {
|
|
|
210
214
|
resetHistory,
|
|
211
215
|
canUseTool,
|
|
212
216
|
onInputChange,
|
|
217
|
+
onJumpToMessage: params.onJumpToMessage,
|
|
213
218
|
});
|
|
214
219
|
}
|
|
215
220
|
/**
|
|
@@ -220,7 +225,7 @@ export async function handlePromptSubmit(params) {
|
|
|
220
225
|
* get `skipAttachments` to avoid duplicating turn-level context.
|
|
221
226
|
*/
|
|
222
227
|
async function executeUserInput(params) {
|
|
223
|
-
const { messages, mainLoopModel, ideSelection, querySource, queryGuard, setToolJSX, getToolUseContext, setUserInputOnProcessing, setAbortController, onQuery, setAppState, onBeforeQuery, resetHistory, canUseTool, queuedCommands, } = params;
|
|
228
|
+
const { messages, mainLoopModel, ideSelection, querySource, queryGuard, setToolJSX, getToolUseContext, setUserInputOnProcessing, setAbortController, onQuery, setAppState, onBeforeQuery, resetHistory, canUseTool, queuedCommands, onJumpToMessage, } = params;
|
|
224
229
|
// Note: paste references are already processed before calling this function
|
|
225
230
|
// (either in handlePromptSubmit before queuing, or before initial execution).
|
|
226
231
|
// Always create a fresh abort controller — queryGuard guarantees no concurrent
|
|
@@ -250,6 +255,7 @@ async function executeUserInput(params) {
|
|
|
250
255
|
let effort;
|
|
251
256
|
let nextInput;
|
|
252
257
|
let submitNextInput;
|
|
258
|
+
let jumpToMessageId;
|
|
253
259
|
// Iterate all commands uniformly. First command gets attachments +
|
|
254
260
|
// ideSelection + pastedContents, rest skip attachments to avoid
|
|
255
261
|
// duplicating turn-level context (IDE selection, todos, diffs).
|
|
@@ -320,6 +326,7 @@ async function executeUserInput(params) {
|
|
|
320
326
|
effort = result.effort;
|
|
321
327
|
nextInput = result.nextInput;
|
|
322
328
|
submitNextInput = result.submitNextInput;
|
|
329
|
+
jumpToMessageId = result.jumpToMessageId;
|
|
323
330
|
}
|
|
324
331
|
}
|
|
325
332
|
queryCheckpoint('query_process_user_input_end');
|
|
@@ -380,6 +387,9 @@ async function executeUserInput(params) {
|
|
|
380
387
|
params.onInputChange(nextInput);
|
|
381
388
|
}
|
|
382
389
|
}
|
|
390
|
+
if (jumpToMessageId) {
|
|
391
|
+
onJumpToMessage?.(jumpToMessageId);
|
|
392
|
+
}
|
|
383
393
|
}); // end runWithWorkload — ALS context naturally scoped, no finally needed
|
|
384
394
|
}
|
|
385
395
|
finally {
|
|
@@ -344,7 +344,7 @@ export async function processSlashCommand(inputString, precedingInputBlocks, ima
|
|
|
344
344
|
};
|
|
345
345
|
}
|
|
346
346
|
// Track slash command usage for feature discovery
|
|
347
|
-
const { messages: newMessages, shouldQuery: messageShouldQuery, allowedTools, model, effort, command: returnedCommand, resultText, nextInput, submitNextInput } = await getMessagesForSlashCommand(commandName, parsedArgs, setToolJSX, context, precedingInputBlocks, imageContentBlocks, isAlreadyProcessing, canUseTool, uuid);
|
|
347
|
+
const { messages: newMessages, shouldQuery: messageShouldQuery, allowedTools, model, effort, command: returnedCommand, resultText, nextInput, submitNextInput, jumpToMessageId } = await getMessagesForSlashCommand(commandName, parsedArgs, setToolJSX, context, precedingInputBlocks, imageContentBlocks, isAlreadyProcessing, canUseTool, uuid);
|
|
348
348
|
// Local slash commands that skip messages
|
|
349
349
|
if (newMessages.length === 0) {
|
|
350
350
|
const eventData = {
|
|
@@ -390,7 +390,8 @@ export async function processSlashCommand(inputString, precedingInputBlocks, ima
|
|
|
390
390
|
shouldQuery: false,
|
|
391
391
|
model,
|
|
392
392
|
nextInput,
|
|
393
|
-
submitNextInput
|
|
393
|
+
submitNextInput,
|
|
394
|
+
jumpToMessageId
|
|
394
395
|
};
|
|
395
396
|
}
|
|
396
397
|
// For invalid commands, preserve both the user message and error
|
|
@@ -455,7 +456,8 @@ export async function processSlashCommand(inputString, precedingInputBlocks, ima
|
|
|
455
456
|
effort,
|
|
456
457
|
resultText,
|
|
457
458
|
nextInput,
|
|
458
|
-
submitNextInput
|
|
459
|
+
submitNextInput,
|
|
460
|
+
jumpToMessageId
|
|
459
461
|
};
|
|
460
462
|
}
|
|
461
463
|
async function getMessagesForSlashCommand(commandName, args, setToolJSX, context, precedingInputBlocks, imageContentBlocks, _isAlreadyProcessing, canUseTool, uuid) {
|
|
@@ -507,7 +509,8 @@ async function getMessagesForSlashCommand(commandName, args, setToolJSX, context
|
|
|
507
509
|
shouldQuery: false,
|
|
508
510
|
command,
|
|
509
511
|
nextInput: options?.nextInput,
|
|
510
|
-
submitNextInput: options?.submitNextInput
|
|
512
|
+
submitNextInput: options?.submitNextInput,
|
|
513
|
+
jumpToMessageId: options?.jumpToMessageId
|
|
511
514
|
});
|
|
512
515
|
return;
|
|
513
516
|
}
|
|
@@ -541,7 +544,8 @@ async function getMessagesForSlashCommand(commandName, args, setToolJSX, context
|
|
|
541
544
|
shouldQuery: options?.shouldQuery ?? false,
|
|
542
545
|
command,
|
|
543
546
|
nextInput: options?.nextInput,
|
|
544
|
-
submitNextInput: options?.submitNextInput
|
|
547
|
+
submitNextInput: options?.submitNextInput,
|
|
548
|
+
jumpToMessageId: options?.jumpToMessageId
|
|
545
549
|
});
|
|
546
550
|
};
|
|
547
551
|
void command.load().then(mod => mod.call(onDone, {
|
|
@@ -0,0 +1,119 @@
|
|
|
1
|
+
import { spawnSync } from 'child_process';
|
|
2
|
+
import envPaths from 'env-paths';
|
|
3
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'fs';
|
|
4
|
+
import { dirname, join } from 'path';
|
|
5
|
+
import { buildMcpToolName } from '../../services/mcp/mcpStringUtils.js';
|
|
6
|
+
import { whichSync } from '../which.js';
|
|
7
|
+
import { SEMBLE_MCP_SERVER_NAME, SEMBLE_MCP_TOOLS } from './common.js';
|
|
8
|
+
const paths = envPaths('claude-cli');
|
|
9
|
+
const SEMBLE_PACKAGE_SPEC = 'semble[mcp]';
|
|
10
|
+
const SEMBLE_BOOTSTRAP_VERSION = 1;
|
|
11
|
+
const SEMBLE_BOOTSTRAP_MARKER = join(paths.cache, 'semble-bootstrap.json');
|
|
12
|
+
const MANAGED_UV_DIR = join(paths.data, 'uv');
|
|
13
|
+
const MANAGED_UV_PATH = join(MANAGED_UV_DIR, process.platform === 'win32' ? 'uv.exe' : 'uv');
|
|
14
|
+
function readBootstrapState() {
|
|
15
|
+
if (!existsSync(SEMBLE_BOOTSTRAP_MARKER)) {
|
|
16
|
+
return null;
|
|
17
|
+
}
|
|
18
|
+
try {
|
|
19
|
+
return JSON.parse(readFileSync(SEMBLE_BOOTSTRAP_MARKER, 'utf8'));
|
|
20
|
+
}
|
|
21
|
+
catch {
|
|
22
|
+
return null;
|
|
23
|
+
}
|
|
24
|
+
}
|
|
25
|
+
function writeBootstrapState() {
|
|
26
|
+
mkdirSync(dirname(SEMBLE_BOOTSTRAP_MARKER), { recursive: true });
|
|
27
|
+
writeFileSync(SEMBLE_BOOTSTRAP_MARKER, JSON.stringify({
|
|
28
|
+
version: SEMBLE_BOOTSTRAP_VERSION,
|
|
29
|
+
packageSpec: SEMBLE_PACKAGE_SPEC,
|
|
30
|
+
}, null, 2), 'utf8');
|
|
31
|
+
}
|
|
32
|
+
function runChecked(command, args, cwd) {
|
|
33
|
+
const result = spawnSync(command, args, {
|
|
34
|
+
cwd,
|
|
35
|
+
encoding: 'utf8',
|
|
36
|
+
stdio: ['ignore', 'pipe', 'pipe'],
|
|
37
|
+
});
|
|
38
|
+
if (result.status !== 0) {
|
|
39
|
+
const stderr = result.stderr?.trim() || result.stdout?.trim() || 'unknown error';
|
|
40
|
+
throw new Error(`${command} ${args.join(' ')} failed: ${stderr}`);
|
|
41
|
+
}
|
|
42
|
+
}
|
|
43
|
+
function installManagedUv() {
|
|
44
|
+
mkdirSync(MANAGED_UV_DIR, { recursive: true });
|
|
45
|
+
if (process.platform === 'win32') {
|
|
46
|
+
const powershellPath = whichSync('powershell') ?? whichSync('pwsh');
|
|
47
|
+
if (!powershellPath) {
|
|
48
|
+
throw new Error('PowerShell is required to install uv automatically on Windows');
|
|
49
|
+
}
|
|
50
|
+
runChecked(powershellPath, [
|
|
51
|
+
'-ExecutionPolicy',
|
|
52
|
+
'ByPass',
|
|
53
|
+
'-Command',
|
|
54
|
+
`$env:UV_INSTALL_DIR='${MANAGED_UV_DIR.replace(/'/g, "''")}'; $env:UV_NO_MODIFY_PATH='1'; irm https://astral.sh/uv/install.ps1 | iex`,
|
|
55
|
+
], process.cwd());
|
|
56
|
+
}
|
|
57
|
+
else {
|
|
58
|
+
const hasCurl = whichSync('curl');
|
|
59
|
+
const hasWget = whichSync('wget');
|
|
60
|
+
if (!hasCurl && !hasWget) {
|
|
61
|
+
throw new Error('curl or wget is required to install uv automatically');
|
|
62
|
+
}
|
|
63
|
+
const installer = hasCurl
|
|
64
|
+
? `curl -LsSf https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="${MANAGED_UV_DIR}" UV_NO_MODIFY_PATH=1 sh`
|
|
65
|
+
: `wget -qO- https://astral.sh/uv/install.sh | env UV_INSTALL_DIR="${MANAGED_UV_DIR}" UV_NO_MODIFY_PATH=1 sh`;
|
|
66
|
+
runChecked('sh', ['-c', installer], process.cwd());
|
|
67
|
+
}
|
|
68
|
+
if (!existsSync(MANAGED_UV_PATH)) {
|
|
69
|
+
throw new Error(`uv installation finished but binary was not found at ${MANAGED_UV_PATH}`);
|
|
70
|
+
}
|
|
71
|
+
return MANAGED_UV_PATH;
|
|
72
|
+
}
|
|
73
|
+
function ensureUvAvailable() {
|
|
74
|
+
return (whichSync('uv') ??
|
|
75
|
+
(existsSync(MANAGED_UV_PATH) ? MANAGED_UV_PATH : installManagedUv()));
|
|
76
|
+
}
|
|
77
|
+
function ensureSembleBootstrap(uvPath, cwd) {
|
|
78
|
+
const current = readBootstrapState();
|
|
79
|
+
if (current?.version === SEMBLE_BOOTSTRAP_VERSION &&
|
|
80
|
+
current.packageSpec === SEMBLE_PACKAGE_SPEC) {
|
|
81
|
+
return;
|
|
82
|
+
}
|
|
83
|
+
// Persist the tool environment so the MCP does not depend on a disposable cache.
|
|
84
|
+
runChecked(uvPath, ['tool', 'install', SEMBLE_PACKAGE_SPEC], cwd);
|
|
85
|
+
// Warm the cache once so Model2Vec downloads the local embedding model eagerly.
|
|
86
|
+
runChecked(uvPath, [
|
|
87
|
+
'tool',
|
|
88
|
+
'run',
|
|
89
|
+
'--from',
|
|
90
|
+
SEMBLE_PACKAGE_SPEC,
|
|
91
|
+
'semble',
|
|
92
|
+
'search',
|
|
93
|
+
'bootstrap',
|
|
94
|
+
cwd,
|
|
95
|
+
'--top-k',
|
|
96
|
+
'1',
|
|
97
|
+
], cwd);
|
|
98
|
+
writeBootstrapState();
|
|
99
|
+
}
|
|
100
|
+
/**
|
|
101
|
+
* Setup the Semble MCP server as a dynamic stdio server.
|
|
102
|
+
* The server bootstraps its runtime dependencies automatically on first use.
|
|
103
|
+
*/
|
|
104
|
+
export function setupSembleMCP() {
|
|
105
|
+
const allowedTools = SEMBLE_MCP_TOOLS.map(tool => buildMcpToolName(SEMBLE_MCP_SERVER_NAME, tool));
|
|
106
|
+
const uvPath = ensureUvAvailable();
|
|
107
|
+
ensureSembleBootstrap(uvPath, process.cwd());
|
|
108
|
+
return {
|
|
109
|
+
mcpConfig: {
|
|
110
|
+
[SEMBLE_MCP_SERVER_NAME]: {
|
|
111
|
+
type: 'stdio',
|
|
112
|
+
command: uvPath,
|
|
113
|
+
args: ['tool', 'run', '--from', SEMBLE_PACKAGE_SPEC, 'semble'],
|
|
114
|
+
scope: 'dynamic',
|
|
115
|
+
},
|
|
116
|
+
},
|
|
117
|
+
allowedTools,
|
|
118
|
+
};
|
|
119
|
+
}
|
package/dist/src/utils/theme.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
import chalk, { Chalk } from 'chalk';
|
|
2
2
|
import { env } from './env.js';
|
|
3
|
-
export const
|
|
3
|
+
export const BUILT_IN_THEME_NAMES = [
|
|
4
4
|
'dark',
|
|
5
5
|
'light',
|
|
6
6
|
'light-daltonized',
|
|
@@ -8,7 +8,8 @@ export const THEME_NAMES = [
|
|
|
8
8
|
'light-ansi',
|
|
9
9
|
'dark-ansi',
|
|
10
10
|
];
|
|
11
|
-
export
|
|
11
|
+
/** Kept as a re-export so existing imports keep working. */
|
|
12
|
+
export const THEME_NAMES = BUILT_IN_THEME_NAMES;
|
|
12
13
|
/**
|
|
13
14
|
* Light theme using explicit RGB values to avoid inconsistencies
|
|
14
15
|
* from users' custom terminal ANSI color definitions
|
|
@@ -484,6 +485,20 @@ const darkDaltonizedTheme = {
|
|
|
484
485
|
rainbow_indigo_shimmer: 'rgb(195,180,230)',
|
|
485
486
|
rainbow_violet_shimmer: 'rgb(230,180,210)',
|
|
486
487
|
};
|
|
488
|
+
// Registry for dynamic JSON themes (catppuccin, dracula, nord, ...)
|
|
489
|
+
// Populated by `bootstrapDynamicThemes()` at CLI startup.
|
|
490
|
+
const dynamicThemes = new Map();
|
|
491
|
+
export function registerTheme(name, theme) {
|
|
492
|
+
dynamicThemes.set(name, theme);
|
|
493
|
+
}
|
|
494
|
+
export function listRegisteredThemes() {
|
|
495
|
+
return [...BUILT_IN_THEME_NAMES, ...dynamicThemes.keys()];
|
|
496
|
+
}
|
|
497
|
+
export function hasTheme(name) {
|
|
498
|
+
return (name === 'auto' ||
|
|
499
|
+
BUILT_IN_THEME_NAMES.includes(name) ||
|
|
500
|
+
dynamicThemes.has(name));
|
|
501
|
+
}
|
|
487
502
|
export function getTheme(themeName) {
|
|
488
503
|
switch (themeName) {
|
|
489
504
|
case 'light':
|
|
@@ -496,8 +511,14 @@ export function getTheme(themeName) {
|
|
|
496
511
|
return lightDaltonizedTheme;
|
|
497
512
|
case 'dark-daltonized':
|
|
498
513
|
return darkDaltonizedTheme;
|
|
499
|
-
|
|
514
|
+
case 'dark':
|
|
515
|
+
return darkTheme;
|
|
516
|
+
default: {
|
|
517
|
+
const dyn = dynamicThemes.get(themeName);
|
|
518
|
+
if (dyn)
|
|
519
|
+
return dyn;
|
|
500
520
|
return darkTheme;
|
|
521
|
+
}
|
|
501
522
|
}
|
|
502
523
|
}
|
|
503
524
|
// Create a chalk instance with 256-color level for Apple Terminal
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
import { BUILT_IN_THEME_NAMES, registerTheme } from '../theme.js';
|
|
2
|
+
import { loadAllThemesSync } from './loader.js';
|
|
3
|
+
import { resolveTheme } from './resolver.js';
|
|
4
|
+
let initialized = false;
|
|
5
|
+
const DEFAULT_THEME_KEYS = new Set([
|
|
6
|
+
'autoAccept',
|
|
7
|
+
'bashBorder',
|
|
8
|
+
'claude',
|
|
9
|
+
'claudeShimmer',
|
|
10
|
+
'claudeBlue_FOR_SYSTEM_SPINNER',
|
|
11
|
+
'claudeBlueShimmer_FOR_SYSTEM_SPINNER',
|
|
12
|
+
'permission',
|
|
13
|
+
'permissionShimmer',
|
|
14
|
+
'planMode',
|
|
15
|
+
'ide',
|
|
16
|
+
'promptBorder',
|
|
17
|
+
'promptBorderShimmer',
|
|
18
|
+
'text',
|
|
19
|
+
'inverseText',
|
|
20
|
+
'inactive',
|
|
21
|
+
'inactiveShimmer',
|
|
22
|
+
'subtle',
|
|
23
|
+
'suggestion',
|
|
24
|
+
'remember',
|
|
25
|
+
'background',
|
|
26
|
+
'success',
|
|
27
|
+
'error',
|
|
28
|
+
'warning',
|
|
29
|
+
'merged',
|
|
30
|
+
'warningShimmer',
|
|
31
|
+
'diffAdded',
|
|
32
|
+
'diffRemoved',
|
|
33
|
+
'diffAddedDimmed',
|
|
34
|
+
'diffRemovedDimmed',
|
|
35
|
+
'diffAddedWord',
|
|
36
|
+
'diffRemovedWord',
|
|
37
|
+
'red_FOR_SUBAGENTS_ONLY',
|
|
38
|
+
'blue_FOR_SUBAGENTS_ONLY',
|
|
39
|
+
'green_FOR_SUBAGENTS_ONLY',
|
|
40
|
+
'yellow_FOR_SUBAGENTS_ONLY',
|
|
41
|
+
'purple_FOR_SUBAGENTS_ONLY',
|
|
42
|
+
'orange_FOR_SUBAGENTS_ONLY',
|
|
43
|
+
'pink_FOR_SUBAGENTS_ONLY',
|
|
44
|
+
'cyan_FOR_SUBAGENTS_ONLY',
|
|
45
|
+
'professionalBlue',
|
|
46
|
+
'chromeYellow',
|
|
47
|
+
'clawd_body',
|
|
48
|
+
'clawd_background',
|
|
49
|
+
'userMessageBackground',
|
|
50
|
+
'userMessageBackgroundHover',
|
|
51
|
+
'messageActionsBackground',
|
|
52
|
+
'selectionBg',
|
|
53
|
+
'bashMessageBackgroundColor',
|
|
54
|
+
'memoryBackgroundColor',
|
|
55
|
+
'rate_limit_fill',
|
|
56
|
+
'rate_limit_empty',
|
|
57
|
+
'fastMode',
|
|
58
|
+
'fastModeShimmer',
|
|
59
|
+
'briefLabelYou',
|
|
60
|
+
'briefLabelClaude',
|
|
61
|
+
'rainbow_red',
|
|
62
|
+
'rainbow_orange',
|
|
63
|
+
'rainbow_yellow',
|
|
64
|
+
'rainbow_green',
|
|
65
|
+
'rainbow_blue',
|
|
66
|
+
'rainbow_indigo',
|
|
67
|
+
'rainbow_violet',
|
|
68
|
+
'rainbow_red_shimmer',
|
|
69
|
+
'rainbow_orange_shimmer',
|
|
70
|
+
'rainbow_yellow_shimmer',
|
|
71
|
+
'rainbow_green_shimmer',
|
|
72
|
+
'rainbow_blue_shimmer',
|
|
73
|
+
'rainbow_indigo_shimmer',
|
|
74
|
+
'rainbow_violet_shimmer',
|
|
75
|
+
]);
|
|
76
|
+
function toTheme(resolved) {
|
|
77
|
+
const out = {};
|
|
78
|
+
for (const key of DEFAULT_THEME_KEYS) {
|
|
79
|
+
out[key] = resolved[key] ?? 'rgb(128,128,128)';
|
|
80
|
+
}
|
|
81
|
+
return out;
|
|
82
|
+
}
|
|
83
|
+
export function bootstrapDynamicThemes() {
|
|
84
|
+
if (initialized)
|
|
85
|
+
return;
|
|
86
|
+
initialized = true;
|
|
87
|
+
const builtInIds = new Set(BUILT_IN_THEME_NAMES);
|
|
88
|
+
let loaded;
|
|
89
|
+
try {
|
|
90
|
+
loaded = loadAllThemesSync();
|
|
91
|
+
}
|
|
92
|
+
catch {
|
|
93
|
+
return;
|
|
94
|
+
}
|
|
95
|
+
for (const t of loaded) {
|
|
96
|
+
// Don't shadow built-ins ('dark', 'light', etc.) coming from cli/*.json.
|
|
97
|
+
if (builtInIds.has(t.id))
|
|
98
|
+
continue;
|
|
99
|
+
try {
|
|
100
|
+
const dark = toTheme(resolveTheme(t.json, 'dark'));
|
|
101
|
+
const light = toTheme(resolveTheme(t.json, 'light'));
|
|
102
|
+
registerTheme(t.id, dark);
|
|
103
|
+
registerTheme(`${t.id}-light`, light);
|
|
104
|
+
}
|
|
105
|
+
catch {
|
|
106
|
+
// Skip malformed themes silently
|
|
107
|
+
}
|
|
108
|
+
}
|
|
109
|
+
}
|
|
@@ -0,0 +1,41 @@
|
|
|
1
|
+
{
|
|
2
|
+
"themes": [
|
|
3
|
+
{ "id": "amoled", "file": "amoled.json" },
|
|
4
|
+
{ "id": "aura", "file": "aura.json" },
|
|
5
|
+
{ "id": "ayu", "file": "ayu.json" },
|
|
6
|
+
{ "id": "carbonfox", "file": "carbonfox.json" },
|
|
7
|
+
{ "id": "catppuccin", "file": "catppuccin.json" },
|
|
8
|
+
{ "id": "catppuccin-frappe", "file": "catppuccin-frappe.json" },
|
|
9
|
+
{ "id": "catppuccin-macchiato", "file": "catppuccin-macchiato.json" },
|
|
10
|
+
{ "id": "cobalt2", "file": "cobalt2.json" },
|
|
11
|
+
{ "id": "cursor", "file": "cursor.json" },
|
|
12
|
+
{ "id": "dracula", "file": "dracula.json" },
|
|
13
|
+
{ "id": "everforest", "file": "everforest.json" },
|
|
14
|
+
{ "id": "flexoki", "file": "flexoki.json" },
|
|
15
|
+
{ "id": "github", "file": "github.json" },
|
|
16
|
+
{ "id": "gruvbox", "file": "gruvbox.json" },
|
|
17
|
+
{ "id": "kanagawa", "file": "kanagawa.json" },
|
|
18
|
+
{ "id": "lucent-orng", "file": "lucent-orng.json" },
|
|
19
|
+
{ "id": "material", "file": "material.json" },
|
|
20
|
+
{ "id": "matrix", "file": "matrix.json" },
|
|
21
|
+
{ "id": "mercury", "file": "mercury.json" },
|
|
22
|
+
{ "id": "monokai", "file": "monokai.json" },
|
|
23
|
+
{ "id": "nightowl", "file": "nightowl.json" },
|
|
24
|
+
{ "id": "nord", "file": "nord.json" },
|
|
25
|
+
{ "id": "oc-2", "file": "oc-2.json" },
|
|
26
|
+
{ "id": "one-dark", "file": "one-dark.json" },
|
|
27
|
+
{ "id": "onedarkpro", "file": "onedarkpro.json" },
|
|
28
|
+
{ "id": "opencode", "file": "opencode.json" },
|
|
29
|
+
{ "id": "orng", "file": "orng.json" },
|
|
30
|
+
{ "id": "osaka-jade", "file": "osaka-jade.json" },
|
|
31
|
+
{ "id": "palenight", "file": "palenight.json" },
|
|
32
|
+
{ "id": "rosepine", "file": "rosepine.json" },
|
|
33
|
+
{ "id": "shadesofpurple", "file": "shadesofpurple.json" },
|
|
34
|
+
{ "id": "solarized", "file": "solarized.json" },
|
|
35
|
+
{ "id": "synthwave84", "file": "synthwave84.json" },
|
|
36
|
+
{ "id": "tokyonight", "file": "tokyonight.json" },
|
|
37
|
+
{ "id": "vercel", "file": "vercel.json" },
|
|
38
|
+
{ "id": "vesper", "file": "vesper.json" },
|
|
39
|
+
{ "id": "zenburn", "file": "zenburn.json" }
|
|
40
|
+
]
|
|
41
|
+
}
|
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/desktop-theme.json",
|
|
3
|
+
"name": "AMOLED",
|
|
4
|
+
"id": "amoled",
|
|
5
|
+
"light": {
|
|
6
|
+
"palette": {
|
|
7
|
+
"neutral": "#f0f0f0",
|
|
8
|
+
"ink": "#0a0a0a",
|
|
9
|
+
"primary": "#6200ff",
|
|
10
|
+
"accent": "#ff0080",
|
|
11
|
+
"success": "#00e676",
|
|
12
|
+
"warning": "#ffab00",
|
|
13
|
+
"error": "#ff1744",
|
|
14
|
+
"info": "#00b0ff",
|
|
15
|
+
"diffAdd": "#00e676",
|
|
16
|
+
"diffDelete": "#ff1744"
|
|
17
|
+
},
|
|
18
|
+
"overrides": {
|
|
19
|
+
"syntax-comment": "#757575",
|
|
20
|
+
"syntax-keyword": "#d500f9",
|
|
21
|
+
"syntax-string": "#00e676",
|
|
22
|
+
"syntax-primitive": "#00b0ff",
|
|
23
|
+
"syntax-property": "#ff9100",
|
|
24
|
+
"syntax-constant": "#6200ff"
|
|
25
|
+
}
|
|
26
|
+
},
|
|
27
|
+
"dark": {
|
|
28
|
+
"palette": {
|
|
29
|
+
"neutral": "#000000",
|
|
30
|
+
"ink": "#ffffff",
|
|
31
|
+
"primary": "#b388ff",
|
|
32
|
+
"accent": "#ff4081",
|
|
33
|
+
"success": "#00ff88",
|
|
34
|
+
"warning": "#ffea00",
|
|
35
|
+
"error": "#ff1744",
|
|
36
|
+
"info": "#18ffff",
|
|
37
|
+
"diffAdd": "#00ff88",
|
|
38
|
+
"diffDelete": "#ff1744"
|
|
39
|
+
},
|
|
40
|
+
"overrides": {
|
|
41
|
+
"syntax-comment": "#555555",
|
|
42
|
+
"syntax-keyword": "#ff00ff",
|
|
43
|
+
"syntax-string": "#00ff88",
|
|
44
|
+
"syntax-primitive": "#18ffff",
|
|
45
|
+
"syntax-property": "#ffea00",
|
|
46
|
+
"syntax-constant": "#b388ff"
|
|
47
|
+
}
|
|
48
|
+
}
|
|
49
|
+
}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
{
|
|
2
|
+
"$schema": "https://opencode.ai/desktop-theme.json",
|
|
3
|
+
"name": "Aura",
|
|
4
|
+
"id": "aura",
|
|
5
|
+
"light": {
|
|
6
|
+
"palette": {
|
|
7
|
+
"neutral": "#f5f0ff",
|
|
8
|
+
"ink": "#2d2640",
|
|
9
|
+
"primary": "#a277ff",
|
|
10
|
+
"accent": "#d94f4f",
|
|
11
|
+
"success": "#40bf7a",
|
|
12
|
+
"warning": "#d9a24a",
|
|
13
|
+
"error": "#d94f4f",
|
|
14
|
+
"info": "#5bb8d9",
|
|
15
|
+
"diffAdd": "#b3e6cc",
|
|
16
|
+
"diffDelete": "#f5b3b3"
|
|
17
|
+
},
|
|
18
|
+
"overrides": {
|
|
19
|
+
"syntax-comment": "#8d88a3",
|
|
20
|
+
"syntax-keyword": "#7b5ae0",
|
|
21
|
+
"syntax-string": "#2b8a57",
|
|
22
|
+
"syntax-primitive": "#2f78b8",
|
|
23
|
+
"syntax-property": "#a96a22",
|
|
24
|
+
"syntax-type": "#2b8a57",
|
|
25
|
+
"syntax-constant": "#d94f4f"
|
|
26
|
+
}
|
|
27
|
+
},
|
|
28
|
+
"dark": {
|
|
29
|
+
"palette": {
|
|
30
|
+
"neutral": "#15141b",
|
|
31
|
+
"ink": "#edecee",
|
|
32
|
+
"primary": "#a277ff",
|
|
33
|
+
"accent": "#ff6767",
|
|
34
|
+
"success": "#61ffca",
|
|
35
|
+
"warning": "#ffca85",
|
|
36
|
+
"error": "#ff6767",
|
|
37
|
+
"info": "#82e2ff",
|
|
38
|
+
"diffAdd": "#61ffca",
|
|
39
|
+
"diffDelete": "#ff6767"
|
|
40
|
+
},
|
|
41
|
+
"overrides": {
|
|
42
|
+
"syntax-comment": "#6d6a7e",
|
|
43
|
+
"syntax-keyword": "#a277ff",
|
|
44
|
+
"syntax-string": "#61ffca",
|
|
45
|
+
"syntax-primitive": "#82e2ff",
|
|
46
|
+
"syntax-property": "#ffca85",
|
|
47
|
+
"syntax-type": "#61ffca",
|
|
48
|
+
"syntax-constant": "#ff6767"
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
}
|