gestalt-mobile 0.4.0 → 0.5.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +7 -0
- package/dist/client/assets/index--vTfuwTT.js +11 -0
- package/dist/client/assets/index-BqpvGqIw.css +1 -0
- package/dist/client/index.html +2 -2
- package/dist/server/server/app.js +2 -0
- package/dist/server/server/composition.js +25 -6
- package/dist/server/server/features/catalog/get-bootstrap/use-case.js +6 -1
- package/dist/server/server/features/sessions/application/start-settings.js +2 -1
- package/dist/server/server/features/sessions/model/relay-session.js +15 -0
- package/dist/server/server/features/sessions/start-session/endpoint.js +6 -0
- package/dist/server/server/features/sessions/start-session/request.js +1 -0
- package/dist/server/server/features/sessions/start-session/use-case.js +32 -1
- package/dist/server/server/features/skills/delete-profile/endpoint.js +24 -0
- package/dist/server/server/features/skills/list-available/endpoint.js +4 -2
- package/dist/server/server/platform/codex/codex-model-catalog.js +63 -0
- package/dist/server/server/platform/codex/session-runtime.js +2 -2
- package/dist/server/server/platform/persistence/migrate.js +8 -1
- package/dist/server/server/platform/persistence/sqlite-session-repository.js +11 -2
- package/dist/server/server/platform/skills/cached-skill-catalog.js +42 -0
- package/dist/server/server/platform/skills/codex-skill-catalog.js +18 -5
- package/dist/server/server/platform/skills/filesystem-skill-profile-store.js +28 -0
- package/package.json +1 -1
- package/dist/client/assets/index-B3MMCSoP.css +0 -1
- package/dist/client/assets/index-i0Y57I_X.js +0 -11
|
@@ -4,23 +4,54 @@
|
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
6
|
import { RelaySession } from '../model/relay-session.js';
|
|
7
|
+
import { DEFAULT_SESSION_MODEL, } from '../application/start-settings.js';
|
|
8
|
+
import { applySkillSelectionSnapshot, } from '../../skills/model/skill-profile.js';
|
|
9
|
+
import { SkillProfileError } from '../../skills/model/errors.js';
|
|
7
10
|
export async function startSession(input, deps) {
|
|
11
|
+
const model = input.model ?? DEFAULT_SESSION_MODEL;
|
|
8
12
|
const [workspace] = await Promise.all([
|
|
9
13
|
deps.workspaces.resolve(input.workspaceId),
|
|
10
14
|
deps.profiles.require(input.profile),
|
|
11
15
|
]);
|
|
16
|
+
if (deps.models) {
|
|
17
|
+
const models = await deps.models.list();
|
|
18
|
+
if (!models.includes(model))
|
|
19
|
+
throw new Error('CODEX_MODEL_UNAVAILABLE');
|
|
20
|
+
}
|
|
21
|
+
const selectedProfile = input.skillProfile
|
|
22
|
+
? await deps.skillProfiles.readGlobalProfile(input.skillProfile)
|
|
23
|
+
: deps.defaultSkillProfile;
|
|
24
|
+
if (input.skillProfile && !selectedProfile)
|
|
25
|
+
throw new SkillProfileError('UNKNOWN_SKILL_PROFILE', 'The selected skill profile does not exist.');
|
|
26
|
+
const [projectProfile, catalog] = await Promise.all([
|
|
27
|
+
deps.skillProfiles.readWorkspaceDefault(workspace.realPath),
|
|
28
|
+
deps.skillCatalog(input.profile).list(workspace.realPath),
|
|
29
|
+
]);
|
|
30
|
+
const sourceProfile = selectedProfile ?? projectProfile;
|
|
31
|
+
const effectiveSkillSelection = {
|
|
32
|
+
...(selectedProfile ? { selectedProfileName: selectedProfile.name } : {}),
|
|
33
|
+
skills: applySkillSelectionSnapshot(catalog.skills, sourceProfile?.skills).map((skill) => ({
|
|
34
|
+
name: skill.name,
|
|
35
|
+
path: skill.path,
|
|
36
|
+
enabled: skill.enabled,
|
|
37
|
+
})),
|
|
38
|
+
};
|
|
39
|
+
const branch = await deps.gitBranch?.(workspace.realPath);
|
|
12
40
|
const session = RelaySession.create({
|
|
13
41
|
id: deps.createId(),
|
|
14
42
|
workspaceId: workspace.id,
|
|
15
43
|
workspacePath: workspace.realPath,
|
|
16
44
|
profile: input.profile,
|
|
45
|
+
model,
|
|
46
|
+
...(branch ? { branch } : {}),
|
|
47
|
+
effectiveSkillSelection,
|
|
17
48
|
now: deps.now(),
|
|
18
49
|
}).snapshot;
|
|
19
50
|
deps.save(session);
|
|
20
51
|
if (!deps.activate)
|
|
21
52
|
return session;
|
|
22
53
|
const active = await deps.activate(session, {
|
|
23
|
-
model
|
|
54
|
+
model,
|
|
24
55
|
sandbox: input.sandbox,
|
|
25
56
|
approvalPolicy: input.approvalPolicy,
|
|
26
57
|
});
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Dyne.org foundation
|
|
3
|
+
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
*/
|
|
6
|
+
import { SkillProfileError } from '../model/errors.js';
|
|
7
|
+
import { normalizeSkillProfileName } from '../model/skill-profile.js';
|
|
8
|
+
import { problem } from '../../../platform/http/problem.js';
|
|
9
|
+
/** Registers the profile-management deletion command; it has no session dependency. */
|
|
10
|
+
export function registerDeleteSkillProfile(app, deps) {
|
|
11
|
+
app.delete('/api/skill-profiles/:name', async (request, reply) => {
|
|
12
|
+
try {
|
|
13
|
+
const name = normalizeSkillProfileName(request.params.name);
|
|
14
|
+
if (!(await deps.deleteGlobalProfile(name)))
|
|
15
|
+
return reply.code(404).type('application/problem+json').send(problem('SKILL_PROFILE_NOT_FOUND', 404, 'The skill profile was not found.'));
|
|
16
|
+
return reply.code(204).send();
|
|
17
|
+
}
|
|
18
|
+
catch (error) {
|
|
19
|
+
if (error instanceof SkillProfileError)
|
|
20
|
+
return reply.code(400).type('application/problem+json').send(problem('INVALID_SKILL_PROFILE', 400, 'The skill profile could not be deleted.'));
|
|
21
|
+
return reply.code(500).type('application/problem+json').send(problem('SKILL_PROFILE_PERSISTENCE_FAILED', 500, 'The skill profile could not be deleted.', true));
|
|
22
|
+
}
|
|
23
|
+
});
|
|
24
|
+
}
|
|
@@ -7,7 +7,7 @@ import { z } from 'zod';
|
|
|
7
7
|
import { applySkillSelectionSnapshot } from '../model/skill-profile.js';
|
|
8
8
|
import { SkillProfileError } from '../model/errors.js';
|
|
9
9
|
import { problem } from '../../../platform/http/problem.js';
|
|
10
|
-
const querySchema = z.object({ workspaceId: z.string().min(1), profile: z.string().min(1) }).strict();
|
|
10
|
+
const querySchema = z.object({ workspaceId: z.string().min(1), profile: z.string().min(1), refresh: z.enum(['true']).optional() }).strict();
|
|
11
11
|
/** Register the workspace-scoped skill discovery REPR slice. */
|
|
12
12
|
export function registerListAvailableSkills(app, deps) {
|
|
13
13
|
app.get('/api/skills', async (request, reply) => {
|
|
@@ -20,7 +20,9 @@ export function registerListAvailableSkills(app, deps) {
|
|
|
20
20
|
deps.profiles.require(parsed.data.profile),
|
|
21
21
|
]);
|
|
22
22
|
const [discovered, project] = await Promise.all([
|
|
23
|
-
|
|
23
|
+
parsed.data.refresh
|
|
24
|
+
? deps.catalog.refresh(profile.name, workspace.realPath)
|
|
25
|
+
: deps.catalog.list(profile.name, workspace.realPath),
|
|
24
26
|
deps.selections.readWorkspaceDefault(workspace.realPath),
|
|
25
27
|
]);
|
|
26
28
|
const skills = project
|
|
@@ -0,0 +1,63 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Dyne.org foundation
|
|
3
|
+
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
*/
|
|
6
|
+
import { z } from 'zod';
|
|
7
|
+
import { launchCodexAppServer } from './codex-process-launcher.js';
|
|
8
|
+
const modelSchema = z.object({ id: z.string().min(1) });
|
|
9
|
+
const resultSchema = z.union([
|
|
10
|
+
z.object({ data: z.array(modelSchema) }),
|
|
11
|
+
z.object({ models: z.array(modelSchema) }),
|
|
12
|
+
z.object({ data: z.object({ models: z.array(modelSchema) }) }),
|
|
13
|
+
]);
|
|
14
|
+
/** Short-lived adapter for the Codex app-server model catalog. */
|
|
15
|
+
export class CodexModelCatalog {
|
|
16
|
+
cwd;
|
|
17
|
+
launch;
|
|
18
|
+
timeoutMs;
|
|
19
|
+
constructor(cwd, launch = launchCodexAppServer, timeoutMs = 5_000) {
|
|
20
|
+
this.cwd = cwd;
|
|
21
|
+
this.launch = launch;
|
|
22
|
+
this.timeoutMs = timeoutMs;
|
|
23
|
+
}
|
|
24
|
+
async list() {
|
|
25
|
+
const server = this.launch({ profile: '', cwd: this.cwd });
|
|
26
|
+
try {
|
|
27
|
+
await this.withTimeout(server.rpc.request('initialize', {
|
|
28
|
+
clientInfo: { name: 'gestalt-mobile', version: '0.1.0' },
|
|
29
|
+
capabilities: null,
|
|
30
|
+
}));
|
|
31
|
+
const result = resultSchema.safeParse(await this.withTimeout(server.rpc.request('model/list', {})));
|
|
32
|
+
if (!result.success)
|
|
33
|
+
return [];
|
|
34
|
+
const models = 'models' in result.data
|
|
35
|
+
? result.data.models
|
|
36
|
+
: Array.isArray(result.data.data)
|
|
37
|
+
? result.data.data
|
|
38
|
+
: result.data.data.models;
|
|
39
|
+
return [...new Set(models.map((model) => model.id))].sort((left, right) => left.localeCompare(right));
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
return [];
|
|
43
|
+
}
|
|
44
|
+
finally {
|
|
45
|
+
server.close();
|
|
46
|
+
}
|
|
47
|
+
}
|
|
48
|
+
async withTimeout(promise) {
|
|
49
|
+
let timer;
|
|
50
|
+
try {
|
|
51
|
+
return await Promise.race([
|
|
52
|
+
promise,
|
|
53
|
+
new Promise((_, reject) => {
|
|
54
|
+
timer = setTimeout(() => reject(new Error('Codex model discovery timed out.')), this.timeoutMs);
|
|
55
|
+
}),
|
|
56
|
+
]);
|
|
57
|
+
}
|
|
58
|
+
finally {
|
|
59
|
+
if (timer)
|
|
60
|
+
clearTimeout(timer);
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
}
|
|
@@ -23,7 +23,7 @@ export class CodexSessionRuntime {
|
|
|
23
23
|
exitUnsubscribers = new Map();
|
|
24
24
|
threadIds = new Map();
|
|
25
25
|
async start(session, now, settings = {}) {
|
|
26
|
-
const process = this.launch({ profile: session.profile, cwd: session.workspacePath, skillsConfig: await this.resolveSkills?.(session
|
|
26
|
+
const process = this.launch({ profile: session.profile, cwd: session.workspacePath, skillsConfig: await this.resolveSkills?.(session) });
|
|
27
27
|
try {
|
|
28
28
|
process.rpc.onNotification((notification) => this.onNotification?.(session.id, notification));
|
|
29
29
|
process.rpc.onServerRequest((request) => this.holdServerRequest(session.id, request));
|
|
@@ -123,7 +123,7 @@ export class CodexSessionRuntime {
|
|
|
123
123
|
async restore(session, now) {
|
|
124
124
|
if (!session.threadId)
|
|
125
125
|
throw new Error('CODEX_THREAD_ID_MISSING');
|
|
126
|
-
const process = this.launch({ profile: session.profile, cwd: session.workspacePath, skillsConfig: await this.resolveSkills?.(session
|
|
126
|
+
const process = this.launch({ profile: session.profile, cwd: session.workspacePath, skillsConfig: await this.resolveSkills?.(session) });
|
|
127
127
|
try {
|
|
128
128
|
process.rpc.onNotification((notification) => this.onNotification?.(session.id, notification));
|
|
129
129
|
process.rpc.onServerRequest((request) => this.holdServerRequest(session.id, request));
|
|
@@ -3,7 +3,14 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
-
const schema = `CREATE TABLE IF NOT EXISTS relay_sessions (id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL, workspace_path TEXT NOT NULL, profile TEXT NOT NULL, thread_id TEXT, state TEXT NOT NULL, desired_state TEXT NOT NULL, active_turn_id TEXT, protocol_version TEXT, failure_count INTEGER NOT NULL DEFAULT 0, next_sequence INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS pending_interactions (session_id TEXT NOT NULL REFERENCES relay_sessions(id) ON DELETE CASCADE, request_id TEXT NOT NULL, kind TEXT NOT NULL, payload_json TEXT NOT NULL, resolved_at TEXT, PRIMARY KEY (session_id, request_id)); CREATE TABLE IF NOT EXISTS session_events (session_id TEXT NOT NULL REFERENCES relay_sessions(id) ON DELETE CASCADE, sequence INTEGER NOT NULL, occurred_at TEXT NOT NULL, type TEXT NOT NULL, payload_json TEXT NOT NULL, PRIMARY KEY (session_id, sequence)); CREATE TABLE IF NOT EXISTS idempotency_results (scope TEXT NOT NULL, key TEXT NOT NULL, status_code INTEGER NOT NULL, body_json TEXT NOT NULL, PRIMARY KEY (scope, key));`;
|
|
6
|
+
const schema = `CREATE TABLE IF NOT EXISTS relay_sessions (id TEXT PRIMARY KEY, workspace_id TEXT NOT NULL, workspace_path TEXT NOT NULL, profile TEXT NOT NULL, model TEXT, branch TEXT, thread_id TEXT, state TEXT NOT NULL, desired_state TEXT NOT NULL, active_turn_id TEXT, protocol_version TEXT, failure_count INTEGER NOT NULL DEFAULT 0, effective_skill_selection_json TEXT, next_sequence INTEGER NOT NULL DEFAULT 1, created_at TEXT NOT NULL, updated_at TEXT NOT NULL); CREATE TABLE IF NOT EXISTS pending_interactions (session_id TEXT NOT NULL REFERENCES relay_sessions(id) ON DELETE CASCADE, request_id TEXT NOT NULL, kind TEXT NOT NULL, payload_json TEXT NOT NULL, resolved_at TEXT, PRIMARY KEY (session_id, request_id)); CREATE TABLE IF NOT EXISTS session_events (session_id TEXT NOT NULL REFERENCES relay_sessions(id) ON DELETE CASCADE, sequence INTEGER NOT NULL, occurred_at TEXT NOT NULL, type TEXT NOT NULL, payload_json TEXT NOT NULL, PRIMARY KEY (session_id, sequence)); CREATE TABLE IF NOT EXISTS idempotency_results (scope TEXT NOT NULL, key TEXT NOT NULL, status_code INTEGER NOT NULL, body_json TEXT NOT NULL, PRIMARY KEY (scope, key));`;
|
|
7
7
|
export function migrate(database) {
|
|
8
8
|
database.exec(schema);
|
|
9
|
+
const columns = database.prepare('PRAGMA table_info(relay_sessions)').all();
|
|
10
|
+
if (!columns.some((column) => column.name === 'effective_skill_selection_json'))
|
|
11
|
+
database.exec('ALTER TABLE relay_sessions ADD COLUMN effective_skill_selection_json TEXT');
|
|
12
|
+
if (!columns.some((column) => column.name === 'model'))
|
|
13
|
+
database.exec('ALTER TABLE relay_sessions ADD COLUMN model TEXT');
|
|
14
|
+
if (!columns.some((column) => column.name === 'branch'))
|
|
15
|
+
database.exec('ALTER TABLE relay_sessions ADD COLUMN branch TEXT');
|
|
9
16
|
}
|
|
@@ -3,6 +3,7 @@
|
|
|
3
3
|
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
4
|
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
5
|
*/
|
|
6
|
+
import { createEffectiveSkillSelection, } from '../../features/sessions/model/relay-session.js';
|
|
6
7
|
export class SqliteSessionRepository {
|
|
7
8
|
db;
|
|
8
9
|
constructor(db) {
|
|
@@ -10,8 +11,10 @@ export class SqliteSessionRepository {
|
|
|
10
11
|
}
|
|
11
12
|
save(session) {
|
|
12
13
|
this.db
|
|
13
|
-
.prepare('INSERT INTO relay_sessions (id,workspace_id,workspace_path,profile,thread_id,state,desired_state,active_turn_id,protocol_version,failure_count,created_at,updated_at) VALUES (
|
|
14
|
-
.run(session.id, session.workspaceId, session.workspacePath, session.profile, session.threadId, session.state, session.desiredState, session.activeTurnId, session.protocolVersion, session.failureCount, session.
|
|
14
|
+
.prepare('INSERT INTO relay_sessions (id,workspace_id,workspace_path,profile,model,branch,thread_id,state,desired_state,active_turn_id,protocol_version,failure_count,effective_skill_selection_json,created_at,updated_at) VALUES (?,?,?,?,?,?,?,?,?,?,?,?,?,?,?) ON CONFLICT(id) DO UPDATE SET workspace_id=excluded.workspace_id,workspace_path=excluded.workspace_path,profile=excluded.profile,model=excluded.model,branch=excluded.branch,thread_id=excluded.thread_id,state=excluded.state,desired_state=excluded.desired_state,active_turn_id=excluded.active_turn_id,protocol_version=excluded.protocol_version,failure_count=excluded.failure_count,effective_skill_selection_json=excluded.effective_skill_selection_json,updated_at=excluded.updated_at')
|
|
15
|
+
.run(session.id, session.workspaceId, session.workspacePath, session.profile, session.model ?? null, session.branch ?? null, session.threadId, session.state, session.desiredState, session.activeTurnId, session.protocolVersion, session.failureCount, session.effectiveSkillSelection === undefined
|
|
16
|
+
? null
|
|
17
|
+
: JSON.stringify(session.effectiveSkillSelection), session.createdAt, session.updatedAt);
|
|
15
18
|
}
|
|
16
19
|
find(id) {
|
|
17
20
|
const row = this.db.prepare('SELECT * FROM relay_sessions WHERE id = ?').get(id);
|
|
@@ -25,17 +28,23 @@ export class SqliteSessionRepository {
|
|
|
25
28
|
}
|
|
26
29
|
}
|
|
27
30
|
function map(row) {
|
|
31
|
+
const effectiveSkillSelection = row.effective_skill_selection_json
|
|
32
|
+
? createEffectiveSkillSelection(JSON.parse(row.effective_skill_selection_json))
|
|
33
|
+
: undefined;
|
|
28
34
|
return {
|
|
29
35
|
id: row.id,
|
|
30
36
|
workspaceId: row.workspace_id,
|
|
31
37
|
workspacePath: row.workspace_path,
|
|
32
38
|
profile: row.profile,
|
|
39
|
+
...(row.model === null ? {} : { model: row.model }),
|
|
40
|
+
...(row.branch === null ? {} : { branch: row.branch }),
|
|
33
41
|
threadId: row.thread_id,
|
|
34
42
|
state: row.state,
|
|
35
43
|
desiredState: row.desired_state,
|
|
36
44
|
activeTurnId: row.active_turn_id,
|
|
37
45
|
protocolVersion: row.protocol_version,
|
|
38
46
|
failureCount: row.failure_count,
|
|
47
|
+
...(effectiveSkillSelection === undefined ? {} : { effectiveSkillSelection }),
|
|
39
48
|
pendingInteractions: [],
|
|
40
49
|
createdAt: row.created_at,
|
|
41
50
|
updatedAt: row.updated_at,
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
/*
|
|
2
|
+
* Copyright (C) 2026 Dyne.org foundation
|
|
3
|
+
* Designed by Denis Roio <jaromil@dyne.org>
|
|
4
|
+
* SPDX-License-Identifier: AGPL-3.0-or-later
|
|
5
|
+
*/
|
|
6
|
+
import { resolve } from 'node:path';
|
|
7
|
+
/** In-memory editor catalog: discovery runs only when the relay starts or a user refreshes it. */
|
|
8
|
+
export class CachedSkillCatalog {
|
|
9
|
+
discover;
|
|
10
|
+
entries = new Map();
|
|
11
|
+
constructor(discover) {
|
|
12
|
+
this.discover = discover;
|
|
13
|
+
}
|
|
14
|
+
async list(profile, workspace) {
|
|
15
|
+
return (this.entries.get(this.key(profile, workspace)) ?? {
|
|
16
|
+
skills: [],
|
|
17
|
+
errors: [
|
|
18
|
+
{
|
|
19
|
+
message: 'Skills are not cached for this workspace and Codex profile. Select Refresh skills to discover them.',
|
|
20
|
+
},
|
|
21
|
+
],
|
|
22
|
+
});
|
|
23
|
+
}
|
|
24
|
+
async refresh(profile, workspace) {
|
|
25
|
+
try {
|
|
26
|
+
const result = await this.discover(profile, workspace);
|
|
27
|
+
this.entries.set(this.key(profile, workspace), result);
|
|
28
|
+
return result;
|
|
29
|
+
}
|
|
30
|
+
catch {
|
|
31
|
+
const result = {
|
|
32
|
+
skills: [],
|
|
33
|
+
errors: [{ message: 'Skill discovery failed. Select Refresh skills to try again.' }],
|
|
34
|
+
};
|
|
35
|
+
this.entries.set(this.key(profile, workspace), result);
|
|
36
|
+
return result;
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
key(profile, workspace) {
|
|
40
|
+
return `${profile}\u0000${resolve(workspace)}`;
|
|
41
|
+
}
|
|
42
|
+
}
|
|
@@ -8,11 +8,21 @@ import { resolve } from 'node:path';
|
|
|
8
8
|
import { availableSkillSchema } from '../../features/skills/model/skill-profile.js';
|
|
9
9
|
import { SkillProfileError } from '../../features/skills/model/errors.js';
|
|
10
10
|
import { launchCodexAppServer } from '../codex/codex-process-launcher.js';
|
|
11
|
+
const optionalString = z.preprocess((value) => (value === null ? undefined : value), z.string().optional());
|
|
12
|
+
const optionalObject = (schema) => z.preprocess((value) => (value === null ? undefined : value), schema.optional());
|
|
13
|
+
const toolSchema = z.object({
|
|
14
|
+
type: z.string(),
|
|
15
|
+
value: z.string(),
|
|
16
|
+
description: optionalString,
|
|
17
|
+
transport: optionalString,
|
|
18
|
+
command: optionalString,
|
|
19
|
+
url: optionalString,
|
|
20
|
+
});
|
|
11
21
|
const wireSkillSchema = z.object({
|
|
12
|
-
name: z.string(), description: z.string(), shortDescription:
|
|
13
|
-
interface: z.object({ displayName:
|
|
14
|
-
dependencies: z.object({ tools: z.array(
|
|
15
|
-
path: z.string(), scope:
|
|
22
|
+
name: z.string(), description: z.string(), shortDescription: optionalString,
|
|
23
|
+
interface: optionalObject(z.object({ displayName: optionalString, shortDescription: optionalString, iconSmall: optionalString, iconLarge: optionalString, brandColor: optionalString, defaultPrompt: optionalString })),
|
|
24
|
+
dependencies: optionalObject(z.object({ tools: optionalObject(z.array(toolSchema)) })),
|
|
25
|
+
path: z.string(), scope: optionalString, enabled: z.boolean(),
|
|
16
26
|
});
|
|
17
27
|
const resultSchema = z.object({ data: z.array(z.object({ cwd: z.string(), skills: z.array(wireSkillSchema), errors: z.array(z.unknown()) })) });
|
|
18
28
|
/** Short-lived Codex app-server adapter: initialize, discover, and always terminate. */
|
|
@@ -29,7 +39,10 @@ export class CodexSkillCatalog {
|
|
|
29
39
|
const canonicalWorkspace = resolve(workspace);
|
|
30
40
|
const server = this.launch({ profile: this.profile, cwd: canonicalWorkspace });
|
|
31
41
|
try {
|
|
32
|
-
await this.withTimeout(server.rpc.request('initialize', {
|
|
42
|
+
await this.withTimeout(server.rpc.request('initialize', {
|
|
43
|
+
clientInfo: { name: 'gestalt-mobile', version: '0.1.0' },
|
|
44
|
+
capabilities: null,
|
|
45
|
+
}));
|
|
33
46
|
const result = await this.withTimeout(server.rpc.request('skills/list', { cwds: [canonicalWorkspace], forceReload: true }));
|
|
34
47
|
const parsed = resultSchema.safeParse(result);
|
|
35
48
|
if (!parsed.success)
|
|
@@ -62,6 +62,24 @@ export class FilesystemSkillProfileStore {
|
|
|
62
62
|
await rm(temporary, { force: true }).catch(() => undefined);
|
|
63
63
|
}
|
|
64
64
|
}
|
|
65
|
+
async deleteGlobalProfile(name) {
|
|
66
|
+
const normalizedName = normalizeSkillProfileName(name);
|
|
67
|
+
const root = await this.globalRoot();
|
|
68
|
+
const path = join(root, `${normalizedName}.yml`);
|
|
69
|
+
try {
|
|
70
|
+
const stat = await lstat(path);
|
|
71
|
+
if (stat.isSymbolicLink() || !stat.isFile()) {
|
|
72
|
+
throw new SkillProfileError('INVALID_SKILL_PROFILE', 'Profile must be a regular file.');
|
|
73
|
+
}
|
|
74
|
+
await rm(path);
|
|
75
|
+
return true;
|
|
76
|
+
}
|
|
77
|
+
catch (error) {
|
|
78
|
+
if (missing(error))
|
|
79
|
+
return false;
|
|
80
|
+
throw error;
|
|
81
|
+
}
|
|
82
|
+
}
|
|
65
83
|
async readWorkspaceDefault(workspace) {
|
|
66
84
|
const root = await realpath(workspace);
|
|
67
85
|
const path = resolve(root, 'gestalt-skills.yml');
|
|
@@ -76,6 +94,16 @@ export class FilesystemSkillProfileStore {
|
|
|
76
94
|
if (!root.startsWith(`${home}/`)) {
|
|
77
95
|
throw new SkillProfileError('INVALID_SKILL_PROFILE', 'Global profile root escaped home.');
|
|
78
96
|
}
|
|
97
|
+
try {
|
|
98
|
+
const stat = await lstat(root);
|
|
99
|
+
if (stat.isSymbolicLink() || !stat.isDirectory()) {
|
|
100
|
+
throw new SkillProfileError('INVALID_SKILL_PROFILE', 'Global profile root must be a directory.');
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
catch (error) {
|
|
104
|
+
if (!missing(error))
|
|
105
|
+
throw error;
|
|
106
|
+
}
|
|
79
107
|
return root;
|
|
80
108
|
}
|
|
81
109
|
async readProfile(path) {
|
package/package.json
CHANGED
|
@@ -1 +0,0 @@
|
|
|
1
|
-
.session-path.svelte-t9laac{text-align:center;text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;margin:0;overflow:hidden}form.svelte-1264sfg>[role=status]:where(.svelte-1264sfg){text-align:start;margin-block:1rem .35rem}.prompt-row.svelte-1264sfg{align-items:end;gap:.5rem;display:flex}textarea.svelte-1264sfg{resize:vertical;flex:auto;min-block-size:2.75rem}.prompt-row.svelte-1264sfg button:where(.svelte-1264sfg){flex:none;place-items:center;min-block-size:3rem;inline-size:3rem;padding:0;display:grid}.prompt-row.svelte-1264sfg svg:where(.svelte-1264sfg){fill:none;stroke:currentColor;stroke-width:2px;stroke-linecap:round;stroke-linejoin:round;block-size:1.35rem;inline-size:1.35rem}.block-cursor.svelte-1264sfg{vertical-align:-.1em;background:currentColor;block-size:1em;inline-size:.55ch;animation:1s steps(2,start) infinite svelte-1264sfg-blink;display:inline-block}@media (prefers-reduced-motion:reduce){.block-cursor.svelte-1264sfg{animation:none}}@keyframes svelte-1264sfg-blink{50%{opacity:0}}ol.svelte-1im954t{inline-size:100%;margin:0;padding:0;list-style:none}li.svelte-1im954t{box-sizing:border-box;white-space:pre-wrap;overflow-wrap:anywhere;inline-size:100%;margin-block-end:0}li.svelte-1im954t+li:where(.svelte-1im954t){margin-block-start:1rem}.prompt-turn.svelte-1im954t{background:color-mix(in srgb, Canvas 94%, CanvasText);border-radius:.375rem;padding:.5rem .625rem}.entry-heading.svelte-1im954t{align-items:baseline;gap:.75rem;display:flex}time.svelte-1im954t{color:#666;white-space:nowrap;margin-inline-start:auto;font-size:.875em}.commentary-toggle.svelte-1im954t{color:#666;background:0 0;border:0;min-block-size:1.5rem;padding:0 .125rem;font-size:.875em}.commentary-content.svelte-1im954t{margin-block:.25rem .5rem}.entry-content.svelte-1im954t{margin-block:.125rem 0;margin-inline:0}pre.svelte-1im954t,code.svelte-1im954t{font-family:ui-monospace,SFMono-Regular,Menlo,Monaco,Consolas,monospace}pre.svelte-1im954t{white-space:pre;overflow-x:auto}.table-scroll.svelte-1im954t{margin-block:.75rem;overflow-x:auto}table.svelte-1im954t{border-collapse:collapse;white-space:normal;width:max-content;min-width:100%}th.svelte-1im954t,td.svelte-1im954t{text-align:left;vertical-align:top;padding:.25rem .5rem}th.svelte-1im954t{font-weight:600}.toast-viewport.svelte-1a0rutm{z-index:10;pointer-events:none;gap:.5rem;max-block-size:calc(100dvh - 7rem);inline-size:min(24rem,100vw - 1.5rem);display:grid;position:fixed;inset-block-start:calc(1rem + env(safe-area-inset-top));inset-inline-end:max(.75rem, env(safe-area-inset-right));overflow-y:auto}.toast.svelte-1a0rutm{box-sizing:border-box;color:canvastext;border:1px solid canvastext;min-inline-size:0;box-shadow:0 .4rem 1.2rem color-mix(in srgb, CanvasText 24%, transparent);pointer-events:auto;background:canvas;border-inline-start-width:.35rem;border-radius:.55rem;grid-template-columns:auto minmax(0,1fr) 44px;align-items:start;gap:.6rem;padding:.65rem;display:grid}.toast.error.svelte-1a0rutm{border-inline-start-style:double}.toast-symbol.svelte-1a0rutm{box-sizing:border-box;border:2px solid;border-radius:50%;place-items:center;block-size:1.6rem;inline-size:1.6rem;font-weight:800;line-height:1;display:grid}.toast-copy.svelte-1a0rutm{overflow-wrap:anywhere;gap:.15rem;min-inline-size:0;padding-block:.1rem;display:grid}.toast-copy.svelte-1a0rutm strong:where(.svelte-1a0rutm){text-transform:capitalize}.toast-copy.svelte-1a0rutm small:where(.svelte-1a0rutm){opacity:.72}button.svelte-1a0rutm{min-block-size:44px;inline-size:44px;color:inherit;font:inherit;background:0 0;border:0;border-radius:.35rem;place-items:center;padding:0;font-size:1.5rem;display:grid}button.svelte-1a0rutm:hover{background:color-mix(in srgb, CanvasText 10%, transparent)}@media (width<=24rem){.toast.svelte-1a0rutm{grid-template-columns:minmax(0,1fr) 44px;gap:.25rem;max-block-size:5rem;padding:.35rem;overflow-y:auto}.toast-symbol.svelte-1a0rutm{display:none}.toast-copy.svelte-1a0rutm strong:where(.svelte-1a0rutm){clip:rect(0, 0, 0, 0);white-space:nowrap;border:0;block-size:1px;inline-size:1px;padding:0;position:absolute;overflow:hidden}}@media (forced-colors:active){.toast.svelte-1a0rutm,.toast-symbol.svelte-1a0rutm{border-color:canvastext}}.evidence-page.svelte-1j55xyh{box-sizing:border-box;min-block-size:100dvh;inline-size:100%;padding:1rem 1rem 7rem}h1.svelte-1j55xyh{margin-block:0 .5rem}.evidence-composer.svelte-1j55xyh{position:fixed;inset-block-end:2.75rem;inset-inline:1rem}.evidence-prompt.svelte-1j55xyh{box-sizing:border-box;inline-size:100%;margin-block-start:.35rem;display:block}.evidence-nav.svelte-1j55xyh{padding:.5rem 1rem calc(.5rem + env(safe-area-inset-bottom));background:canvas;border-block-start:1px solid canvastext;grid-template-columns:repeat(3,minmax(0,1fr));gap:.5rem;display:grid;position:fixed;inset-block-end:0;inset-inline:0}.evidence-nav.svelte-1j55xyh button:where(.svelte-1j55xyh){text-overflow:ellipsis;min-block-size:44px;min-inline-size:0;overflow:hidden}@media (width<=24rem){.evidence-page.svelte-1j55xyh{padding:.5rem .5rem 7rem}.evidence-composer.svelte-1j55xyh{inset-inline:.5rem}}.filesystem-tree.svelte-6z3q4v{inline-size:100%;max-inline-size:100%;display:grid;overflow-x:clip}.tree-row.svelte-6z3q4v{min-inline-size:0;align-items:stretch;padding-inline-start:min(calc(var(--tree-level) * .75rem), 4.5rem);display:flex}.disclosure.svelte-6z3q4v,.disclosure-spacer.svelte-6z3q4v{flex:0 0 44px;min-block-size:44px;inline-size:44px}.disclosure.svelte-6z3q4v{color:inherit;font:inherit;background:0 0;border:0;border-radius:.35rem;place-items:center;padding:0;font-size:1.25rem;font-weight:700;display:grid}.disclosure.svelte-6z3q4v:hover{background:color-mix(in srgb, CanvasText 10%, transparent)}.tree-item.svelte-6z3q4v{min-block-size:44px;min-inline-size:0;color:inherit;font:inherit;text-align:start;background:0 0;border:1px solid #0000;border-radius:.35rem;flex:auto;align-items:center;gap:.5rem;padding:.3rem .5rem;display:flex;overflow:hidden}.tree-item.svelte-6z3q4v:hover{background:color-mix(in srgb, CanvasText 8%, transparent)}.tree-item.selected.svelte-6z3q4v{background:color-mix(in srgb, Highlight 16%, Canvas);border-color:currentColor;font-weight:650}.tree-item[aria-disabled=true].svelte-6z3q4v{opacity:.58}.tree-item.repository.svelte-6z3q4v{border-inline-start-width:3px}.selection-mark.svelte-6z3q4v{text-align:center;flex:0 0 1rem;inline-size:1rem}.node-copy.svelte-6z3q4v{flex:auto;min-inline-size:0;display:grid}.node-name.svelte-6z3q4v,.node-path.svelte-6z3q4v{text-overflow:ellipsis;white-space:nowrap;min-inline-size:0;overflow:hidden}.node-path.svelte-6z3q4v{opacity:.72;font-size:.75em;font-weight:400}.repository-badge.svelte-6z3q4v{border:1px solid;border-radius:999px;flex:none;padding:.1rem .35rem;font-size:.72em;font-weight:700;line-height:1.2}@media (width<=24rem){.tree-row.svelte-6z3q4v{padding-inline-start:min(calc(var(--tree-level) * .25rem), 1.5rem)}.tree-item.svelte-6z3q4v{gap:.35rem;padding-inline:.35rem}.repository-badge.svelte-6z3q4v{display:none}}@media (forced-colors:active){.tree-item.selected.svelte-6z3q4v,.tree-item.repository.svelte-6z3q4v,.repository-badge.svelte-6z3q4v{border-color:canvastext}}.evidence-shell.svelte-1d6w4ti{box-sizing:border-box;inline-size:min(100%,48rem);min-inline-size:0;margin-inline:auto;padding:1rem}.eyebrow.svelte-1d6w4ti{letter-spacing:.08em;text-transform:uppercase;margin:0;font-size:.8rem;font-weight:750}h1.svelte-1d6w4ti{margin-block:.25rem .5rem;font-size:clamp(1.35rem,6vw,2rem);line-height:1.15}.instructions.svelte-1d6w4ti{max-inline-size:42rem;margin-block:0 1rem}.tree-panel.svelte-1d6w4ti{border:1px solid color-mix(in srgb, CanvasText 35%, transparent);border-radius:.6rem;min-inline-size:0;padding:.5rem}@media (width<=24rem){.evidence-shell.svelte-1d6w4ti{padding:.5rem}.tree-panel.svelte-1d6w4ti{padding:.25rem}}.git-view.svelte-pudr3t{gap:1.5rem;inline-size:100%;min-inline-size:0;display:grid}.git-tree.svelte-pudr3t,.repository-details.svelte-pudr3t{min-inline-size:0}.section-heading.svelte-pudr3t h3:where(.svelte-pudr3t),.repository-details.svelte-pudr3t h3:where(.svelte-pudr3t){margin-block:0 .35rem}.section-heading.svelte-pudr3t p:where(.svelte-pudr3t){color:color-mix(in srgb, CanvasText 70%, Canvas);margin-block:0 .75rem}.clone-form.svelte-pudr3t{gap:.35rem;min-inline-size:0;display:grid}.clone-controls.svelte-pudr3t{grid-template-columns:minmax(10rem,.45fr) minmax(0,1fr) auto;align-items:end;gap:.5rem;display:grid}.clone-field.svelte-pudr3t{gap:.35rem;min-inline-size:0;display:grid}.field-label.svelte-pudr3t{font-weight:600}.clone-destination.svelte-pudr3t output:where(.svelte-pudr3t){overflow-wrap:anywhere;border:1px solid color-mix(in srgb, CanvasText 35%, Canvas);border-radius:.35rem;min-block-size:2.75rem;padding:.65rem .75rem}.clone-controls.svelte-pudr3t button:where(.svelte-pudr3t){min-inline-size:5.75rem}.clone-form.svelte-pudr3t p:where(.svelte-pudr3t){color:color-mix(in srgb, CanvasText 70%, Canvas);margin:0;font-size:.875rem}.clone-status.svelte-pudr3t{font-weight:600;color:canvastext!important}.git-overview.svelte-pudr3t{grid-template-columns:minmax(0,1fr) auto;align-items:start;gap:1rem;display:grid}.git-status.svelte-pudr3t p:where(.svelte-pudr3t){margin-block:.5rem}.git-actions.svelte-pudr3t{gap:.5rem;display:grid}.git-actions.svelte-pudr3t button:where(.svelte-pudr3t){min-inline-size:6rem}.commit-history.svelte-pudr3t{margin-block-start:2rem}.commit-history.svelte-pudr3t h3:where(.svelte-pudr3t){margin-block-end:.75rem}.commit-list.svelte-pudr3t{gap:.5rem;margin:0;padding:0;list-style:none;display:grid}.commit-entry.svelte-pudr3t{border-block-end:1px solid color-mix(in srgb, CanvasText 20%, Canvas);gap:.35rem;min-inline-size:0;padding:.8rem 0;display:grid}.commit-subject.svelte-pudr3t,.commit-meta.svelte-pudr3t{gap:.65rem;min-inline-size:0;display:flex}.commit-subject.svelte-pudr3t{overflow-wrap:anywhere;align-items:baseline;font-weight:600}.commit-subject.svelte-pudr3t code:where(.svelte-pudr3t){color:color-mix(in srgb, CanvasText 75%, Canvas);flex:none;font-size:.875em}.commit-meta.svelte-pudr3t{color:color-mix(in srgb, CanvasText 70%, Canvas);flex-wrap:wrap;justify-content:space-between;font-size:.875rem}.commit-meta.svelte-pudr3t time:where(.svelte-pudr3t){overflow-wrap:anywhere;min-inline-size:0;max-inline-size:100%}@media (width<=28rem){.clone-controls.svelte-pudr3t{grid-template-columns:1fr}.clone-controls.svelte-pudr3t button:where(.svelte-pudr3t){inline-size:100%}}.session-list.svelte-hxb22h{gap:1rem;inline-size:100%;margin-block:0 1.5rem;padding-inline:0;list-style:none;display:grid}.managed-session.svelte-hxb22h{box-sizing:border-box;border:1px solid canvastext;border-radius:.5rem;grid-template-columns:max-content minmax(0,1fr);align-items:start;gap:.75rem;inline-size:100%;padding:.75rem;display:grid}.open-session.svelte-hxb22h{background:color-mix(in srgb, Highlight 12%, Canvas);border-inline-start:.3rem solid highlight}.session-details.svelte-hxb22h{min-inline-size:0}.workspace-path.svelte-hxb22h{overflow-wrap:anywhere}.session-actions.svelte-hxb22h{flex-wrap:wrap;gap:.5rem;display:flex}.recent-session.svelte-hxb22h{grid-template-columns:minmax(0,1fr) max-content;align-items:start;gap:.75rem;display:grid}.form-row.svelte-hxb22h{gap:.5rem;margin-block-end:.75rem;display:grid}.session-base.svelte-hxb22h{gap:.65rem;min-inline-size:0;margin-block:1rem;display:grid}.session-base-heading.svelte-hxb22h h3:where(.svelte-hxb22h),.session-base-heading.svelte-hxb22h p:where(.svelte-hxb22h){margin:0}.session-base-heading.svelte-hxb22h p:where(.svelte-hxb22h){margin-block-start:.25rem}.tree-panel.svelte-hxb22h{box-sizing:border-box;overscroll-behavior:contain;scrollbar-gutter:stable;border:1px solid color-mix(in srgb, CanvasText 35%, transparent);border-radius:.6rem;max-block-size:min(22rem,48vh);min-inline-size:0;padding:.35rem;overflow-y:auto}.session-settings-row.svelte-hxb22h{grid-template-columns:repeat(auto-fit,minmax(min(100%,13rem),1fr));align-items:end}.form-field.svelte-hxb22h{gap:.25rem;min-inline-size:0;display:grid}.new-session-button.svelte-hxb22h{color:canvas;box-shadow:inset 0 .15rem 0 color-mix(in srgb, Canvas 45%, transparent);background:canvastext;border-color:canvastext;font-weight:700}@media (width<=28rem){.tree-panel.svelte-hxb22h{padding:.2rem}.new-session-button.svelte-hxb22h{grid-column:1/-1;inline-size:100%}}.skills-view.svelte-dg6sjj{min-inline-size:0;padding:1rem max(1rem, env(safe-area-inset-right)) calc(5rem + env(safe-area-inset-bottom)) max(1rem, env(safe-area-inset-left))}.intro.svelte-dg6sjj,.save-intent.svelte-dg6sjj,.summary.svelte-dg6sjj{max-inline-size:70ch}form.svelte-dg6sjj,.field-grid.svelte-dg6sjj{gap:.65rem;display:grid}.field-grid.svelte-dg6sjj{grid-template-columns:minmax(0,1fr)}label.svelte-dg6sjj{font-weight:650}select.svelte-dg6sjj,input.svelte-dg6sjj,button.svelte-dg6sjj{box-sizing:border-box;min-block-size:3rem;inline-size:100%;font:inherit}input.svelte-dg6sjj,select.svelte-dg6sjj{padding-inline:.7rem;font-size:1rem}button.svelte-dg6sjj{padding-inline:1rem}.notice.svelte-dg6sjj{border-inline-start:.3rem solid #976600;padding-inline-start:.7rem}.error.svelte-dg6sjj{color:#8a1c14;border-inline-start:.3rem solid #b42318;padding-inline-start:.7rem}.skill-list.svelte-dg6sjj{gap:.75rem;padding:0;list-style:none;display:grid}.skill-card.svelte-dg6sjj{border:1px solid color-mix(in srgb, var(--brand-color,currentColor) 35%, transparent);border-radius:.5rem;min-inline-size:0;padding:.75rem}.skill-toggle.svelte-dg6sjj{flex-wrap:wrap;align-items:center;gap:.65rem;min-block-size:3rem;display:flex}.skill-toggle.svelte-dg6sjj>span:where(.svelte-dg6sjj){overflow-wrap:anywhere;min-inline-size:0}.skill-toggle.svelte-dg6sjj input:where(.svelte-dg6sjj){flex:none;min-block-size:1.25rem;inline-size:1.25rem}.state.svelte-dg6sjj{margin-inline-start:auto;font-weight:500}details.svelte-dg6sjj{overflow-wrap:anywhere}summary.svelte-dg6sjj{cursor:pointer;align-content:center;min-block-size:3rem}dl.svelte-dg6sjj{grid-template-columns:minmax(7rem,auto) minmax(0,1fr);gap:.45rem .75rem;display:grid}dt.svelte-dg6sjj{font-weight:650}dd.svelte-dg6sjj{min-inline-size:0;margin:0}dd.svelte-dg6sjj ul:where(.svelte-dg6sjj){margin:0;padding-inline-start:1.25rem}.path.svelte-dg6sjj{overflow-wrap:anywhere}.svelte-dg6sjj:where(button:where(.svelte-dg6sjj),input:where(.svelte-dg6sjj),select:where(.svelte-dg6sjj),summary:where(.svelte-dg6sjj)):focus-visible{outline-offset:2px;outline:3px solid #1261a0}@media (prefers-color-scheme:dark){.error.svelte-dg6sjj{color:#ffb4ab}.notice.svelte-dg6sjj{color:#ffd8a8}}@media (width>=42rem){.field-grid.svelte-dg6sjj{grid-template-columns:minmax(10rem,1fr) minmax(0,2fr);align-items:center}}@media (width<=30rem){dl.svelte-dg6sjj{grid-template-columns:1fr;gap:.2rem}}.chat-view.svelte-y74n0s{margin-inline:calc(.25rem - var(--page-inline-padding))}.evidence-mode.svelte-y74n0s{box-sizing:border-box;inline-size:100%;min-inline-size:0;padding:0}:root{--bottom-navigation-clearance:calc(4rem + env(safe-area-inset-bottom));--sticky-header-clearance:calc(5rem + env(safe-area-inset-top));--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light dark;font-family:system-ui,sans-serif}@media (prefers-color-scheme:dark){:root{--lightningcss-light: ;--lightningcss-dark:initial}}:root[data-theme=light]{--lightningcss-light:initial;--lightningcss-dark: ;color-scheme:light}:root[data-theme=dark]{--lightningcss-light: ;--lightningcss-dark:initial;color-scheme:dark}body{min-block-size:100dvh;margin:0}.visually-hidden{clip-path:inset(50%)!important;white-space:nowrap!important;border:0!important;block-size:1px!important;inline-size:1px!important;margin:-1px!important;padding:0!important;position:absolute!important;overflow:hidden!important}main{--page-inline-padding:1rem;box-sizing:border-box;min-block-size:100dvh;max-inline-size:42rem;padding:calc(1rem + env(safe-area-inset-top)) var(--page-inline-padding) calc(var(--bottom-navigation-clearance) + 1rem);margin-inline:auto}@media (width<=24rem){main{--page-inline-padding:.5rem}}.app-header{z-index:1;margin-block:calc(-1rem - env(safe-area-inset-top)) 1.5rem;padding:calc(1rem + env(safe-area-inset-top)) 0 .75rem;background:color-mix(in srgb, Canvas 78%, transparent);-webkit-backdrop-filter:blur(.75rem);backdrop-filter:blur(.75rem);flex-wrap:wrap;justify-content:space-between;align-items:center;gap:1rem;display:flex;position:sticky;inset-block-start:0}.app-header [role=status]{overflow-wrap:anywhere;text-align:end;min-inline-size:0;margin:0}.brand{align-items:center;gap:.4rem;display:flex}.menu-trigger{min-block-size:44px;inline-size:44px;color:inherit;background:0 0;border:0;flex:none;place-items:center;margin-inline-start:auto;padding:0;display:grid}.menu-lines{gap:.25rem;inline-size:1.25rem;display:grid}.menu-lines span{box-sizing:border-box;background:currentColor;block-size:1px;display:block}.menu-lines span:first-child,.menu-lines span:nth-child(2),.menu-lines span:last-child{block-size:2px}@media (width<=28rem){.app-header [role=status]{flex-basis:100%;margin-inline-start:0}}.brand-icon{block-size:2rem;inline-size:2rem}.brand-logotype{block-size:auto;inline-size:6.25rem}.dark-asset,:root[data-theme=dark] .light-asset{display:none}:root[data-theme=dark] .dark-asset{display:initial}@media (prefers-color-scheme:dark){:root:not([data-theme]) .light-asset{display:none}:root:not([data-theme]) .dark-asset{display:initial}}.configuration-panel{color:canvastext;min-inline-size:min(13rem,100vw - 2rem);box-shadow:0 .5rem 1.5rem color-mix(in srgb, CanvasText 25%, transparent);background:canvas;border:1px solid canvastext;border-radius:.5rem;padding:.75rem;position:fixed;inset:4.5rem 1rem auto auto}.configuration-logo{block-size:auto;inline-size:4.5rem;margin-block-end:.5rem;margin-inline-start:auto;display:block}.appearance-control{align-items:center;gap:.5rem;display:flex}.appearance-control span{letter-spacing:-.08em;font-weight:700}.appearance-control select{field-sizing:content;flex:none;min-block-size:2.25rem;inline-size:auto;padding-inline:.5rem}.bottom-navigation{padding:.5rem calc(.25rem + env(safe-area-inset-right)) calc(.5rem + env(safe-area-inset-bottom)) calc(.25rem + env(safe-area-inset-left));background:canvas;border-block-start:1px solid canvastext;grid-template-columns:repeat(4,minmax(0,1fr));gap:.25rem;display:grid;position:fixed;inset-block-end:0;inset-inline:0}.bottom-navigation button{overflow-wrap:anywhere;word-break:break-word;min-inline-size:0;max-inline-size:100%;padding-inline:0;transition:color .12s,background-color .12s,box-shadow .12s}.bottom-navigation button[aria-pressed=true]{color:canvas;box-shadow:inset 0 .2rem 0 color-mix(in srgb, Canvas 70%, transparent);background:canvastext;font-weight:700}.bottom-navigation button:disabled{color:color-mix(in srgb, CanvasText 40%, transparent);cursor:not-allowed;opacity:.65}@media (width<=30rem){:root{--bottom-navigation-clearance:calc(10rem + env(safe-area-inset-bottom));--sticky-header-clearance:calc(14rem + env(safe-area-inset-top))}.bottom-navigation{grid-template-columns:repeat(2,minmax(0,1fr))}.bottom-navigation button{text-align:center;padding-inline:.25rem}}button{min-block-size:44px;font:inherit}.git-view button{scroll-margin-block:var(--sticky-header-clearance) var(--bottom-navigation-clearance)}input,select,textarea{box-sizing:border-box;min-block-size:44px;inline-size:100%;font:inherit;font-size:max(1rem,16px)}:focus-visible{outline-offset:3px;outline:3px solid canvastext}@media (prefers-reduced-motion:reduce){*,:before,:after{scroll-behavior:auto!important;transition-duration:.01ms!important;animation-duration:.01ms!important}}@media (forced-colors:active){button,input,select,textarea{border:1px solid canvastext}}
|