mirror of
https://github.com/QwenLM/qwen-code.git
synced 2026-05-01 21:20:44 +00:00
improve to change extension path instead of tmp path and fix shell multiple input
This commit is contained in:
parent
257934f1e9
commit
b5186d3c8a
6 changed files with 427 additions and 119 deletions
|
|
@ -111,7 +111,13 @@ export class ShellProcessor implements IPromptProcessor {
|
|||
|
||||
const resolvedCommand = command
|
||||
.replaceAll(SHORTHAND_ARGS_PLACEHOLDER, userArgsEscaped) // Replace {{args}}
|
||||
.replaceAll('$ARGUMENTS', userArgsEscaped); // Replace $ARGUMENTS
|
||||
.replaceAll(
|
||||
'$ARGUMENTS',
|
||||
userArgsRaw
|
||||
.split(' ')
|
||||
.map((arg) => escapeShellArg(arg, shell))
|
||||
.join(' '),
|
||||
);
|
||||
return { ...injection, resolvedCommand };
|
||||
},
|
||||
);
|
||||
|
|
|
|||
|
|
@ -18,6 +18,7 @@ import {
|
|||
type ClaudeMarketplaceConfig,
|
||||
} from './claude-converter.js';
|
||||
import { HookType } from '../hooks/types.js';
|
||||
import { performVariableReplacement } from './variables.js';
|
||||
|
||||
describe('convertClaudeToQwenConfig', () => {
|
||||
it('should convert basic Claude config', () => {
|
||||
|
|
@ -571,3 +572,81 @@ describe('convertClaudePluginPackage', () => {
|
|||
fs.rmSync(result.convertedDir, { recursive: true, force: true });
|
||||
});
|
||||
});
|
||||
|
||||
describe('performVariableReplacement for Claude extensions', () => {
|
||||
let testDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'claude-var-test-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should replace ${CLAUDE_PLUGIN_ROOT} in markdown files', () => {
|
||||
const extDir = path.join(testDir, 'ext-md');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const mdContent = `# Test Extension
|
||||
Run \`\${CLAUDE_PLUGIN_ROOT}/scripts/setup.sh\` to configure.`;
|
||||
fs.writeFileSync(path.join(extDir, 'README.md'), mdContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'README.md'), 'utf-8');
|
||||
expect(result).toContain(`${extDir}/scripts/setup.sh`);
|
||||
expect(result).not.toContain('${CLAUDE_PLUGIN_ROOT}');
|
||||
});
|
||||
|
||||
it('should replace .claude with .qwen in shell scripts', () => {
|
||||
const extDir = path.join(testDir, 'ext-sh');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const shContent = `#!/bin/bash
|
||||
CONFIG_DIR="$HOME/.claude/config"
|
||||
CACHE_DIR="~/.claude/cache"
|
||||
LOCAL_DIR="./.claude/local"`;
|
||||
fs.writeFileSync(path.join(extDir, 'setup.sh'), shContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'setup.sh'), 'utf-8');
|
||||
expect(result).toContain('$HOME/.qwen/config');
|
||||
expect(result).toContain('~/.qwen/cache');
|
||||
expect(result).toContain('./.qwen/local');
|
||||
expect(result).not.toContain('.claude');
|
||||
});
|
||||
|
||||
it('should replace role with type in shell scripts', () => {
|
||||
const extDir = path.join(testDir, 'ext-role');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const shContent = `#!/bin/bash
|
||||
echo '{"role":"assistant","content":"hello"}'`;
|
||||
fs.writeFileSync(path.join(extDir, 'process.sh'), shContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'process.sh'), 'utf-8');
|
||||
expect(result).toContain('"type":"assistant"');
|
||||
expect(result).not.toContain('"role":"assistant"');
|
||||
});
|
||||
|
||||
it('should update transcript parsing logic in shell scripts', () => {
|
||||
const extDir = path.join(testDir, 'ext-transcript');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const shContent = `#!/bin/bash
|
||||
echo "$transcript" | jq '.message.content | map(select(.type == "text"))'`;
|
||||
fs.writeFileSync(path.join(extDir, 'parse.sh'), shContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'parse.sh'), 'utf-8');
|
||||
expect(result).toContain('.message.parts | map(select(has("text")))');
|
||||
expect(result).not.toContain('.message.content');
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -30,117 +30,6 @@ import { substituteHookVariables } from './variables.js';
|
|||
|
||||
const debugLogger = createDebugLogger('CLAUDE_CONVERTER');
|
||||
|
||||
/**
|
||||
* Perform variable replacement in all markdown and shell script files of the extension.
|
||||
* This is done during the conversion phase to avoid modifying files during every extension load.
|
||||
* @param extensionPath - The path to the extension directory
|
||||
*/
|
||||
export function performVariableReplacement(extensionPath: string): void {
|
||||
// Process markdown files
|
||||
const mdGlobPattern = '**/*.md';
|
||||
const mdGlobOptions = {
|
||||
cwd: extensionPath,
|
||||
nodir: true,
|
||||
};
|
||||
|
||||
try {
|
||||
const mdFiles = glob.sync(mdGlobPattern, mdGlobOptions);
|
||||
|
||||
for (const file of mdFiles) {
|
||||
const filePath = path.join(extensionPath, file);
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Replace ${CLAUDE_PLUGIN_ROOT} with the actual extension path
|
||||
const updatedContent = content.replace(
|
||||
/\$\{CLAUDE_PLUGIN_ROOT\}/g,
|
||||
extensionPath,
|
||||
);
|
||||
|
||||
// Replace Markdown shell syntax ```! ... ``` with system-recognized !{...} syntax
|
||||
// This regex finds code blocks with ! language identifier and captures their content
|
||||
const updatedMdContent = updatedContent.replace(
|
||||
/```!(?:\s*\n)?([\s\S]*?)\n*```/g,
|
||||
'!{$1}',
|
||||
);
|
||||
|
||||
// Only write if content was actually changed
|
||||
if (updatedMdContent !== content) {
|
||||
fs.writeFileSync(filePath, updatedMdContent, 'utf8');
|
||||
debugLogger.debug(
|
||||
`Updated variables and syntax in file: ${filePath}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to process file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to scan markdown files in extension directory ${extensionPath}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Process shell script files
|
||||
const scriptGlobPattern = '**/*.sh';
|
||||
const scriptGlobOptions = {
|
||||
cwd: extensionPath,
|
||||
nodir: true,
|
||||
};
|
||||
|
||||
try {
|
||||
const scriptFiles = glob.sync(scriptGlobPattern, scriptGlobOptions);
|
||||
|
||||
for (const file of scriptFiles) {
|
||||
const filePath = path.join(extensionPath, file);
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Replace references to "role":"assistant" with "type":"assistant" in shell scripts
|
||||
const updatedScriptContent = content.replace(
|
||||
/"role":"assistant"/g,
|
||||
'"type":"assistant"',
|
||||
);
|
||||
|
||||
// Replace transcript parsing logic to adapt to actual transcript structure
|
||||
// Change from .message.content | map(select(.type == "text")) to .message.parts | map(select(has("text")))
|
||||
const adaptedScriptContent = updatedScriptContent.replace(
|
||||
/\.message\.content\s*\|\s*map\(select\(\.type\s*==\s*"text"\)\)/g,
|
||||
'.message.parts | map(select(has("text")))',
|
||||
);
|
||||
|
||||
// Replace references to ".claude" directory with ".qwen" in shell scripts
|
||||
// Only match path references (e.g., ~/.claude/, $HOME/.claude, ./.claude/)
|
||||
// Avoid matching URLs, comments, or string literals containing .claude
|
||||
const finalScriptContent = adaptedScriptContent.replace(
|
||||
/(\$\{?HOME\}?\/|~\/)?\.claude(\/|$)/g,
|
||||
'$1.qwen$2',
|
||||
);
|
||||
|
||||
// Only write if content was actually changed
|
||||
if (finalScriptContent !== content) {
|
||||
fs.writeFileSync(filePath, finalScriptContent, 'utf8');
|
||||
debugLogger.debug(
|
||||
`Updated transcript format and replaced .claude with .qwen in shell script: ${filePath}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to process shell script file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to scan shell script files in extension directory ${extensionPath}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
export interface ClaudePluginConfig {
|
||||
name: string;
|
||||
version: string;
|
||||
|
|
@ -619,14 +508,10 @@ export async function convertClaudePluginPackage(
|
|||
}
|
||||
}
|
||||
|
||||
// Step 9.1: Convert collected agent files from Claude format to Qwen format
|
||||
// Step 9: Convert collected agent files from Claude format to Qwen format
|
||||
const agentsDestDir = path.join(tmpDir, 'agents');
|
||||
await convertAgentFiles(agentsDestDir);
|
||||
|
||||
// Step 9.2: Perform variable replacement in markdown and shell script files
|
||||
// This is done during conversion to avoid modifying files during every extension load
|
||||
performVariableReplacement(tmpDir);
|
||||
|
||||
// Step 10: Convert to Qwen format config
|
||||
const qwenConfig = convertClaudeToQwenConfig(mergedConfig);
|
||||
|
||||
|
|
|
|||
|
|
@ -30,6 +30,7 @@ import {
|
|||
INSTALL_METADATA_FILENAME,
|
||||
recursivelyHydrateStrings,
|
||||
substituteHookVariables,
|
||||
performVariableReplacement,
|
||||
} from './variables.js';
|
||||
import { resolveEnvVarsInObject } from '../utils/envVarResolver.js';
|
||||
import {
|
||||
|
|
@ -987,6 +988,11 @@ export class ExtensionManager {
|
|||
await copyExtension(localSourcePath, destinationPath);
|
||||
}
|
||||
|
||||
// Perform variable replacement in extension files (e.g., ${CLAUDE_PLUGIN_ROOT}) for Claude extensions
|
||||
if (originSource === 'Claude') {
|
||||
performVariableReplacement(destinationPath);
|
||||
}
|
||||
|
||||
const metadataString = JSON.stringify(installMetadata, null, 2);
|
||||
const metadataPath = path.join(
|
||||
destinationPath,
|
||||
|
|
|
|||
|
|
@ -4,9 +4,16 @@
|
|||
* SPDX-License-Identifier: Apache-2.0
|
||||
*/
|
||||
|
||||
import { expect, describe, it } from 'vitest';
|
||||
import { hydrateString, substituteHookVariables } from './variables.js';
|
||||
import { expect, describe, it, beforeEach, afterEach } from 'vitest';
|
||||
import {
|
||||
hydrateString,
|
||||
substituteHookVariables,
|
||||
performVariableReplacement,
|
||||
} from './variables.js';
|
||||
import { HookType } from '../hooks/types.js';
|
||||
import * as fs from 'node:fs';
|
||||
import * as path from 'node:path';
|
||||
import * as os from 'node:os';
|
||||
|
||||
describe('hydrateString', () => {
|
||||
it('should replace a single variable', () => {
|
||||
|
|
@ -194,3 +201,212 @@ describe('substituteHookVariables', () => {
|
|||
expect(result!['Stop']![0].hooks![0].command).toBe('echo "hello world"');
|
||||
});
|
||||
});
|
||||
|
||||
describe('performVariableReplacement', () => {
|
||||
let testDir: string;
|
||||
|
||||
beforeEach(() => {
|
||||
testDir = fs.mkdtempSync(path.join(os.tmpdir(), 'var-replace-test-'));
|
||||
});
|
||||
|
||||
afterEach(() => {
|
||||
if (fs.existsSync(testDir)) {
|
||||
fs.rmSync(testDir, { recursive: true, force: true });
|
||||
}
|
||||
});
|
||||
|
||||
it('should replace ${CLAUDE_PLUGIN_ROOT} in markdown files', () => {
|
||||
const extDir = path.join(testDir, 'ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const mdContent = [
|
||||
'# README',
|
||||
'',
|
||||
'Configuration file is at `${CLAUDE_PLUGIN_ROOT}/config.json`.',
|
||||
'Run `${CLAUDE_PLUGIN_ROOT}/scripts/setup.sh` to initialize.',
|
||||
]
|
||||
.join('\n')
|
||||
.replace(/`\${CLAUDE_PLUGIN_ROOT}/g, '`${CLAUDE_PLUGIN_ROOT}');
|
||||
fs.writeFileSync(path.join(extDir, 'README.md'), mdContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'README.md'), 'utf-8');
|
||||
expect(result).toContain(`${extDir}/config.json`);
|
||||
expect(result).toContain(`${extDir}/scripts/setup.sh`);
|
||||
expect(result).not.toContain('${CLAUDE_PLUGIN_ROOT}');
|
||||
});
|
||||
|
||||
it('should convert ```! syntax to !{} in markdown files', () => {
|
||||
const extDir = path.join(testDir, 'ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const mdContent = `## Commands
|
||||
|
||||
\`\`\`!
|
||||
npm install
|
||||
npm run build
|
||||
\`\`\`
|
||||
|
||||
Some text.
|
||||
|
||||
\`\`\`!
|
||||
echo "Hello World"
|
||||
\`\`\`
|
||||
`;
|
||||
fs.writeFileSync(path.join(extDir, 'guide.md'), mdContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'guide.md'), 'utf-8');
|
||||
expect(result).toContain('!{npm install\nnpm run build}');
|
||||
expect(result).toContain('!{echo "Hello World"}');
|
||||
expect(result).not.toContain('```!');
|
||||
});
|
||||
|
||||
it('should replace "role":"assistant" with "type":"assistant" in shell scripts', () => {
|
||||
const extDir = path.join(testDir, 'ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const shContent = `#!/bin/bash
|
||||
# Process response
|
||||
echo '{"role":"assistant","content":"Hello"}'
|
||||
echo '{"role":"user","content":"Hi"}'
|
||||
echo '{"role":"assistant","content":"How can I help?"}'
|
||||
`;
|
||||
fs.writeFileSync(path.join(extDir, 'process.sh'), shContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'process.sh'), 'utf-8');
|
||||
expect(result).toContain('"type":"assistant"');
|
||||
expect(result).not.toContain('"role":"assistant"');
|
||||
// Should not affect other roles
|
||||
expect(result).toContain('"role":"user"');
|
||||
});
|
||||
|
||||
it('should update transcript parsing in shell scripts', () => {
|
||||
const extDir = path.join(testDir, 'ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const shContent = `#!/bin/bash
|
||||
# Parse transcript
|
||||
jq '.message.content | map(select(.type == "text"))' <<< "$response"
|
||||
`;
|
||||
fs.writeFileSync(path.join(extDir, 'parse.sh'), shContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'parse.sh'), 'utf-8');
|
||||
expect(result).toContain('.message.parts | map(select(has("text")))');
|
||||
expect(result).not.toContain('.message.content');
|
||||
});
|
||||
|
||||
it('should replace .claude with .qwen in shell scripts', () => {
|
||||
const extDir = path.join(testDir, 'ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
const shContent = [
|
||||
'#!/bin/bash',
|
||||
'HOME_CLAUDE="$HOME/.claude"',
|
||||
'CACHE_DIR="~/.claude/cache"',
|
||||
'LOCAL_DIR="./.claude/local"',
|
||||
'CONFIG="${CLAUDE_PLUGIN_ROOT}/.claude/config"',
|
||||
'# Not replaced: https://example.com/.claude/page',
|
||||
]
|
||||
.join('\n')
|
||||
.replace('${CLAUDE_PLUGIN_ROOT}', '${CLAUDE_PLUGIN_ROOT}');
|
||||
fs.writeFileSync(path.join(extDir, 'setup.sh'), shContent, 'utf-8');
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const result = fs.readFileSync(path.join(extDir, 'setup.sh'), 'utf-8');
|
||||
expect(result).toContain('$HOME/.qwen');
|
||||
expect(result).toContain('~/.qwen/cache');
|
||||
expect(result).toContain('./.qwen/local');
|
||||
expect(result).toContain('.qwen/config');
|
||||
// URL should not be affected
|
||||
expect(result).toContain('https://example.com/.claude/page');
|
||||
});
|
||||
|
||||
it('should handle multiple markdown files', () => {
|
||||
const extDir = path.join(testDir, 'ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
fs.mkdirSync(path.join(extDir, 'docs'), { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(extDir, 'README.md'),
|
||||
'Path: `${CLAUDE_PLUGIN_ROOT}/readme`',
|
||||
'utf-8',
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(extDir, 'docs', 'guide.md'),
|
||||
'Path: `${CLAUDE_PLUGIN_ROOT}/docs/guide`',
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const readme = fs.readFileSync(path.join(extDir, 'README.md'), 'utf-8');
|
||||
const guide = fs.readFileSync(
|
||||
path.join(extDir, 'docs', 'guide.md'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
expect(readme).toContain(`${extDir}/readme`);
|
||||
expect(guide).toContain(`${extDir}/docs/guide`);
|
||||
});
|
||||
|
||||
it('should handle multiple shell script files', () => {
|
||||
const extDir = path.join(testDir, 'ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
fs.mkdirSync(path.join(extDir, 'scripts'), { recursive: true });
|
||||
|
||||
fs.writeFileSync(
|
||||
path.join(extDir, 'setup.sh'),
|
||||
'echo "${CLAUDE_PLUGIN_ROOT}/setup"',
|
||||
'utf-8',
|
||||
);
|
||||
fs.writeFileSync(
|
||||
path.join(extDir, 'scripts', 'helper.sh'),
|
||||
'echo "${CLAUDE_PLUGIN_ROOT}/scripts/helper"',
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
performVariableReplacement(extDir);
|
||||
|
||||
const setup = fs.readFileSync(path.join(extDir, 'setup.sh'), 'utf-8');
|
||||
const helper = fs.readFileSync(
|
||||
path.join(extDir, 'scripts', 'helper.sh'),
|
||||
'utf-8',
|
||||
);
|
||||
|
||||
expect(setup).toContain(`${extDir}/setup`);
|
||||
expect(helper).toContain(`${extDir}/scripts/helper`);
|
||||
});
|
||||
|
||||
it('should handle empty directories gracefully', () => {
|
||||
const extDir = path.join(testDir, 'empty-ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
// Should not throw
|
||||
expect(() => performVariableReplacement(extDir)).not.toThrow();
|
||||
});
|
||||
|
||||
it('should handle directories with no matching files', () => {
|
||||
const extDir = path.join(testDir, 'ext');
|
||||
fs.mkdirSync(extDir, { recursive: true });
|
||||
|
||||
// Create non-matching files
|
||||
fs.writeFileSync(path.join(extDir, 'file.txt'), 'content', 'utf-8');
|
||||
fs.writeFileSync(path.join(extDir, 'script.py'), 'print("hello")', 'utf-8');
|
||||
|
||||
// Should not throw
|
||||
expect(() => performVariableReplacement(extDir)).not.toThrow();
|
||||
|
||||
// Files should remain unchanged
|
||||
expect(fs.readFileSync(path.join(extDir, 'file.txt'), 'utf-8')).toBe(
|
||||
'content',
|
||||
);
|
||||
});
|
||||
});
|
||||
|
|
|
|||
|
|
@ -8,6 +8,11 @@ import { type VariableSchema, VARIABLE_SCHEMA } from './variableSchema.js';
|
|||
import path from 'node:path';
|
||||
import { QWEN_DIR } from '../config/storage.js';
|
||||
import type { HookEventName, HookDefinition } from '../hooks/types.js';
|
||||
import * as fs from 'node:fs';
|
||||
import { glob } from 'glob';
|
||||
import { createDebugLogger } from '../utils/debugLogger.js';
|
||||
|
||||
const debugLogger = createDebugLogger('Extension:variables');
|
||||
|
||||
// Re-export types for substituteHookVariables
|
||||
export type { HookEventName, HookDefinition };
|
||||
|
|
@ -111,3 +116,114 @@ export function substituteHookVariables(
|
|||
|
||||
return clonedHooks;
|
||||
}
|
||||
|
||||
/**
|
||||
* Perform variable replacement in all markdown and shell script files of the extension.
|
||||
* This is done during the conversion phase to avoid modifying files during every extension load.
|
||||
* @param extensionPath - The path to the extension directory
|
||||
*/
|
||||
export function performVariableReplacement(extensionPath: string): void {
|
||||
// Process markdown files
|
||||
const mdGlobPattern = '**/*.md';
|
||||
const mdGlobOptions = {
|
||||
cwd: extensionPath,
|
||||
nodir: true,
|
||||
};
|
||||
|
||||
try {
|
||||
const mdFiles = glob.sync(mdGlobPattern, mdGlobOptions);
|
||||
|
||||
for (const file of mdFiles) {
|
||||
const filePath = path.join(extensionPath, file);
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Replace ${CLAUDE_PLUGIN_ROOT} with the actual extension path
|
||||
const updatedContent = content.replace(
|
||||
/\$\{CLAUDE_PLUGIN_ROOT\}/g,
|
||||
extensionPath,
|
||||
);
|
||||
|
||||
// Replace Markdown shell syntax ```! ... ``` with system-recognized !{...} syntax
|
||||
// This regex finds code blocks with ! language identifier and captures their content
|
||||
const updatedMdContent = updatedContent.replace(
|
||||
/```!(?:\s*\n)?([\s\S]*?)\n*```/g,
|
||||
'!{$1}',
|
||||
);
|
||||
|
||||
// Only write if content was actually changed
|
||||
if (updatedMdContent !== content) {
|
||||
fs.writeFileSync(filePath, updatedMdContent, 'utf8');
|
||||
debugLogger.debug(
|
||||
`Updated variables and syntax in file: ${filePath}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to process file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to scan markdown files in extension directory ${extensionPath}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
|
||||
// Process shell script files
|
||||
const scriptGlobPattern = '**/*.sh';
|
||||
const scriptGlobOptions = {
|
||||
cwd: extensionPath,
|
||||
nodir: true,
|
||||
};
|
||||
|
||||
try {
|
||||
const scriptFiles = glob.sync(scriptGlobPattern, scriptGlobOptions);
|
||||
|
||||
for (const file of scriptFiles) {
|
||||
const filePath = path.join(extensionPath, file);
|
||||
|
||||
try {
|
||||
const content = fs.readFileSync(filePath, 'utf8');
|
||||
|
||||
// Replace references to "role":"assistant" with "type":"assistant" in shell scripts
|
||||
const updatedScriptContent = content.replace(
|
||||
/"role":"assistant"/g,
|
||||
'"type":"assistant"',
|
||||
);
|
||||
|
||||
// Replace transcript parsing logic to adapt to actual transcript structure
|
||||
// Change from .message.content | map(select(.type == "text")) to .message.parts | map(select(has("text")))
|
||||
const adaptedScriptContent = updatedScriptContent.replace(
|
||||
/\.message\.content\s*\|\s*map\(select\(\.type\s*==\s*"text"\)\)/g,
|
||||
'.message.parts | map(select(has("text")))',
|
||||
);
|
||||
|
||||
// Replace references to ".claude" directory with ".qwen" in shell scripts
|
||||
// Only match path references (e.g., ~/.claude/, $HOME/.claude, ./.claude/)
|
||||
// Avoid matching URLs, comments, or string literals containing .claude
|
||||
const finalScriptContent = adaptedScriptContent.replace(
|
||||
/(\$\{?HOME\}?\/|~\/)?\.claude(\/|$)/g,
|
||||
'$1.qwen$2',
|
||||
);
|
||||
|
||||
// Only write if content was actually changed
|
||||
if (finalScriptContent !== content) {
|
||||
fs.writeFileSync(filePath, finalScriptContent, 'utf8');
|
||||
debugLogger.debug(
|
||||
`Updated transcript format and replaced .claude with .qwen in shell script: ${filePath}`,
|
||||
);
|
||||
}
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to process shell script file ${filePath}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
} catch (error) {
|
||||
debugLogger.warn(
|
||||
`Failed to scan shell script files in extension directory ${extensionPath}: ${error instanceof Error ? error.message : String(error)}`,
|
||||
);
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue