🎉 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,128 @@
|
||||
import * as fs from 'node:fs'
|
||||
import * as os from 'node:os'
|
||||
import * as path from 'node:path'
|
||||
import { afterEach, describe, expect, it, vi } from 'vitest'
|
||||
import {
|
||||
buildExtensionCatalog,
|
||||
executeExtensionInstallCommand,
|
||||
executeExtensionPackageRemoval,
|
||||
parsePiInstallCommand,
|
||||
} from './piagentui-extension-manager.js'
|
||||
|
||||
const tempRoots: string[] = []
|
||||
|
||||
function makeTempRoot() {
|
||||
const root = fs.mkdtempSync(path.join(os.tmpdir(), 'piagentui-extension-manager-'))
|
||||
tempRoots.push(root)
|
||||
return root
|
||||
}
|
||||
|
||||
function writeJson(filePath: string, value: unknown) {
|
||||
fs.mkdirSync(path.dirname(filePath), { recursive: true })
|
||||
fs.writeFileSync(filePath, JSON.stringify(value, null, 2), 'utf8')
|
||||
}
|
||||
|
||||
afterEach(() => {
|
||||
for (const root of tempRoots.splice(0)) {
|
||||
fs.rmSync(root, { recursive: true, force: true })
|
||||
}
|
||||
})
|
||||
|
||||
describe('parsePiInstallCommand', () => {
|
||||
it('extracts source and local scope from a safe Pi install command', () => {
|
||||
expect(parsePiInstallCommand('pi install npm:pi-hud')).toEqual({ source: 'npm:pi-hud', local: false })
|
||||
expect(parsePiInstallCommand('pi install npm:@scope/pkg -l')).toEqual({ source: 'npm:@scope/pkg', local: true })
|
||||
expect(parsePiInstallCommand('pi install -l "D:/Projects/my extension"')).toEqual({
|
||||
source: 'D:/Projects/my extension',
|
||||
local: true,
|
||||
})
|
||||
})
|
||||
|
||||
it('rejects commands that are not a single Pi install operation', () => {
|
||||
expect(() => parsePiInstallCommand('npm install pi-hud')).toThrow(/pi install/i)
|
||||
expect(() => parsePiInstallCommand('pi remove npm:pi-hud')).toThrow(/pi install/i)
|
||||
expect(() => parsePiInstallCommand('pi install npm:pi-hud && rm -rf .')).toThrow(/unsupported/i)
|
||||
expect(() => parsePiInstallCommand('pi install')).toThrow(/missing/i)
|
||||
})
|
||||
})
|
||||
|
||||
describe('buildExtensionCatalog', () => {
|
||||
it('groups configured packages with metadata, resource counts, and active or inactive status', () => {
|
||||
const homeDir = makeTempRoot()
|
||||
const cwd = path.join(homeDir, 'project')
|
||||
const userPackageRoot = path.join(homeDir, '.pi', 'agent', 'npm', 'node_modules', 'pi-demo')
|
||||
const projectPackageRoot = path.join(cwd, '.pi', 'npm', 'node_modules', 'project-demo')
|
||||
const missingPackageSource = 'npm:missing-demo'
|
||||
|
||||
writeJson(path.join(homeDir, '.pi', 'agent', 'settings.json'), {
|
||||
packages: ['npm:pi-demo', { source: missingPackageSource, extensions: [] }],
|
||||
})
|
||||
writeJson(path.join(cwd, '.pi', 'settings.json'), {
|
||||
packages: [{ source: 'npm:project-demo', extensions: [] }],
|
||||
})
|
||||
|
||||
writeJson(path.join(userPackageRoot, 'package.json'), {
|
||||
name: 'pi-demo',
|
||||
version: '1.2.3',
|
||||
description: 'Demo package',
|
||||
pi: { extensions: ['./extensions'], skills: ['./skills'] },
|
||||
})
|
||||
fs.mkdirSync(path.join(userPackageRoot, 'extensions'), { recursive: true })
|
||||
fs.writeFileSync(path.join(userPackageRoot, 'extensions', 'demo.ts'), 'export default {}', 'utf8')
|
||||
fs.mkdirSync(path.join(userPackageRoot, 'skills', 'demo'), { recursive: true })
|
||||
fs.writeFileSync(path.join(userPackageRoot, 'skills', 'demo', 'SKILL.md'), '---\nname: demo\n---', 'utf8')
|
||||
|
||||
writeJson(path.join(projectPackageRoot, 'package.json'), {
|
||||
name: 'project-demo',
|
||||
version: '0.1.0',
|
||||
description: 'Project package',
|
||||
pi: { extensions: ['./extensions'] },
|
||||
})
|
||||
fs.mkdirSync(path.join(projectPackageRoot, 'extensions'), { recursive: true })
|
||||
fs.writeFileSync(path.join(projectPackageRoot, 'extensions', 'project.ts'), 'export default {}', 'utf8')
|
||||
|
||||
const catalog = buildExtensionCatalog({ cwd, homeDir })
|
||||
|
||||
expect(catalog.summary).toEqual({ total: 3, active: 1, inactive: 1, missing: 1, error: 0 })
|
||||
|
||||
const userPackage = catalog.packages.find(pkg => pkg.source === 'npm:pi-demo')
|
||||
expect(userPackage).toMatchObject({
|
||||
source: 'npm:pi-demo',
|
||||
scope: 'user',
|
||||
packageName: 'pi-demo',
|
||||
version: '1.2.3',
|
||||
description: 'Demo package',
|
||||
status: 'active',
|
||||
installedPath: userPackageRoot,
|
||||
})
|
||||
expect(userPackage?.resources.extensions).toEqual([
|
||||
expect.objectContaining({
|
||||
name: 'demo.ts',
|
||||
enabled: true,
|
||||
path: path.join(userPackageRoot, 'extensions', 'demo.ts'),
|
||||
}),
|
||||
])
|
||||
expect(userPackage?.resources.skills).toEqual([expect.objectContaining({ name: 'demo', enabled: true })])
|
||||
|
||||
const projectPackage = catalog.packages.find(pkg => pkg.source === 'npm:project-demo')
|
||||
expect(projectPackage).toMatchObject({ source: 'npm:project-demo', scope: 'project', status: 'inactive' })
|
||||
expect(projectPackage?.resources.extensions).toEqual([
|
||||
expect.objectContaining({ name: 'project.ts', enabled: false }),
|
||||
])
|
||||
|
||||
const missingPackage = catalog.packages.find(pkg => pkg.source === missingPackageSource)
|
||||
expect(missingPackage).toMatchObject({ source: missingPackageSource, scope: 'user', status: 'missing' })
|
||||
})
|
||||
})
|
||||
|
||||
describe('extension package operations', () => {
|
||||
it('runs parsed install and removal through the Pi CLI without executing pasted shell text', async () => {
|
||||
const runner = vi.fn(async () => ({ stdout: 'ok', stderr: '' }))
|
||||
|
||||
await executeExtensionInstallCommand('pi install npm:pi-hud -l', { cwd: '/repo', runner })
|
||||
await executeExtensionPackageRemoval({ source: 'npm:pi-hud', scope: 'project' }, { cwd: '/repo', runner })
|
||||
|
||||
expect(runner).toHaveBeenNthCalledWith(1, ['install', 'npm:pi-hud', '-l'], { cwd: '/repo' })
|
||||
expect(runner).toHaveBeenNthCalledWith(2, ['remove', 'npm:pi-hud', '-l'], { cwd: '/repo' })
|
||||
})
|
||||
})
|
||||
Reference in new issue
Block a user