🎉 Initial commit: PiAgentUI — AI Agent Desktop & Web UI
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s
Build & Push Frontend Docker Image / build-and-push (push) Canceled after 0s
Build Validation / validate (push) Canceled after 6s
Build & Push Backend Docker Image / build-and-push (push) Failing after 10m10s
Build & Push Gateway Docker Image / build-and-push (push) Failing after 10m9s
A professional workspace for AI coding agents with: - Real-time streaming chat with SSE - Session management (fork, clone, tree navigation) - MCP tools browser & metadata panel - Skills browser (global, project, package sources) - Voice input & STT provider configuration - Integrated terminal & file explorer - Model selector with capability metadata - Keyboard-first UI with command palette - 8 languages (en, pt-BR, es, ja, zh-CN, ar, bn, hi) - Tauri 2 desktop app - React 19 + TypeScript + Vite 8 + Tailwind CSS 4 - Pi Agent extension runtime Originally forked from OpenCodeUI, now an independent project. License: GPL-3.0-only
This commit is contained in:
commit
b3489e1ede
983 files changed
+158475
No files matched your search
@@ -0,0 +1,204 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* bump-version.mjs - One-command version bump for OpenCodeUI
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/bump-version.mjs <version>
|
||||
*
|
||||
* Examples:
|
||||
* node scripts/bump-version.mjs 0.2.0 # stable release
|
||||
* node scripts/bump-version.mjs 0.2.1-canary.1 # canary release
|
||||
*
|
||||
* What it does:
|
||||
* 1. Updates version in package.json, src-tauri/Cargo.toml, src-tauri/tauri.conf.json
|
||||
* 2. Prepends a new entry to CHANGELOG.md with git log since last tag
|
||||
* 3. Prints the git commands you need to run next (tag + push)
|
||||
*/
|
||||
|
||||
import { readFileSync, writeFileSync, existsSync } from 'fs'
|
||||
import { execSync } from 'child_process'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const root = resolve(__dirname, '..')
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// Parse args
|
||||
// ---------------------------------------------------------------------------
|
||||
const version = process.argv[2]
|
||||
|
||||
if (!version) {
|
||||
console.error('Usage: node scripts/bump-version.mjs <version>')
|
||||
console.error(' e.g. node scripts/bump-version.mjs 0.2.0')
|
||||
console.error(' e.g. node scripts/bump-version.mjs 0.2.1-canary.1')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
// Basic semver validation (with optional prerelease)
|
||||
const semverRe = /^\d+\.\d+\.\d+(-[a-zA-Z0-9]+(\.\d+)?)?$/
|
||||
if (!semverRe.test(version)) {
|
||||
console.error(`Invalid semver: "${version}"`)
|
||||
console.error('Expected format: MAJOR.MINOR.PATCH or MAJOR.MINOR.PATCH-prerelease.N')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
const tagName = `v${version}`
|
||||
const today = new Date().toISOString().slice(0, 10)
|
||||
const isPrerelease = version.includes('-')
|
||||
const stableTagRe = /^v\d+\.\d+\.\d+$/
|
||||
const existingChangelogPath = resolve(root, 'CHANGELOG.md')
|
||||
const lineEnding =
|
||||
existsSync(existingChangelogPath) && /\r\n/.test(readFileSync(existingChangelogPath, 'utf-8')) ? '\r\n' : '\n'
|
||||
|
||||
function formatWithPrettier(relativePath) {
|
||||
execSync(`npx prettier --write "${relativePath}"`, {
|
||||
cwd: root,
|
||||
stdio: 'pipe',
|
||||
})
|
||||
}
|
||||
|
||||
function replaceCargoPackageVersion(lockContent, packageName, nextVersion) {
|
||||
const packageBlocks = lockContent.split('[[package]]')
|
||||
const updatedBlocks = packageBlocks.map((block, index) => {
|
||||
if (index === 0) return block
|
||||
if (!block.includes(`name = "${packageName}"`)) return block
|
||||
return block.replace(/^(version\s*=\s*)"[^"]*"/m, `$1"${nextVersion}"`)
|
||||
})
|
||||
return updatedBlocks.join('[[package]]')
|
||||
}
|
||||
|
||||
function getReleaseBaseTag() {
|
||||
if (isPrerelease) {
|
||||
return execSync('git describe --tags --abbrev=0 2>/dev/null', {
|
||||
encoding: 'utf-8',
|
||||
cwd: root,
|
||||
}).trim()
|
||||
}
|
||||
|
||||
const mergedTags = execSync('git tag --merged HEAD --sort=-v:refname', {
|
||||
encoding: 'utf-8',
|
||||
cwd: root,
|
||||
})
|
||||
.split(/\r?\n/)
|
||||
.map(tag => tag.trim())
|
||||
.filter(Boolean)
|
||||
|
||||
const lastStableTag = mergedTags.find(tag => stableTagRe.test(tag) && tag !== tagName)
|
||||
if (!lastStableTag) {
|
||||
throw new Error('No previous stable tag found')
|
||||
}
|
||||
|
||||
return lastStableTag
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 1. Update package.json
|
||||
// ---------------------------------------------------------------------------
|
||||
const pkgPath = resolve(root, 'package.json')
|
||||
const pkg = JSON.parse(readFileSync(pkgPath, 'utf-8'))
|
||||
const oldVersion = pkg.version
|
||||
|
||||
execSync(`npm version ${version} --no-git-tag-version --allow-same-version`, {
|
||||
cwd: root,
|
||||
stdio: 'pipe',
|
||||
})
|
||||
console.log(` package.json ${oldVersion} -> ${version}`)
|
||||
console.log(` package-lock.json ${oldVersion} -> ${version}`)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 2. Update src-tauri/Cargo.toml
|
||||
// ---------------------------------------------------------------------------
|
||||
const cargoPath = resolve(root, 'src-tauri/Cargo.toml')
|
||||
let cargo = readFileSync(cargoPath, 'utf-8')
|
||||
const cargoPackageNameMatch = cargo.match(/^(name\s*=\s*)"([^"]+)"/m)
|
||||
const cargoPackageName = cargoPackageNameMatch?.[2]
|
||||
cargo = cargo.replace(/^(version\s*=\s*)"[^"]*"/m, `$1"${version}"`)
|
||||
writeFileSync(cargoPath, cargo)
|
||||
console.log(` src-tauri/Cargo.toml ${oldVersion} -> ${version}`)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 3. Update src-tauri/tauri.conf.json
|
||||
// ---------------------------------------------------------------------------
|
||||
const tauriConfPath = resolve(root, 'src-tauri/tauri.conf.json')
|
||||
const tauriConf = JSON.parse(readFileSync(tauriConfPath, 'utf-8'))
|
||||
tauriConf.version = version
|
||||
writeFileSync(tauriConfPath, JSON.stringify(tauriConf, null, 2) + '\n')
|
||||
formatWithPrettier('src-tauri/tauri.conf.json')
|
||||
console.log(` src-tauri/tauri.conf ${oldVersion} -> ${version}`)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 4. Update src-tauri/Cargo.lock (workspace package entry)
|
||||
// ---------------------------------------------------------------------------
|
||||
const cargoLockPath = resolve(root, 'src-tauri/Cargo.lock')
|
||||
if (cargoPackageName && existsSync(cargoLockPath)) {
|
||||
const cargoLock = readFileSync(cargoLockPath, 'utf-8')
|
||||
const updatedCargoLock = replaceCargoPackageVersion(cargoLock, cargoPackageName, version)
|
||||
if (updatedCargoLock !== cargoLock) {
|
||||
writeFileSync(cargoLockPath, updatedCargoLock)
|
||||
console.log(` src-tauri/Cargo.lock ${oldVersion} -> ${version}`)
|
||||
}
|
||||
}
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 5. Generate changelog entry from git log
|
||||
// ---------------------------------------------------------------------------
|
||||
let commits = ''
|
||||
try {
|
||||
// Stable release: compare from previous stable tag.
|
||||
// Pre-release: compare from the latest reachable tag.
|
||||
const lastTag = getReleaseBaseTag()
|
||||
|
||||
commits = execSync(`git log ${lastTag}..HEAD --pretty=format:"- %s (%h)" --no-merges`, {
|
||||
encoding: 'utf-8',
|
||||
cwd: root,
|
||||
}).trim()
|
||||
} catch {
|
||||
// No previous tag — include all commits
|
||||
try {
|
||||
commits = execSync('git log --pretty=format:"- %s (%h)" --no-merges', {
|
||||
encoding: 'utf-8',
|
||||
cwd: root,
|
||||
}).trim()
|
||||
} catch {
|
||||
commits = '- Initial release'
|
||||
}
|
||||
}
|
||||
|
||||
if (!commits) {
|
||||
commits = '- No changes since last tag'
|
||||
}
|
||||
|
||||
const releaseType = isPrerelease ? ' (Pre-release)' : ''
|
||||
const changelogEntry = `## [${tagName}] - ${today}${releaseType}${lineEnding}${lineEnding}${commits.replace(/\n/g, lineEnding)}${lineEnding}`
|
||||
|
||||
const changelogPath = resolve(root, 'CHANGELOG.md')
|
||||
if (existsSync(changelogPath)) {
|
||||
const existing = readFileSync(changelogPath, 'utf-8')
|
||||
const headerSeparator = existing.match(/\r?\n\r?\n/)
|
||||
if (headerSeparator) {
|
||||
const headerEnd = existing.indexOf(headerSeparator[0])
|
||||
const separatorLength = headerSeparator[0].length
|
||||
const header = existing.slice(0, headerEnd + separatorLength)
|
||||
const body = existing.slice(headerEnd + separatorLength)
|
||||
writeFileSync(changelogPath, header + changelogEntry + lineEnding + body)
|
||||
} else {
|
||||
writeFileSync(changelogPath, existing + lineEnding + changelogEntry)
|
||||
}
|
||||
} else {
|
||||
writeFileSync(changelogPath, `# Changelog${lineEnding}${lineEnding}${changelogEntry}`)
|
||||
}
|
||||
console.log(` CHANGELOG.md added entry for ${tagName}`)
|
||||
|
||||
// ---------------------------------------------------------------------------
|
||||
// 6. Print next steps
|
||||
// ---------------------------------------------------------------------------
|
||||
console.log(`
|
||||
Done! Next steps:
|
||||
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${version}"
|
||||
git tag ${tagName}
|
||||
git push && git push origin ${tagName}
|
||||
`)
|
||||
@@ -0,0 +1,70 @@
|
||||
/**
|
||||
* Copy only the material-icon-theme SVGs that are actually referenced
|
||||
* by src/utils/materialIcons.ts to public/material-icons/.
|
||||
*
|
||||
* Runs automatically via postinstall hook.
|
||||
*/
|
||||
|
||||
import { copyFileSync, mkdirSync, existsSync, readFileSync, rmSync } from 'fs'
|
||||
import { resolve, dirname } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const root = resolve(__dirname, '..')
|
||||
|
||||
const srcDir = resolve(root, 'node_modules/material-icon-theme/icons')
|
||||
const destDir = resolve(root, 'public/material-icons')
|
||||
|
||||
if (!existsSync(srcDir)) {
|
||||
console.warn('[copy-material-icons] Source not found, skipping:', srcDir)
|
||||
process.exit(0)
|
||||
}
|
||||
|
||||
// Parse materialIcons.ts to extract every icon name referenced
|
||||
const tsSource = readFileSync(resolve(root, 'src/utils/materialIcons.ts'), 'utf-8')
|
||||
|
||||
const icons = new Set()
|
||||
|
||||
// Icon values after colon: 'icon-name'
|
||||
for (const m of tsSource.matchAll(/:\s*'([a-z0-9_-]+)'/g)) {
|
||||
icons.add(m[1])
|
||||
}
|
||||
// Array items [ext, icon]
|
||||
for (const m of tsSource.matchAll(/\[\s*'[^']+'\s*,\s*'([a-z0-9_-]+)'\s*\]/g)) {
|
||||
icons.add(m[1])
|
||||
}
|
||||
// Defaults
|
||||
icons.add('file')
|
||||
icons.add('folder')
|
||||
icons.add('folder-open')
|
||||
|
||||
// Folder icons need both base and -open variants
|
||||
for (const icon of [...icons]) {
|
||||
if (icon.startsWith('folder-') && !icon.endsWith('-open')) {
|
||||
icons.add(icon + '-open')
|
||||
}
|
||||
}
|
||||
|
||||
// Clean destination and copy only needed files
|
||||
if (existsSync(destDir)) {
|
||||
rmSync(destDir, { recursive: true })
|
||||
}
|
||||
mkdirSync(destDir, { recursive: true })
|
||||
|
||||
let copied = 0
|
||||
let missing = 0
|
||||
for (const icon of icons) {
|
||||
const srcPath = resolve(srcDir, icon + '.svg')
|
||||
const destPath = resolve(destDir, icon + '.svg')
|
||||
if (existsSync(srcPath)) {
|
||||
copyFileSync(srcPath, destPath)
|
||||
copied++
|
||||
} else {
|
||||
console.warn(` [WARN] Missing: ${icon}.svg`)
|
||||
missing++
|
||||
}
|
||||
}
|
||||
|
||||
console.log(
|
||||
`[copy-material-icons] Copied ${copied} icons to public/material-icons/` + (missing ? ` (${missing} missing)` : ''),
|
||||
)
|
||||
@@ -0,0 +1,13 @@
|
||||
#!/bin/bash
|
||||
|
||||
# Set environment variables for Android development
|
||||
export ANDROID_HOME="/e/app/Android/Sdk"
|
||||
export NDK_HOME="/e/app/Android/Sdk/ndk/29.0.14206865"
|
||||
export JAVA_HOME="/e/app/Android/Android Studio/jbr"
|
||||
export PATH="$HOME/.cargo/bin:$PATH"
|
||||
|
||||
echo "Checking connected Android devices..."
|
||||
adb devices
|
||||
|
||||
echo "Starting Tauri Android Dev..."
|
||||
npm run tauri android dev
|
||||
@@ -0,0 +1,94 @@
|
||||
#!/usr/bin/env node
|
||||
|
||||
/**
|
||||
* prepare-release.mjs - Validate before version bumping
|
||||
*
|
||||
* Usage:
|
||||
* node scripts/prepare-release.mjs <version>
|
||||
* node scripts/prepare-release.mjs <version> --skip-validate
|
||||
*
|
||||
* What it does:
|
||||
* 1. Ensures the git worktree is clean before starting
|
||||
* 2. Runs `npm run validate` by default
|
||||
* 3. Runs bump-version.mjs to update release files
|
||||
* 4. Prints the remaining git steps (commit, tag, push)
|
||||
*/
|
||||
|
||||
import { execSync } from 'child_process'
|
||||
import { dirname, resolve } from 'path'
|
||||
import { fileURLToPath } from 'url'
|
||||
|
||||
const __dirname = dirname(fileURLToPath(import.meta.url))
|
||||
const root = resolve(__dirname, '..')
|
||||
|
||||
const args = process.argv.slice(2)
|
||||
const showHelp = args.includes('--help') || args.includes('-h')
|
||||
const skipValidate = args.includes('--skip-validate')
|
||||
const version = args.find(arg => !arg.startsWith('-'))
|
||||
|
||||
if (showHelp || !version) {
|
||||
console.log('Usage: node scripts/prepare-release.mjs <version> [--skip-validate]')
|
||||
console.log(' e.g. node scripts/prepare-release.mjs 0.2.0')
|
||||
console.log(' e.g. node scripts/prepare-release.mjs 0.2.1-canary.1')
|
||||
process.exit(showHelp ? 0 : 1)
|
||||
}
|
||||
|
||||
const tagName = `v${version}`
|
||||
|
||||
function run(command, label) {
|
||||
console.log(`\n> ${label}`)
|
||||
execSync(command, { cwd: root, stdio: 'inherit' })
|
||||
}
|
||||
|
||||
function read(command) {
|
||||
return execSync(command, {
|
||||
cwd: root,
|
||||
encoding: 'utf-8',
|
||||
stdio: ['ignore', 'pipe', 'pipe'],
|
||||
}).trim()
|
||||
}
|
||||
|
||||
const worktreeStatus = read('git status --short')
|
||||
if (worktreeStatus) {
|
||||
console.error('Release preparation requires a clean git worktree.')
|
||||
console.error('Please commit, stash, or discard local changes first.')
|
||||
process.exit(1)
|
||||
}
|
||||
|
||||
try {
|
||||
const localTag = read(`git tag -l ${tagName}`)
|
||||
if (localTag === tagName) {
|
||||
console.error(`Tag ${tagName} already exists locally.`)
|
||||
process.exit(1)
|
||||
}
|
||||
} catch {
|
||||
// Ignore local tag lookup failures and continue.
|
||||
}
|
||||
|
||||
try {
|
||||
const remoteTag = read(`git ls-remote --tags origin refs/tags/${tagName}`)
|
||||
if (remoteTag) {
|
||||
console.error(`Tag ${tagName} already exists on origin.`)
|
||||
process.exit(1)
|
||||
}
|
||||
} catch {
|
||||
// If origin is unavailable, let later git commands surface the real error.
|
||||
}
|
||||
|
||||
if (!skipValidate) {
|
||||
run('npm run validate', 'Running release validation')
|
||||
} else {
|
||||
console.log('\n> Skipping release validation (--skip-validate)')
|
||||
}
|
||||
|
||||
run(`node scripts/bump-version.mjs ${version}`, `Preparing release ${tagName}`)
|
||||
|
||||
console.log(`
|
||||
Release preparation finished for ${tagName}.
|
||||
|
||||
Next steps:
|
||||
git add -A
|
||||
git commit -m "chore: bump version to ${version}"
|
||||
git tag ${tagName}
|
||||
git push && git push origin ${tagName}
|
||||
`)
|
||||
Reference in new issue
Block a user