commandmate 0.3.1 → 0.3.3
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/.next/BUILD_ID +1 -1
- package/.next/app-build-manifest.json +11 -11
- package/.next/app-path-routes-manifest.json +1 -1
- package/.next/build-manifest.json +4 -4
- package/.next/cache/.tsbuildinfo +1 -1
- package/.next/cache/config.json +3 -3
- package/.next/cache/webpack/client-production/0.pack +0 -0
- package/.next/cache/webpack/client-production/1.pack +0 -0
- package/.next/cache/webpack/client-production/2.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack +0 -0
- package/.next/cache/webpack/client-production/index.pack.old +0 -0
- package/.next/cache/webpack/edge-server-production/0.pack +0 -0
- package/.next/cache/webpack/edge-server-production/index.pack +0 -0
- package/.next/cache/webpack/server-production/0.pack +0 -0
- package/.next/cache/webpack/server-production/index.pack +0 -0
- package/.next/next-server.js.nft.json +1 -1
- package/.next/prerender-manifest.json +1 -1
- package/.next/required-server-files.json +1 -1
- package/.next/routes-manifest.json +1 -1
- package/.next/server/app/_not-found/page.js.nft.json +1 -1
- package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
- package/.next/server/app/api/app/update-check/route.js +1 -1
- package/.next/server/app/api/external-apps/[id]/health/route.js +1 -1
- package/.next/server/app/api/external-apps/[id]/route.js +1 -1
- package/.next/server/app/api/external-apps/route.js +1 -1
- package/.next/server/app/api/hooks/claude-done/route.js +1 -1
- package/.next/server/app/api/ollama/models/route.js +1 -0
- package/.next/server/app/api/ollama/models/route.js.nft.json +1 -0
- package/.next/server/app/api/ollama/models.body +1 -0
- package/.next/server/app/api/ollama/models.meta +1 -0
- package/.next/server/app/api/repositories/clone/[jobId]/route.js +1 -1
- package/.next/server/app/api/repositories/clone/route.js +1 -1
- package/.next/server/app/api/repositories/excluded/route.js +7 -7
- package/.next/server/app/api/repositories/restore/route.js +3 -3
- package/.next/server/app/api/repositories/route.js +13 -11
- package/.next/server/app/api/repositories/route.js.nft.json +1 -1
- package/.next/server/app/api/repositories/scan/route.js +1 -1
- package/.next/server/app/api/repositories/sync/route.js +3 -3
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js +1 -0
- package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js.nft.json +1 -0
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +9 -0
- package/.next/server/app/api/worktrees/[id]/execution-logs/route.js.nft.json +1 -0
- package/.next/server/app/api/worktrees/[id]/files/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/[filename]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/logs/route.js +2 -2
- package/.next/server/app/api/worktrees/[id]/memos/[memoId]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/memos/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +1 -0
- package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js.nft.json +1 -0
- package/.next/server/app/api/worktrees/[id]/schedules/route.js +4 -0
- package/.next/server/app/api/worktrees/[id]/schedules/route.js.nft.json +1 -0
- package/.next/server/app/api/worktrees/[id]/search/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/tree/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/upload/[...path]/route.js +1 -1
- package/.next/server/app/api/worktrees/[id]/viewed/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js +1 -1
- package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
- package/.next/server/app/login/page.js.nft.json +1 -1
- package/.next/server/app/login/page_client-reference-manifest.js +1 -1
- package/.next/server/app/page.js +1 -1
- package/.next/server/app/page.js.nft.json +1 -1
- package/.next/server/app/page_client-reference-manifest.js +1 -1
- package/.next/server/app/proxy/[...path]/route.js +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/page.js +8 -3
- package/.next/server/app/worktrees/[id]/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page.js.nft.json +1 -1
- package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
- package/.next/server/app-paths-manifest.json +10 -5
- package/.next/server/chunks/2314.js +1 -0
- package/.next/server/chunks/3860.js +1 -1
- package/.next/server/chunks/4559.js +1 -1
- package/.next/server/chunks/539.js +10 -10
- package/.next/server/chunks/5853.js +1 -1
- package/.next/server/chunks/6228.js +1 -0
- package/.next/server/chunks/7425.js +112 -37
- package/.next/server/chunks/7566.js +1 -1
- package/.next/server/chunks/8693.js +1 -1
- package/.next/server/chunks/9446.js +1 -0
- package/.next/server/functions-config-manifest.json +1 -1
- package/.next/server/middleware-build-manifest.js +1 -1
- package/.next/server/middleware-manifest.json +5 -5
- package/.next/server/pages/500.html +1 -1
- package/.next/server/server-reference-manifest.json +1 -1
- package/.next/static/chunks/8091-274bc0716106e7fc.js +1 -0
- package/.next/static/chunks/app/page-060057e02b841125.js +1 -0
- package/.next/static/chunks/app/worktrees/[id]/page-78580947c201d698.js +1 -0
- package/.next/static/chunks/{main-db79434ee4a6c931.js → main-2feda12a4d321111.js} +1 -1
- package/.next/static/css/e85de230ef5ddc40.css +3 -0
- package/.next/trace +5 -5
- package/.next/types/app/api/ollama/models/route.ts +343 -0
- package/.next/types/app/api/worktrees/[id]/execution-logs/[logId]/route.ts +343 -0
- package/.next/types/app/api/worktrees/[id]/execution-logs/route.ts +343 -0
- package/.next/types/app/api/worktrees/[id]/schedules/[scheduleId]/route.ts +343 -0
- package/.next/types/app/api/worktrees/[id]/schedules/route.ts +343 -0
- package/README.md +74 -76
- package/dist/cli/utils/docs-reader.d.ts.map +1 -1
- package/dist/cli/utils/docs-reader.js +1 -0
- package/dist/server/server.js +5 -0
- package/dist/server/src/config/cmate-constants.js +79 -0
- package/dist/server/src/config/schedule-config.js +60 -0
- package/dist/server/src/lib/auto-yes-manager.js +2 -2
- package/dist/server/src/lib/claude-executor.js +158 -0
- package/dist/server/src/lib/cli-patterns.js +73 -9
- package/dist/server/src/lib/cli-tools/gemini.js +81 -22
- package/dist/server/src/lib/cli-tools/manager.js +4 -2
- package/dist/server/src/lib/cli-tools/types.js +64 -2
- package/dist/server/src/lib/cli-tools/vibe-local.js +163 -0
- package/dist/server/src/lib/cmate-parser.js +262 -0
- package/dist/server/src/lib/db-instance.js +3 -0
- package/dist/server/src/lib/db-migrations.js +145 -2
- package/dist/server/src/lib/db.js +51 -1
- package/dist/server/src/lib/env-sanitizer.js +57 -0
- package/dist/server/src/lib/prompt-detector.js +4 -3
- package/dist/server/src/lib/response-poller.js +22 -11
- package/dist/server/src/lib/schedule-manager.js +401 -0
- package/dist/server/src/lib/selected-agents-validator.js +99 -0
- package/dist/server/src/types/cmate.js +6 -0
- package/dist/server/src/types/sidebar.js +9 -4
- package/package.json +2 -1
- package/.next/server/chunks/7536.js +0 -1
- package/.next/static/chunks/8091-925542bdfc843dce.js +0 -1
- package/.next/static/chunks/app/page-238b5a70d8c101e9.js +0 -1
- package/.next/static/chunks/app/worktrees/[id]/page-a556551ce5c69dec.js +0 -1
- package/.next/static/css/b9ea6a4fad17dc32.css +0 -3
- /package/.next/static/{hmAjbCPjxX_C0Os7rphI1 → O7EDFfAYQNe_HRbORxQAC}/_buildManifest.js +0 -0
- /package/.next/static/{hmAjbCPjxX_C0Os7rphI1 → O7EDFfAYQNe_HRbORxQAC}/_ssgManifest.js +0 -0
|
@@ -0,0 +1,343 @@
|
|
|
1
|
+
// File: /home/runner/work/CommandMate/CommandMate/src/app/api/worktrees/[id]/schedules/route.ts
|
|
2
|
+
import * as entry from '../../../../../../../src/app/api/worktrees/[id]/schedules/route.js'
|
|
3
|
+
import type { NextRequest } from 'next/server.js'
|
|
4
|
+
|
|
5
|
+
type TEntry = typeof import('../../../../../../../src/app/api/worktrees/[id]/schedules/route.js')
|
|
6
|
+
|
|
7
|
+
// Check that the entry is a valid entry
|
|
8
|
+
checkFields<Diff<{
|
|
9
|
+
GET?: Function
|
|
10
|
+
HEAD?: Function
|
|
11
|
+
OPTIONS?: Function
|
|
12
|
+
POST?: Function
|
|
13
|
+
PUT?: Function
|
|
14
|
+
DELETE?: Function
|
|
15
|
+
PATCH?: Function
|
|
16
|
+
config?: {}
|
|
17
|
+
generateStaticParams?: Function
|
|
18
|
+
revalidate?: RevalidateRange<TEntry> | false
|
|
19
|
+
dynamic?: 'auto' | 'force-dynamic' | 'error' | 'force-static'
|
|
20
|
+
dynamicParams?: boolean
|
|
21
|
+
fetchCache?: 'auto' | 'force-no-store' | 'only-no-store' | 'default-no-store' | 'default-cache' | 'only-cache' | 'force-cache'
|
|
22
|
+
preferredRegion?: 'auto' | 'global' | 'home' | string | string[]
|
|
23
|
+
runtime?: 'nodejs' | 'experimental-edge' | 'edge'
|
|
24
|
+
maxDuration?: number
|
|
25
|
+
|
|
26
|
+
}, TEntry, ''>>()
|
|
27
|
+
|
|
28
|
+
// Check the prop type of the entry function
|
|
29
|
+
if ('GET' in entry) {
|
|
30
|
+
checkFields<
|
|
31
|
+
Diff<
|
|
32
|
+
ParamCheck<Request | NextRequest>,
|
|
33
|
+
{
|
|
34
|
+
__tag__: 'GET'
|
|
35
|
+
__param_position__: 'first'
|
|
36
|
+
__param_type__: FirstArg<MaybeField<TEntry, 'GET'>>
|
|
37
|
+
},
|
|
38
|
+
'GET'
|
|
39
|
+
>
|
|
40
|
+
>()
|
|
41
|
+
checkFields<
|
|
42
|
+
Diff<
|
|
43
|
+
ParamCheck<PageParams>,
|
|
44
|
+
{
|
|
45
|
+
__tag__: 'GET'
|
|
46
|
+
__param_position__: 'second'
|
|
47
|
+
__param_type__: SecondArg<MaybeField<TEntry, 'GET'>>
|
|
48
|
+
},
|
|
49
|
+
'GET'
|
|
50
|
+
>
|
|
51
|
+
>()
|
|
52
|
+
|
|
53
|
+
checkFields<
|
|
54
|
+
Diff<
|
|
55
|
+
{
|
|
56
|
+
__tag__: 'GET',
|
|
57
|
+
__return_type__: Response | void | never | Promise<Response | void | never>
|
|
58
|
+
},
|
|
59
|
+
{
|
|
60
|
+
__tag__: 'GET',
|
|
61
|
+
__return_type__: ReturnType<MaybeField<TEntry, 'GET'>>
|
|
62
|
+
},
|
|
63
|
+
'GET'
|
|
64
|
+
>
|
|
65
|
+
>()
|
|
66
|
+
}
|
|
67
|
+
// Check the prop type of the entry function
|
|
68
|
+
if ('HEAD' in entry) {
|
|
69
|
+
checkFields<
|
|
70
|
+
Diff<
|
|
71
|
+
ParamCheck<Request | NextRequest>,
|
|
72
|
+
{
|
|
73
|
+
__tag__: 'HEAD'
|
|
74
|
+
__param_position__: 'first'
|
|
75
|
+
__param_type__: FirstArg<MaybeField<TEntry, 'HEAD'>>
|
|
76
|
+
},
|
|
77
|
+
'HEAD'
|
|
78
|
+
>
|
|
79
|
+
>()
|
|
80
|
+
checkFields<
|
|
81
|
+
Diff<
|
|
82
|
+
ParamCheck<PageParams>,
|
|
83
|
+
{
|
|
84
|
+
__tag__: 'HEAD'
|
|
85
|
+
__param_position__: 'second'
|
|
86
|
+
__param_type__: SecondArg<MaybeField<TEntry, 'HEAD'>>
|
|
87
|
+
},
|
|
88
|
+
'HEAD'
|
|
89
|
+
>
|
|
90
|
+
>()
|
|
91
|
+
|
|
92
|
+
checkFields<
|
|
93
|
+
Diff<
|
|
94
|
+
{
|
|
95
|
+
__tag__: 'HEAD',
|
|
96
|
+
__return_type__: Response | void | never | Promise<Response | void | never>
|
|
97
|
+
},
|
|
98
|
+
{
|
|
99
|
+
__tag__: 'HEAD',
|
|
100
|
+
__return_type__: ReturnType<MaybeField<TEntry, 'HEAD'>>
|
|
101
|
+
},
|
|
102
|
+
'HEAD'
|
|
103
|
+
>
|
|
104
|
+
>()
|
|
105
|
+
}
|
|
106
|
+
// Check the prop type of the entry function
|
|
107
|
+
if ('OPTIONS' in entry) {
|
|
108
|
+
checkFields<
|
|
109
|
+
Diff<
|
|
110
|
+
ParamCheck<Request | NextRequest>,
|
|
111
|
+
{
|
|
112
|
+
__tag__: 'OPTIONS'
|
|
113
|
+
__param_position__: 'first'
|
|
114
|
+
__param_type__: FirstArg<MaybeField<TEntry, 'OPTIONS'>>
|
|
115
|
+
},
|
|
116
|
+
'OPTIONS'
|
|
117
|
+
>
|
|
118
|
+
>()
|
|
119
|
+
checkFields<
|
|
120
|
+
Diff<
|
|
121
|
+
ParamCheck<PageParams>,
|
|
122
|
+
{
|
|
123
|
+
__tag__: 'OPTIONS'
|
|
124
|
+
__param_position__: 'second'
|
|
125
|
+
__param_type__: SecondArg<MaybeField<TEntry, 'OPTIONS'>>
|
|
126
|
+
},
|
|
127
|
+
'OPTIONS'
|
|
128
|
+
>
|
|
129
|
+
>()
|
|
130
|
+
|
|
131
|
+
checkFields<
|
|
132
|
+
Diff<
|
|
133
|
+
{
|
|
134
|
+
__tag__: 'OPTIONS',
|
|
135
|
+
__return_type__: Response | void | never | Promise<Response | void | never>
|
|
136
|
+
},
|
|
137
|
+
{
|
|
138
|
+
__tag__: 'OPTIONS',
|
|
139
|
+
__return_type__: ReturnType<MaybeField<TEntry, 'OPTIONS'>>
|
|
140
|
+
},
|
|
141
|
+
'OPTIONS'
|
|
142
|
+
>
|
|
143
|
+
>()
|
|
144
|
+
}
|
|
145
|
+
// Check the prop type of the entry function
|
|
146
|
+
if ('POST' in entry) {
|
|
147
|
+
checkFields<
|
|
148
|
+
Diff<
|
|
149
|
+
ParamCheck<Request | NextRequest>,
|
|
150
|
+
{
|
|
151
|
+
__tag__: 'POST'
|
|
152
|
+
__param_position__: 'first'
|
|
153
|
+
__param_type__: FirstArg<MaybeField<TEntry, 'POST'>>
|
|
154
|
+
},
|
|
155
|
+
'POST'
|
|
156
|
+
>
|
|
157
|
+
>()
|
|
158
|
+
checkFields<
|
|
159
|
+
Diff<
|
|
160
|
+
ParamCheck<PageParams>,
|
|
161
|
+
{
|
|
162
|
+
__tag__: 'POST'
|
|
163
|
+
__param_position__: 'second'
|
|
164
|
+
__param_type__: SecondArg<MaybeField<TEntry, 'POST'>>
|
|
165
|
+
},
|
|
166
|
+
'POST'
|
|
167
|
+
>
|
|
168
|
+
>()
|
|
169
|
+
|
|
170
|
+
checkFields<
|
|
171
|
+
Diff<
|
|
172
|
+
{
|
|
173
|
+
__tag__: 'POST',
|
|
174
|
+
__return_type__: Response | void | never | Promise<Response | void | never>
|
|
175
|
+
},
|
|
176
|
+
{
|
|
177
|
+
__tag__: 'POST',
|
|
178
|
+
__return_type__: ReturnType<MaybeField<TEntry, 'POST'>>
|
|
179
|
+
},
|
|
180
|
+
'POST'
|
|
181
|
+
>
|
|
182
|
+
>()
|
|
183
|
+
}
|
|
184
|
+
// Check the prop type of the entry function
|
|
185
|
+
if ('PUT' in entry) {
|
|
186
|
+
checkFields<
|
|
187
|
+
Diff<
|
|
188
|
+
ParamCheck<Request | NextRequest>,
|
|
189
|
+
{
|
|
190
|
+
__tag__: 'PUT'
|
|
191
|
+
__param_position__: 'first'
|
|
192
|
+
__param_type__: FirstArg<MaybeField<TEntry, 'PUT'>>
|
|
193
|
+
},
|
|
194
|
+
'PUT'
|
|
195
|
+
>
|
|
196
|
+
>()
|
|
197
|
+
checkFields<
|
|
198
|
+
Diff<
|
|
199
|
+
ParamCheck<PageParams>,
|
|
200
|
+
{
|
|
201
|
+
__tag__: 'PUT'
|
|
202
|
+
__param_position__: 'second'
|
|
203
|
+
__param_type__: SecondArg<MaybeField<TEntry, 'PUT'>>
|
|
204
|
+
},
|
|
205
|
+
'PUT'
|
|
206
|
+
>
|
|
207
|
+
>()
|
|
208
|
+
|
|
209
|
+
checkFields<
|
|
210
|
+
Diff<
|
|
211
|
+
{
|
|
212
|
+
__tag__: 'PUT',
|
|
213
|
+
__return_type__: Response | void | never | Promise<Response | void | never>
|
|
214
|
+
},
|
|
215
|
+
{
|
|
216
|
+
__tag__: 'PUT',
|
|
217
|
+
__return_type__: ReturnType<MaybeField<TEntry, 'PUT'>>
|
|
218
|
+
},
|
|
219
|
+
'PUT'
|
|
220
|
+
>
|
|
221
|
+
>()
|
|
222
|
+
}
|
|
223
|
+
// Check the prop type of the entry function
|
|
224
|
+
if ('DELETE' in entry) {
|
|
225
|
+
checkFields<
|
|
226
|
+
Diff<
|
|
227
|
+
ParamCheck<Request | NextRequest>,
|
|
228
|
+
{
|
|
229
|
+
__tag__: 'DELETE'
|
|
230
|
+
__param_position__: 'first'
|
|
231
|
+
__param_type__: FirstArg<MaybeField<TEntry, 'DELETE'>>
|
|
232
|
+
},
|
|
233
|
+
'DELETE'
|
|
234
|
+
>
|
|
235
|
+
>()
|
|
236
|
+
checkFields<
|
|
237
|
+
Diff<
|
|
238
|
+
ParamCheck<PageParams>,
|
|
239
|
+
{
|
|
240
|
+
__tag__: 'DELETE'
|
|
241
|
+
__param_position__: 'second'
|
|
242
|
+
__param_type__: SecondArg<MaybeField<TEntry, 'DELETE'>>
|
|
243
|
+
},
|
|
244
|
+
'DELETE'
|
|
245
|
+
>
|
|
246
|
+
>()
|
|
247
|
+
|
|
248
|
+
checkFields<
|
|
249
|
+
Diff<
|
|
250
|
+
{
|
|
251
|
+
__tag__: 'DELETE',
|
|
252
|
+
__return_type__: Response | void | never | Promise<Response | void | never>
|
|
253
|
+
},
|
|
254
|
+
{
|
|
255
|
+
__tag__: 'DELETE',
|
|
256
|
+
__return_type__: ReturnType<MaybeField<TEntry, 'DELETE'>>
|
|
257
|
+
},
|
|
258
|
+
'DELETE'
|
|
259
|
+
>
|
|
260
|
+
>()
|
|
261
|
+
}
|
|
262
|
+
// Check the prop type of the entry function
|
|
263
|
+
if ('PATCH' in entry) {
|
|
264
|
+
checkFields<
|
|
265
|
+
Diff<
|
|
266
|
+
ParamCheck<Request | NextRequest>,
|
|
267
|
+
{
|
|
268
|
+
__tag__: 'PATCH'
|
|
269
|
+
__param_position__: 'first'
|
|
270
|
+
__param_type__: FirstArg<MaybeField<TEntry, 'PATCH'>>
|
|
271
|
+
},
|
|
272
|
+
'PATCH'
|
|
273
|
+
>
|
|
274
|
+
>()
|
|
275
|
+
checkFields<
|
|
276
|
+
Diff<
|
|
277
|
+
ParamCheck<PageParams>,
|
|
278
|
+
{
|
|
279
|
+
__tag__: 'PATCH'
|
|
280
|
+
__param_position__: 'second'
|
|
281
|
+
__param_type__: SecondArg<MaybeField<TEntry, 'PATCH'>>
|
|
282
|
+
},
|
|
283
|
+
'PATCH'
|
|
284
|
+
>
|
|
285
|
+
>()
|
|
286
|
+
|
|
287
|
+
checkFields<
|
|
288
|
+
Diff<
|
|
289
|
+
{
|
|
290
|
+
__tag__: 'PATCH',
|
|
291
|
+
__return_type__: Response | void | never | Promise<Response | void | never>
|
|
292
|
+
},
|
|
293
|
+
{
|
|
294
|
+
__tag__: 'PATCH',
|
|
295
|
+
__return_type__: ReturnType<MaybeField<TEntry, 'PATCH'>>
|
|
296
|
+
},
|
|
297
|
+
'PATCH'
|
|
298
|
+
>
|
|
299
|
+
>()
|
|
300
|
+
}
|
|
301
|
+
|
|
302
|
+
// Check the arguments and return type of the generateStaticParams function
|
|
303
|
+
if ('generateStaticParams' in entry) {
|
|
304
|
+
checkFields<Diff<{ params: PageParams }, FirstArg<MaybeField<TEntry, 'generateStaticParams'>>, 'generateStaticParams'>>()
|
|
305
|
+
checkFields<Diff<{ __tag__: 'generateStaticParams', __return_type__: any[] | Promise<any[]> }, { __tag__: 'generateStaticParams', __return_type__: ReturnType<MaybeField<TEntry, 'generateStaticParams'>> }>>()
|
|
306
|
+
}
|
|
307
|
+
|
|
308
|
+
type PageParams = any
|
|
309
|
+
export interface PageProps {
|
|
310
|
+
params?: any
|
|
311
|
+
searchParams?: any
|
|
312
|
+
}
|
|
313
|
+
export interface LayoutProps {
|
|
314
|
+
children?: React.ReactNode
|
|
315
|
+
|
|
316
|
+
params?: any
|
|
317
|
+
}
|
|
318
|
+
|
|
319
|
+
// =============
|
|
320
|
+
// Utility types
|
|
321
|
+
type RevalidateRange<T> = T extends { revalidate: any } ? NonNegative<T['revalidate']> : never
|
|
322
|
+
|
|
323
|
+
// If T is unknown or any, it will be an empty {} type. Otherwise, it will be the same as Omit<T, keyof Base>.
|
|
324
|
+
type OmitWithTag<T, K extends keyof any, _M> = Omit<T, K>
|
|
325
|
+
type Diff<Base, T extends Base, Message extends string = ''> = 0 extends (1 & T) ? {} : OmitWithTag<T, keyof Base, Message>
|
|
326
|
+
|
|
327
|
+
type FirstArg<T extends Function> = T extends (...args: [infer T, any]) => any ? unknown extends T ? any : T : never
|
|
328
|
+
type SecondArg<T extends Function> = T extends (...args: [any, infer T]) => any ? unknown extends T ? any : T : never
|
|
329
|
+
type MaybeField<T, K extends string> = T extends { [k in K]: infer G } ? G extends Function ? G : never : never
|
|
330
|
+
|
|
331
|
+
type ParamCheck<T> = {
|
|
332
|
+
__tag__: string
|
|
333
|
+
__param_position__: string
|
|
334
|
+
__param_type__: T
|
|
335
|
+
}
|
|
336
|
+
|
|
337
|
+
function checkFields<_ extends { [k in keyof any]: never }>() {}
|
|
338
|
+
|
|
339
|
+
// https://github.com/sindresorhus/type-fest
|
|
340
|
+
type Numeric = number | bigint
|
|
341
|
+
type Zero = 0 | 0n
|
|
342
|
+
type Negative<T extends Numeric> = T extends Zero ? never : `${T}` extends `-${string}` ? T : never
|
|
343
|
+
type NonNegative<T extends Numeric> = T extends Zero ? T : Negative<T> extends never ? T : '__invalid_negative_number__'
|
package/README.md
CHANGED
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
# CommandMate
|
|
2
2
|
|
|
3
|
+
[](https://github.com/Kewton/CommandMate)
|
|
3
4
|

|
|
4
5
|

|
|
5
6
|

|
|
@@ -16,12 +17,17 @@
|
|
|
16
17
|
|
|
17
18
|
Not a "remote control" — a **mobile dev cockpit**.
|
|
18
19
|
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
|
|
22
|
-
|
|
20
|
+
```bash
|
|
21
|
+
npx commandmate
|
|
22
|
+
```
|
|
23
|
+
|
|
24
|
+
**From install to mobile monitoring in 60 seconds.** macOS / Linux · Node.js v20+ · npm · git · tmux · openssl
|
|
23
25
|
|
|
24
|
-
|
|
26
|
+
---
|
|
27
|
+
|
|
28
|
+
AI writes code for you — but you're stuck watching a terminal, afraid to walk away.
|
|
29
|
+
Close the lid and every session dies.
|
|
30
|
+
**CommandMate keeps it alive, and puts the controls on your phone.**
|
|
25
31
|
|
|
26
32
|
Of course, it works great on desktop too — the two-column layout gives you a full overview of all sessions and worktrees at a glance.
|
|
27
33
|
|
|
@@ -29,83 +35,69 @@ Of course, it works great on desktop too — the two-column layout gives you a f
|
|
|
29
35
|
<img src="./docs/images/demo-desktop.gif" alt="CommandMate desktop demo" width="600">
|
|
30
36
|
</p>
|
|
31
37
|
|
|
32
|
-
```bash
|
|
33
|
-
npx commandmate
|
|
34
|
-
```
|
|
35
|
-
|
|
36
38
|
---
|
|
37
39
|
|
|
38
|
-
##
|
|
40
|
+
## Key Features
|
|
39
41
|
|
|
40
|
-
|
|
|
41
|
-
|
|
42
|
+
| Feature | What it does | Why it matters |
|
|
43
|
+
|---------|-------------|----------------|
|
|
42
44
|
| **Auto Yes Mode** | Agent runs without stopping for confirmations | No babysitting — Claude Code keeps working while you're away |
|
|
43
45
|
| **Git Worktree Sessions** | One session per worktree, parallel execution | Multiple tasks progress simultaneously |
|
|
44
46
|
| **Mobile Web UI** | Full session control from any browser | Monitor and steer from your phone |
|
|
45
47
|
| **File Viewer** | Browse worktree files from the browser | Review code changes without touching your PC |
|
|
46
48
|
| **Markdown Editor** | Edit Markdown files in the browser | Update AI instructions on the go |
|
|
47
49
|
| **Screenshot Instructions** | Attach images to your prompts | Snap a bug → "Fix this" — the agent sees the screenshot |
|
|
50
|
+
| **Token Authentication** | SHA-256 hashed token + HTTPS + rate limiting | Secure remote access — no credentials leaked, brute-force protected |
|
|
51
|
+
| **Scheduled Execution** | Cron-based auto-run via CMATE.md | Daily reviews, nightly tests — Claude Code works on a schedule |
|
|
48
52
|
|
|
49
53
|
---
|
|
50
54
|
|
|
51
|
-
##
|
|
55
|
+
## Use Cases
|
|
52
56
|
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
npm install -g commandmate
|
|
61
|
-
commandmate init
|
|
62
|
-
commandmate start --daemon
|
|
63
|
-
```
|
|
64
|
-
|
|
65
|
-
Open http://localhost:3000 in your browser.
|
|
66
|
-
|
|
67
|
-
See the [CLI Setup Guide](./docs/en/user-guide/cli-setup-guide.md) for details.
|
|
57
|
+
| Scenario | How CommandMate helps |
|
|
58
|
+
|----------|----------------------|
|
|
59
|
+
| **Couch coding** | Start a task on your PC, then monitor and steer from the sofa |
|
|
60
|
+
| **Commute review** | Review AI-generated code changes on the train |
|
|
61
|
+
| **Overnight runs** | Let Claude Code work all night — check progress from bed |
|
|
62
|
+
| **Visual bug fix** | Snap a UI bug on your phone, send it with "Fix this" |
|
|
63
|
+
| **Parallel tasks** | Run multiple worktree sessions, manage them all from one dashboard |
|
|
68
64
|
|
|
69
65
|
---
|
|
70
66
|
|
|
71
67
|
## Comparison
|
|
72
68
|
|
|
73
|
-
| Feature | CommandMate | Happy Coder | claude-squad | Omnara |
|
|
74
|
-
|
|
75
|
-
| Auto Yes Mode | Yes | No | Yes (TUI only) | No |
|
|
76
|
-
| Git Worktree Management | Yes | No | Yes (TUI only) | No |
|
|
77
|
-
|
|
|
78
|
-
|
|
|
79
|
-
|
|
|
80
|
-
|
|
|
81
|
-
|
|
|
82
|
-
|
|
|
69
|
+
| Feature | CommandMate | Remote Control (Official) | Happy Coder | claude-squad | Omnara |
|
|
70
|
+
|---------|:-----------:|:------------------------:|:-----------:|:------------:|:------:|
|
|
71
|
+
| Auto Yes Mode | Yes | No | No | Yes (TUI only) | No |
|
|
72
|
+
| Git Worktree Management | Yes | No | No | Yes (TUI only) | No |
|
|
73
|
+
| Parallel Sessions | Yes | **No (1 only)** | Yes | Yes | No |
|
|
74
|
+
| Mobile Web UI | Yes | Yes (claude.ai) | Yes | **No** | Yes |
|
|
75
|
+
| File Viewer | Yes | No | No | No | No |
|
|
76
|
+
| Markdown Editor | Yes | No | No | No | No |
|
|
77
|
+
| Screenshot Instructions | Yes | No | No | Not possible | No |
|
|
78
|
+
| Scheduled Execution | Yes | No | No | No | No |
|
|
79
|
+
| Survives Laptop Close | Yes (daemon) | **No (terminal must stay open)** | Yes | Yes | Yes |
|
|
80
|
+
| Token Authentication | Yes | N/A (Anthropic account) | N/A (app) | No | N/A (cloud) |
|
|
81
|
+
| Free / OSS | Yes | Requires Pro/Max | Free + Paid | Yes | $20/mo |
|
|
82
|
+
| Runs 100% Locally | Yes | Via Anthropic API | Server-routed | Yes | Cloud fallback |
|
|
83
83
|
|
|
84
84
|
---
|
|
85
85
|
|
|
86
|
-
##
|
|
86
|
+
## Screenshots
|
|
87
87
|
|
|
88
|
-
|
|
89
|
-
1. Start tasks on your PC
|
|
90
|
-
$ commandmate start --daemon
|
|
91
|
-
→ Claude Code begins working with Auto Yes
|
|
88
|
+
### Desktop
|
|
92
89
|
|
|
93
|
-
|
|
90
|
+

|
|
94
91
|
|
|
95
|
-
|
|
96
|
-
→ Web UI shows all sessions at a glance
|
|
92
|
+
### Mobile
|
|
97
93
|
|
|
98
|
-
|
|
99
|
-
|
|
94
|
+
| Top Page | Worktree (History) | Worktree (Terminal) |
|
|
95
|
+
|----------|-------------------|-------------------|
|
|
96
|
+
|  |  |  |
|
|
100
97
|
|
|
101
|
-
|
|
102
|
-
→ Edit a Markdown instruction file, or type a new prompt
|
|
98
|
+
### Worktree Detail (Desktop)
|
|
103
99
|
|
|
104
|
-
|
|
105
|
-
→ Screenshot Instructions: attach a photo and say "Fix this"
|
|
106
|
-
|
|
107
|
-
7. Claude Code sees the image and starts fixing
|
|
108
|
-
```
|
|
100
|
+

|
|
109
101
|
|
|
110
102
|
---
|
|
111
103
|
|
|
@@ -115,7 +107,7 @@ Runs **100% locally**. No external server, no cloud relay, no account required.
|
|
|
115
107
|
|
|
116
108
|
- Fully open-source ([MIT License](./LICENSE))
|
|
117
109
|
- Local database, local sessions
|
|
118
|
-
- For remote access, use a VPN or authenticated reverse proxy
|
|
110
|
+
- For remote access, use a tunneling service ([Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/), [ngrok](https://ngrok.com/), [Pinggy](https://pinggy.io/)), a VPN, or an authenticated reverse proxy
|
|
119
111
|
|
|
120
112
|
See the [Security Guide](./docs/security-guide.md) and [Trust & Safety](./docs/en/TRUST_AND_SAFETY.md) for details.
|
|
121
113
|
|
|
@@ -136,6 +128,25 @@ Each Git worktree gets its own tmux session, so multiple tasks run in parallel w
|
|
|
136
128
|
|
|
137
129
|
---
|
|
138
130
|
|
|
131
|
+
<details>
|
|
132
|
+
<summary><strong>Quick Start (detailed)</strong></summary>
|
|
133
|
+
|
|
134
|
+
```bash
|
|
135
|
+
# Install & start in one command
|
|
136
|
+
npx commandmate
|
|
137
|
+
|
|
138
|
+
# Or install globally
|
|
139
|
+
npm install -g commandmate
|
|
140
|
+
commandmate init
|
|
141
|
+
commandmate start --daemon
|
|
142
|
+
```
|
|
143
|
+
|
|
144
|
+
Open http://localhost:3000 in your browser.
|
|
145
|
+
|
|
146
|
+
See the [CLI Setup Guide](./docs/en/user-guide/cli-setup-guide.md) for details.
|
|
147
|
+
|
|
148
|
+
</details>
|
|
149
|
+
|
|
139
150
|
<details>
|
|
140
151
|
<summary><strong>CLI Commands</strong></summary>
|
|
141
152
|
|
|
@@ -196,25 +207,6 @@ See `commandmate --help` for all options.
|
|
|
196
207
|
|
|
197
208
|
</details>
|
|
198
209
|
|
|
199
|
-
<details>
|
|
200
|
-
<summary><strong>Screenshots</strong></summary>
|
|
201
|
-
|
|
202
|
-
### Desktop
|
|
203
|
-
|
|
204
|
-

|
|
205
|
-
|
|
206
|
-
### Worktree Detail View (Message / Console / History)
|
|
207
|
-
|
|
208
|
-
| Desktop | Mobile (History) | Mobile (Terminal) |
|
|
209
|
-
|---------|-----------------|-------------------|
|
|
210
|
-
|  |  |  |
|
|
211
|
-
|
|
212
|
-
### Top Page (Mobile)
|
|
213
|
-
|
|
214
|
-

|
|
215
|
-
|
|
216
|
-
</details>
|
|
217
|
-
|
|
218
210
|
<details>
|
|
219
211
|
<summary><strong>Troubleshooting & FAQ</strong></summary>
|
|
220
212
|
|
|
@@ -258,7 +250,13 @@ Claude Code sets `CLAUDECODE=1` to prevent nesting. CommandMate removes this aut
|
|
|
258
250
|
A: CommandMate runs a web server on your PC. To access it from your phone, your phone and PC must be on the same network (Wi-Fi). Run `commandmate init` and enable external access — this sets `CM_BIND=0.0.0.0`. Then open `http://<your-PC-IP>:3000` in your phone's browser.
|
|
259
251
|
|
|
260
252
|
**Q: Can I access it from outside my home network?**
|
|
261
|
-
A: Yes. Use a tunneling service
|
|
253
|
+
A: Yes. Use a tunneling service to securely expose your local server without opening router ports:
|
|
254
|
+
|
|
255
|
+
- [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) — free, requires Cloudflare account
|
|
256
|
+
- [ngrok](https://ngrok.com/) — free tier available, easy setup
|
|
257
|
+
- [Pinggy](https://pinggy.io/) — no sign-up required, simple SSH-based tunnel
|
|
258
|
+
|
|
259
|
+
Alternatively, a VPN or an authenticated reverse proxy (Basic Auth, OIDC, etc.) also works. **Do not** expose the server directly to the internet without authentication.
|
|
262
260
|
|
|
263
261
|
**Q: Does it work on iPhone / Android?**
|
|
264
262
|
A: Yes. CommandMate's Web UI is responsive and works on any modern mobile browser (Safari, Chrome, etc.). No app install required.
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"docs-reader.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/docs-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;
|
|
1
|
+
{"version":3,"file":"docs-reader.d.ts","sourceRoot":"","sources":["../../../src/cli/utils/docs-reader.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAmDH;;GAEG;AACH,wBAAgB,oBAAoB,IAAI,MAAM,EAAE,CAE/C;AAED;;GAEG;AACH,wBAAgB,cAAc,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAEvD;AAED;;;;;GAKG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAOnD;AAED;;;;;;GAMG;AACH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,KAAK,CAAC;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,EAAE,CAAA;CAAE,CAAC,CA0BvF"}
|
|
@@ -66,6 +66,7 @@ const SECTION_MAP = {
|
|
|
66
66
|
'workflow-examples': 'docs/user-guide/workflow-examples.md',
|
|
67
67
|
'cli-setup': 'docs/user-guide/cli-setup-guide.md',
|
|
68
68
|
'agents': 'docs/user-guide/agents-guide.md',
|
|
69
|
+
'cmate-schedules': 'docs/user-guide/cmate-schedules-guide.md',
|
|
69
70
|
'architecture': 'docs/architecture.md',
|
|
70
71
|
'readme': 'README.md',
|
|
71
72
|
};
|
package/dist/server/server.js
CHANGED
|
@@ -39,6 +39,7 @@ const worktrees_1 = require("./src/lib/worktrees");
|
|
|
39
39
|
const db_instance_1 = require("./src/lib/db-instance");
|
|
40
40
|
const response_poller_1 = require("./src/lib/response-poller");
|
|
41
41
|
const auto_yes_manager_1 = require("./src/lib/auto-yes-manager");
|
|
42
|
+
const schedule_manager_1 = require("./src/lib/schedule-manager");
|
|
42
43
|
const db_migrations_1 = require("./src/lib/db-migrations");
|
|
43
44
|
const env_1 = require("./src/lib/env");
|
|
44
45
|
const db_repository_1 = require("./src/lib/db-repository");
|
|
@@ -227,6 +228,8 @@ app.prepare().then(() => {
|
|
|
227
228
|
console.log(`> WebSocket server ready`);
|
|
228
229
|
// Initialize worktrees after server starts
|
|
229
230
|
await initializeWorktrees();
|
|
231
|
+
// [S3-010] Initialize schedule manager AFTER worktrees are ready
|
|
232
|
+
(0, schedule_manager_1.initScheduleManager)();
|
|
230
233
|
});
|
|
231
234
|
// Graceful shutdown with timeout
|
|
232
235
|
let isShuttingDown = false;
|
|
@@ -241,6 +244,8 @@ app.prepare().then(() => {
|
|
|
241
244
|
(0, response_poller_1.stopAllPolling)();
|
|
242
245
|
// Issue #138: Stop all auto-yes pollers
|
|
243
246
|
(0, auto_yes_manager_1.stopAllAutoYesPolling)();
|
|
247
|
+
// Issue #294: Stop all scheduled executions (SIGKILL fire-and-forget)
|
|
248
|
+
(0, schedule_manager_1.stopAllSchedules)();
|
|
244
249
|
// Close WebSocket connections immediately (don't wait)
|
|
245
250
|
(0, ws_server_1.closeWebSocket)();
|
|
246
251
|
// Force exit after 3 seconds if graceful shutdown fails
|
|
@@ -0,0 +1,79 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/**
|
|
3
|
+
* CMATE.md Shared Constants
|
|
4
|
+
* Issue #294: Constants shared between server-side parser and client-side validator
|
|
5
|
+
*
|
|
6
|
+
* This module has NO Node.js dependencies (no 'fs'), so it can be safely
|
|
7
|
+
* imported from both server and client code.
|
|
8
|
+
*/
|
|
9
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
10
|
+
exports.MAX_SCHEDULE_ENTRIES = exports.MAX_CRON_EXPRESSION_LENGTH = exports.NAME_PATTERN = exports.CONTROL_CHAR_PATTERN = exports.CMATE_FILENAME = void 0;
|
|
11
|
+
exports.sanitizeContent = sanitizeContent;
|
|
12
|
+
exports.isValidCronExpression = isValidCronExpression;
|
|
13
|
+
// =============================================================================
|
|
14
|
+
// File
|
|
15
|
+
// =============================================================================
|
|
16
|
+
/** CMATE.md filename */
|
|
17
|
+
exports.CMATE_FILENAME = 'CMATE.md';
|
|
18
|
+
// =============================================================================
|
|
19
|
+
// Sanitization
|
|
20
|
+
// =============================================================================
|
|
21
|
+
/**
|
|
22
|
+
* Unicode control character regex for sanitization.
|
|
23
|
+
* Matches: C0 control chars (except \t \n \r), C1 control chars,
|
|
24
|
+
* zero-width characters, directional control characters.
|
|
25
|
+
*
|
|
26
|
+
* NOTE: No /g flag on the export — callers must use String.replace(pattern, '')
|
|
27
|
+
* with /g or String.replaceAll() to avoid lastIndex state issues.
|
|
28
|
+
*
|
|
29
|
+
* [S4-002] Strips potentially dangerous Unicode control characters
|
|
30
|
+
*/
|
|
31
|
+
exports.CONTROL_CHAR_PATTERN =
|
|
32
|
+
// eslint-disable-next-line no-control-regex
|
|
33
|
+
/[\x00-\x08\x0B\x0C\x0E-\x1F\x7F\x80-\x9F\u200B-\u200F\u2028-\u202F\uFEFF]/;
|
|
34
|
+
/**
|
|
35
|
+
* Remove Unicode control characters from a string.
|
|
36
|
+
* Preserves tabs (\t), newlines (\n), and carriage returns (\r).
|
|
37
|
+
*
|
|
38
|
+
* @param content - Raw string to sanitize
|
|
39
|
+
* @returns Sanitized string with control characters removed
|
|
40
|
+
*/
|
|
41
|
+
function sanitizeContent(content) {
|
|
42
|
+
// Use RegExp constructor with /g to avoid lastIndex state on the shared pattern
|
|
43
|
+
return content.replace(new RegExp(exports.CONTROL_CHAR_PATTERN.source, 'g'), '');
|
|
44
|
+
}
|
|
45
|
+
// =============================================================================
|
|
46
|
+
// Name Validation
|
|
47
|
+
// =============================================================================
|
|
48
|
+
/**
|
|
49
|
+
* Name validation pattern.
|
|
50
|
+
* Allows: ASCII word chars, Japanese chars (CJK, Hiragana, Katakana, Symbols),
|
|
51
|
+
* spaces, and hyphens. Length: 1-100 characters.
|
|
52
|
+
*
|
|
53
|
+
* [S4-011] Prevents injection through name field
|
|
54
|
+
*/
|
|
55
|
+
exports.NAME_PATTERN = /^[\w\u3000-\u303F\u3040-\u309F\u30A0-\u30FF\u4E00-\u9FFF\uF900-\uFAFF\s-]{1,100}$/;
|
|
56
|
+
// =============================================================================
|
|
57
|
+
// Limits
|
|
58
|
+
// =============================================================================
|
|
59
|
+
/** Maximum cron expression length */
|
|
60
|
+
exports.MAX_CRON_EXPRESSION_LENGTH = 100;
|
|
61
|
+
/** Maximum number of schedule entries per worktree */
|
|
62
|
+
exports.MAX_SCHEDULE_ENTRIES = 100;
|
|
63
|
+
// =============================================================================
|
|
64
|
+
// Cron Validation
|
|
65
|
+
// =============================================================================
|
|
66
|
+
/**
|
|
67
|
+
* Validate a cron expression.
|
|
68
|
+
* Checks length and basic format (5-6 fields separated by spaces).
|
|
69
|
+
*
|
|
70
|
+
* @param expression - Cron expression to validate
|
|
71
|
+
* @returns true if the expression appears valid
|
|
72
|
+
*/
|
|
73
|
+
function isValidCronExpression(expression) {
|
|
74
|
+
if (expression.length > exports.MAX_CRON_EXPRESSION_LENGTH) {
|
|
75
|
+
return false;
|
|
76
|
+
}
|
|
77
|
+
const parts = expression.trim().split(/\s+/);
|
|
78
|
+
return parts.length >= 5 && parts.length <= 6;
|
|
79
|
+
}
|