import * as Schema from "effect/Schema";import * as SchemaTransformation from "effect/SchemaTransformation";import { ThreadEnvMode } from "./environment.ts";import { ProjectScriptIcon } from "./orchestration.ts";/** File name of the checked-in T3 project file, resolved at the workspace root. */export const T3_PROJECT_FILE_NAME = "t3.json";/** Public URL of the published JSON Schema for {@link T3ProjectFile}. */export const T3_PROJECT_FILE_SCHEMA_URL = "https://t3.codes/schema/t3.json";const T3_PROJECT_FILE_PATH_MAX_LENGTH = 512;const T3_PROJECT_FILE_MAX_SCRIPTS = 50;// Annotations go on the encoded (string) side so they survive into the// published JSON Schema; decoding still trims and re-validates non-emptiness.const trimmedNonEmpty = (annotations: { readonly description: string }, maxLength?: number) => { const annotated = Schema.String.annotate(annotations); const encoded = maxLength === undefined ? annotated.check(Schema.isNonEmpty()) : annotated.check(Schema.isNonEmpty(), Schema.isMaxLength(maxLength)); return encoded.pipe(Schema.decodeTo(encoded, SchemaTransformation.trim()));};export const T3ProjectFileScript = Schema.Struct({ name: trimmedNonEmpty({ description: "Display name for the script, shown in the T3 Code scripts menu.", }), command: trimmedNonEmpty({ description: "Shell command executed in a T3 Code terminal at the project root.", }), icon: Schema.optionalKey( ProjectScriptIcon.annotate({ description: 'Icon shown next to the script in the scripts menu. Defaults to "play".', }), ), runOnWorktreeCreate: Schema.optionalKey( Schema.Boolean.annotate({ description: "When true, the script runs automatically after a worktree is created for a new thread.", }), ), previewUrl: Schema.optionalKey( trimmedNonEmpty({ description: "URL opened in the in-app browser preview when this script runs. Only honored on the desktop build.", }), ), autoOpenPreview: Schema.optionalKey( Schema.Boolean.annotate({ description: "When true, automatically open the preview panel at `previewUrl` the moment the script starts.", }), ),}).annotate({ description: "A project script that team members can import into T3 Code.",});export type T3ProjectFileScript = typeof T3ProjectFileScript.Type;export const T3ProjectFile = Schema.Struct({ $schema: Schema.optionalKey( Schema.String.annotate({ description: `URL of the JSON Schema for this file, typically "${T3_PROJECT_FILE_SCHEMA_URL}".`, }), ), iconPath: Schema.optionalKey( trimmedNonEmpty( { description: 'Workspace-relative path to the project icon (e.g. "assets/logo.svg"). Checked before T3 Code\'s built-in icon locations.', }, T3_PROJECT_FILE_PATH_MAX_LENGTH, ), ), defaultThreadEnvMode: Schema.optionalKey( ThreadEnvMode.annotate({ description: 'Where new threads start for this repository: "worktree" for a fresh git worktree, "local" for the current checkout. A per-project setting in T3 Code overrides this; when neither is set, the global default applies.', }), ), scripts: Schema.optionalKey( Schema.Array(T3ProjectFileScript) .annotate({ description: "Project scripts shared with everyone who opens this repository in T3 Code.", }) .check(Schema.isMaxLength(T3_PROJECT_FILE_MAX_SCRIPTS)), ),}).annotate({ title: "T3 project file", description: "Checked-in project configuration for T3 Code (t3.json at the repository root). See https://t3.codes for documentation.",});export type T3ProjectFile = typeof T3ProjectFile.Type;
import * as Schema from "effect/Schema";
import * as SchemaTransformation from "effect/SchemaTransformation";
import { ThreadEnvMode } from "./environment.ts";
import { ProjectScriptIcon } from "./orchestration.ts";
/** File name of the checked-in T3 project file, resolved at the workspace root. */
export const T3_PROJECT_FILE_NAME = "t3.json";
/** Public URL of the published JSON Schema for {@link T3ProjectFile}. */
export const T3_PROJECT_FILE_SCHEMA_URL = "https://t3.codes/schema/t3.json";
const T3_PROJECT_FILE_PATH_MAX_LENGTH = 512;
const T3_PROJECT_FILE_MAX_SCRIPTS = 50;
// Annotations go on the encoded (string) side so they survive into the
// published JSON Schema; decoding still trims and re-validates non-emptiness.
const trimmedNonEmpty = (annotations: { readonly description: string }, maxLength?: number) => {
const annotated = Schema.String.annotate(annotations);
const encoded =
maxLength === undefined
? annotated.check(Schema.isNonEmpty())
: annotated.check(Schema.isNonEmpty(), Schema.isMaxLength(maxLength));
return encoded.pipe(Schema.decodeTo(encoded, SchemaTransformation.trim()));
};
export const T3ProjectFileScript = Schema.Struct({
name: trimmedNonEmpty({
description: "Display name for the script, shown in the T3 Code scripts menu.",
}),
command: trimmedNonEmpty({
description: "Shell command executed in a T3 Code terminal at the project root.",
}),
icon: Schema.optionalKey(
ProjectScriptIcon.annotate({
description: 'Icon shown next to the script in the scripts menu. Defaults to "play".',
}),
),
runOnWorktreeCreate: Schema.optionalKey(
Schema.Boolean.annotate({
description:
"When true, the script runs automatically after a worktree is created for a new thread.",
}),
),
previewUrl: Schema.optionalKey(
trimmedNonEmpty({
description:
"URL opened in the in-app browser preview when this script runs. Only honored on the desktop build.",
}),
),
autoOpenPreview: Schema.optionalKey(
Schema.Boolean.annotate({
description:
"When true, automatically open the preview panel at `previewUrl` the moment the script starts.",
}),
),
}).annotate({
description: "A project script that team members can import into T3 Code.",
});
export type T3ProjectFileScript = typeof T3ProjectFileScript.Type;
export const T3ProjectFile = Schema.Struct({
$schema: Schema.optionalKey(
Schema.String.annotate({
description: `URL of the JSON Schema for this file, typically "${T3_PROJECT_FILE_SCHEMA_URL}".`,
}),
),
iconPath: Schema.optionalKey(
trimmedNonEmpty(
{
description:
'Workspace-relative path to the project icon (e.g. "assets/logo.svg"). Checked before T3 Code\'s built-in icon locations.',
},
T3_PROJECT_FILE_PATH_MAX_LENGTH,
),
),
defaultThreadEnvMode: Schema.optionalKey(
ThreadEnvMode.annotate({
description:
'Where new threads start for this repository: "worktree" for a fresh git worktree, "local" for the current checkout. A per-project setting in T3 Code overrides this; when neither is set, the global default applies.',
}),
),
scripts: Schema.optionalKey(
Schema.Array(T3ProjectFileScript)
.annotate({
description: "Project scripts shared with everyone who opens this repository in T3 Code.",
})
.check(Schema.isMaxLength(T3_PROJECT_FILE_MAX_SCRIPTS)),
),
}).annotate({
title: "T3 project file",
description:
"Checked-in project configuration for T3 Code (t3.json at the repository root). See https://t3.codes for documentation.",
});
export type T3ProjectFile = typeof T3ProjectFile.Type;