commandmate 0.3.2 → 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.
Files changed (111) hide show
  1. package/.next/BUILD_ID +1 -1
  2. package/.next/app-build-manifest.json +13 -13
  3. package/.next/app-path-routes-manifest.json +1 -1
  4. package/.next/build-manifest.json +4 -4
  5. package/.next/cache/.tsbuildinfo +1 -1
  6. package/.next/cache/config.json +3 -3
  7. package/.next/cache/webpack/client-production/0.pack +0 -0
  8. package/.next/cache/webpack/client-production/1.pack +0 -0
  9. package/.next/cache/webpack/client-production/2.pack +0 -0
  10. package/.next/cache/webpack/client-production/index.pack +0 -0
  11. package/.next/cache/webpack/client-production/index.pack.old +0 -0
  12. package/.next/cache/webpack/edge-server-production/0.pack +0 -0
  13. package/.next/cache/webpack/edge-server-production/index.pack +0 -0
  14. package/.next/cache/webpack/server-production/0.pack +0 -0
  15. package/.next/cache/webpack/server-production/index.pack +0 -0
  16. package/.next/next-server.js.nft.json +1 -1
  17. package/.next/prerender-manifest.json +1 -1
  18. package/.next/required-server-files.json +1 -1
  19. package/.next/routes-manifest.json +1 -1
  20. package/.next/server/app/_not-found/page_client-reference-manifest.js +1 -1
  21. package/.next/server/app/api/app/update-check/route.js +1 -1
  22. package/.next/server/app/api/ollama/models/route.js +1 -0
  23. package/.next/server/app/api/ollama/models/route.js.nft.json +1 -0
  24. package/.next/server/app/api/ollama/models.body +1 -0
  25. package/.next/server/app/api/ollama/models.meta +1 -0
  26. package/.next/server/app/api/repositories/route.js +3 -3
  27. package/.next/server/app/api/repositories/route.js.nft.json +1 -1
  28. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js +1 -1
  29. package/.next/server/app/api/worktrees/[id]/auto-yes/route.js.nft.json +1 -1
  30. package/.next/server/app/api/worktrees/[id]/cli-tool/route.js +1 -1
  31. package/.next/server/app/api/worktrees/[id]/current-output/route.js +1 -1
  32. package/.next/server/app/api/worktrees/[id]/current-output/route.js.nft.json +1 -1
  33. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js +1 -1
  34. package/.next/server/app/api/worktrees/[id]/execution-logs/[logId]/route.js.nft.json +1 -1
  35. package/.next/server/app/api/worktrees/[id]/execution-logs/route.js +1 -1
  36. package/.next/server/app/api/worktrees/[id]/execution-logs/route.js.nft.json +1 -1
  37. package/.next/server/app/api/worktrees/[id]/interrupt/route.js +1 -1
  38. package/.next/server/app/api/worktrees/[id]/interrupt/route.js.nft.json +1 -1
  39. package/.next/server/app/api/worktrees/[id]/kill-session/route.js +1 -1
  40. package/.next/server/app/api/worktrees/[id]/kill-session/route.js.nft.json +1 -1
  41. package/.next/server/app/api/worktrees/[id]/messages/route.js +1 -1
  42. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js +1 -1
  43. package/.next/server/app/api/worktrees/[id]/prompt-response/route.js.nft.json +1 -1
  44. package/.next/server/app/api/worktrees/[id]/respond/route.js +1 -1
  45. package/.next/server/app/api/worktrees/[id]/respond/route.js.nft.json +1 -1
  46. package/.next/server/app/api/worktrees/[id]/route.js +1 -1
  47. package/.next/server/app/api/worktrees/[id]/route.js.nft.json +1 -1
  48. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js +1 -1
  49. package/.next/server/app/api/worktrees/[id]/schedules/[scheduleId]/route.js.nft.json +1 -1
  50. package/.next/server/app/api/worktrees/[id]/schedules/route.js +2 -2
  51. package/.next/server/app/api/worktrees/[id]/schedules/route.js.nft.json +1 -1
  52. package/.next/server/app/api/worktrees/[id]/send/route.js +1 -1
  53. package/.next/server/app/api/worktrees/[id]/send/route.js.nft.json +1 -1
  54. package/.next/server/app/api/worktrees/[id]/slash-commands/route.js +1 -1
  55. package/.next/server/app/api/worktrees/[id]/start-polling/route.js +1 -1
  56. package/.next/server/app/api/worktrees/[id]/start-polling/route.js.nft.json +1 -1
  57. package/.next/server/app/api/worktrees/route.js +1 -1
  58. package/.next/server/app/api/worktrees/route.js.nft.json +1 -1
  59. package/.next/server/app/login/page_client-reference-manifest.js +1 -1
  60. package/.next/server/app/page.js +1 -1
  61. package/.next/server/app/page_client-reference-manifest.js +1 -1
  62. package/.next/server/app/worktrees/[id]/files/[...path]/page_client-reference-manifest.js +1 -1
  63. package/.next/server/app/worktrees/[id]/page.js +4 -4
  64. package/.next/server/app/worktrees/[id]/page_client-reference-manifest.js +1 -1
  65. package/.next/server/app/worktrees/[id]/terminal/page_client-reference-manifest.js +1 -1
  66. package/.next/server/app-paths-manifest.json +7 -6
  67. package/.next/server/chunks/2314.js +1 -1
  68. package/.next/server/chunks/4559.js +1 -1
  69. package/.next/server/chunks/539.js +10 -10
  70. package/.next/server/chunks/5853.js +1 -1
  71. package/.next/server/chunks/6228.js +1 -1
  72. package/.next/server/chunks/7425.js +59 -39
  73. package/.next/server/chunks/7566.js +1 -1
  74. package/.next/server/chunks/8693.js +1 -1
  75. package/.next/server/chunks/9446.js +1 -0
  76. package/.next/server/functions-config-manifest.json +1 -1
  77. package/.next/server/middleware-build-manifest.js +1 -1
  78. package/.next/server/middleware-manifest.json +5 -5
  79. package/.next/server/pages/500.html +1 -1
  80. package/.next/server/server-reference-manifest.json +1 -1
  81. package/.next/static/chunks/8091-274bc0716106e7fc.js +1 -0
  82. package/.next/static/chunks/app/page-060057e02b841125.js +1 -0
  83. package/.next/static/chunks/app/worktrees/[id]/page-78580947c201d698.js +1 -0
  84. package/.next/static/chunks/{main-db79434ee4a6c931.js → main-2feda12a4d321111.js} +1 -1
  85. package/.next/static/css/{bd6065b03ddb3efd.css → e85de230ef5ddc40.css} +1 -1
  86. package/.next/trace +5 -5
  87. package/.next/types/app/api/ollama/models/route.ts +343 -0
  88. package/README.md +74 -76
  89. package/dist/server/src/config/schedule-config.js +7 -1
  90. package/dist/server/src/lib/auto-yes-manager.js +2 -2
  91. package/dist/server/src/lib/claude-executor.js +15 -4
  92. package/dist/server/src/lib/cli-patterns.js +73 -9
  93. package/dist/server/src/lib/cli-tools/gemini.js +81 -22
  94. package/dist/server/src/lib/cli-tools/manager.js +4 -2
  95. package/dist/server/src/lib/cli-tools/types.js +64 -2
  96. package/dist/server/src/lib/cli-tools/vibe-local.js +163 -0
  97. package/dist/server/src/lib/cmate-parser.js +25 -3
  98. package/dist/server/src/lib/db-migrations.js +50 -1
  99. package/dist/server/src/lib/db.js +51 -1
  100. package/dist/server/src/lib/prompt-detector.js +4 -3
  101. package/dist/server/src/lib/response-poller.js +22 -11
  102. package/dist/server/src/lib/schedule-manager.js +6 -2
  103. package/dist/server/src/lib/selected-agents-validator.js +99 -0
  104. package/dist/server/src/types/sidebar.js +9 -4
  105. package/package.json +1 -1
  106. package/.next/server/chunks/7536.js +0 -1
  107. package/.next/static/chunks/8091-925542bdfc843dce.js +0 -1
  108. package/.next/static/chunks/app/page-238b5a70d8c101e9.js +0 -1
  109. package/.next/static/chunks/app/worktrees/[id]/page-0c889ab3f30d5af7.js +0 -1
  110. /package/.next/static/{j8HFvzDZj7tHjAnhpXUno → O7EDFfAYQNe_HRbORxQAC}/_buildManifest.js +0 -0
  111. /package/.next/static/{j8HFvzDZj7tHjAnhpXUno → O7EDFfAYQNe_HRbORxQAC}/_ssgManifest.js +0 -0
