Initial project import
This commit is contained in:
commit
d189bb22f4
19 files changed
+361
No files matched your search
+15
@@ -0,0 +1,15 @@
|
||||
.DS_Store
|
||||
__MACOSX/
|
||||
*.dylib
|
||||
*.so
|
||||
*.dll
|
||||
*.exe
|
||||
*.o
|
||||
*.obj
|
||||
*.a
|
||||
*.out
|
||||
build/
|
||||
dist/
|
||||
.env
|
||||
*.key
|
||||
*.pem
|
||||
@@ -0,0 +1,19 @@
|
||||
AGILA DEVELOPER SDK LICENSE v1.0
|
||||
|
||||
Copyright (c) 2026 Matthew Mcclung. All rights reserved.
|
||||
|
||||
Permission is granted to use, copy, and modify the SDK files located under `sdk/`, `examples/`, and `templates/` solely to develop, test, and distribute applications, plugins, themes, and development tools intended to run on or interoperate with authorized Agila products.
|
||||
|
||||
You may distribute your own applications in source or binary form. You may include the minimum SDK interface files required to build your application.
|
||||
|
||||
You may not:
|
||||
1. use this SDK to create, clone, reimplement, or distribute an operating system, system runtime, display server, shell, kernel, security subsystem, compatibility layer, or platform that substitutes for Agila;
|
||||
2. reverse engineer or attempt to discover proprietary Agila implementations, protocols, private ABIs, service internals, signing systems, or security controls;
|
||||
3. claim compatibility, certification, sponsorship, or endorsement without written permission;
|
||||
4. redistribute this SDK as a competing SDK or standalone commercial product;
|
||||
5. use Agila names, logos, or trade dress except to truthfully state that an application was built for Agila, subject to the trademark policy;
|
||||
6. remove copyright, license, or boundary notices.
|
||||
|
||||
Files under `open/` are governed by the separate license found in `open/LICENSE-APACHE-2.0.txt` and are not subject to the restrictions above except for trademark rights.
|
||||
|
||||
THE SDK IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND. TO THE MAXIMUM EXTENT PERMITTED BY LAW, THE COPYRIGHT HOLDER IS NOT LIABLE FOR ANY CLAIM, DAMAGES, OR OTHER LIABILITY ARISING FROM USE OF THE SDK.
|
||||
@@ -0,0 +1,27 @@
|
||||
# Agila Developer SDK
|
||||
|
||||
Build applications, plugins, tools, and user experiences for Agila without exposing the proprietary Agila OS implementation.
|
||||
|
||||
## What this package is
|
||||
|
||||
- A public-facing Flying V SDK contract.
|
||||
- A host-safe sandbox for learning and prototyping.
|
||||
- Examples and templates for third-party applications.
|
||||
- A separate `open/` area containing components that may be reused by Agila Linux and other projects under permissive licenses.
|
||||
|
||||
## What this package is not
|
||||
|
||||
- Agila OS source code.
|
||||
- A bootable operating system.
|
||||
- A display server, kernel, scheduler, security engine, runtime, or private service implementation.
|
||||
- Permission to clone, reimplement, redistribute, or present another operating system as Agila-compatible.
|
||||
|
||||
See `LICENSE-AGILA-SDK.txt`, `TRADEMARKS.md`, and `docs/BOUNDARIES.md`.
|
||||
|
||||
## Quick start
|
||||
|
||||
```bash
|
||||
fv run examples/hello_agila/main.fv
|
||||
```
|
||||
|
||||
The included implementation is a development sandbox. Production applications use the proprietary Agila runtime supplied with Agila OS.
|
||||
@@ -0,0 +1,3 @@
|
||||
# Security
|
||||
|
||||
Do not publish suspected private Agila source, credentials, signing material, service endpoints, or private ABI details in public issues. Report them privately to the project owner and rotate any exposed credentials immediately.
|
||||
@@ -0,0 +1,5 @@
|
||||
# Agila trademarks
|
||||
|
||||
Agila, Agila OS, Agila Kit, Flying V, Sea Display, AGX, and associated logos and visual identities are trademarks or project marks controlled by their owner.
|
||||
|
||||
Developers may use wording such as “Built for Agila” to describe genuine compatibility. Do not name a fork, operating system, SDK, runtime, or product in a way that implies it is an official Agila product.
|
||||
@@ -0,0 +1,25 @@
|
||||
# Public and private boundaries
|
||||
|
||||
## Public developer surface
|
||||
|
||||
- Stable application-facing types and methods
|
||||
- UI components intended for applications
|
||||
- Audio and graphics utility APIs
|
||||
- Examples, templates, and documentation
|
||||
- Plugin and extension contracts released by Agila
|
||||
|
||||
## Proprietary Agila implementation
|
||||
|
||||
The following are intentionally absent:
|
||||
|
||||
- Kernel and boot code
|
||||
- Scheduling and memory-management logic
|
||||
- Security and policy engines
|
||||
- Private system-service implementations
|
||||
- Service transport and private ABI details
|
||||
- Hardware framebuffer and display-server internals
|
||||
- AGX optimization, dispatch, caching, and policy implementations
|
||||
- Flying V compiler internals
|
||||
- Signing, entitlement, and package-verification systems
|
||||
|
||||
Applications should depend only on documented public contracts.
|
||||
@@ -0,0 +1,9 @@
|
||||
# Publishing checklist
|
||||
|
||||
Before uploading a release:
|
||||
|
||||
1. Build from this clean directory, never from a private Agila source tree.
|
||||
2. Confirm no `.DS_Store`, `__MACOSX`, binaries, keys, tokens, certificates, or server addresses are present.
|
||||
3. Confirm every file belongs to either the restricted SDK or the `open/` component set.
|
||||
4. Do not add private headers, private ABI names, internal roadmaps, or implementation notes.
|
||||
5. Tag the release and publish its SHA-256 checksum.
|
||||
@@ -0,0 +1,15 @@
|
||||
import agila
|
||||
|
||||
fn main() {
|
||||
mut app := agila.new_app('dev.example.hello', 'Hello Agila') or {
|
||||
eprintln(err.msg())
|
||||
return
|
||||
}
|
||||
app.start() or {
|
||||
eprintln(err.msg())
|
||||
return
|
||||
}
|
||||
app.request(.ui) or { eprintln(err.msg()) }
|
||||
agila.log('Hello from a third-party Agila application!')
|
||||
app.stop()
|
||||
}
|
||||
@@ -0,0 +1,11 @@
|
||||
import agila
|
||||
|
||||
fn main() {
|
||||
view := agila.Column{
|
||||
children: [
|
||||
agila.Text{value: 'Build something awesome'},
|
||||
agila.Button{label: 'Launch'},
|
||||
]
|
||||
}
|
||||
agila.preview(view)
|
||||
}
|
||||
@@ -0,0 +1,15 @@
|
||||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
Copyright 2026 Matthew Mcclung
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at the URL above.
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
@@ -0,0 +1,8 @@
|
||||
# Agila open components
|
||||
|
||||
These standalone components are intentionally independent of proprietary Agila OS internals and may be considered for use in Agila Linux or other projects.
|
||||
|
||||
- `audio/` — basic waveform helpers
|
||||
- `font/` — bitmap font data identified by the original package as public domain
|
||||
|
||||
These files use the Apache License 2.0 except where a file states a different compatible license.
|
||||
@@ -0,0 +1,18 @@
|
||||
// Standalone waveform helpers. Apache-2.0 licensed.
|
||||
module agila_open_audio
|
||||
|
||||
import math
|
||||
|
||||
pub fn sine_wave(sample_rate int, frequency f64, duration f64, amplitude f64) []i16 {
|
||||
if sample_rate <= 0 || frequency <= 0 || duration <= 0 {
|
||||
return []i16{}
|
||||
}
|
||||
count := int(f64(sample_rate) * duration)
|
||||
mut samples := []i16{cap: count}
|
||||
for i in 0 .. count {
|
||||
t := f64(i) / f64(sample_rate)
|
||||
value := math.sin(2.0 * math.pi * frequency * t) * amplitude
|
||||
samples << i16(value)
|
||||
}
|
||||
return samples
|
||||
}
|
||||
@@ -0,0 +1,13 @@
|
||||
// font5x7.fv - 8x8 bitmap font (ASCII 32-126)
|
||||
module agila
|
||||
|
||||
const font5x7 = [u8(0x00), 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xbc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x40, 0x78, 0xc8, 0x5c, 0x08, 0x00, 0x00, 0x00, 0x00, 0x98, 0xa8, 0xe8, 0x00, 0x00, 0x00, 0x00, 0x08, 0x34, 0x68, 0xa0, 0x40, 0x00, 0x00, 0x00, 0x60, 0x9c, 0xa4, 0xc0, 0xa0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x8c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0xf8, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0x20, 0x20, 0x20, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x30, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xcc, 0x84, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0xfc, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0xa4, 0x9c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0xa4, 0xd8, 0x00, 0x00, 0x00, 0x00, 0x40, 0x50, 0x4c, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x94, 0x64, 0x00, 0x00, 0x00, 0x00, 0x00, 0xec, 0xa4, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0x64, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0xa4, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x94, 0x94, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x00, 0x90, 0x00, 0x00, 0x00, 0x00, 0x50, 0x50, 0x50, 0x50, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x60, 0x60, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xb4, 0x0c, 0x00, 0x00, 0x00, 0x00, 0x70, 0x0c, 0x94, 0x94, 0x70, 0x00, 0x00, 0x00, 0x80, 0x78, 0x4c, 0x70, 0x80, 0x00, 0x00, 0x00, 0x00, 0x94, 0x94, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x48, 0x84, 0x84, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0x84, 0x78, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x94, 0x94, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x14, 0x14, 0x00, 0x00, 0x00, 0x00, 0x30, 0xcc, 0x84, 0xa4, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x10, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x84, 0xfc, 0x84, 0x00, 0x00, 0x00, 0x00, 0x80, 0x84, 0x84, 0x7c, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x38, 0x44, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x08, 0x10, 0x0c, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x08, 0x20, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x30, 0xcc, 0x84, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x14, 0x1c, 0x00, 0x00, 0x00, 0x00, 0x30, 0xcc, 0x84, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x14, 0x14, 0x6c, 0x80, 0x00, 0x00, 0x00, 0x00, 0x9c, 0x84, 0xe4, 0x00, 0x00, 0x00, 0x00, 0x04, 0x04, 0xfc, 0x04, 0x04, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x04, 0x30, 0xc0, 0x38, 0x00, 0x00, 0x00, 0x00, 0x1c, 0xc0, 0x30, 0xc0, 0x0c, 0x00, 0x00, 0x00, 0x80, 0x48, 0x30, 0x4c, 0x00, 0x00, 0x00, 0x00, 0x04, 0x08, 0xf0, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc4, 0x84, 0x8c, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x18, 0x60, 0x80, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x08, 0x0c, 0x18, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb0, 0xb0, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x90, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90, 0xfe, 0x00, 0x00, 0x00, 0x00, 0x00, 0xb8, 0xa8, 0xb8, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0xfe, 0x12, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x10, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x88, 0xfc, 0x80, 0x80, 0x00, 0x00, 0x00, 0x00, 0x10, 0xf2, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfe, 0x20, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x04, 0xfc, 0x80, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x10, 0xf0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x00, 0x30, 0x10, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x90, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x90, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0xf0, 0x10, 0x10, 0x10, 0x00, 0x00, 0x00, 0x00, 0xb0, 0x90, 0xd0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x10, 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0xc0, 0x80, 0xf0, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x80, 0x30, 0x00, 0x00, 0x00, 0x00, 0x30, 0xc0, 0x60, 0xc0, 0x10, 0x00, 0x00, 0x00, 0x00, 0x90, 0x60, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x60, 0x80, 0x20, 0x00, 0x00, 0x00, 0x00, 0x00, 0x90, 0x90, 0x90, 0x00, 0x00, 0x00, 0x00, 0x00, 0x20, 0xde, 0x02, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0xfc, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xdc, 0x20, 0x00, 0x00, 0x00, 0x00, 0x20, 0x10, 0x20, 0x40, 0x00, 0x00, 0x00, 0x00]
|
||||
|
||||
pub fn glyph5x7(ch rune) []u8 {
|
||||
code := int(ch) - 32
|
||||
if code < 0 || code > 94 {
|
||||
return font5x7[0..8] // space
|
||||
}
|
||||
off := code * 8
|
||||
return font5x7[off..off+8]
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
// Public Agila application contract and host-safe sandbox.
|
||||
// This file does not implement Agila OS services or private ABIs.
|
||||
module agila
|
||||
|
||||
pub struct AgilaError {
|
||||
pub:
|
||||
code int
|
||||
message string
|
||||
}
|
||||
|
||||
pub fn (err AgilaError) msg() string {
|
||||
return err.message
|
||||
}
|
||||
|
||||
pub enum Capability {
|
||||
ui
|
||||
audio
|
||||
storage
|
||||
networking
|
||||
graphics
|
||||
}
|
||||
|
||||
pub struct App {
|
||||
pub:
|
||||
id string
|
||||
name string
|
||||
mut:
|
||||
started bool
|
||||
}
|
||||
|
||||
pub fn new_app(id string, name string) !App {
|
||||
if id.len == 0 || name.len == 0 {
|
||||
return AgilaError{code: 1, message: 'Application id and name are required'}
|
||||
}
|
||||
return App{id: id, name: name}
|
||||
}
|
||||
|
||||
pub fn (mut app App) start() ! {
|
||||
if app.started {
|
||||
return AgilaError{code: 2, message: 'Application is already started'}
|
||||
}
|
||||
app.started = true
|
||||
println('[Agila SDK sandbox] started ${app.name}')
|
||||
}
|
||||
|
||||
pub fn (mut app App) stop() {
|
||||
app.started = false
|
||||
println('[Agila SDK sandbox] stopped ${app.name}')
|
||||
}
|
||||
|
||||
pub fn (app &App) request(cap Capability) ! {
|
||||
if !app.started {
|
||||
return AgilaError{code: 3, message: 'Start the application before requesting capabilities'}
|
||||
}
|
||||
println('[Agila SDK sandbox] requested capability: ${cap}')
|
||||
}
|
||||
|
||||
pub fn log(text string) {
|
||||
println('[Agila app] ${text}')
|
||||
}
|
||||
@@ -0,0 +1,29 @@
|
||||
// Application-facing graphics contracts only. No AGX implementation is included.
|
||||
module agila
|
||||
|
||||
pub struct Vec2 { pub: x f32; y f32 }
|
||||
pub struct Vec3 { pub: x f32; y f32; z f32 }
|
||||
|
||||
pub struct MeshDescriptor {
|
||||
pub:
|
||||
vertex_count int
|
||||
index_count int
|
||||
}
|
||||
|
||||
pub struct TextureDescriptor {
|
||||
pub:
|
||||
width int
|
||||
height int
|
||||
format string
|
||||
}
|
||||
|
||||
pub fn validate_mesh(desc MeshDescriptor) ! {
|
||||
if desc.vertex_count <= 0 || desc.index_count <= 0 {
|
||||
return AgilaError{code: 20, message: 'Mesh counts must be positive'}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn submit_mesh(desc MeshDescriptor) ! {
|
||||
validate_mesh(desc)!
|
||||
println('[Agila SDK sandbox graphics] submitted mesh ${desc.vertex_count}/${desc.index_count}')
|
||||
}
|
||||
@@ -0,0 +1,64 @@
|
||||
// Safe application-level UI model. Rendering is provided by the proprietary Agila runtime.
|
||||
module agila
|
||||
|
||||
pub struct Color {
|
||||
pub:
|
||||
r u8
|
||||
g u8
|
||||
b u8
|
||||
a u8 = 255
|
||||
}
|
||||
|
||||
pub fn rgb(hex u32) Color {
|
||||
return Color{
|
||||
r: u8((hex >> 16) & 0xff)
|
||||
g: u8((hex >> 8) & 0xff)
|
||||
b: u8(hex & 0xff)
|
||||
}
|
||||
}
|
||||
|
||||
pub struct Theme {
|
||||
pub:
|
||||
background Color
|
||||
surface Color
|
||||
text Color
|
||||
accent Color
|
||||
}
|
||||
|
||||
pub fn default_theme() Theme {
|
||||
return Theme{
|
||||
background: rgb(0x08080e)
|
||||
surface: rgb(0x171827)
|
||||
text: rgb(0xf5f1ff)
|
||||
accent: rgb(0xc084fc)
|
||||
}
|
||||
}
|
||||
|
||||
pub interface Widget {
|
||||
describe() string
|
||||
}
|
||||
|
||||
pub struct Text {
|
||||
pub:
|
||||
value string
|
||||
}
|
||||
|
||||
pub fn (t Text) describe() string { return 'Text(${t.value})' }
|
||||
|
||||
pub struct Button {
|
||||
pub:
|
||||
label string
|
||||
}
|
||||
|
||||
pub fn (b Button) describe() string { return 'Button(${b.label})' }
|
||||
|
||||
pub struct Column {
|
||||
pub:
|
||||
children []Widget
|
||||
}
|
||||
|
||||
pub fn (c Column) describe() string { return 'Column(${c.children.len} children)' }
|
||||
|
||||
pub fn preview(widget Widget) {
|
||||
println('[Agila SDK sandbox UI] ${widget.describe()}')
|
||||
}
|
||||
@@ -0,0 +1,7 @@
|
||||
Module {
|
||||
name: 'agila_sdk'
|
||||
description: 'Public application SDK contract and host-safe sandbox for Agila.'
|
||||
version: '0.2.0-alpha'
|
||||
license: 'Agila Developer SDK License v1.0'
|
||||
dependencies: []
|
||||
}
|
||||
@@ -0,0 +1,3 @@
|
||||
# Agila application template
|
||||
|
||||
Copy this folder, choose a reverse-domain application id, and build only against documented APIs in `sdk/agila`.
|
||||
@@ -0,0 +1,15 @@
|
||||
import agila
|
||||
|
||||
fn main() {
|
||||
mut app := agila.new_app('dev.example.hello', 'Hello Agila') or {
|
||||
eprintln(err.msg())
|
||||
return
|
||||
}
|
||||
app.start() or {
|
||||
eprintln(err.msg())
|
||||
return
|
||||
}
|
||||
app.request(.ui) or { eprintln(err.msg()) }
|
||||
agila.log('Hello from a third-party Agila application!')
|
||||
app.stop()
|
||||
}
|
||||
Reference in new issue
Block a user