@hachej/boring-automation 0.1.71
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 +5 -0
- package/dist/front/index.d.ts +7 -0
- package/dist/front/index.js +122 -0
- package/dist/server/index.d.ts +71 -0
- package/dist/server/index.js +451 -0
- package/dist/shared/index.d.ts +266 -0
- package/dist/shared/index.js +80 -0
- package/package.json +81 -0
package/README.md
ADDED
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
# @hachej/boring-automation
|
|
2
|
+
|
|
3
|
+
Trusted Boring workspace plugin for scheduled prompt automations.
|
|
4
|
+
|
|
5
|
+
Slice 1 provides the package shell, single-workspace file-backed automation store, automation/prompt CRUD routes, and read-only run-history routes. Execution and scheduling are intentionally gated on the issue #590 seam-confirmation slice.
|
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
import { BoringFrontFactoryWithId } from '@hachej/boring-workspace/plugin';
|
|
2
|
+
export { Automation, AutomationCreate, AutomationCreateInput, AutomationCreateSchema, AutomationPatch, AutomationPatchInput, AutomationPatchSchema, AutomationRun, AutomationRunCreate, AutomationRunCreateInput, AutomationRunCreateSchema, AutomationRunPatch, AutomationRunPatchInput, AutomationRunPatchSchema, AutomationRunStatus, AutomationRunStatusSchema, AutomationRunTrigger, AutomationRunTriggerSchema, BORING_AUTOMATION_ERROR_CODES, BORING_AUTOMATION_PLUGIN_ID, BORING_AUTOMATION_PLUGIN_LABEL, BORING_AUTOMATION_ROUTE_PREFIX, BoringAutomationErrorCode, IdParamsSchema, PromptUpdateSchema } from '../shared/index.js';
|
|
3
|
+
import 'zod';
|
|
4
|
+
|
|
5
|
+
declare const boringAutomationPlugin: BoringFrontFactoryWithId;
|
|
6
|
+
|
|
7
|
+
export { boringAutomationPlugin, boringAutomationPlugin as default };
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
"use client";
|
|
2
|
+
|
|
3
|
+
// src/front/index.tsx
|
|
4
|
+
import { definePlugin } from "@hachej/boring-workspace/plugin";
|
|
5
|
+
import { CalendarClock } from "lucide-react";
|
|
6
|
+
|
|
7
|
+
// src/shared/constants.ts
|
|
8
|
+
var BORING_AUTOMATION_PLUGIN_ID = "boring-automation";
|
|
9
|
+
var BORING_AUTOMATION_PLUGIN_LABEL = "Automations";
|
|
10
|
+
var BORING_AUTOMATION_ROUTE_PREFIX = "/api/v1/boring-automation";
|
|
11
|
+
|
|
12
|
+
// src/shared/error-codes.ts
|
|
13
|
+
var BORING_AUTOMATION_ERROR_CODES = {
|
|
14
|
+
INVALID_BODY: "BORING_AUTOMATION_INVALID_BODY",
|
|
15
|
+
AUTOMATION_NOT_FOUND: "BORING_AUTOMATION_NOT_FOUND",
|
|
16
|
+
RUN_NOT_FOUND: "BORING_AUTOMATION_RUN_NOT_FOUND"
|
|
17
|
+
};
|
|
18
|
+
|
|
19
|
+
// src/shared/schema.ts
|
|
20
|
+
import { z } from "zod";
|
|
21
|
+
var nonEmptyString = z.string().trim().min(1);
|
|
22
|
+
var isoString = z.string().datetime({ offset: true });
|
|
23
|
+
var nonNegativeInteger = z.number().int().nonnegative();
|
|
24
|
+
var AutomationRunStatusSchema = z.enum(["queued", "running", "succeeded", "failed", "cancelled"]);
|
|
25
|
+
var AutomationRunTriggerSchema = z.enum(["manual", "scheduled"]);
|
|
26
|
+
var AutomationCreateSchema = z.object({
|
|
27
|
+
title: nonEmptyString,
|
|
28
|
+
enabled: z.boolean().optional(),
|
|
29
|
+
cron: nonEmptyString,
|
|
30
|
+
timezone: nonEmptyString,
|
|
31
|
+
model: nonEmptyString,
|
|
32
|
+
prompt: z.string().optional()
|
|
33
|
+
}).strict();
|
|
34
|
+
var AutomationPatchSchema = z.object({
|
|
35
|
+
title: nonEmptyString.optional(),
|
|
36
|
+
enabled: z.boolean().optional(),
|
|
37
|
+
cron: nonEmptyString.optional(),
|
|
38
|
+
timezone: nonEmptyString.optional(),
|
|
39
|
+
model: nonEmptyString.optional()
|
|
40
|
+
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided");
|
|
41
|
+
var PromptUpdateSchema = z.object({
|
|
42
|
+
prompt: z.string()
|
|
43
|
+
}).strict();
|
|
44
|
+
var AutomationRunCreateSchema = z.object({
|
|
45
|
+
automationId: nonEmptyString,
|
|
46
|
+
sessionId: nonEmptyString.nullable().optional(),
|
|
47
|
+
status: AutomationRunStatusSchema.optional(),
|
|
48
|
+
trigger: AutomationRunTriggerSchema,
|
|
49
|
+
scheduledFor: isoString.nullable().optional(),
|
|
50
|
+
startedAt: isoString.nullable().optional(),
|
|
51
|
+
completedAt: isoString.nullable().optional(),
|
|
52
|
+
durationMs: nonNegativeInteger.nullable().optional(),
|
|
53
|
+
inputTokens: nonNegativeInteger.nullable().optional(),
|
|
54
|
+
outputTokens: nonNegativeInteger.nullable().optional(),
|
|
55
|
+
totalTokens: nonNegativeInteger.nullable().optional(),
|
|
56
|
+
promptSnapshot: z.string(),
|
|
57
|
+
modelSnapshot: nonEmptyString,
|
|
58
|
+
error: z.string().nullable().optional()
|
|
59
|
+
}).strict();
|
|
60
|
+
var AutomationRunPatchSchema = z.object({
|
|
61
|
+
sessionId: nonEmptyString.nullable().optional(),
|
|
62
|
+
status: AutomationRunStatusSchema.optional(),
|
|
63
|
+
scheduledFor: isoString.nullable().optional(),
|
|
64
|
+
startedAt: isoString.nullable().optional(),
|
|
65
|
+
completedAt: isoString.nullable().optional(),
|
|
66
|
+
durationMs: nonNegativeInteger.nullable().optional(),
|
|
67
|
+
inputTokens: nonNegativeInteger.nullable().optional(),
|
|
68
|
+
outputTokens: nonNegativeInteger.nullable().optional(),
|
|
69
|
+
totalTokens: nonNegativeInteger.nullable().optional(),
|
|
70
|
+
error: z.string().nullable().optional()
|
|
71
|
+
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided");
|
|
72
|
+
var IdParamsSchema = z.object({ id: nonEmptyString });
|
|
73
|
+
|
|
74
|
+
// src/front/index.tsx
|
|
75
|
+
import { jsx, jsxs } from "react/jsx-runtime";
|
|
76
|
+
function AutomationPanel() {
|
|
77
|
+
return /* @__PURE__ */ jsxs("div", { className: "flex h-full min-h-0 flex-col bg-background p-4 text-sm text-foreground", children: [
|
|
78
|
+
/* @__PURE__ */ jsxs("div", { className: "flex items-center gap-2 font-medium", children: [
|
|
79
|
+
/* @__PURE__ */ jsx(CalendarClock, { className: "h-4 w-4 text-muted-foreground" }),
|
|
80
|
+
BORING_AUTOMATION_PLUGIN_LABEL
|
|
81
|
+
] }),
|
|
82
|
+
/* @__PURE__ */ jsx("p", { className: "mt-2 max-w-prose text-sm text-muted-foreground", children: "Automation UI is implemented in the next slice. Slice 1 provides the trusted plugin shell, file-backed store, and CRUD routes." })
|
|
83
|
+
] });
|
|
84
|
+
}
|
|
85
|
+
var boringAutomationPlugin = definePlugin({
|
|
86
|
+
id: BORING_AUTOMATION_PLUGIN_ID,
|
|
87
|
+
label: BORING_AUTOMATION_PLUGIN_LABEL,
|
|
88
|
+
panels: [
|
|
89
|
+
{
|
|
90
|
+
id: `${BORING_AUTOMATION_PLUGIN_ID}.panel`,
|
|
91
|
+
label: BORING_AUTOMATION_PLUGIN_LABEL,
|
|
92
|
+
icon: CalendarClock,
|
|
93
|
+
component: AutomationPanel,
|
|
94
|
+
placement: "center",
|
|
95
|
+
source: "builtin"
|
|
96
|
+
}
|
|
97
|
+
],
|
|
98
|
+
commands: [
|
|
99
|
+
{
|
|
100
|
+
id: `${BORING_AUTOMATION_PLUGIN_ID}.open`,
|
|
101
|
+
title: "Open Automations",
|
|
102
|
+
panelId: `${BORING_AUTOMATION_PLUGIN_ID}.panel`
|
|
103
|
+
}
|
|
104
|
+
]
|
|
105
|
+
});
|
|
106
|
+
var front_default = boringAutomationPlugin;
|
|
107
|
+
export {
|
|
108
|
+
AutomationCreateSchema,
|
|
109
|
+
AutomationPatchSchema,
|
|
110
|
+
AutomationRunCreateSchema,
|
|
111
|
+
AutomationRunPatchSchema,
|
|
112
|
+
AutomationRunStatusSchema,
|
|
113
|
+
AutomationRunTriggerSchema,
|
|
114
|
+
BORING_AUTOMATION_ERROR_CODES,
|
|
115
|
+
BORING_AUTOMATION_PLUGIN_ID,
|
|
116
|
+
BORING_AUTOMATION_PLUGIN_LABEL,
|
|
117
|
+
BORING_AUTOMATION_ROUTE_PREFIX,
|
|
118
|
+
IdParamsSchema,
|
|
119
|
+
PromptUpdateSchema,
|
|
120
|
+
boringAutomationPlugin,
|
|
121
|
+
front_default as default
|
|
122
|
+
};
|
|
@@ -0,0 +1,71 @@
|
|
|
1
|
+
import { WorkspaceServerPlugin } from '@hachej/boring-workspace/server';
|
|
2
|
+
import { Automation, AutomationCreate, AutomationPatch, AutomationRunCreate, AutomationRun, AutomationRunPatch, BoringAutomationErrorCode } from '../shared/index.js';
|
|
3
|
+
export { AutomationCreateInput, AutomationCreateSchema, AutomationPatchInput, AutomationPatchSchema, AutomationRunCreateInput, AutomationRunCreateSchema, AutomationRunPatchInput, AutomationRunPatchSchema, AutomationRunStatus, AutomationRunStatusSchema, AutomationRunTrigger, AutomationRunTriggerSchema, BORING_AUTOMATION_ERROR_CODES, BORING_AUTOMATION_PLUGIN_ID, BORING_AUTOMATION_PLUGIN_LABEL, BORING_AUTOMATION_ROUTE_PREFIX, IdParamsSchema, PromptUpdateSchema } from '../shared/index.js';
|
|
4
|
+
import { FastifyInstance } from 'fastify';
|
|
5
|
+
import 'zod';
|
|
6
|
+
|
|
7
|
+
/** Plugin-local dependency injection seam for the single-workspace automation store. */
|
|
8
|
+
interface AutomationStore {
|
|
9
|
+
listAutomations(): Promise<Automation[]>;
|
|
10
|
+
getAutomation(id: string): Promise<Automation | null>;
|
|
11
|
+
createAutomation(input: AutomationCreate): Promise<Automation>;
|
|
12
|
+
updateAutomation(id: string, patch: AutomationPatch): Promise<Automation>;
|
|
13
|
+
deleteAutomation(id: string): Promise<void>;
|
|
14
|
+
getPrompt(automationId: string): Promise<string>;
|
|
15
|
+
updatePrompt(automationId: string, body: string): Promise<void>;
|
|
16
|
+
createRun(input: AutomationRunCreate): Promise<AutomationRun>;
|
|
17
|
+
updateRun(runId: string, patch: AutomationRunPatch): Promise<AutomationRun>;
|
|
18
|
+
listRuns(automationId: string): Promise<AutomationRun[]>;
|
|
19
|
+
findRunningRun(automationId: string): Promise<AutomationRun | null>;
|
|
20
|
+
}
|
|
21
|
+
declare class AutomationStoreError extends Error {
|
|
22
|
+
readonly code: BoringAutomationErrorCode;
|
|
23
|
+
constructor(code: BoringAutomationErrorCode, message: string);
|
|
24
|
+
}
|
|
25
|
+
declare function automationNotFound(id: string): AutomationStoreError;
|
|
26
|
+
declare function runNotFound(id: string): AutomationStoreError;
|
|
27
|
+
|
|
28
|
+
type AtomicWriter = (path: string, content: string) => Promise<void>;
|
|
29
|
+
interface FileAutomationStoreOptions {
|
|
30
|
+
writer?: AtomicWriter;
|
|
31
|
+
}
|
|
32
|
+
declare class FileAutomationStore implements AutomationStore {
|
|
33
|
+
private readonly rootDir;
|
|
34
|
+
private state;
|
|
35
|
+
private loadInFlight;
|
|
36
|
+
private writeChain;
|
|
37
|
+
private readonly writer;
|
|
38
|
+
constructor(rootDir: string, options?: FileAutomationStoreOptions);
|
|
39
|
+
listAutomations(): Promise<Automation[]>;
|
|
40
|
+
getAutomation(id: string): Promise<Automation | null>;
|
|
41
|
+
createAutomation(input: AutomationCreate): Promise<Automation>;
|
|
42
|
+
updateAutomation(id: string, patch: AutomationPatch): Promise<Automation>;
|
|
43
|
+
deleteAutomation(id: string): Promise<void>;
|
|
44
|
+
getPrompt(automationId: string): Promise<string>;
|
|
45
|
+
updatePrompt(automationId: string, body: string): Promise<void>;
|
|
46
|
+
createRun(input: AutomationRunCreate): Promise<AutomationRun>;
|
|
47
|
+
updateRun(runId: string, patch: AutomationRunPatch): Promise<AutomationRun>;
|
|
48
|
+
listRuns(automationId: string): Promise<AutomationRun[]>;
|
|
49
|
+
findRunningRun(automationId: string): Promise<AutomationRun | null>;
|
|
50
|
+
private statePath;
|
|
51
|
+
private promptPath;
|
|
52
|
+
private writePromptFile;
|
|
53
|
+
private mutate;
|
|
54
|
+
private load;
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
interface AutomationRoutesOptions {
|
|
58
|
+
store: AutomationStore;
|
|
59
|
+
}
|
|
60
|
+
declare function automationRoutes(app: FastifyInstance, opts: AutomationRoutesOptions): Promise<void>;
|
|
61
|
+
|
|
62
|
+
interface BoringAutomationServerPluginOptions {
|
|
63
|
+
workspaceRoot?: string;
|
|
64
|
+
store?: AutomationStore;
|
|
65
|
+
}
|
|
66
|
+
declare function createBoringAutomationServerPlugin(options?: BoringAutomationServerPluginOptions): WorkspaceServerPlugin;
|
|
67
|
+
declare function defaultBoringAutomationServerPlugin(options?: BoringAutomationServerPluginOptions, ctx?: {
|
|
68
|
+
workspaceRoot?: string;
|
|
69
|
+
}): WorkspaceServerPlugin;
|
|
70
|
+
|
|
71
|
+
export { Automation, AutomationCreate, AutomationPatch, type AutomationRoutesOptions, AutomationRun, AutomationRunCreate, AutomationRunPatch, type AutomationStore, AutomationStoreError, BoringAutomationErrorCode, type BoringAutomationServerPluginOptions, FileAutomationStore, type FileAutomationStoreOptions, automationNotFound, automationRoutes, createBoringAutomationServerPlugin, defaultBoringAutomationServerPlugin as default, runNotFound };
|
|
@@ -0,0 +1,451 @@
|
|
|
1
|
+
// src/server/index.ts
|
|
2
|
+
import { join as join2 } from "path";
|
|
3
|
+
import { defineServerPlugin } from "@hachej/boring-workspace/server";
|
|
4
|
+
|
|
5
|
+
// src/shared/constants.ts
|
|
6
|
+
var BORING_AUTOMATION_PLUGIN_ID = "boring-automation";
|
|
7
|
+
var BORING_AUTOMATION_PLUGIN_LABEL = "Automations";
|
|
8
|
+
var BORING_AUTOMATION_ROUTE_PREFIX = "/api/v1/boring-automation";
|
|
9
|
+
|
|
10
|
+
// src/shared/error-codes.ts
|
|
11
|
+
var BORING_AUTOMATION_ERROR_CODES = {
|
|
12
|
+
INVALID_BODY: "BORING_AUTOMATION_INVALID_BODY",
|
|
13
|
+
AUTOMATION_NOT_FOUND: "BORING_AUTOMATION_NOT_FOUND",
|
|
14
|
+
RUN_NOT_FOUND: "BORING_AUTOMATION_RUN_NOT_FOUND"
|
|
15
|
+
};
|
|
16
|
+
|
|
17
|
+
// src/shared/schema.ts
|
|
18
|
+
import { z } from "zod";
|
|
19
|
+
var nonEmptyString = z.string().trim().min(1);
|
|
20
|
+
var isoString = z.string().datetime({ offset: true });
|
|
21
|
+
var nonNegativeInteger = z.number().int().nonnegative();
|
|
22
|
+
var AutomationRunStatusSchema = z.enum(["queued", "running", "succeeded", "failed", "cancelled"]);
|
|
23
|
+
var AutomationRunTriggerSchema = z.enum(["manual", "scheduled"]);
|
|
24
|
+
var AutomationCreateSchema = z.object({
|
|
25
|
+
title: nonEmptyString,
|
|
26
|
+
enabled: z.boolean().optional(),
|
|
27
|
+
cron: nonEmptyString,
|
|
28
|
+
timezone: nonEmptyString,
|
|
29
|
+
model: nonEmptyString,
|
|
30
|
+
prompt: z.string().optional()
|
|
31
|
+
}).strict();
|
|
32
|
+
var AutomationPatchSchema = z.object({
|
|
33
|
+
title: nonEmptyString.optional(),
|
|
34
|
+
enabled: z.boolean().optional(),
|
|
35
|
+
cron: nonEmptyString.optional(),
|
|
36
|
+
timezone: nonEmptyString.optional(),
|
|
37
|
+
model: nonEmptyString.optional()
|
|
38
|
+
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided");
|
|
39
|
+
var PromptUpdateSchema = z.object({
|
|
40
|
+
prompt: z.string()
|
|
41
|
+
}).strict();
|
|
42
|
+
var AutomationRunCreateSchema = z.object({
|
|
43
|
+
automationId: nonEmptyString,
|
|
44
|
+
sessionId: nonEmptyString.nullable().optional(),
|
|
45
|
+
status: AutomationRunStatusSchema.optional(),
|
|
46
|
+
trigger: AutomationRunTriggerSchema,
|
|
47
|
+
scheduledFor: isoString.nullable().optional(),
|
|
48
|
+
startedAt: isoString.nullable().optional(),
|
|
49
|
+
completedAt: isoString.nullable().optional(),
|
|
50
|
+
durationMs: nonNegativeInteger.nullable().optional(),
|
|
51
|
+
inputTokens: nonNegativeInteger.nullable().optional(),
|
|
52
|
+
outputTokens: nonNegativeInteger.nullable().optional(),
|
|
53
|
+
totalTokens: nonNegativeInteger.nullable().optional(),
|
|
54
|
+
promptSnapshot: z.string(),
|
|
55
|
+
modelSnapshot: nonEmptyString,
|
|
56
|
+
error: z.string().nullable().optional()
|
|
57
|
+
}).strict();
|
|
58
|
+
var AutomationRunPatchSchema = z.object({
|
|
59
|
+
sessionId: nonEmptyString.nullable().optional(),
|
|
60
|
+
status: AutomationRunStatusSchema.optional(),
|
|
61
|
+
scheduledFor: isoString.nullable().optional(),
|
|
62
|
+
startedAt: isoString.nullable().optional(),
|
|
63
|
+
completedAt: isoString.nullable().optional(),
|
|
64
|
+
durationMs: nonNegativeInteger.nullable().optional(),
|
|
65
|
+
inputTokens: nonNegativeInteger.nullable().optional(),
|
|
66
|
+
outputTokens: nonNegativeInteger.nullable().optional(),
|
|
67
|
+
totalTokens: nonNegativeInteger.nullable().optional(),
|
|
68
|
+
error: z.string().nullable().optional()
|
|
69
|
+
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided");
|
|
70
|
+
var IdParamsSchema = z.object({ id: nonEmptyString });
|
|
71
|
+
|
|
72
|
+
// src/server/fileStore.ts
|
|
73
|
+
import { randomUUID } from "crypto";
|
|
74
|
+
import { mkdir, readFile, rename, writeFile } from "fs/promises";
|
|
75
|
+
import { dirname, join } from "path";
|
|
76
|
+
|
|
77
|
+
// src/server/store.ts
|
|
78
|
+
var AutomationStoreError = class extends Error {
|
|
79
|
+
constructor(code, message) {
|
|
80
|
+
super(message);
|
|
81
|
+
this.code = code;
|
|
82
|
+
}
|
|
83
|
+
code;
|
|
84
|
+
};
|
|
85
|
+
function automationNotFound(id) {
|
|
86
|
+
return new AutomationStoreError(BORING_AUTOMATION_ERROR_CODES.AUTOMATION_NOT_FOUND, `automation ${id} not found`);
|
|
87
|
+
}
|
|
88
|
+
function runNotFound(id) {
|
|
89
|
+
return new AutomationStoreError(BORING_AUTOMATION_ERROR_CODES.RUN_NOT_FOUND, `automation run ${id} not found`);
|
|
90
|
+
}
|
|
91
|
+
|
|
92
|
+
// src/server/fileStore.ts
|
|
93
|
+
var EMPTY_STATE = {
|
|
94
|
+
automations: {},
|
|
95
|
+
runs: {}
|
|
96
|
+
};
|
|
97
|
+
var SAFE_PROMPT_ID = /^[a-zA-Z0-9_-]+$/;
|
|
98
|
+
var DEFAULT_PROMPT = "";
|
|
99
|
+
var FileAutomationStore = class {
|
|
100
|
+
constructor(rootDir, options = {}) {
|
|
101
|
+
this.rootDir = rootDir;
|
|
102
|
+
this.writer = options.writer ?? writeAtomic;
|
|
103
|
+
}
|
|
104
|
+
rootDir;
|
|
105
|
+
state = null;
|
|
106
|
+
loadInFlight = null;
|
|
107
|
+
writeChain = Promise.resolve();
|
|
108
|
+
writer;
|
|
109
|
+
async listAutomations() {
|
|
110
|
+
const state = await this.load();
|
|
111
|
+
return Object.values(state.automations).sort((a, b) => b.updatedAt.localeCompare(a.updatedAt)).map(clone);
|
|
112
|
+
}
|
|
113
|
+
async getAutomation(id) {
|
|
114
|
+
const state = await this.load();
|
|
115
|
+
const automation = state.automations[id];
|
|
116
|
+
return automation ? clone(automation) : null;
|
|
117
|
+
}
|
|
118
|
+
async createAutomation(input) {
|
|
119
|
+
const now = nowIso();
|
|
120
|
+
const id = randomUUID();
|
|
121
|
+
const automation = {
|
|
122
|
+
id,
|
|
123
|
+
title: input.title,
|
|
124
|
+
enabled: input.enabled ?? true,
|
|
125
|
+
cron: input.cron,
|
|
126
|
+
timezone: input.timezone,
|
|
127
|
+
model: input.model,
|
|
128
|
+
promptRef: promptRefForId(id),
|
|
129
|
+
createdAt: now,
|
|
130
|
+
updatedAt: now
|
|
131
|
+
};
|
|
132
|
+
await this.writePromptFile(automation.id, input.prompt ?? DEFAULT_PROMPT);
|
|
133
|
+
await this.mutate((state) => {
|
|
134
|
+
state.automations[automation.id] = clone(automation);
|
|
135
|
+
});
|
|
136
|
+
return clone(automation);
|
|
137
|
+
}
|
|
138
|
+
async updateAutomation(id, patch) {
|
|
139
|
+
let updated;
|
|
140
|
+
await this.mutate((state) => {
|
|
141
|
+
const automation = state.automations[id];
|
|
142
|
+
if (!automation) throw automationNotFound(id);
|
|
143
|
+
updated = {
|
|
144
|
+
...automation,
|
|
145
|
+
...patch,
|
|
146
|
+
id: automation.id,
|
|
147
|
+
promptRef: automation.promptRef,
|
|
148
|
+
createdAt: automation.createdAt,
|
|
149
|
+
updatedAt: nowIso()
|
|
150
|
+
};
|
|
151
|
+
state.automations[id] = updated;
|
|
152
|
+
});
|
|
153
|
+
return clone(requireValue(updated));
|
|
154
|
+
}
|
|
155
|
+
async deleteAutomation(id) {
|
|
156
|
+
await this.mutate((state) => {
|
|
157
|
+
if (!state.automations[id]) throw automationNotFound(id);
|
|
158
|
+
delete state.automations[id];
|
|
159
|
+
});
|
|
160
|
+
}
|
|
161
|
+
async getPrompt(automationId) {
|
|
162
|
+
const automation = await this.getAutomation(automationId);
|
|
163
|
+
if (!automation) throw automationNotFound(automationId);
|
|
164
|
+
try {
|
|
165
|
+
return await readFile(this.promptPath(automationId), "utf8");
|
|
166
|
+
} catch (error) {
|
|
167
|
+
if (error.code === "ENOENT") return DEFAULT_PROMPT;
|
|
168
|
+
throw error;
|
|
169
|
+
}
|
|
170
|
+
}
|
|
171
|
+
async updatePrompt(automationId, body) {
|
|
172
|
+
const automation = await this.getAutomation(automationId);
|
|
173
|
+
if (!automation) throw automationNotFound(automationId);
|
|
174
|
+
await this.writePromptFile(automationId, body);
|
|
175
|
+
await this.mutate((state) => {
|
|
176
|
+
const current = state.automations[automationId];
|
|
177
|
+
if (!current) throw automationNotFound(automationId);
|
|
178
|
+
current.updatedAt = nowIso();
|
|
179
|
+
});
|
|
180
|
+
}
|
|
181
|
+
async createRun(input) {
|
|
182
|
+
const now = nowIso();
|
|
183
|
+
let run;
|
|
184
|
+
await this.mutate((state) => {
|
|
185
|
+
if (!state.automations[input.automationId]) throw automationNotFound(input.automationId);
|
|
186
|
+
run = {
|
|
187
|
+
id: randomUUID(),
|
|
188
|
+
automationId: input.automationId,
|
|
189
|
+
sessionId: input.sessionId ?? null,
|
|
190
|
+
status: input.status ?? "queued",
|
|
191
|
+
trigger: input.trigger,
|
|
192
|
+
scheduledFor: input.scheduledFor ?? null,
|
|
193
|
+
startedAt: input.startedAt ?? null,
|
|
194
|
+
completedAt: input.completedAt ?? null,
|
|
195
|
+
durationMs: input.durationMs ?? null,
|
|
196
|
+
inputTokens: input.inputTokens ?? null,
|
|
197
|
+
outputTokens: input.outputTokens ?? null,
|
|
198
|
+
totalTokens: input.totalTokens ?? null,
|
|
199
|
+
promptSnapshot: input.promptSnapshot,
|
|
200
|
+
modelSnapshot: input.modelSnapshot,
|
|
201
|
+
error: input.error ?? null,
|
|
202
|
+
createdAt: now,
|
|
203
|
+
updatedAt: now
|
|
204
|
+
};
|
|
205
|
+
state.runs[run.id] = clone(run);
|
|
206
|
+
});
|
|
207
|
+
return clone(requireValue(run));
|
|
208
|
+
}
|
|
209
|
+
async updateRun(runId, patch) {
|
|
210
|
+
let updated;
|
|
211
|
+
await this.mutate((state) => {
|
|
212
|
+
const run = state.runs[runId];
|
|
213
|
+
if (!run) throw runNotFound(runId);
|
|
214
|
+
updated = applyRunPatch(run, patch);
|
|
215
|
+
state.runs[runId] = updated;
|
|
216
|
+
});
|
|
217
|
+
return clone(requireValue(updated));
|
|
218
|
+
}
|
|
219
|
+
async listRuns(automationId) {
|
|
220
|
+
const automation = await this.getAutomation(automationId);
|
|
221
|
+
if (!automation) throw automationNotFound(automationId);
|
|
222
|
+
const state = await this.load();
|
|
223
|
+
return Object.values(state.runs).filter((run) => run.automationId === automationId).sort((a, b) => runSortTimestamp(b).localeCompare(runSortTimestamp(a))).map(clone);
|
|
224
|
+
}
|
|
225
|
+
async findRunningRun(automationId) {
|
|
226
|
+
const runs = await this.listRuns(automationId);
|
|
227
|
+
return runs.find((run) => run.status === "running") ?? null;
|
|
228
|
+
}
|
|
229
|
+
statePath() {
|
|
230
|
+
return join(this.rootDir, "store.json");
|
|
231
|
+
}
|
|
232
|
+
promptPath(automationId) {
|
|
233
|
+
if (!SAFE_PROMPT_ID.test(automationId)) throw automationNotFound(automationId);
|
|
234
|
+
return join(this.rootDir, "prompts", `${automationId}.md`);
|
|
235
|
+
}
|
|
236
|
+
async writePromptFile(automationId, body) {
|
|
237
|
+
await this.writer(this.promptPath(automationId), body);
|
|
238
|
+
}
|
|
239
|
+
async mutate(fn) {
|
|
240
|
+
const run = this.writeChain.then(async () => {
|
|
241
|
+
const state = clone(await this.load());
|
|
242
|
+
await fn(state);
|
|
243
|
+
await this.writer(this.statePath(), `${JSON.stringify(state, null, 2)}
|
|
244
|
+
`);
|
|
245
|
+
this.state = state;
|
|
246
|
+
});
|
|
247
|
+
this.writeChain = run.catch(() => void 0);
|
|
248
|
+
return run;
|
|
249
|
+
}
|
|
250
|
+
async load() {
|
|
251
|
+
if (this.state) return this.state;
|
|
252
|
+
if (!this.loadInFlight) {
|
|
253
|
+
this.loadInFlight = (async () => {
|
|
254
|
+
try {
|
|
255
|
+
const raw = await readFile(this.statePath(), "utf8");
|
|
256
|
+
const parsed = JSON.parse(raw);
|
|
257
|
+
this.state = {
|
|
258
|
+
automations: parsed.automations && typeof parsed.automations === "object" ? parsed.automations : {},
|
|
259
|
+
runs: parsed.runs && typeof parsed.runs === "object" ? parsed.runs : {}
|
|
260
|
+
};
|
|
261
|
+
} catch (error) {
|
|
262
|
+
if (error.code !== "ENOENT") throw error;
|
|
263
|
+
this.state = clone(EMPTY_STATE);
|
|
264
|
+
}
|
|
265
|
+
return this.state;
|
|
266
|
+
})().finally(() => {
|
|
267
|
+
this.loadInFlight = null;
|
|
268
|
+
});
|
|
269
|
+
}
|
|
270
|
+
return this.loadInFlight;
|
|
271
|
+
}
|
|
272
|
+
};
|
|
273
|
+
function promptRefForId(id) {
|
|
274
|
+
return `prompts/${id}.md`;
|
|
275
|
+
}
|
|
276
|
+
function applyRunPatch(run, patch) {
|
|
277
|
+
const next = { ...run, updatedAt: nowIso() };
|
|
278
|
+
for (const [key, value] of Object.entries(patch)) {
|
|
279
|
+
if (value !== void 0) next[key] = value;
|
|
280
|
+
}
|
|
281
|
+
return next;
|
|
282
|
+
}
|
|
283
|
+
function runSortTimestamp(run) {
|
|
284
|
+
return run.startedAt ?? run.scheduledFor ?? run.createdAt;
|
|
285
|
+
}
|
|
286
|
+
async function writeAtomic(path, content) {
|
|
287
|
+
await mkdir(dirname(path), { recursive: true });
|
|
288
|
+
const tmp = join(dirname(path), `.${randomUUID()}.tmp`);
|
|
289
|
+
await writeFile(tmp, content, "utf8");
|
|
290
|
+
await rename(tmp, path);
|
|
291
|
+
}
|
|
292
|
+
function nowIso() {
|
|
293
|
+
return (/* @__PURE__ */ new Date()).toISOString();
|
|
294
|
+
}
|
|
295
|
+
function requireValue(value) {
|
|
296
|
+
if (value === void 0) throw new Error("expected automation store mutation to produce a value");
|
|
297
|
+
return value;
|
|
298
|
+
}
|
|
299
|
+
function clone(value) {
|
|
300
|
+
return JSON.parse(JSON.stringify(value));
|
|
301
|
+
}
|
|
302
|
+
|
|
303
|
+
// src/server/routes.ts
|
|
304
|
+
import { ZodError } from "zod";
|
|
305
|
+
async function automationRoutes(app, opts) {
|
|
306
|
+
app.get(`${BORING_AUTOMATION_ROUTE_PREFIX}/automations`, async (_request, reply) => {
|
|
307
|
+
try {
|
|
308
|
+
return { ok: true, automations: await opts.store.listAutomations() };
|
|
309
|
+
} catch (cause) {
|
|
310
|
+
return sendError(reply, cause);
|
|
311
|
+
}
|
|
312
|
+
});
|
|
313
|
+
app.post(`${BORING_AUTOMATION_ROUTE_PREFIX}/automations`, async (request, reply) => {
|
|
314
|
+
try {
|
|
315
|
+
const automation = await opts.store.createAutomation(parseBody(AutomationCreateSchema, request.body));
|
|
316
|
+
return reply.status(201).send({ ok: true, automation });
|
|
317
|
+
} catch (cause) {
|
|
318
|
+
return sendError(reply, cause);
|
|
319
|
+
}
|
|
320
|
+
});
|
|
321
|
+
app.get(`${BORING_AUTOMATION_ROUTE_PREFIX}/automations/:id`, async (request, reply) => {
|
|
322
|
+
try {
|
|
323
|
+
const { id } = parseParams(IdParamsSchema, request.params);
|
|
324
|
+
const automation = await opts.store.getAutomation(id);
|
|
325
|
+
if (!automation) throw automationNotFound(id);
|
|
326
|
+
return { ok: true, automation };
|
|
327
|
+
} catch (cause) {
|
|
328
|
+
return sendError(reply, cause);
|
|
329
|
+
}
|
|
330
|
+
});
|
|
331
|
+
app.patch(`${BORING_AUTOMATION_ROUTE_PREFIX}/automations/:id`, async (request, reply) => {
|
|
332
|
+
try {
|
|
333
|
+
const { id } = parseParams(IdParamsSchema, request.params);
|
|
334
|
+
const automation = await opts.store.updateAutomation(id, parseBody(AutomationPatchSchema, request.body));
|
|
335
|
+
return { ok: true, automation };
|
|
336
|
+
} catch (cause) {
|
|
337
|
+
return sendError(reply, cause);
|
|
338
|
+
}
|
|
339
|
+
});
|
|
340
|
+
app.delete(`${BORING_AUTOMATION_ROUTE_PREFIX}/automations/:id`, async (request, reply) => {
|
|
341
|
+
try {
|
|
342
|
+
const { id } = parseParams(IdParamsSchema, request.params);
|
|
343
|
+
await opts.store.deleteAutomation(id);
|
|
344
|
+
return reply.status(204).send();
|
|
345
|
+
} catch (cause) {
|
|
346
|
+
return sendError(reply, cause);
|
|
347
|
+
}
|
|
348
|
+
});
|
|
349
|
+
app.get(`${BORING_AUTOMATION_ROUTE_PREFIX}/automations/:id/prompt`, async (request, reply) => {
|
|
350
|
+
try {
|
|
351
|
+
const { id } = parseParams(IdParamsSchema, request.params);
|
|
352
|
+
return { ok: true, prompt: await opts.store.getPrompt(id) };
|
|
353
|
+
} catch (cause) {
|
|
354
|
+
return sendError(reply, cause);
|
|
355
|
+
}
|
|
356
|
+
});
|
|
357
|
+
app.put(`${BORING_AUTOMATION_ROUTE_PREFIX}/automations/:id/prompt`, async (request, reply) => {
|
|
358
|
+
try {
|
|
359
|
+
const { id } = parseParams(IdParamsSchema, request.params);
|
|
360
|
+
const { prompt } = parseBody(PromptUpdateSchema, request.body);
|
|
361
|
+
await opts.store.updatePrompt(id, prompt);
|
|
362
|
+
return { ok: true };
|
|
363
|
+
} catch (cause) {
|
|
364
|
+
return sendError(reply, cause);
|
|
365
|
+
}
|
|
366
|
+
});
|
|
367
|
+
app.get(`${BORING_AUTOMATION_ROUTE_PREFIX}/automations/:id/runs`, async (request, reply) => {
|
|
368
|
+
try {
|
|
369
|
+
const { id } = parseParams(IdParamsSchema, request.params);
|
|
370
|
+
return { ok: true, runs: await opts.store.listRuns(id) };
|
|
371
|
+
} catch (cause) {
|
|
372
|
+
return sendError(reply, cause);
|
|
373
|
+
}
|
|
374
|
+
});
|
|
375
|
+
}
|
|
376
|
+
function parseBody(schema, body) {
|
|
377
|
+
return parse(schema, body);
|
|
378
|
+
}
|
|
379
|
+
function parseParams(schema, params) {
|
|
380
|
+
return parse(schema, params);
|
|
381
|
+
}
|
|
382
|
+
function parse(schema, value) {
|
|
383
|
+
const parsed = schema.safeParse(value);
|
|
384
|
+
if (!parsed.success) throw parsed.error;
|
|
385
|
+
return parsed.data;
|
|
386
|
+
}
|
|
387
|
+
function sendError(reply, cause) {
|
|
388
|
+
if (cause instanceof ZodError) {
|
|
389
|
+
return reply.status(400).send({
|
|
390
|
+
ok: false,
|
|
391
|
+
code: BORING_AUTOMATION_ERROR_CODES.INVALID_BODY,
|
|
392
|
+
error: cause.issues[0]?.message ?? "invalid request"
|
|
393
|
+
});
|
|
394
|
+
}
|
|
395
|
+
if (cause instanceof AutomationStoreError) {
|
|
396
|
+
return reply.status(httpStatusForStoreError(cause)).send({ ok: false, code: cause.code, error: cause.message });
|
|
397
|
+
}
|
|
398
|
+
throw cause;
|
|
399
|
+
}
|
|
400
|
+
function httpStatusForStoreError(error) {
|
|
401
|
+
switch (error.code) {
|
|
402
|
+
case BORING_AUTOMATION_ERROR_CODES.INVALID_BODY:
|
|
403
|
+
return 400;
|
|
404
|
+
case BORING_AUTOMATION_ERROR_CODES.AUTOMATION_NOT_FOUND:
|
|
405
|
+
case BORING_AUTOMATION_ERROR_CODES.RUN_NOT_FOUND:
|
|
406
|
+
return 404;
|
|
407
|
+
}
|
|
408
|
+
}
|
|
409
|
+
|
|
410
|
+
// src/server/index.ts
|
|
411
|
+
function createBoringAutomationServerPlugin(options = {}) {
|
|
412
|
+
const store = options.store ?? createDefaultStore(options.workspaceRoot);
|
|
413
|
+
return defineServerPlugin({
|
|
414
|
+
id: BORING_AUTOMATION_PLUGIN_ID,
|
|
415
|
+
label: BORING_AUTOMATION_PLUGIN_LABEL,
|
|
416
|
+
routes: async (app) => {
|
|
417
|
+
await automationRoutes(app, { store });
|
|
418
|
+
}
|
|
419
|
+
});
|
|
420
|
+
}
|
|
421
|
+
function createDefaultStore(workspaceRoot) {
|
|
422
|
+
if (!workspaceRoot) throw new Error("createBoringAutomationServerPlugin requires workspaceRoot when store is not provided");
|
|
423
|
+
return new FileAutomationStore(join2(workspaceRoot, ".pi", "automation"));
|
|
424
|
+
}
|
|
425
|
+
function defaultBoringAutomationServerPlugin(options, ctx) {
|
|
426
|
+
return createBoringAutomationServerPlugin({
|
|
427
|
+
...options,
|
|
428
|
+
workspaceRoot: options?.workspaceRoot ?? ctx?.workspaceRoot
|
|
429
|
+
});
|
|
430
|
+
}
|
|
431
|
+
export {
|
|
432
|
+
AutomationCreateSchema,
|
|
433
|
+
AutomationPatchSchema,
|
|
434
|
+
AutomationRunCreateSchema,
|
|
435
|
+
AutomationRunPatchSchema,
|
|
436
|
+
AutomationRunStatusSchema,
|
|
437
|
+
AutomationRunTriggerSchema,
|
|
438
|
+
AutomationStoreError,
|
|
439
|
+
BORING_AUTOMATION_ERROR_CODES,
|
|
440
|
+
BORING_AUTOMATION_PLUGIN_ID,
|
|
441
|
+
BORING_AUTOMATION_PLUGIN_LABEL,
|
|
442
|
+
BORING_AUTOMATION_ROUTE_PREFIX,
|
|
443
|
+
FileAutomationStore,
|
|
444
|
+
IdParamsSchema,
|
|
445
|
+
PromptUpdateSchema,
|
|
446
|
+
automationNotFound,
|
|
447
|
+
automationRoutes,
|
|
448
|
+
createBoringAutomationServerPlugin,
|
|
449
|
+
defaultBoringAutomationServerPlugin as default,
|
|
450
|
+
runNotFound
|
|
451
|
+
};
|
|
@@ -0,0 +1,266 @@
|
|
|
1
|
+
import { z } from 'zod';
|
|
2
|
+
|
|
3
|
+
declare const BORING_AUTOMATION_PLUGIN_ID = "boring-automation";
|
|
4
|
+
declare const BORING_AUTOMATION_PLUGIN_LABEL = "Automations";
|
|
5
|
+
declare const BORING_AUTOMATION_ROUTE_PREFIX = "/api/v1/boring-automation";
|
|
6
|
+
|
|
7
|
+
declare const BORING_AUTOMATION_ERROR_CODES: {
|
|
8
|
+
readonly INVALID_BODY: "BORING_AUTOMATION_INVALID_BODY";
|
|
9
|
+
readonly AUTOMATION_NOT_FOUND: "BORING_AUTOMATION_NOT_FOUND";
|
|
10
|
+
readonly RUN_NOT_FOUND: "BORING_AUTOMATION_RUN_NOT_FOUND";
|
|
11
|
+
};
|
|
12
|
+
type BoringAutomationErrorCode = typeof BORING_AUTOMATION_ERROR_CODES[keyof typeof BORING_AUTOMATION_ERROR_CODES];
|
|
13
|
+
|
|
14
|
+
declare const AutomationRunStatusSchema: z.ZodEnum<["queued", "running", "succeeded", "failed", "cancelled"]>;
|
|
15
|
+
declare const AutomationRunTriggerSchema: z.ZodEnum<["manual", "scheduled"]>;
|
|
16
|
+
declare const AutomationCreateSchema: z.ZodObject<{
|
|
17
|
+
title: z.ZodString;
|
|
18
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
19
|
+
cron: z.ZodString;
|
|
20
|
+
timezone: z.ZodString;
|
|
21
|
+
model: z.ZodString;
|
|
22
|
+
prompt: z.ZodOptional<z.ZodString>;
|
|
23
|
+
}, "strict", z.ZodTypeAny, {
|
|
24
|
+
title: string;
|
|
25
|
+
cron: string;
|
|
26
|
+
timezone: string;
|
|
27
|
+
model: string;
|
|
28
|
+
enabled?: boolean | undefined;
|
|
29
|
+
prompt?: string | undefined;
|
|
30
|
+
}, {
|
|
31
|
+
title: string;
|
|
32
|
+
cron: string;
|
|
33
|
+
timezone: string;
|
|
34
|
+
model: string;
|
|
35
|
+
enabled?: boolean | undefined;
|
|
36
|
+
prompt?: string | undefined;
|
|
37
|
+
}>;
|
|
38
|
+
declare const AutomationPatchSchema: z.ZodEffects<z.ZodObject<{
|
|
39
|
+
title: z.ZodOptional<z.ZodString>;
|
|
40
|
+
enabled: z.ZodOptional<z.ZodBoolean>;
|
|
41
|
+
cron: z.ZodOptional<z.ZodString>;
|
|
42
|
+
timezone: z.ZodOptional<z.ZodString>;
|
|
43
|
+
model: z.ZodOptional<z.ZodString>;
|
|
44
|
+
}, "strict", z.ZodTypeAny, {
|
|
45
|
+
title?: string | undefined;
|
|
46
|
+
enabled?: boolean | undefined;
|
|
47
|
+
cron?: string | undefined;
|
|
48
|
+
timezone?: string | undefined;
|
|
49
|
+
model?: string | undefined;
|
|
50
|
+
}, {
|
|
51
|
+
title?: string | undefined;
|
|
52
|
+
enabled?: boolean | undefined;
|
|
53
|
+
cron?: string | undefined;
|
|
54
|
+
timezone?: string | undefined;
|
|
55
|
+
model?: string | undefined;
|
|
56
|
+
}>, {
|
|
57
|
+
title?: string | undefined;
|
|
58
|
+
enabled?: boolean | undefined;
|
|
59
|
+
cron?: string | undefined;
|
|
60
|
+
timezone?: string | undefined;
|
|
61
|
+
model?: string | undefined;
|
|
62
|
+
}, {
|
|
63
|
+
title?: string | undefined;
|
|
64
|
+
enabled?: boolean | undefined;
|
|
65
|
+
cron?: string | undefined;
|
|
66
|
+
timezone?: string | undefined;
|
|
67
|
+
model?: string | undefined;
|
|
68
|
+
}>;
|
|
69
|
+
declare const PromptUpdateSchema: z.ZodObject<{
|
|
70
|
+
prompt: z.ZodString;
|
|
71
|
+
}, "strict", z.ZodTypeAny, {
|
|
72
|
+
prompt: string;
|
|
73
|
+
}, {
|
|
74
|
+
prompt: string;
|
|
75
|
+
}>;
|
|
76
|
+
declare const AutomationRunCreateSchema: z.ZodObject<{
|
|
77
|
+
automationId: z.ZodString;
|
|
78
|
+
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
79
|
+
status: z.ZodOptional<z.ZodEnum<["queued", "running", "succeeded", "failed", "cancelled"]>>;
|
|
80
|
+
trigger: z.ZodEnum<["manual", "scheduled"]>;
|
|
81
|
+
scheduledFor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
82
|
+
startedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
83
|
+
completedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
84
|
+
durationMs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
85
|
+
inputTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
86
|
+
outputTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
87
|
+
totalTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
88
|
+
promptSnapshot: z.ZodString;
|
|
89
|
+
modelSnapshot: z.ZodString;
|
|
90
|
+
error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
91
|
+
}, "strict", z.ZodTypeAny, {
|
|
92
|
+
automationId: string;
|
|
93
|
+
trigger: "manual" | "scheduled";
|
|
94
|
+
promptSnapshot: string;
|
|
95
|
+
modelSnapshot: string;
|
|
96
|
+
error?: string | null | undefined;
|
|
97
|
+
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
98
|
+
sessionId?: string | null | undefined;
|
|
99
|
+
scheduledFor?: string | null | undefined;
|
|
100
|
+
startedAt?: string | null | undefined;
|
|
101
|
+
completedAt?: string | null | undefined;
|
|
102
|
+
durationMs?: number | null | undefined;
|
|
103
|
+
inputTokens?: number | null | undefined;
|
|
104
|
+
outputTokens?: number | null | undefined;
|
|
105
|
+
totalTokens?: number | null | undefined;
|
|
106
|
+
}, {
|
|
107
|
+
automationId: string;
|
|
108
|
+
trigger: "manual" | "scheduled";
|
|
109
|
+
promptSnapshot: string;
|
|
110
|
+
modelSnapshot: string;
|
|
111
|
+
error?: string | null | undefined;
|
|
112
|
+
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
113
|
+
sessionId?: string | null | undefined;
|
|
114
|
+
scheduledFor?: string | null | undefined;
|
|
115
|
+
startedAt?: string | null | undefined;
|
|
116
|
+
completedAt?: string | null | undefined;
|
|
117
|
+
durationMs?: number | null | undefined;
|
|
118
|
+
inputTokens?: number | null | undefined;
|
|
119
|
+
outputTokens?: number | null | undefined;
|
|
120
|
+
totalTokens?: number | null | undefined;
|
|
121
|
+
}>;
|
|
122
|
+
declare const AutomationRunPatchSchema: z.ZodEffects<z.ZodObject<{
|
|
123
|
+
sessionId: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
124
|
+
status: z.ZodOptional<z.ZodEnum<["queued", "running", "succeeded", "failed", "cancelled"]>>;
|
|
125
|
+
scheduledFor: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
126
|
+
startedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
127
|
+
completedAt: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
128
|
+
durationMs: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
129
|
+
inputTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
130
|
+
outputTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
131
|
+
totalTokens: z.ZodOptional<z.ZodNullable<z.ZodNumber>>;
|
|
132
|
+
error: z.ZodOptional<z.ZodNullable<z.ZodString>>;
|
|
133
|
+
}, "strict", z.ZodTypeAny, {
|
|
134
|
+
error?: string | null | undefined;
|
|
135
|
+
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
136
|
+
sessionId?: string | null | undefined;
|
|
137
|
+
scheduledFor?: string | null | undefined;
|
|
138
|
+
startedAt?: string | null | undefined;
|
|
139
|
+
completedAt?: string | null | undefined;
|
|
140
|
+
durationMs?: number | null | undefined;
|
|
141
|
+
inputTokens?: number | null | undefined;
|
|
142
|
+
outputTokens?: number | null | undefined;
|
|
143
|
+
totalTokens?: number | null | undefined;
|
|
144
|
+
}, {
|
|
145
|
+
error?: string | null | undefined;
|
|
146
|
+
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
147
|
+
sessionId?: string | null | undefined;
|
|
148
|
+
scheduledFor?: string | null | undefined;
|
|
149
|
+
startedAt?: string | null | undefined;
|
|
150
|
+
completedAt?: string | null | undefined;
|
|
151
|
+
durationMs?: number | null | undefined;
|
|
152
|
+
inputTokens?: number | null | undefined;
|
|
153
|
+
outputTokens?: number | null | undefined;
|
|
154
|
+
totalTokens?: number | null | undefined;
|
|
155
|
+
}>, {
|
|
156
|
+
error?: string | null | undefined;
|
|
157
|
+
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
158
|
+
sessionId?: string | null | undefined;
|
|
159
|
+
scheduledFor?: string | null | undefined;
|
|
160
|
+
startedAt?: string | null | undefined;
|
|
161
|
+
completedAt?: string | null | undefined;
|
|
162
|
+
durationMs?: number | null | undefined;
|
|
163
|
+
inputTokens?: number | null | undefined;
|
|
164
|
+
outputTokens?: number | null | undefined;
|
|
165
|
+
totalTokens?: number | null | undefined;
|
|
166
|
+
}, {
|
|
167
|
+
error?: string | null | undefined;
|
|
168
|
+
status?: "queued" | "running" | "succeeded" | "failed" | "cancelled" | undefined;
|
|
169
|
+
sessionId?: string | null | undefined;
|
|
170
|
+
scheduledFor?: string | null | undefined;
|
|
171
|
+
startedAt?: string | null | undefined;
|
|
172
|
+
completedAt?: string | null | undefined;
|
|
173
|
+
durationMs?: number | null | undefined;
|
|
174
|
+
inputTokens?: number | null | undefined;
|
|
175
|
+
outputTokens?: number | null | undefined;
|
|
176
|
+
totalTokens?: number | null | undefined;
|
|
177
|
+
}>;
|
|
178
|
+
declare const IdParamsSchema: z.ZodObject<{
|
|
179
|
+
id: z.ZodString;
|
|
180
|
+
}, "strip", z.ZodTypeAny, {
|
|
181
|
+
id: string;
|
|
182
|
+
}, {
|
|
183
|
+
id: string;
|
|
184
|
+
}>;
|
|
185
|
+
type AutomationCreateInput = z.infer<typeof AutomationCreateSchema>;
|
|
186
|
+
type AutomationPatchInput = z.infer<typeof AutomationPatchSchema>;
|
|
187
|
+
type AutomationRunCreateInput = z.infer<typeof AutomationRunCreateSchema>;
|
|
188
|
+
type AutomationRunPatchInput = z.infer<typeof AutomationRunPatchSchema>;
|
|
189
|
+
|
|
190
|
+
type AutomationRunStatus = "queued" | "running" | "succeeded" | "failed" | "cancelled";
|
|
191
|
+
type AutomationRunTrigger = "manual" | "scheduled";
|
|
192
|
+
interface Automation {
|
|
193
|
+
id: string;
|
|
194
|
+
title: string;
|
|
195
|
+
enabled: boolean;
|
|
196
|
+
cron: string;
|
|
197
|
+
timezone: string;
|
|
198
|
+
model: string;
|
|
199
|
+
promptRef: string;
|
|
200
|
+
createdAt: string;
|
|
201
|
+
updatedAt: string;
|
|
202
|
+
}
|
|
203
|
+
interface AutomationCreate {
|
|
204
|
+
title: string;
|
|
205
|
+
enabled?: boolean;
|
|
206
|
+
cron: string;
|
|
207
|
+
timezone: string;
|
|
208
|
+
model: string;
|
|
209
|
+
prompt?: string;
|
|
210
|
+
}
|
|
211
|
+
interface AutomationPatch {
|
|
212
|
+
title?: string;
|
|
213
|
+
enabled?: boolean;
|
|
214
|
+
cron?: string;
|
|
215
|
+
timezone?: string;
|
|
216
|
+
model?: string;
|
|
217
|
+
}
|
|
218
|
+
interface AutomationRun {
|
|
219
|
+
id: string;
|
|
220
|
+
automationId: string;
|
|
221
|
+
sessionId: string | null;
|
|
222
|
+
status: AutomationRunStatus;
|
|
223
|
+
trigger: AutomationRunTrigger;
|
|
224
|
+
scheduledFor: string | null;
|
|
225
|
+
startedAt: string | null;
|
|
226
|
+
completedAt: string | null;
|
|
227
|
+
durationMs: number | null;
|
|
228
|
+
inputTokens: number | null;
|
|
229
|
+
outputTokens: number | null;
|
|
230
|
+
totalTokens: number | null;
|
|
231
|
+
promptSnapshot: string;
|
|
232
|
+
modelSnapshot: string;
|
|
233
|
+
error: string | null;
|
|
234
|
+
createdAt: string;
|
|
235
|
+
updatedAt: string;
|
|
236
|
+
}
|
|
237
|
+
interface AutomationRunCreate {
|
|
238
|
+
automationId: string;
|
|
239
|
+
sessionId?: string | null;
|
|
240
|
+
status?: AutomationRunStatus;
|
|
241
|
+
trigger: AutomationRunTrigger;
|
|
242
|
+
scheduledFor?: string | null;
|
|
243
|
+
startedAt?: string | null;
|
|
244
|
+
completedAt?: string | null;
|
|
245
|
+
durationMs?: number | null;
|
|
246
|
+
inputTokens?: number | null;
|
|
247
|
+
outputTokens?: number | null;
|
|
248
|
+
totalTokens?: number | null;
|
|
249
|
+
promptSnapshot: string;
|
|
250
|
+
modelSnapshot: string;
|
|
251
|
+
error?: string | null;
|
|
252
|
+
}
|
|
253
|
+
interface AutomationRunPatch {
|
|
254
|
+
sessionId?: string | null;
|
|
255
|
+
status?: AutomationRunStatus;
|
|
256
|
+
scheduledFor?: string | null;
|
|
257
|
+
startedAt?: string | null;
|
|
258
|
+
completedAt?: string | null;
|
|
259
|
+
durationMs?: number | null;
|
|
260
|
+
inputTokens?: number | null;
|
|
261
|
+
outputTokens?: number | null;
|
|
262
|
+
totalTokens?: number | null;
|
|
263
|
+
error?: string | null;
|
|
264
|
+
}
|
|
265
|
+
|
|
266
|
+
export { type Automation, type AutomationCreate, type AutomationCreateInput, AutomationCreateSchema, type AutomationPatch, type AutomationPatchInput, AutomationPatchSchema, type AutomationRun, type AutomationRunCreate, type AutomationRunCreateInput, AutomationRunCreateSchema, type AutomationRunPatch, type AutomationRunPatchInput, AutomationRunPatchSchema, type AutomationRunStatus, AutomationRunStatusSchema, type AutomationRunTrigger, AutomationRunTriggerSchema, BORING_AUTOMATION_ERROR_CODES, BORING_AUTOMATION_PLUGIN_ID, BORING_AUTOMATION_PLUGIN_LABEL, BORING_AUTOMATION_ROUTE_PREFIX, type BoringAutomationErrorCode, IdParamsSchema, PromptUpdateSchema };
|
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
// src/shared/constants.ts
|
|
2
|
+
var BORING_AUTOMATION_PLUGIN_ID = "boring-automation";
|
|
3
|
+
var BORING_AUTOMATION_PLUGIN_LABEL = "Automations";
|
|
4
|
+
var BORING_AUTOMATION_ROUTE_PREFIX = "/api/v1/boring-automation";
|
|
5
|
+
|
|
6
|
+
// src/shared/error-codes.ts
|
|
7
|
+
var BORING_AUTOMATION_ERROR_CODES = {
|
|
8
|
+
INVALID_BODY: "BORING_AUTOMATION_INVALID_BODY",
|
|
9
|
+
AUTOMATION_NOT_FOUND: "BORING_AUTOMATION_NOT_FOUND",
|
|
10
|
+
RUN_NOT_FOUND: "BORING_AUTOMATION_RUN_NOT_FOUND"
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
// src/shared/schema.ts
|
|
14
|
+
import { z } from "zod";
|
|
15
|
+
var nonEmptyString = z.string().trim().min(1);
|
|
16
|
+
var isoString = z.string().datetime({ offset: true });
|
|
17
|
+
var nonNegativeInteger = z.number().int().nonnegative();
|
|
18
|
+
var AutomationRunStatusSchema = z.enum(["queued", "running", "succeeded", "failed", "cancelled"]);
|
|
19
|
+
var AutomationRunTriggerSchema = z.enum(["manual", "scheduled"]);
|
|
20
|
+
var AutomationCreateSchema = z.object({
|
|
21
|
+
title: nonEmptyString,
|
|
22
|
+
enabled: z.boolean().optional(),
|
|
23
|
+
cron: nonEmptyString,
|
|
24
|
+
timezone: nonEmptyString,
|
|
25
|
+
model: nonEmptyString,
|
|
26
|
+
prompt: z.string().optional()
|
|
27
|
+
}).strict();
|
|
28
|
+
var AutomationPatchSchema = z.object({
|
|
29
|
+
title: nonEmptyString.optional(),
|
|
30
|
+
enabled: z.boolean().optional(),
|
|
31
|
+
cron: nonEmptyString.optional(),
|
|
32
|
+
timezone: nonEmptyString.optional(),
|
|
33
|
+
model: nonEmptyString.optional()
|
|
34
|
+
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided");
|
|
35
|
+
var PromptUpdateSchema = z.object({
|
|
36
|
+
prompt: z.string()
|
|
37
|
+
}).strict();
|
|
38
|
+
var AutomationRunCreateSchema = z.object({
|
|
39
|
+
automationId: nonEmptyString,
|
|
40
|
+
sessionId: nonEmptyString.nullable().optional(),
|
|
41
|
+
status: AutomationRunStatusSchema.optional(),
|
|
42
|
+
trigger: AutomationRunTriggerSchema,
|
|
43
|
+
scheduledFor: isoString.nullable().optional(),
|
|
44
|
+
startedAt: isoString.nullable().optional(),
|
|
45
|
+
completedAt: isoString.nullable().optional(),
|
|
46
|
+
durationMs: nonNegativeInteger.nullable().optional(),
|
|
47
|
+
inputTokens: nonNegativeInteger.nullable().optional(),
|
|
48
|
+
outputTokens: nonNegativeInteger.nullable().optional(),
|
|
49
|
+
totalTokens: nonNegativeInteger.nullable().optional(),
|
|
50
|
+
promptSnapshot: z.string(),
|
|
51
|
+
modelSnapshot: nonEmptyString,
|
|
52
|
+
error: z.string().nullable().optional()
|
|
53
|
+
}).strict();
|
|
54
|
+
var AutomationRunPatchSchema = z.object({
|
|
55
|
+
sessionId: nonEmptyString.nullable().optional(),
|
|
56
|
+
status: AutomationRunStatusSchema.optional(),
|
|
57
|
+
scheduledFor: isoString.nullable().optional(),
|
|
58
|
+
startedAt: isoString.nullable().optional(),
|
|
59
|
+
completedAt: isoString.nullable().optional(),
|
|
60
|
+
durationMs: nonNegativeInteger.nullable().optional(),
|
|
61
|
+
inputTokens: nonNegativeInteger.nullable().optional(),
|
|
62
|
+
outputTokens: nonNegativeInteger.nullable().optional(),
|
|
63
|
+
totalTokens: nonNegativeInteger.nullable().optional(),
|
|
64
|
+
error: z.string().nullable().optional()
|
|
65
|
+
}).strict().refine((value) => Object.keys(value).length > 0, "at least one field must be provided");
|
|
66
|
+
var IdParamsSchema = z.object({ id: nonEmptyString });
|
|
67
|
+
export {
|
|
68
|
+
AutomationCreateSchema,
|
|
69
|
+
AutomationPatchSchema,
|
|
70
|
+
AutomationRunCreateSchema,
|
|
71
|
+
AutomationRunPatchSchema,
|
|
72
|
+
AutomationRunStatusSchema,
|
|
73
|
+
AutomationRunTriggerSchema,
|
|
74
|
+
BORING_AUTOMATION_ERROR_CODES,
|
|
75
|
+
BORING_AUTOMATION_PLUGIN_ID,
|
|
76
|
+
BORING_AUTOMATION_PLUGIN_LABEL,
|
|
77
|
+
BORING_AUTOMATION_ROUTE_PREFIX,
|
|
78
|
+
IdParamsSchema,
|
|
79
|
+
PromptUpdateSchema
|
|
80
|
+
};
|
package/package.json
ADDED
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@hachej/boring-automation",
|
|
3
|
+
"version": "0.1.71",
|
|
4
|
+
"type": "module",
|
|
5
|
+
"private": false,
|
|
6
|
+
"license": "MIT",
|
|
7
|
+
"description": "Scheduled prompt automation plugin for Boring workspace.",
|
|
8
|
+
"repository": {
|
|
9
|
+
"type": "git",
|
|
10
|
+
"url": "https://github.com/hachej/boring-ui"
|
|
11
|
+
},
|
|
12
|
+
"homepage": "https://github.com/hachej/boring-ui",
|
|
13
|
+
"boring": {
|
|
14
|
+
"id": "boring-automation",
|
|
15
|
+
"label": "Automations",
|
|
16
|
+
"front": "dist/front/index.js",
|
|
17
|
+
"server": "dist/server/index.js"
|
|
18
|
+
},
|
|
19
|
+
"files": [
|
|
20
|
+
"dist"
|
|
21
|
+
],
|
|
22
|
+
"exports": {
|
|
23
|
+
".": {
|
|
24
|
+
"types": "./dist/front/index.d.ts",
|
|
25
|
+
"import": "./dist/front/index.js"
|
|
26
|
+
},
|
|
27
|
+
"./front": {
|
|
28
|
+
"types": "./dist/front/index.d.ts",
|
|
29
|
+
"import": "./dist/front/index.js"
|
|
30
|
+
},
|
|
31
|
+
"./server": {
|
|
32
|
+
"types": "./dist/server/index.d.ts",
|
|
33
|
+
"import": "./dist/server/index.js"
|
|
34
|
+
},
|
|
35
|
+
"./shared": {
|
|
36
|
+
"types": "./dist/shared/index.d.ts",
|
|
37
|
+
"import": "./dist/shared/index.js"
|
|
38
|
+
},
|
|
39
|
+
"./package.json": "./package.json"
|
|
40
|
+
},
|
|
41
|
+
"sideEffects": false,
|
|
42
|
+
"scripts": {
|
|
43
|
+
"build": "tsup",
|
|
44
|
+
"typecheck": "tsc --noEmit",
|
|
45
|
+
"test": "vitest run --no-file-parallelism",
|
|
46
|
+
"lint": "pnpm run typecheck",
|
|
47
|
+
"clean": "rm -rf dist .tsbuildinfo"
|
|
48
|
+
},
|
|
49
|
+
"peerDependencies": {
|
|
50
|
+
"@hachej/boring-workspace": "workspace:*",
|
|
51
|
+
"fastify": "^5.9.0",
|
|
52
|
+
"react": "^18.0.0 || ^19.0.0",
|
|
53
|
+
"react-dom": "^18.0.0 || ^19.0.0"
|
|
54
|
+
},
|
|
55
|
+
"dependencies": {
|
|
56
|
+
"@hachej/boring-ui-kit": "workspace:*",
|
|
57
|
+
"lucide-react": "^1.21.0",
|
|
58
|
+
"zod": "^3.23.0"
|
|
59
|
+
},
|
|
60
|
+
"devDependencies": {
|
|
61
|
+
"@hachej/boring-workspace": "workspace:*",
|
|
62
|
+
"@testing-library/jest-dom": "^6.9.1",
|
|
63
|
+
"@testing-library/react": "^16.3.0",
|
|
64
|
+
"@types/node": "^22.20.0",
|
|
65
|
+
"@types/react": "^19.2.17",
|
|
66
|
+
"@types/react-dom": "^19.0.0",
|
|
67
|
+
"@vitejs/plugin-react": "^4.0.0",
|
|
68
|
+
"fastify": "^5.9.0",
|
|
69
|
+
"jsdom": "^29.1.1",
|
|
70
|
+
"react": "^19.2.7",
|
|
71
|
+
"react-dom": "^19.2.7",
|
|
72
|
+
"tsup": "^8.4.0",
|
|
73
|
+
"typescript": "~6.0.3",
|
|
74
|
+
"vitest": "^4.1.9"
|
|
75
|
+
},
|
|
76
|
+
"peerDependenciesMeta": {
|
|
77
|
+
"fastify": {
|
|
78
|
+
"optional": true
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
}
|