Implemented opencode plugins

This commit is contained in:
Umar Adilov 2025-12-03 18:22:34 +05:00
parent f6ba8dac45
commit edbd384432
4 changed files with 507 additions and 1 deletions

View File

@ -0,0 +1,70 @@
import type { Plugin } from "@opencode-ai/plugin";
import { Axios } from "axios";
import { z } from "zod";
const { vmOrchestrationStatusUpdateUrl } = z
.object({
vmOrchestrationStatusUpdateUrl: z.string(),
})
.parse({
vmOrchestrationStatusUpdateUrl:
process.env.TAYLORDB_VM_ORCHESTRATION_STATUS_UPDATE_URL,
});
const axios = new Axios({
baseURL: vmOrchestrationStatusUpdateUrl,
});
const updateAppStatus = async (status: "Errored" | "Active" | "Pending") => {
await axios.put("/", {
status,
});
};
export const DevServerHMRPlugin: Plugin = async ({ client, $ }) => {
return {
event: async ({ event }) => {
if (event.type !== "session.idle") return;
const result = await $`pnpm build`.catch((error) => error);
if (result.exitCode !== 0) {
if (!client.session["tries"]) {
client.session["tries"] = 0;
} else {
client.session["tries"]++;
}
if (client.session["tries"] > 3) {
await updateAppStatus("Errored");
console.log("CHANGE STATUS TO ERRORED");
return;
}
await client.session.prompt({
path: { id: event.properties.sessionID },
body: {
parts: [
{
type: "text",
text: `While building the project, the following error occurred:\n\n${result.stderr.toString()}\n\nPlease fix the error and try again.`,
},
],
},
});
}
await updateAppStatus("Active");
console.log("CHANGE STATUS TO ACTIVE");
},
"chat.message": async (input, output) => {
await updateAppStatus("Pending");
console.log("CHANGE STATUS TO PENDING");
},
};
};

View File

@ -0,0 +1,26 @@
import type { Plugin } from "@opencode-ai/plugin";
import micromatch from "micromatch";
const uneditableFiles = [
".env",
".env.local",
".env.development",
".env.production",
"src/lib/*.ts",
"opencode.json",
];
export const FileProtectionPlugin: Plugin = async ({ client, $ }) => {
return {
"tool.execute.before": async (input, output) => {
if (
input.tool === "edit" &&
uneditableFiles.some((pattern) =>
micromatch.isMatch(output.args.filePath, pattern)
)
) {
throw new Error(`Do not edit ${output.args.filePath} files`);
}
},
};
};

View File

