mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-04-29 20:20:57 +00:00
feat: add docs
This commit is contained in:
parent
a546e84887
commit
6e641b8def
14 changed files with 467 additions and 327 deletions
|
|
@ -1,4 +1,11 @@
|
|||
import type {
|
||||
ExtensionConfig,
|
||||
ExtensionRequestOptions,
|
||||
SkillConfig,
|
||||
SubagentConfig,
|
||||
} from '@qwen-code/qwen-code-core';
|
||||
import type { ConfirmationRequest } from '../../ui/types.js';
|
||||
import chalk from 'chalk';
|
||||
|
||||
/**
|
||||
* Requests consent from the user to perform an action, by reading a Y/n
|
||||
|
|
@ -85,3 +92,106 @@ async function promptForConsentInteractive(
|
|||
});
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a consent string for installing an extension based on it's
|
||||
* extensionConfig.
|
||||
*/
|
||||
export function extensionConsentString(
|
||||
extensionConfig: ExtensionConfig,
|
||||
commands: string[] = [],
|
||||
skills: SkillConfig[] = [],
|
||||
subagents: SubagentConfig[] = [],
|
||||
): string {
|
||||
const output: string[] = [];
|
||||
const mcpServerEntries = Object.entries(extensionConfig.mcpServers || {});
|
||||
output.push(`Installing extension "${extensionConfig.name}".`);
|
||||
output.push(
|
||||
'**Extensions may introduce unexpected behavior. Ensure you have investigated the extension source and trust the author.**',
|
||||
);
|
||||
|
||||
if (mcpServerEntries.length) {
|
||||
output.push('This extension will run the following MCP servers:');
|
||||
for (const [key, mcpServer] of mcpServerEntries) {
|
||||
const isLocal = !!mcpServer.command;
|
||||
const source =
|
||||
mcpServer.httpUrl ??
|
||||
`${mcpServer.command || ''}${mcpServer.args ? ' ' + mcpServer.args.join(' ') : ''}`;
|
||||
output.push(` * ${key} (${isLocal ? 'local' : 'remote'}): ${source}`);
|
||||
}
|
||||
}
|
||||
if (commands && commands.length > 0) {
|
||||
output.push(
|
||||
`This extension will add the following commands: ${commands.join(', ')}.`,
|
||||
);
|
||||
}
|
||||
if (extensionConfig.contextFileName) {
|
||||
output.push(
|
||||
`This extension will append info to your QWEN.md context using ${extensionConfig.contextFileName}`,
|
||||
);
|
||||
}
|
||||
if (extensionConfig.excludeTools) {
|
||||
output.push(
|
||||
`This extension will exclude the following core tools: ${extensionConfig.excludeTools}`,
|
||||
);
|
||||
}
|
||||
if (skills.length > 0) {
|
||||
output.push('This extension will install the following skills:');
|
||||
for (const skill of skills) {
|
||||
output.push(` * ${chalk.bold(skill.name)}: ${skill.description}`);
|
||||
}
|
||||
}
|
||||
if (subagents.length > 0) {
|
||||
output.push('This extension will install the following subagents:');
|
||||
for (const subagent of subagents) {
|
||||
output.push(` * ${chalk.bold(subagent.name)}: ${subagent.description}`);
|
||||
}
|
||||
}
|
||||
return output.join('\n');
|
||||
}
|
||||
|
||||
/**
|
||||
* Requests consent from the user to install an extension (extensionConfig), if
|
||||
* there is any difference between the consent string for `extensionConfig` and
|
||||
* `previousExtensionConfig`.
|
||||
*
|
||||
* Always requests consent if previousExtensionConfig is null.
|
||||
*
|
||||
* Throws if the user does not consent.
|
||||
*/
|
||||
export const requestConsentOrFail = async (
|
||||
requestConsent: (consent: string) => Promise<boolean>,
|
||||
options?: ExtensionRequestOptions,
|
||||
) => {
|
||||
if (!options) return;
|
||||
const {
|
||||
extensionConfig,
|
||||
commands = [],
|
||||
skills = [],
|
||||
subagents = [],
|
||||
previousExtensionConfig,
|
||||
previousCommands = [],
|
||||
previousSkills = [],
|
||||
previousSubagents = [],
|
||||
} = options;
|
||||
const extensionConsent = extensionConsentString(
|
||||
extensionConfig,
|
||||
commands,
|
||||
skills,
|
||||
subagents,
|
||||
);
|
||||
if (previousExtensionConfig) {
|
||||
const previousExtensionConsent = extensionConsentString(
|
||||
previousExtensionConfig,
|
||||
previousCommands,
|
||||
previousSkills,
|
||||
previousSubagents,
|
||||
);
|
||||
if (previousExtensionConsent === extensionConsent) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
if (!(await requestConsent(extensionConsent))) {
|
||||
throw new Error(`Installation cancelled for "${extensionConfig.name}".`);
|
||||
}
|
||||
};
|
||||
|
|
|
|||
|
|
@ -13,7 +13,10 @@ import {
|
|||
import { getErrorMessage } from '../../utils/errors.js';
|
||||
import { isWorkspaceTrusted } from '../../config/trustedFolders.js';
|
||||
import { loadSettings } from '../../config/settings.js';
|
||||
import { requestConsentNonInteractive } from './consent.js';
|
||||
import {
|
||||
requestConsentOrFail,
|
||||
requestConsentNonInteractive,
|
||||
} from './consent.js';
|
||||
|
||||
interface InstallArgs {
|
||||
source: string;
|
||||
|
|
@ -39,8 +42,8 @@ export async function handleInstall(args: InstallArgs) {
|
|||
}
|
||||
|
||||
const requestConsent = args.consent
|
||||
? () => Promise.resolve(true)
|
||||
: requestConsentNonInteractive;
|
||||
? () => Promise.resolve()
|
||||
: requestConsentOrFail.bind(null, requestConsentNonInteractive);
|
||||
const workspaceDir = process.cwd();
|
||||
const extensionManager = new ExtensionManager({
|
||||
workspaceDir,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@
|
|||
import type { CommandModule } from 'yargs';
|
||||
import { type ExtensionInstallMetadata } from '@qwen-code/qwen-code-core';
|
||||
import { getErrorMessage } from '../../utils/errors.js';
|
||||
import { requestConsentNonInteractive } from './consent.js';
|
||||
import {
|
||||
requestConsentNonInteractive,
|
||||
requestConsentOrFail,
|
||||
} from './consent.js';
|
||||
import { getExtensionManager } from './utils.js';
|
||||
|
||||
interface InstallArgs {
|
||||
|
|
@ -24,7 +27,7 @@ export async function handleLink(args: InstallArgs) {
|
|||
|
||||
const extension = await extensionManager.installExtension(
|
||||
installMetadata,
|
||||
requestConsentNonInteractive,
|
||||
requestConsentOrFail.bind(null, requestConsentNonInteractive),
|
||||
);
|
||||
console.log(
|
||||
`Extension "${extension.name}" linked successfully and enabled.`,
|
||||
|
|
|
|||
|
|
@ -7,7 +7,10 @@
|
|||
import type { CommandModule } from 'yargs';
|
||||
import { getErrorMessage } from '../../utils/errors.js';
|
||||
import { ExtensionManager } from '@qwen-code/qwen-code-core';
|
||||
import { requestConsentNonInteractive } from './consent.js';
|
||||
import {
|
||||
requestConsentNonInteractive,
|
||||
requestConsentOrFail,
|
||||
} from './consent.js';
|
||||
import { isWorkspaceTrusted } from '../../config/trustedFolders.js';
|
||||
import { loadSettings } from '../../config/settings.js';
|
||||
|
||||
|
|
@ -20,7 +23,10 @@ export async function handleUninstall(args: UninstallArgs) {
|
|||
const workspaceDir = process.cwd();
|
||||
const extensionManager = new ExtensionManager({
|
||||
workspaceDir,
|
||||
requestConsent: requestConsentNonInteractive,
|
||||
requestConsent: requestConsentOrFail.bind(
|
||||
null,
|
||||
requestConsentNonInteractive,
|
||||
),
|
||||
isWorkspaceTrusted: !!isWorkspaceTrusted(
|
||||
loadSettings(workspaceDir).merged,
|
||||
),
|
||||
|
|
|
|||
|
|
@ -6,14 +6,20 @@
|
|||
|
||||
import { ExtensionManager } from '@qwen-code/qwen-code-core';
|
||||
import { loadSettings } from '../../config/settings.js';
|
||||
import { requestConsentNonInteractive } from './consent.js';
|
||||
import {
|
||||
requestConsentOrFail,
|
||||
requestConsentNonInteractive,
|
||||
} from './consent.js';
|
||||
import { isWorkspaceTrusted } from '../../config/trustedFolders.js';
|
||||
|
||||
export async function getExtensionManager(): Promise<ExtensionManager> {
|
||||
const workspaceDir = process.cwd();
|
||||
const extensionManager = new ExtensionManager({
|
||||
workspaceDir,
|
||||
requestConsent: requestConsentNonInteractive,
|
||||
requestConsent: requestConsentOrFail.bind(
|
||||
null,
|
||||
requestConsentNonInteractive,
|
||||
),
|
||||
isWorkspaceTrusted: !!isWorkspaceTrusted(loadSettings(workspaceDir).merged),
|
||||
});
|
||||
await extensionManager.refreshCache();
|
||||
|
|
|
|||
|
|
@ -68,11 +68,6 @@ export function createSlashCommandFromDefinition(
|
|||
.map((segment) => segment.replaceAll(':', '_'))
|
||||
.join(':');
|
||||
|
||||
// Prefix command name with extension name if provided
|
||||
const commandName = extensionName
|
||||
? `${extensionName}:${baseCommandName}`
|
||||
: baseCommandName;
|
||||
|
||||
// Add extension name tag for extension commands
|
||||
const defaultDescription = `Custom command from ${path.basename(filePath)}`;
|
||||
let description = definition.description || defaultDescription;
|
||||
|
|
@ -109,7 +104,7 @@ export function createSlashCommandFromDefinition(
|
|||
}
|
||||
|
||||
return {
|
||||
name: commandName,
|
||||
name: baseCommandName,
|
||||
description,
|
||||
kind: CommandKind.FILE,
|
||||
extensionName,
|
||||
|
|
@ -119,7 +114,7 @@ export function createSlashCommandFromDefinition(
|
|||
): Promise<SlashCommandActionReturn> => {
|
||||
if (!context.invocation) {
|
||||
console.error(
|
||||
`[FileCommandLoader] Critical error: Command '${commandName}' was executed without invocation context.`,
|
||||
`[FileCommandLoader] Critical error: Command '${baseCommandName}' was executed without invocation context.`,
|
||||
);
|
||||
return {
|
||||
type: 'submit_prompt',
|
||||
|
|
|
|||
|
|
@ -102,7 +102,10 @@ import { processVisionSwitchOutcome } from './hooks/useVisionAutoSwitch.js';
|
|||
import { useSubagentCreateDialog } from './hooks/useSubagentCreateDialog.js';
|
||||
import { useAgentsManagerDialog } from './hooks/useAgentsManagerDialog.js';
|
||||
import { useAttentionNotifications } from './hooks/useAttentionNotifications.js';
|
||||
import { requestConsentInteractive } from '../commands/extensions/consent.js';
|
||||
import {
|
||||
requestConsentInteractive,
|
||||
requestConsentOrFail,
|
||||
} from '../commands/extensions/consent.js';
|
||||
|
||||
const CTRL_EXIT_PROMPT_DURATION_MS = 1000;
|
||||
|
||||
|
|
@ -165,8 +168,10 @@ export const AppContainer = (props: AppContainerProps) => {
|
|||
|
||||
const extensionManager = config.getExtensionManager();
|
||||
|
||||
extensionManager.setRequestConsent(async (description) =>
|
||||
requestConsentInteractive(description, addConfirmUpdateExtensionRequest),
|
||||
extensionManager.setRequestConsent(
|
||||
requestConsentOrFail.bind(null, (description) =>
|
||||
requestConsentInteractive(description, addConfirmUpdateExtensionRequest),
|
||||
),
|
||||
);
|
||||
|
||||
const { addConfirmUpdateExtensionRequest, confirmUpdateExtensionRequests } =
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue