@houston-ai/routines 0.2.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 +42 -0
- package/package.json +22 -0
- package/src/heartbeat-config.tsx +184 -0
- package/src/index.ts +42 -0
- package/src/routine-card.tsx +87 -0
- package/src/routine-detail-actions.tsx +104 -0
- package/src/routine-detail-page.tsx +112 -0
- package/src/routine-edit-form.tsx +158 -0
- package/src/routine-run-history.tsx +87 -0
- package/src/routine-run-page.tsx +95 -0
- package/src/routines-grid.tsx +80 -0
- package/src/schedule-builder.tsx +144 -0
- package/src/schedule-cron-utils.ts +122 -0
- package/src/schedule-picker-fields.tsx +118 -0
- package/src/styles.css +1 -0
- package/src/types.ts +105 -0
|
@@ -0,0 +1,118 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Picker fields used by ScheduleBuilder — time, day-of-week, day-of-month.
|
|
3
|
+
*/
|
|
4
|
+
import { cn } from "@houston-ai/core"
|
|
5
|
+
|
|
6
|
+
const inputClass = cn(
|
|
7
|
+
"px-3 py-2 rounded-xl border border-border bg-background",
|
|
8
|
+
"text-sm text-foreground",
|
|
9
|
+
"focus:outline-none focus:border-border/80 transition-colors",
|
|
10
|
+
)
|
|
11
|
+
|
|
12
|
+
const labelClass = "text-xs font-medium text-muted-foreground mb-1.5 block"
|
|
13
|
+
|
|
14
|
+
const DAYS_OF_WEEK = [
|
|
15
|
+
{ value: 0, label: "Sun" },
|
|
16
|
+
{ value: 1, label: "Mon" },
|
|
17
|
+
{ value: 2, label: "Tue" },
|
|
18
|
+
{ value: 3, label: "Wed" },
|
|
19
|
+
{ value: 4, label: "Thu" },
|
|
20
|
+
{ value: 5, label: "Fri" },
|
|
21
|
+
{ value: 6, label: "Sat" },
|
|
22
|
+
]
|
|
23
|
+
|
|
24
|
+
export function TimePicker({
|
|
25
|
+
value,
|
|
26
|
+
onChange,
|
|
27
|
+
}: {
|
|
28
|
+
value: string
|
|
29
|
+
onChange: (time: string) => void
|
|
30
|
+
}) {
|
|
31
|
+
return (
|
|
32
|
+
<div>
|
|
33
|
+
<label className={labelClass}>Time</label>
|
|
34
|
+
<input
|
|
35
|
+
type="time"
|
|
36
|
+
value={value}
|
|
37
|
+
onChange={(e) => onChange(e.target.value)}
|
|
38
|
+
className={cn(inputClass, "w-full")}
|
|
39
|
+
/>
|
|
40
|
+
</div>
|
|
41
|
+
)
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
export function DayOfWeekPicker({
|
|
45
|
+
value,
|
|
46
|
+
onChange,
|
|
47
|
+
}: {
|
|
48
|
+
value: number
|
|
49
|
+
onChange: (day: number) => void
|
|
50
|
+
}) {
|
|
51
|
+
return (
|
|
52
|
+
<div>
|
|
53
|
+
<label className={labelClass}>Day</label>
|
|
54
|
+
<div className="flex gap-1">
|
|
55
|
+
{DAYS_OF_WEEK.map((day) => (
|
|
56
|
+
<button
|
|
57
|
+
key={day.value}
|
|
58
|
+
onClick={() => onChange(day.value)}
|
|
59
|
+
className={cn(
|
|
60
|
+
"size-8 rounded-lg text-xs font-medium transition-colors",
|
|
61
|
+
value === day.value
|
|
62
|
+
? "bg-primary text-primary-foreground"
|
|
63
|
+
: "border border-border text-muted-foreground hover:bg-secondary",
|
|
64
|
+
)}
|
|
65
|
+
>
|
|
66
|
+
{day.label}
|
|
67
|
+
</button>
|
|
68
|
+
))}
|
|
69
|
+
</div>
|
|
70
|
+
</div>
|
|
71
|
+
)
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function DayOfMonthPicker({
|
|
75
|
+
value,
|
|
76
|
+
onChange,
|
|
77
|
+
}: {
|
|
78
|
+
value: number
|
|
79
|
+
onChange: (day: number) => void
|
|
80
|
+
}) {
|
|
81
|
+
return (
|
|
82
|
+
<div>
|
|
83
|
+
<label className={labelClass}>Day of month</label>
|
|
84
|
+
<input
|
|
85
|
+
type="number"
|
|
86
|
+
min={1}
|
|
87
|
+
max={31}
|
|
88
|
+
value={value}
|
|
89
|
+
onChange={(e) => onChange(Number(e.target.value))}
|
|
90
|
+
className={cn(inputClass, "w-24")}
|
|
91
|
+
/>
|
|
92
|
+
</div>
|
|
93
|
+
)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
export function CronInput({
|
|
97
|
+
value,
|
|
98
|
+
onChange,
|
|
99
|
+
}: {
|
|
100
|
+
value: string
|
|
101
|
+
onChange: (cron: string) => void
|
|
102
|
+
}) {
|
|
103
|
+
return (
|
|
104
|
+
<div>
|
|
105
|
+
<label className={labelClass}>Cron Expression</label>
|
|
106
|
+
<input
|
|
107
|
+
type="text"
|
|
108
|
+
value={value}
|
|
109
|
+
onChange={(e) => onChange(e.target.value)}
|
|
110
|
+
placeholder="* * * * *"
|
|
111
|
+
className={cn(inputClass, "w-full font-mono")}
|
|
112
|
+
/>
|
|
113
|
+
<p className="text-[11px] text-muted-foreground mt-1">
|
|
114
|
+
Format: minute hour day-of-month month day-of-week
|
|
115
|
+
</p>
|
|
116
|
+
</div>
|
|
117
|
+
)
|
|
118
|
+
}
|
package/src/styles.css
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
@source ".";
|
package/src/types.ts
ADDED
|
@@ -0,0 +1,105 @@
|
|
|
1
|
+
// Generic routine types — mirrors Houston's Routine model without Tauri/backend coupling.
|
|
2
|
+
|
|
3
|
+
export type TriggerType = "on_approval" | "scheduled" | "periodic" | "manual"
|
|
4
|
+
export type RoutineStatus = "active" | "paused" | "needs_setup" | "error"
|
|
5
|
+
export type ApprovalMode = "manual" | "auto_approve"
|
|
6
|
+
|
|
7
|
+
export type RunStatus =
|
|
8
|
+
| "running"
|
|
9
|
+
| "completed"
|
|
10
|
+
| "failed"
|
|
11
|
+
| "approved"
|
|
12
|
+
| "needs_you"
|
|
13
|
+
| "done"
|
|
14
|
+
| "error"
|
|
15
|
+
|
|
16
|
+
export interface Routine {
|
|
17
|
+
id: string
|
|
18
|
+
project_id: string
|
|
19
|
+
goal_id: string | null
|
|
20
|
+
skill_id: string | null
|
|
21
|
+
name: string
|
|
22
|
+
description: string
|
|
23
|
+
trigger_type: TriggerType
|
|
24
|
+
trigger_config: string
|
|
25
|
+
status: RoutineStatus
|
|
26
|
+
approval_mode: ApprovalMode
|
|
27
|
+
context: string
|
|
28
|
+
run_count: number
|
|
29
|
+
approval_count: number
|
|
30
|
+
last_run_at: string | null
|
|
31
|
+
created_at: string
|
|
32
|
+
updated_at: string
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
export interface RoutineRun {
|
|
36
|
+
id: string
|
|
37
|
+
routine_id: string
|
|
38
|
+
project_id: string
|
|
39
|
+
status: RunStatus
|
|
40
|
+
session_id: string | null
|
|
41
|
+
claude_session_id: string | null
|
|
42
|
+
output_files: string | null
|
|
43
|
+
cost_usd: number | null
|
|
44
|
+
duration_ms: number | null
|
|
45
|
+
output_title: string | null
|
|
46
|
+
output_summary: string | null
|
|
47
|
+
feedback_text: string | null
|
|
48
|
+
is_test_run: boolean
|
|
49
|
+
created_at: string
|
|
50
|
+
completed_at: string | null
|
|
51
|
+
approved_at: string | null
|
|
52
|
+
}
|
|
53
|
+
|
|
54
|
+
export interface Skill {
|
|
55
|
+
id: string
|
|
56
|
+
name: string
|
|
57
|
+
description: string
|
|
58
|
+
}
|
|
59
|
+
|
|
60
|
+
export const TRIGGER_LABELS: Record<TriggerType, string> = {
|
|
61
|
+
on_approval: "On approval",
|
|
62
|
+
scheduled: "Scheduled",
|
|
63
|
+
periodic: "Periodic",
|
|
64
|
+
manual: "Manual",
|
|
65
|
+
}
|
|
66
|
+
|
|
67
|
+
/** Form state for the routine edit form */
|
|
68
|
+
export interface RoutineFormState {
|
|
69
|
+
name: string
|
|
70
|
+
description: string
|
|
71
|
+
context: string
|
|
72
|
+
triggerType: TriggerType
|
|
73
|
+
approvalMode: ApprovalMode
|
|
74
|
+
skillId: string | null
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// --- Heartbeat & schedule types ---
|
|
78
|
+
|
|
79
|
+
export interface HeartbeatConfig {
|
|
80
|
+
enabled: boolean
|
|
81
|
+
intervalMinutes: number
|
|
82
|
+
prompt: string
|
|
83
|
+
activeHoursStart?: string
|
|
84
|
+
activeHoursEnd?: string
|
|
85
|
+
suppressionToken: string
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
export type SchedulePreset =
|
|
89
|
+
| "every_30min"
|
|
90
|
+
| "hourly"
|
|
91
|
+
| "daily"
|
|
92
|
+
| "weekdays"
|
|
93
|
+
| "weekly"
|
|
94
|
+
| "monthly"
|
|
95
|
+
| "custom"
|
|
96
|
+
|
|
97
|
+
export const SCHEDULE_PRESET_LABELS: Record<SchedulePreset, string> = {
|
|
98
|
+
every_30min: "Every 30 minutes",
|
|
99
|
+
hourly: "Every hour",
|
|
100
|
+
daily: "Daily",
|
|
101
|
+
weekdays: "Weekdays only",
|
|
102
|
+
weekly: "Weekly",
|
|
103
|
+
monthly: "Monthly",
|
|
104
|
+
custom: "Custom (cron)",
|
|
105
|
+
}
|