@@ -0,0 +1,343 @@
1
+ // File: /home/runner/work/CommandMate/CommandMate/src/app/api/ollama/models/route.ts
2
+ import * as entry from '../../../../../../src/app/api/ollama/models/route.js'
3
+ import type { NextRequest } from 'next/server.js'
4
+
5
+ type TEntry = typeof import('../../../../../../src/app/api/ollama/models/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
+ [![GitHub Stars](https://img.shields.io/github/stars/Kewton/CommandMate?style=social)](https://github.com/Kewton/CommandMate)
3
4
  ![npm version](https://img.shields.io/npm/v/commandmate)
4
5
  ![npm downloads](https://img.shields.io/npm/dm/commandmate)
5
6
  ![license](https://img.shields.io/github/license/Kewton/CommandMate)
@@ -16,12 +17,17 @@
16
17
 
17
18
  Not a "remote control" — a **mobile dev cockpit**.
18
19
 
19
- AI writes code for you. That was supposed to mean freedom.
20
- Instead, you're watching a terminal, afraid to walk away.
21
- Your friends are at dinner. Your kid needs a bath. You just want ten minutes on the couch.
22
- But close the lid and every session dies. So you sit there — babysitting the machine that was supposed to babysit your code.
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
- CommandMate changes that. Auto Yes keeps the agent moving. Sessions survive in the background. And a Web UI on your phone means you can check in, review diffs, edit instructions, and send screenshots — from anywhere.
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
- ## The 6 Pillars
40
+ ## Key Features
39
41
 
40
- | Pillar | What it does | Why it matters |
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
- ## Quick Start
55
+ ## Use Cases
52
56
 
53
- **Prerequisites:** macOS / Linux, Node.js v20+, npm, git, tmux, openssl
54
-
55
- ```bash
56
- # Install & start in one command
57
- npx commandmate
58
-
59
- # Or install globally
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
- | Mobile Web UI | Yes | Yes | **No** | Yes |
78
- | File Viewer | Yes | No | No | No |
79
- | Markdown Editor | Yes | No | No | No |
80
- | Screenshot Instructions | Yes | No | Not possible | No |
81
- | Free / OSS | Yes | Free + Paid | Yes | $20/mo |
82
- | Runs 100% Locally | Yes | Server-routed | Yes | Cloud fallback |
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
- ## Workflow
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
- 2. Close your laptop and go
90
+ ![Desktop view](./docs/images/screenshot-desktop.png)
94
91
 
95
- 3. Check in from your phone
96
- → Web UI shows all sessions at a glance
92
+ ### Mobile
97
93
 
98
- 4. Review code changes
99
- → File Viewer lets you read diffs on mobile
94
+ | Top Page | Worktree (History) | Worktree (Terminal) |
95
+ |----------|-------------------|-------------------|
96
+ | ![Mobile view](./docs/images/screenshot-mobile.png) | ![Mobile - History](./docs/images/screenshot-worktree-mobile.png) | ![Mobile - Terminal](./docs/images/screenshot-worktree-mobile-terminal.png) |
100
97
 
101
- 5. Adjust direction
102
- → Edit a Markdown instruction file, or type a new prompt
98
+ ### Worktree Detail (Desktop)
103
99
 
104
- 6. Snap a bug
105
- → Screenshot Instructions: attach a photo and say "Fix this"
106
-
107
- 7. Claude Code sees the image and starts fixing
108
- ```
100
+ ![Desktop - Worktree detail](./docs/images/screenshot-worktree-desktop.png)
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
- ![Desktop view](./docs/images/screenshot-desktop.png)
205
-
206
- ### Worktree Detail View (Message / Console / History)
207
-
208
- | Desktop | Mobile (History) | Mobile (Terminal) |
209
- |---------|-----------------|-------------------|
210
- | ![Desktop - Worktree detail](./docs/images/screenshot-worktree-desktop.png) | ![Mobile - History](./docs/images/screenshot-worktree-mobile.png) | ![Mobile - Terminal](./docs/images/screenshot-worktree-mobile-terminal.png) |
211
-
212
- ### Top Page (Mobile)
213
-
214
- ![Mobile view](./docs/images/screenshot-mobile.png)
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 like [Cloudflare Tunnel](https://developers.cloudflare.com/cloudflare-one/connections/connect-networks/) to securely expose your local server without opening router ports. 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.
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.
@@ -10,7 +10,7 @@
10
10
  * [S4-014] UUID v4 format validation
11
11
  */
12
12
  Object.defineProperty(exports, "__esModule", { value: true });
13
- exports.UUID_V4_PATTERN = exports.DEFAULT_PERMISSIONS = exports.CODEX_SANDBOXES = exports.CLAUDE_PERMISSIONS = exports.MAX_SCHEDULE_CRON_LENGTH = exports.MAX_SCHEDULE_MESSAGE_LENGTH = exports.MAX_SCHEDULE_NAME_LENGTH = void 0;
13
+ exports.UUID_V4_PATTERN = exports.DEFAULT_PERMISSIONS = exports.VIBE_LOCAL_PERMISSIONS = exports.GEMINI_PERMISSIONS = exports.CODEX_SANDBOXES = exports.CLAUDE_PERMISSIONS = exports.MAX_SCHEDULE_CRON_LENGTH = exports.MAX_SCHEDULE_MESSAGE_LENGTH = exports.MAX_SCHEDULE_NAME_LENGTH = void 0;
14
14
  exports.isValidUuidV4 = isValidUuidV4;
15
15
  // =============================================================================
16
16
  // Validation Constants
@@ -28,10 +28,16 @@ exports.MAX_SCHEDULE_CRON_LENGTH = 100;
28
28
  exports.CLAUDE_PERMISSIONS = ['default', 'acceptEdits', 'plan', 'dontAsk', 'bypassPermissions'];
29
29
  /** Allowed sandbox values for codex CLI (--sandbox) */
30
30
  exports.CODEX_SANDBOXES = ['read-only', 'workspace-write', 'danger-full-access'];
31
+ /** Allowed permission values for gemini CLI (no permission flags) */
32
+ exports.GEMINI_PERMISSIONS = [];
33
+ /** Allowed permission values for vibe-local CLI (no permission flags) */
34
+ exports.VIBE_LOCAL_PERMISSIONS = [];
31
35
  /** Default permission per CLI tool */
32
36
  exports.DEFAULT_PERMISSIONS = {
33
37
  claude: 'acceptEdits',
34
38
  codex: 'workspace-write',
39
+ gemini: '',
40
+ 'vibe-local': '',
35
41
  };
36
42
  // =============================================================================
37
43
  // UUID Validation
@@ -403,7 +403,7 @@ async function captureAndCleanOutput(worktreeId, cliToolId) {
403
403
  // captureSessionOutput() default is 1000 lines, but tmux buffer capture
404
404
  // requires 5000 to avoid truncating long outputs.
405
405
  const output = await (0, cli_session_1.captureSessionOutput)(worktreeId, cliToolId, 5000);
406
- return (0, cli_patterns_1.stripAnsi)(output);
406
+ return (0, cli_patterns_1.stripBoxDrawing)((0, cli_patterns_1.stripAnsi)(output));
407
407
  }
408
408
  /**
409
409
  * Process stop condition check using delta-based approach.
@@ -465,7 +465,7 @@ async function detectAndRespondToPrompt(worktreeId, pollerState, cliToolId, clea
465
465
  try {
466
466
  // 1. Detect prompt
467
467
  const promptOptions = (0, cli_patterns_1.buildDetectPromptOptions)(cliToolId);
468
- const promptDetection = (0, prompt_detector_1.detectPrompt)(cleanOutput, promptOptions);
468
+ const promptDetection = (0, prompt_detector_1.detectPrompt)((0, cli_patterns_1.stripBoxDrawing)(cleanOutput), promptOptions);
469
469
  if (!promptDetection.isPrompt || !promptDetection.promptData) {
470
470
  // No prompt detected - reset lastAnsweredPromptKey (Issue #306)
471
471
  pollerState.lastAnsweredPromptKey = null;
@@ -30,7 +30,7 @@ exports.EXECUTION_TIMEOUT_MS = 5 * 60 * 1000;
30
30
  /** Maximum message length sent to claude -p */
31
31
  exports.MAX_MESSAGE_LENGTH = 10000;
32
32
  /** Allowed CLI tool identifiers for scheduled execution */
33
- exports.ALLOWED_CLI_TOOLS = new Set(['claude', 'codex']);
33
+ exports.ALLOWED_CLI_TOOLS = new Set(['claude', 'codex', 'gemini', 'vibe-local']);
34
34
  // =============================================================================
35
35
  // Executor
36
36
  // =============================================================================
@@ -55,17 +55,27 @@ function truncateOutput(output) {
55
55
  *
56
56
  * - claude: -p <message> --output-format text --permission-mode <permission>
57
57
  * - codex: exec <message> --sandbox <permission>
58
+ * - gemini: -p <message>
59
+ * - vibe-local: [-p <message> -y] or [--model <model> -p <message> -y]
58
60
  * - others: -p <message> (fallback)
59
61
  *
60
62
  * @param message - Prompt message
61
63
  * @param cliToolId - CLI tool identifier
62
64
  * @param permission - Permission mode (claude: --permission-mode, codex: --sandbox)
65
+ * @param options - Additional options (e.g., model for vibe-local)
63
66
  * @returns Array of CLI arguments
64
67
  */
65
- function buildCliArgs(message, cliToolId, permission) {
68
+ function buildCliArgs(message, cliToolId, permission, options) {
66
69
  switch (cliToolId) {
67
70
  case 'codex':
68
71
  return ['exec', message, '--sandbox', permission ?? 'workspace-write'];
72
+ case 'gemini':
73
+ return ['-p', message];
74
+ case 'vibe-local':
75
+ if (options?.model) {
76
+ return ['--model', options.model, '-p', message, '-y'];
77
+ }
78
+ return ['-p', message, '-y'];
69
79
  case 'claude':
70
80
  default:
71
81
  return ['-p', message, '--output-format', 'text', '--permission-mode', permission ?? 'acceptEdits'];
@@ -78,9 +88,10 @@ function buildCliArgs(message, cliToolId, permission) {
78
88
  * @param cwd - Working directory (worktree path from DB)
79
89
  * @param cliToolId - CLI tool to use (default: 'claude')
80
90
  * @param permission - Permission mode (claude: --permission-mode, codex: --sandbox)
91
+ * @param options - Additional options (e.g., model for vibe-local)
81
92
  * @returns Execution result with output and status
82
93
  */
83
- async function executeClaudeCommand(message, cwd, cliToolId = 'claude', permission) {
94
+ async function executeClaudeCommand(message, cwd, cliToolId = 'claude', permission, options) {
84
95
  // Validate cliToolId against whitelist [SEC-001]
85
96
  if (!exports.ALLOWED_CLI_TOOLS.has(cliToolId)) {
86
97
  return {
@@ -94,7 +105,7 @@ async function executeClaudeCommand(message, cwd, cliToolId = 'claude', permissi
94
105
  const truncatedMessage = message.length > exports.MAX_MESSAGE_LENGTH
95
106
  ? message.substring(0, exports.MAX_MESSAGE_LENGTH)
96
107
  : message;
97
- const args = buildCliArgs(truncatedMessage, cliToolId, permission);
108
+ const args = buildCliArgs(truncatedMessage, cliToolId, permission, options);
98
109
  return new Promise((resolve) => {
99
110
  const child = (0, child_process_1.execFile)(cliToolId, args, {
100
111
  cwd,