@ -0,0 +1,410 @@
/**
* Copyright (c) 2025 TaylorDB
*
* This source code is licensed under the MIT license found in the
* LICENSE file in the root directory of this source tree.
*/
interface FileInformation {
fieldname: string;
originalname: string;
encoding: string;
mimetype: string;
destination: string;
filename: string;
path: string;
size: number;
format: string;
width: number;
height: number;
}
interface UploadResponse {
collectionName: string;
fileInformation: FileInformation;
metadata: {
thumbnails: any[];
clips: any[];
};
baseId: string;
storageAdaptor: string;
_id: string;
__v: number;
}
export interface AttachmentColumnValue {
url: string;
fileType: string;
size: number;
}
export class Attachment {
public readonly collectionName: string;
public readonly fileInformation: FileInformation;
public readonly metadata: { thumbnails: any[]; clips: any[] };
public readonly baseId: string;
public readonly storageAdaptor: string;
public readonly _id: string;
constructor(data: UploadResponse) {
this.collectionName = data.collectionName;
this.fileInformation = data.fileInformation;
this.metadata = data.metadata;
this.baseId = data.baseId;
this.storageAdaptor = data.storageAdaptor;
this._id = data._id;
}
toColumnValue(): AttachmentColumnValue {
return {
url: this.fileInformation.path,
fileType: this.fileInformation.mimetype,
size: this.fileInformation.size,
};
}
}
type IsWithinOperatorValue =
| "pastWeek"
| "pastMonth"
| "pastYear"
| "nextWeek"
| "nextMonth"
| "nextYear"
| "daysFromNow"
| "daysAgo"
| "currentWeek"
| "currentMonth"
| "currentYear";
type DefaultDateFilterValue =
| (
| "today"
| "tomorrow"
| "yesterday"
| "oneWeekAgo"
| "oneWeekFromNow"
| "oneMonthAgo"
| "oneMonthFromNow"
)
| ["exactDay" | "exactTimestamp", string]
| ["daysAgo" | "daysFromNow", number];
type DateFilters = {
"=": DefaultDateFilterValue;
"!=": DefaultDateFilterValue;
"<": DefaultDateFilterValue;
">": DefaultDateFilterValue;
"<=": DefaultDateFilterValue;
">=": DefaultDateFilterValue;
isWithIn:
| IsWithinOperatorValue
| { value: "daysAgo" | "daysFromNow"; date: number };
isEmpty: boolean;
isNotEmpty: boolean;
};
type DateAggregations = {
empty: number;
filled: number;
unique: number;
percentEmpty: number;
percentFilled: number;
percentUnique: number;
min: number | null;
max: number | null;
daysRange: number | null;
monthRange: number | null;
};
type TextFilters = {
"=": string;
"!=": string;
caseEqual: string;
hasAnyOf: string[];
contains: string;
startsWith: string;
endsWith: string;
doesNotContain: string;
isEmpty: never;
isNotEmpty: never;
};
type LinkFilters = {
hasAnyOf: number[];
hasAllOf: number[];
isExactly: number[];
"=": number;
hasNoneOf: number[];
contains: string;
doesNotContain: string;
isEmpty: never;
isNotEmpty: never;
};
type SelectFilters<O extends readonly string[]> = {
hasAnyOf: O[number][];
hasAllOf: O[number][];
isExactly: O[number][];
"=": O[number];
hasNoneOf: O[number][];
contains: string;
doesNotContain: string;
isEmpty: never;
isNotEmpty: never;
};
type LinkAggregations = {
empty: number;
filled: number;
percentEmpty: number;
percentFilled: number;
};
type NumberFilters = {
"=": number;
"!=": number;
">": number;
">=": number;
"<": number;
"<=": number;
hasAnyOf: number[];
hasNoneOf: number[];
isEmpty: never;
isNotEmpty: never;
};
type NumberAggregations = {
sum: number;
average: number;
median: number;
min: number | null;
max: number | null;
range: number;
standardDeviation: number;
histogram: Record<string, number>;
empty: number;
filled: number;
unique: number;
percentEmpty: number;
percentFilled: number;
percentUnique: number;
};
type CheckboxFilters = {
"=": number;
};
/**
*
* Column types
*
*/
export type ColumnType<
S,
U,
I,
R extends boolean,
F extends { [key: string]: any } = object,
A extends { [key: string]: any } = object
> = {
raw: S;
insert: I;
update: U;
filters: F;
aggregations: A;
isRequired: R;
};
export type DateColumnType<R extends boolean> = ColumnType<
string,
string,
string,
R,
DateFilters,
DateAggregations
>;
export type TextColumnType<R extends boolean> = ColumnType<
string,
string,
string,
R,
TextFilters
>;
export type ALinkColumnType<
T extends string,
S,
U,
I,
R extends boolean,
F extends { [key: string]: any } = LinkFilters,
A extends LinkAggregations = LinkAggregations
> = ColumnType<S, U, I, R, F, A> & {
linkedTo: T;
};
export type LinkColumnType<
T extends string,
R extends boolean
> = ALinkColumnType<
T,
object,
number | number[] | { newIds: number[]; deletedIds: number[] },
number | number[],
R
>;
export type AttachmentColumnType<R extends boolean> = ALinkColumnType<
"attachmentTable",
Attachment[],
Attachment[] | { newIds: number[]; deletedIds: number[] } | number[],
Attachment[] | number[],
R
>;
export type NumberColumnType<R extends boolean> = ColumnType<
number,
number,
number,
R,
NumberFilters,
NumberAggregations
>;
export type CheckboxColumnType<R extends boolean> = ColumnType<
boolean,
boolean,
boolean,
R,
CheckboxFilters
>;
export type AutoGeneratedNumberColumnType = ColumnType<
number,
never,
never,
false,
NumberFilters,
NumberAggregations
>;
export type AutoGeneratedDateColumnType = ColumnType<
string,
never,
never,
false,
DateFilters,
DateAggregations
>;
export type SingleSelectColumnType<
O extends readonly string[],
R extends boolean
> = ALinkColumnType<
"selectTable",
O[number],
O[number] | O[number][],
O[number] | O[number][],
R,
SelectFilters<O>
>;
export type TableRaws<T extends keyof TaylorDatabase> = {
[K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType<
infer S,
any,
any,
infer R,
any,
any
>
? R extends true
? S
: S | undefined
: never;
};
export type TableInserts<T extends keyof TaylorDatabase> = {
[K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType<
any,
infer I,
any,
infer R,
any,
any
>
? R extends true
? I
: I | undefined
: never;
};
export type TableUpdates<T extends keyof TaylorDatabase> = {
[K in keyof TaylorDatabase[T]]: TaylorDatabase[T][K] extends ColumnType<
any,
any,
infer U,
any,
any,
any
>
? U
: never;
};
export type SelectTable = {
id: AutoGeneratedNumberColumnType;
name: TextColumnType<true>;
color: TextColumnType<true>;
};
export type AttachmentTable = {
id: AutoGeneratedNumberColumnType;
name: TextColumnType<true>;
metadata: TextColumnType<true>;
size: NumberColumnType<true>;
fileType: TextColumnType<true>;
url: TextColumnType<true>;
};
export type CollaboratorsTable = {
id: AutoGeneratedNumberColumnType;
name: TextColumnType<true>;
emailAddress: TextColumnType<true>;
avatar: TextColumnType<true>;
};
export type TaylorDatabase = {
/**
*
*
* Internal tables, these tables can not be queried directly.
*
*/
selectTable: SelectTable;
attachmentTable: AttachmentTable;
collaboratorsTable: CollaboratorsTable;
apps: AppsTable;
};
export type VmStatusOptions = ["Active", "Inactive", "Pending", "Errored"];
export type AppStatusOptions = ["Active", "Inactive", "Pending", "Errored"];
export type AppsTable = {
name: TextColumnType<true>;
slug: TextColumnType<true>;
description: TextColumnType<false>;
templateRepoUrl: TextColumnType<true>;
devUrl: TextColumnType<false>;
opencodeUrl: TextColumnType<false>;
isPublic: CheckboxColumnType<false>;
isPublished: CheckboxColumnType<false>;
status: SingleSelectColumnType<VmStatusOptions, false>;
appStatus: SingleSelectColumnType<AppStatusOptions, false>;
icon: TextColumnType<false>;
color: TextColumnType<false>;
healthcheck: TextColumnType<false>;
publishUrl: TextColumnType<false>;
};

View File

@ -4,7 +4,7 @@
<meta charset="UTF-8" />
<link rel="icon" type="image/svg+xml" href="/vite.svg" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>task tracker</title>
<title>blank</title>
</head>
<body>
<div id="root"></div>