Files
start-os/sdk/base/lib/s9pk/index.ts
Aiden McClelland f2142f0bb3 add documentation for ai agents (#3115)
* add documentation for ai agents

* docs: consolidate CLAUDE.md and CONTRIBUTING.md, add style guidelines

- Refactor CLAUDE.md to reference CONTRIBUTING.md for build/test/format info
- Expand CONTRIBUTING.md with comprehensive build targets, env vars, and testing
- Add code style guidelines section with conventional commits
- Standardize SDK prettier config to use single quotes (matching web)
- Add project-level Claude Code settings to disable co-author attribution

* style(sdk): apply prettier with single quotes

Run prettier across sdk/base and sdk/package to apply the
standardized quote style (single quotes matching web).

* docs: add USER.md for per-developer TODO filtering

- Add agents/USER.md to .gitignore (contains user identifier)
- Document session startup flow in CLAUDE.md:
  - Create USER.md if missing, prompting for identifier
  - Filter TODOs by @username tags
  - Offer relevant TODOs on session start

* docs: add i18n documentation task to agent TODOs

* docs: document i18n ID patterns in core/

Add agents/i18n-patterns.md covering rust-i18n setup, translation file
format, t!() macro usage, key naming conventions, and locale selection.
Remove completed TODO item and add reference in CLAUDE.md.

* chore: clarify that all builds work on any OS with Docker
2026-02-06 00:10:16 +01:00

129 lines
3.6 KiB
TypeScript

import {
DataUrl,
DependencyMetadata,
Manifest,
MerkleArchiveCommitment,
PackageId,
} from '../osBindings'
import { ArrayBufferReader, MerkleArchive } from './merkleArchive'
import mime from 'mime'
import { DirectoryContents } from './merkleArchive/directoryContents'
import { FileContents } from './merkleArchive/fileContents'
const magicAndVersion = new Uint8Array([59, 59, 2])
export function compare(a: Uint8Array, b: Uint8Array) {
if (a.length !== b.length) return false
for (let i = 0; i < a.length; i++) {
if (a[i] !== b[i]) return false
}
return true
}
export class S9pk {
private constructor(
readonly manifest: Manifest,
readonly archive: MerkleArchive,
readonly size: number,
) {}
static async deserialize(
source: Blob,
commitment: MerkleArchiveCommitment | null,
): Promise<S9pk> {
const header = new ArrayBufferReader(
await source
.slice(0, magicAndVersion.length + MerkleArchive.headerSize)
.arrayBuffer(),
)
const magicVersion = new Uint8Array(header.next(magicAndVersion.length))
if (!compare(magicVersion, magicAndVersion)) {
throw new Error('Invalid Magic or Unexpected Version')
}
const archive = await MerkleArchive.deserialize(
source,
's9pk',
header,
commitment,
)
const manifest = JSON.parse(
new TextDecoder().decode(
await archive.contents
.getPath(['manifest.json'])
?.verifiedFileContents(),
),
)
return new S9pk(manifest, archive, source.size)
}
async icon(): Promise<DataUrl> {
const iconName = Object.keys(this.archive.contents.contents).find(
(name) =>
name.startsWith('icon.') && mime.getType(name)?.startsWith('image/'),
)
if (!iconName) {
throw new Error('no icon found in archive')
}
return (
`data:${mime.getType(iconName)};base64,` +
Buffer.from(
await this.archive.contents.getPath([iconName])!.verifiedFileContents(),
).toString('base64')
)
}
async dependencyMetadataFor(id: PackageId) {
const entry = this.archive.contents.getPath([
'dependencies',
id,
'metadata.json',
])
if (!entry) return null
return JSON.parse(
new TextDecoder().decode(await entry.verifiedFileContents()),
) as { title: string }
}
async dependencyIconFor(id: PackageId) {
const dir = this.archive.contents.getPath(['dependencies', id])
if (!dir || !(dir.contents instanceof DirectoryContents)) return null
const iconName = Object.keys(dir.contents.contents).find(
(name) =>
name.startsWith('icon.') && mime.getType(name)?.startsWith('image/'),
)
if (!iconName) return null
return (
`data:${mime.getType(iconName)};base64,` +
Buffer.from(
await dir.contents.getPath([iconName])!.verifiedFileContents(),
).toString('base64')
)
}
async dependencyMetadata() {
return Object.fromEntries(
await Promise.all(
Object.entries(this.manifest.dependencies)
.filter(([_, info]) => !!info)
.map(async ([id, info]) => [
id,
{
...(await this.dependencyMetadataFor(id)),
icon: await this.dependencyIconFor(id),
description: info!.description,
optional: info!.optional,
},
]),
),
)
}
async license(): Promise<string> {
const file = this.archive.contents.getPath(['LICENSE.md'])
if (!file || !(file.contents instanceof FileContents))
throw new Error('license.md not found in archive')
return new TextDecoder().decode(await file.verifiedFileContents())
}
}