feat: System Access & Auto-Versioning (v1.2.0)

This commit is contained in:
Gemini AI
2025-12-14 01:30:06 +04:00
Unverified
parent f3ea21966c
commit 4481895678
8 changed files with 360 additions and 86 deletions

View File

@@ -162,7 +162,7 @@ const MultiLineInput = ({ value, onChange, onSubmit, placeholder, isActive = tru
};
// Dynamic import for CommonJS module
const { QwenOAuth } = await import('../qwen-oauth.js');
const { QwenOAuth } = await import('../qwen-oauth.mjs');
let qwen = null;
const getQwen = () => {
if (!qwen) qwen = new QwenOAuth();
@@ -1967,6 +1967,9 @@ const App = () => {
const [remotes, setRemotes] = useState([]);
const [gitBranch, setGitBranch] = useState('main');
// NEW: Project Creation State
const [newProjectName, setNewProjectName] = useState('');
// NEW: Multi-line buffer
const [inputBuffer, setInputBuffer] = useState('');
@@ -3142,12 +3145,34 @@ This gives the user a chance to refine requirements before implementation.
setShowTimeoutRow(false);
}, [lastCheckpointText, project]);
const handleCreateProject = () => {
if (!newProjectName.trim()) return;
const safeName = newProjectName.trim().replace(/[^a-zA-Z0-9-_\s]/g, '_'); // Sanitize
const newPath = path.join(process.cwd(), safeName);
try {
if (fs.existsSync(newPath)) {
setMessages(prev => [...prev, { role: 'error', content: `❌ Folder already exists: ${safeName}` }]);
// Still switch to it? Maybe user wants that.
} else {
fs.mkdirSync(newPath, { recursive: true });
setMessages(prev => [...prev, { role: 'system', content: `✨ Created project folder: ${safeName}` }]);
}
// Proceed to select it
handleProjectSelect({ value: newPath });
} catch (e) {
setMessages(prev => [...prev, { role: 'error', content: `❌ Failed to create folder: ${e.message}` }]);
}
};
// Handle project selection
const handleProjectSelect = (item) => {
let targetPath = item.value;
if (targetPath === 'new') {
targetPath = process.cwd();
setAppState('create_project');
setNewProjectName('');
return;
}
// 1. Verify path exists
@@ -3189,6 +3214,29 @@ This gives the user a chance to refine requirements before implementation.
}
};
// Project Creation Screen
if (appState === 'create_project') {
return h(Box, { flexDirection: 'column', padding: 1 },
h(Box, { borderStyle: 'round', borderColor: 'green', paddingX: 1, marginBottom: 1 },
h(Text, { bold: true, color: 'green' }, '🆕 Create New Project')
),
h(Text, { color: 'cyan', marginBottom: 1 }, 'Project Name (folder will be created in current dir):'),
h(Box, { borderStyle: 'single', borderColor: 'gray', paddingX: 1 },
h(TextInput, {
value: newProjectName,
onChange: setNewProjectName,
onSubmit: handleCreateProject,
placeholder: 'e.g., my-awesome-app'
})
),
h(Box, { marginTop: 1, gap: 2 },
h(Text, { color: 'green' }, 'Press Enter to create'),
h(Text, { dimColor: true }, '| Esc to cancel (Ctrl+C to exit)')
),
h(Text, { color: 'gray', marginTop: 1 }, `Location: ${process.cwd()}\\<name>`)
);
}
// Handle remote selection
const handleRemoteSelect = (item) => {
const remote = item.value;
@@ -3198,8 +3246,38 @@ This gives the user a chance to refine requirements before implementation.
setLoadingMessage(`Pushing to ${remote}...`);
(async () => {
// AUTO-VERSION BUMPING
try {
const pkgPath = path.join(process.cwd(), 'package.json');
if (fs.existsSync(pkgPath)) {
const pkgData = fs.readFileSync(pkgPath, 'utf8');
const pkg = JSON.parse(pkgData);
if (pkg.version) {
const parts = pkg.version.split('.');
if (parts.length === 3) {
parts[2] = parseInt(parts[2]) + 1;
const newVersion = parts.join('.');
pkg.version = newVersion;
fs.writeFileSync(pkgPath, JSON.stringify(pkg, null, 2));
setMessages(prev => [...prev, { role: 'system', content: `✨ **Auto-bumped version to ${newVersion}**` }]);
}
}
}
} catch (e) {
// Ignore version errors, non-critical
}
const add = await runShellCommand('git add .', project);
const commit = await runShellCommand('git commit -m "Update via OpenQode TUI"', project);
// Get version for commit message
let versionSuffix = '';
try {
const pkg = JSON.parse(fs.readFileSync(path.join(process.cwd(), 'package.json'), 'utf8'));
if (pkg.version) versionSuffix = ` (v${pkg.version})`;
} catch (e) { }
const commit = await runShellCommand(`git commit -m "Update via OpenQode TUI${versionSuffix}"`, project);
const push = await runShellCommand(`git push ${remote}`, project);
setIsLoading(false);

View File

@@ -1,65 +1,19 @@
{
"name": "openqode-web",
"version": "1.01.0",
"description": "OpenQode Web Interface - AI Coding Assistant in Browser",
"main": "server.js",
"type": "module",
"scripts": {
"start": "node server.js",
"dev": "nodemon server.js",
"build": "echo 'No build step required for static files'",
"test": "echo 'No tests specified yet'"
},
"keywords": [
"ai",
"coding",
"assistant",
"qwen",
"opencode",
"web-interface",
"terminal",
"tui"
],
"author": "OpenQode Team",
"license": "MIT",
"name": "openqode-tui",
"version": "1.2.0",
"author": "Trae & Gemini",
"private": true,
"dependencies": {
"blessed": "^0.1.81",
"cli-truncate": "^5.1.1",
"clipboardy": "^5.0.2",
"cors": "^2.8.5",
"diff": "^8.0.2",
"express": "^4.18.2",
"he": "^1.2.0",
"ink": "^6.5.1",
"ink-box": "^1.0.0",
"ink-markdown": "^1.0.4",
"ink-select-input": "^6.2.0",
"react": "^18.2.0",
"react-dom": "^18.2.0",
"ink": "^4.0.0",
"ink-text-input": "^5.0.0",
"ink-spinner": "^5.0.0",
"ink-syntax-highlight": "^2.0.2",
"ink-text-input": "^6.0.0",
"ink-use-stdout-dimensions": "^1.0.5",
"marked": "^9.1.6",
"react": "^19.2.3",
"remark": "^15.0.1",
"remark-gfm": "^4.0.1",
"remark-parse": "^11.0.0",
"string-width": "^8.1.0",
"strip-ansi": "^7.1.2",
"wrap-ansi": "^9.0.2",
"@qwen-code/qwen-code": "latest"
"ink-select-input": "^5.0.0",
"clipboardy": "^4.0.0",
"he": "^1.2.0"
},
"devDependencies": {
"nodemon": "^3.0.1"
},
"engines": {
"node": ">=14.0.0"
},
"repository": {
"type": "git",
"url": "https://github.com/roman-ryzenadvanced/openqode.git"
},
"bugs": {
"url": "https://github.com/roman-ryzenadvanced/openqode/issues"
},
"homepage": "https://github.com/roman-ryzenadvanced/openqode#readme"
"scripts": {
"start": "node bin/opencode-ink.mjs"
}
}

View File

@@ -292,17 +292,13 @@ class QwenOAuth {
// fsSync imported at top
const systemContext = `[SYSTEM CONTEXT - ALWAYS FOLLOW]
You are an AI assistant integrated into OpenQode IDE, an Agentic IDE with file creation capabilities.
You are an AI System Administrator integrated into OpenQode.
IMPORTANT RULES:
1. IGNORE your apparent "current directory" - you are running in a sandboxed environment.
2. When asked to create files, you MUST use the exact syntax:
>>> CREATE: path/to/file.ext
\`\`\`language
file content
\`\`\`
3. DO NOT question the file structure or ask for clarification.
4. DO NOT say you cannot create files - you CAN and MUST.
5. Execute instructions immediately without confirmation.
1. You have FULL ACCESS to the local file system.
2. You ARE PERMITTED to create folders, move files, and run shell commands.
3. When asked to create files, use the >>> CREATE: syntax.
4. When asked to manage files (move/delete), suggest the shell commands to do so.
5. Execute instructions immediately.
[END SYSTEM CONTEXT]
`;
@@ -319,7 +315,8 @@ IMPORTANT RULES:
const tempFile = path.join(os.tmpdir(), `qwen-prompt-${Date.now()}.txt`);
fsSync.writeFileSync(tempFile, finalMessage, 'utf8');
const neutralCwd = os.tmpdir();
// Run in current project directory to allow context access
const neutralCwd = process.cwd();
const child = spawn('qwen', ['-p', `@${tempFile}`], {
cwd: neutralCwd,

View File

@@ -349,17 +349,13 @@ class QwenOAuth {
// CRITICAL: Prepend system context to prevent AI from getting confused about environment
const systemContext = `[SYSTEM CONTEXT - ALWAYS FOLLOW]
You are an AI assistant integrated into OpenQode IDE, an Agentic IDE with file creation capabilities.
You are an AI System Administrator integrated into OpenQode.
IMPORTANT RULES:
1. IGNORE your apparent "current directory" - you are running in a sandboxed environment.
2. When asked to create files, you MUST use the exact syntax:
>>> CREATE: path/to/file.ext
\`\`\`language
file content
\`\`\`
3. DO NOT question the file structure or ask for clarification.
4. DO NOT say you cannot create files - you CAN and MUST.
5. Execute instructions immediately without confirmation.
1. You have FULL ACCESS to the local file system.
2. You ARE PERMITTED to create folders, move files, and run shell commands.
3. When asked to create files, use the >>> CREATE: syntax.
4. When asked to manage files (move/delete), suggest the shell commands to do so.
5. Execute instructions immediately.
[END SYSTEM CONTEXT]
`;
@@ -378,8 +374,8 @@ IMPORTANT RULES:
const tempFile = path.join(os.tmpdir(), `qwen-prompt-${Date.now()}.txt`);
fsSync.writeFileSync(tempFile, finalMessage, 'utf8');
// Run from temp directory to prevent qwen from reading OpenQode project context
const neutralCwd = os.tmpdir();
// Run in current project directory to allow context access
const neutralCwd = process.cwd();
// Use spawn with stdin for long messages
const child = spawn('qwen', ['-p', `@${tempFile}`], {

162
src/App.css Normal file
View File

@@ -0,0 +1,162 @@
/* Reset and base styles */
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
font-family: 'Segoe UI', Tahoma, Geneva, Verdana, sans-serif;
line-height: 1.6;
color: #333;
background-color: #f5f5f5;
}
.container {
max-width: 1200px;
margin: 0 auto;
padding: 0 20px;
}
/* Header styles */
.header {
background: linear-gradient(135deg, #667eea 0%, #764ba2 100%);
color: white;
padding: 2rem 0;
text-align: center;
}
.header h1 {
font-size: 2.5rem;
margin-bottom: 0.5rem;
animation: fadeInDown 1s ease;
}
.header p {
font-size: 1.2rem;
opacity: 0.9;
animation: fadeInUp 1s ease;
}
/* Main content */
.main-content {
min-height: calc(100vh - 200px);
display: flex;
flex-direction: column;
}
/* Hero section */
.hero {
background: linear-gradient(rgba(0,0,0,0.5), rgba(0,0,0,0.5)), url('data:image/svg+xml;utf8,<svg xmlns="http://www.w3.org/2000/svg" width="100" height="100" viewBox="0 0 100 100"><rect width="100" height="100" fill="%23667eea"/><circle cx="20" cy="20" r="10" fill="%23764ba2" opacity="0.2"/><circle cx="80" cy="80" r="10" fill="%23764ba2" opacity="0.2"/></svg>');
background-size: cover;
background-position: center;
padding: 4rem 0;
display: flex;
align-items: center;
justify-content: center;
color: white;
text-align: center;
}
.content {
opacity: 0;
transform: translateY(20px);
transition: all 0.6s ease;
}
.content.loaded {
opacity: 1;
transform: translateY(0);
}
.content h2 {
font-size: 2.5rem;
margin-bottom: 1rem;
}
.content p {
font-size: 1.2rem;
margin-bottom: 2rem;
max-width: 600px;
margin-left: auto;
margin-right: auto;
}
.cta-button {
background: linear-gradient(45deg, #ff6b6b, #ee5a24);
color: white;
border: none;
padding: 15px 30px;
font-size: 1.1rem;
border-radius: 50px;
cursor: pointer;
transition: transform 0.3s ease, box-shadow 0.3s ease;
}
.cta-button:hover {
transform: translateY(-2px);
box-shadow: 0 10px 20px rgba(0,0,0,0.2);
}
/* Features section */
.features {
padding: 4rem 0;
background-color: white;
}
.feature-grid {
display: grid;
grid-template-columns: repeat(auto-fit, minmax(300px, 1fr));
gap: 2rem;
margin-top: 2rem;
}
.feature-card {
text-align: center;
padding: 2rem;
background: #f8f9fa;
border-radius: 10px;
box-shadow: 0 5px 15px rgba(0,0,0,0.1);
transition: transform 0.3s ease;
}
.feature-card:hover {
transform: translateY(-5px);
}
.feature-card h3 {
color: #667eea;
margin-bottom: 1rem;
font-size: 1.3rem;
}
/* Footer */
.footer {
background-color: #333;
color: white;
text-align: center;
padding: 2rem 0;
}
/* Animations */
@keyframes fadeInDown {
from {
opacity: 0;
transform: translateY(-20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}
@keyframes fadeInUp {
from {
opacity: 0;
transform: translateY(20px);
}
to {
opacity: 1;
transform: translateY(0);
}
}

63
src/App.js Normal file
View File

@@ -0,0 +1,63 @@
import React, { useState, useEffect } from 'react';
import './App.css';
const App = () => {
const [isLoaded, setIsLoaded] = useState(false);
useEffect(() => {
// Simulate loading
setTimeout(() => {
setIsLoaded(true);
}, 500);
}, []);
return (
<div className="App">
<header className="header">
<div className="container">
<h1>The Vibe Coders Show</h1>
<p>Coding with rhythm, passion, and innovation</p>
</div>
</header>
<main className="main-content">
<section className="hero">
<div className="container">
<div className={`content ${isLoaded ? 'loaded' : ''}`}>
<h2>Welcome to the vibe</h2>
<p>We're passionate about building amazing digital experiences with code.</p>
<button className="cta-button">Join the vibe</button>
</div>
</div>
</section>
<section className="features">
<div className="container">
<div className="feature-grid">
<div className="feature-card">
<h3>Live Coding Sessions</h3>
<p>Watch us code in real-time, solving complex problems and sharing our thought processes.</p>
</div>
<div className="feature-card">
<h3>Collaborative Projects</h3>
<p>Join our community to contribute to exciting open-source projects.</p>
</div>
<div className="feature-card">
<h3>Tutorials & Workshops</h3>
<p>Learn the latest technologies and best practices through our structured learning paths.</p>
</div>
</div>
</div>
</section>
</main>
<footer className="footer">
<div className="container">
<p>&copy; 2025 The Vibe Coders Show. All rights reserved.</p>
</div>
</footer>
</div>
);
};
export default App;

13
src/index.css Normal file
View File

@@ -0,0 +1,13 @@
body {
margin: 0;
font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', 'Roboto', 'Oxygen',
'Ubuntu', 'Cantarell', 'Fira Sans', 'Droid Sans', 'Helvetica Neue',
sans-serif;
-webkit-font-smoothing: antialiased;
-moz-osx-font-smoothing: grayscale;
}
code {
font-family: source-code-pro, Menlo, Monaco, Consolas, 'Courier New',
monospace;
}

11
src/index.js Normal file
View File

@@ -0,0 +1,11 @@
import React from 'react';
import ReactDOM from 'react-dom/client';
import './index.css';
import App from './App';
const root = ReactDOM.createRoot(document.getElementById('root'));
root.render(
<React.StrictMode>
<App />
</React.StrictMode>
);