proctolog 0.1.1 → 0.3.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.
- checksums.yaml +4 -4
- data/CHANGELOG.md +111 -3
- data/README.md +32 -18
- data/app/controllers/proctolog/application_controller.rb +2 -2
- data/app/controllers/proctolog/bursts_controller.rb +1 -1
- data/app/controllers/proctolog/choices_controller.rb +3 -1
- data/app/controllers/proctolog/decisions_controller.rb +1 -1
- data/app/controllers/proctolog/notes_controller.rb +2 -2
- data/app/controllers/proctolog/prompts_controller.rb +1 -1
- data/app/controllers/proctolog/pull_requests_controller.rb +2 -4
- data/app/controllers/proctolog/review_data_controller.rb +3 -1
- data/app/controllers/proctolog/shares_controller.rb +27 -0
- data/app/helpers/proctolog/decisions_helper.rb +6 -2
- data/app/helpers/proctolog/desk_helper.rb +48 -8
- data/app/helpers/proctolog/rounds_helper.rb +4 -2
- data/app/helpers/proctolog/shares_helper.rb +11 -0
- data/app/helpers/proctolog/standings_helper.rb +5 -4
- data/app/models/proctolog/burst.rb +17 -8
- data/app/models/proctolog/choices.rb +8 -0
- data/app/models/proctolog/doctor.rb +23 -12
- data/app/models/proctolog/github.rb +1 -1
- data/app/models/proctolog/localhost.rb +17 -0
- data/app/models/proctolog/marks.rb +1 -1
- data/app/models/proctolog/note.rb +4 -0
- data/app/models/proctolog/owner.rb +3 -0
- data/app/models/proctolog/pick.rb +12 -0
- data/app/models/proctolog/rounds.rb +37 -0
- data/app/models/proctolog/server.rb +1 -7
- data/app/models/proctolog/setup.rb +190 -0
- data/app/models/proctolog/share/ngrok.rb +120 -0
- data/app/models/proctolog/share/settings.rb +23 -0
- data/app/models/proctolog/share/tailscale.rb +103 -0
- data/app/models/proctolog/share.rb +101 -0
- data/app/models/proctolog/standing.rb +24 -7
- data/app/views/proctolog/bursts/_line.html.erb +2 -2
- data/app/views/proctolog/bursts/_side.html.erb +6 -5
- data/app/views/proctolog/bursts/_side_stop.html.erb +4 -4
- data/app/views/proctolog/notes/_card.html.erb +7 -3
- data/app/views/proctolog/open_pull_requests/_side.html.erb +4 -3
- data/app/views/proctolog/open_pull_requests/_start.html.erb +8 -0
- data/app/views/proctolog/open_pull_requests/index.html.erb +4 -5
- data/app/views/proctolog/open_pull_requests/start/_first_time.html.erb +3 -0
- data/app/views/proctolog/open_pull_requests/start/_nothing_open.html.erb +3 -0
- data/app/views/proctolog/open_pull_requests/start/_nothing_written.html.erb +3 -0
- data/app/views/proctolog/shares/_line.html.erb +5 -0
- data/app/views/proctolog/shares/show.html.erb +48 -0
- data/app/views/proctolog/shares/ways/_ngrok.html.erb +6 -0
- data/app/views/proctolog/shares/ways/_tailscale.html.erb +2 -0
- data/bin/proctolog +154 -27
- data/completions/_proctolog +31 -6
- data/config/application.rb +23 -4
- data/config/initializers/grouse.rb +1 -1
- data/config/routes.rb +3 -0
- data/docs/manual/bursts.md +7 -2
- data/docs/manual/deciding.md +4 -1
- data/docs/manual/handover.md +6 -0
- data/docs/manual/pr-format.md +1 -1
- data/docs/manual/reading.md +54 -27
- data/docs/manual/setup.md +59 -0
- data/docs/manual/sharing.md +77 -0
- data/docs/manual/updates.md +11 -4
- data/lib/proctolog/changelog/news.rb +67 -0
- data/lib/proctolog/changelog.rb +75 -0
- data/lib/proctolog/installation.rb +19 -0
- data/lib/proctolog/version.rb +1 -1
- data/public/asking.js +31 -4
- data/public/decisions.js +4 -4
- data/public/desk.js +10 -6
- data/public/keys.js +6 -0
- data/public/notes.js +164 -58
- data/public/pick.css +21 -0
- data/public/respond.css +6 -0
- data/public/respond.js +51 -7
- data/public/review-drafts.js +47 -9
- data/public/review-rounds.js +27 -3
- data/public/share.css +58 -0
- data/public/side.js +11 -20
- data/public/terminal.css +295 -34
- data/public/viewer.js +700 -173
- metadata +22 -1
data/bin/proctolog
CHANGED
|
@@ -17,6 +17,7 @@ end
|
|
|
17
17
|
require_relative "../config/application"
|
|
18
18
|
require_relative "../app/models/proctolog/project"
|
|
19
19
|
require "rbconfig"
|
|
20
|
+
require "socket"
|
|
20
21
|
require "thor"
|
|
21
22
|
|
|
22
23
|
module Proctolog
|
|
@@ -26,9 +27,15 @@ module Proctolog
|
|
|
26
27
|
|
|
27
28
|
# A command prints what it was asked for and nothing else, and asks
|
|
28
29
|
# 1Password for no keys. The desk's log, with what Grouse says at boot it
|
|
29
|
-
# is missing, and the keys are for `serve`, which says so in
|
|
30
|
-
|
|
30
|
+
# is missing, and the keys are for the owner's `serve`, which says so in
|
|
31
|
+
# the block. A trial desk serves without them.
|
|
32
|
+
def boot!(&)
|
|
31
33
|
Project.current = Project.discover(options[:project] || Dir.pwd)
|
|
34
|
+
boot_desk!(&)
|
|
35
|
+
end
|
|
36
|
+
|
|
37
|
+
# The desk alone, for a command of this Mac's rather than a checkout's.
|
|
38
|
+
def boot_desk!
|
|
32
39
|
Desk.config.log_level = :error
|
|
33
40
|
Desk.config.x.reads_keys = false
|
|
34
41
|
yield Desk.config if block_given?
|
|
@@ -128,6 +135,62 @@ module Proctolog
|
|
|
128
135
|
def tree = super
|
|
129
136
|
end
|
|
130
137
|
|
|
138
|
+
class ShareCommands < Thor
|
|
139
|
+
include Booting
|
|
140
|
+
|
|
141
|
+
def self.exit_on_failure? = true
|
|
142
|
+
|
|
143
|
+
class_option :port, type: :numeric, default: 4040, aliases: "-p", desc: "The port of the desk"
|
|
144
|
+
default_command :start
|
|
145
|
+
|
|
146
|
+
desc "start", "Share the desk through Tailscale or ngrok, and print its address"
|
|
147
|
+
long_desc <<~TEXT
|
|
148
|
+
Lets you reach the desk on this Mac from your phone, or a computer elsewhere. Through Tailscale, only
|
|
149
|
+
the devices signed in to your tailnet reach it, at this Mac's name there, and nothing goes on the
|
|
150
|
+
internet. Through ngrok, it is on the internet behind ngrok's sign-in with Google, which lets in only
|
|
151
|
+
the addresses --allow names, your Git email when none is named. Without --via, it shares the way named
|
|
152
|
+
last, or the first one ready, Tailscale first. The share stays open until `proctolog share stop`, the
|
|
153
|
+
desk running or not; one already open is only printed. `proctolog share status` says what each way
|
|
154
|
+
needs first.
|
|
155
|
+
TEXT
|
|
156
|
+
option :via, type: :string, enum: %w[tailscale ngrok], desc: "The way to share it, remembered for the next time"
|
|
157
|
+
option :allow, type: :string, repeatable: true, desc: "An email that may sign in through ngrok, in place of your Git email"
|
|
158
|
+
def start
|
|
159
|
+
boot_desk!
|
|
160
|
+
abort "No desk answers on :#{port}. Start one with the share: proctolog serve --share#{port_option}" unless Localhost.answers?(port, within: 1)
|
|
161
|
+
puts Share.open(port, via: options[:via], allow: options[:allow])
|
|
162
|
+
puts "Stop it with: proctolog share stop#{port_option}"
|
|
163
|
+
rescue Share::Refused => e
|
|
164
|
+
abort e.message
|
|
165
|
+
end
|
|
166
|
+
|
|
167
|
+
desc "stop", "Stop sharing the desk: it is only on this Mac again"
|
|
168
|
+
def stop
|
|
169
|
+
boot_desk!
|
|
170
|
+
share = Share.at(port)
|
|
171
|
+
share&.close
|
|
172
|
+
puts share ? "The desk on :#{port} is only on this Mac again." : "The desk on :#{port} was not shared."
|
|
173
|
+
rescue Share::Refused => e
|
|
174
|
+
abort e.message
|
|
175
|
+
end
|
|
176
|
+
|
|
177
|
+
desc "status", "Say whether the desk is shared, and what Tailscale and ngrok each need to share it"
|
|
178
|
+
def status
|
|
179
|
+
boot_desk!
|
|
180
|
+
puts Share.at(port) || "The desk on :#{port} is only on this Mac, at #{Desk.address(port)}."
|
|
181
|
+
Share.ways.each { puts it.ready? ? "ok #{it.name}: ready, and #{it.who}" : "-- #{it.unready}" }
|
|
182
|
+
end
|
|
183
|
+
|
|
184
|
+
desc "tree", "Print a tree of all available commands", hide: true
|
|
185
|
+
def tree = super
|
|
186
|
+
|
|
187
|
+
no_commands do
|
|
188
|
+
def port = options[:port]
|
|
189
|
+
|
|
190
|
+
def port_option = (" --port #{port}" unless port == 4040)
|
|
191
|
+
end
|
|
192
|
+
end
|
|
193
|
+
|
|
131
194
|
class CLI < Thor
|
|
132
195
|
include Booting
|
|
133
196
|
|
|
@@ -135,28 +198,43 @@ module Proctolog
|
|
|
135
198
|
|
|
136
199
|
class_option :project, type: :string, aliases: "-C", desc: "The Git checkout whose bursts to open"
|
|
137
200
|
|
|
201
|
+
no_commands do
|
|
202
|
+
# Bare, proctolog shows its commands under the one to start with.
|
|
203
|
+
def help(command = nil, subcommand = false)
|
|
204
|
+
puts "New here? proctolog setup takes you to the desk, a step at a time.", "" unless command
|
|
205
|
+
super
|
|
206
|
+
end
|
|
207
|
+
end
|
|
208
|
+
|
|
138
209
|
desc "burst SUBCOMMAND", "Start a burst of your own, or list the bursts there are"
|
|
139
210
|
subcommand "burst", BurstCommands
|
|
140
211
|
|
|
141
212
|
desc "unopened SUBCOMMAND", "List the pull requests nobody has opened yet, or say where one is written"
|
|
142
213
|
subcommand "unopened", UnopenedCommands
|
|
143
214
|
|
|
144
|
-
desc "
|
|
215
|
+
desc "share SUBCOMMAND", "Share the desk with your phone or another computer, through Tailscale or ngrok"
|
|
216
|
+
subcommand "share", ShareCommands
|
|
217
|
+
|
|
218
|
+
desc "serve", "Open the desk on http://proctolog.localhost:4040"
|
|
145
219
|
option :port, type: :numeric, default: 4040, aliases: "-p", desc: "The port to listen on"
|
|
220
|
+
option :share, type: :boolean, default: false, desc: "Share it too, as `proctolog share` does"
|
|
146
221
|
def serve
|
|
147
222
|
boot! do |config|
|
|
148
223
|
config.log_level = :warn
|
|
149
|
-
config.x.reads_keys =
|
|
224
|
+
config.x.reads_keys = !Desk.trial?
|
|
225
|
+
config.x.port = options[:port]
|
|
150
226
|
end
|
|
151
227
|
require "rackup"
|
|
152
228
|
Release.newer&.then { puts it.news }
|
|
153
229
|
Housekeeping.start
|
|
154
|
-
Rackup::Handler.get("puma").run(Desk, Host: "localhost", Port: options[:port])
|
|
230
|
+
Rackup::Handler.get("puma").run(Desk, Host: "localhost", Port: options[:port]) do |launcher|
|
|
231
|
+
launcher.events.after_booted { opened }
|
|
232
|
+
end
|
|
155
233
|
rescue Errno::EADDRINUSE
|
|
156
|
-
abort "Port #{options[:port]} is taken. If that is the desk, it is open at
|
|
234
|
+
abort "Port #{options[:port]} is taken. If that is the desk, it is open at #{Desk.address(options[:port])}; " \
|
|
157
235
|
"for a second one: proctolog serve --port #{options[:port] + 1}"
|
|
158
236
|
rescue Project::Missing => e
|
|
159
|
-
abort e.message
|
|
237
|
+
abort "#{e.message} New here? proctolog setup takes you to the desk, a step at a time."
|
|
160
238
|
end
|
|
161
239
|
|
|
162
240
|
desc "route [BURST]", "Print the order to take a burst in"
|
|
@@ -241,25 +319,20 @@ module Proctolog
|
|
|
241
319
|
map %w[-v --version] => :version
|
|
242
320
|
def version = puts("PRoctolog #{VERSION}")
|
|
243
321
|
|
|
244
|
-
desc "install", "
|
|
322
|
+
desc "install", "Another name for setup: link this PRoctolog, then set it up a step at a time"
|
|
245
323
|
long_desc <<~TEXT
|
|
246
|
-
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
`update` runs it for the version it installs.
|
|
324
|
+
Does what `proctolog setup` does, whichever of the two you remember: links the proctolog command, zsh's
|
|
325
|
+
completion and the agent skills to this PRoctolog, then takes you through the rest a step at a time.
|
|
326
|
+
`proctolog help setup` says how.
|
|
250
327
|
TEXT
|
|
251
|
-
def install
|
|
252
|
-
puts Installation.here.install
|
|
253
|
-
rescue Installation::Refused => e
|
|
254
|
-
abort e.message
|
|
255
|
-
end
|
|
328
|
+
def install = setup
|
|
256
329
|
|
|
257
330
|
desc "update", "Install the latest PRoctolog, and link to it"
|
|
258
331
|
long_desc <<~TEXT
|
|
259
332
|
Asks RubyGems for the latest PRoctolog and, when it is newer than this one, installs it the way this one
|
|
260
333
|
was: with `rv tool install` for one rvx runs, with `gem install` otherwise. A checkout is pulled
|
|
261
334
|
forward instead, when it is clean and on origin's own branch, and its bundle installed. Then runs the
|
|
262
|
-
new one's `
|
|
335
|
+
new one's `setup`, which links it. A desk open now runs on as it was: restart it to run the new one.
|
|
263
336
|
TEXT
|
|
264
337
|
def update
|
|
265
338
|
installation = Installation.here
|
|
@@ -275,13 +348,22 @@ module Proctolog
|
|
|
275
348
|
end
|
|
276
349
|
|
|
277
350
|
no_commands do
|
|
351
|
+
# Once the desk listens: where it is, and where else, when it is shared.
|
|
352
|
+
# A share that cannot open leaves the desk open on this Mac.
|
|
353
|
+
def opened
|
|
354
|
+
puts "The desk is open at #{Desk.address}."
|
|
355
|
+
puts(options[:share] ? Share.open(Desk.port) : Share.at(Desk.port) || return)
|
|
356
|
+
rescue Share::Refused => e
|
|
357
|
+
warn "The desk is not shared. #{e.message}"
|
|
358
|
+
end
|
|
359
|
+
|
|
278
360
|
# rv runs as the shell would, not on the Ruby and gems it gave this one.
|
|
279
361
|
def take_with_rv(latest)
|
|
280
362
|
Outside.run do |environment|
|
|
281
363
|
system(environment, "rv", "tool", "install", "proctolog@#{latest}", "--gem-server", "#{Release.server}/", exception: true)
|
|
282
364
|
puts taken(latest)
|
|
283
365
|
# rvx runs the newest it holds, which is now this one; rv 0.7 runs no proctolog@VERSION.
|
|
284
|
-
exec(environment, "rvx", "--no-install", "proctolog", "
|
|
366
|
+
exec(environment.merge(Installation.updating_from(VERSION)), "rvx", "--no-install", "proctolog", "setup")
|
|
285
367
|
end
|
|
286
368
|
end
|
|
287
369
|
|
|
@@ -290,17 +372,20 @@ module Proctolog
|
|
|
290
372
|
system(RbConfig.ruby, File.join(RbConfig::CONFIG["bindir"], "gem"), "install", "proctolog", "--version", latest.version, "--clear-sources", "--source", Release.server, exception: true)
|
|
291
373
|
puts taken(latest)
|
|
292
374
|
# RubyGems' launcher takes the version to run as its first word.
|
|
293
|
-
exec(RbConfig.ruby, File.join(Gem.bindir, "proctolog"), "_#{latest}_", "
|
|
375
|
+
exec(Installation.updating_from(VERSION), RbConfig.ruby, File.join(Gem.bindir, "proctolog"), "_#{latest}_", "setup")
|
|
294
376
|
end
|
|
295
377
|
|
|
296
378
|
# The checkout's own bundle, on this Ruby, as bin/proctolog runs it.
|
|
379
|
+
# Nothing to take when origin has nothing newer, as for a gem.
|
|
297
380
|
def take_with_git(installation)
|
|
298
|
-
|
|
381
|
+
pull = installation.pull
|
|
382
|
+
return puts("PRoctolog #{VERSION} in #{installation.root} is the latest: origin has nothing newer.") unless pull.moved?
|
|
383
|
+
|
|
299
384
|
Bundler.with_unbundled_env do
|
|
300
385
|
system(RbConfig.ruby, Gem.bin_path("bundler", "bundle"), "install", "--quiet", chdir: installation.root.to_s, exception: true)
|
|
301
386
|
end
|
|
302
387
|
puts "Pulled #{installation.root} forward. Restart the desk to run it."
|
|
303
|
-
exec(RbConfig.ruby, installation.root.join("bin/install").to_s)
|
|
388
|
+
exec(Installation.updating_from(VERSION), RbConfig.ruby, installation.root.join("bin/install").to_s)
|
|
304
389
|
end
|
|
305
390
|
|
|
306
391
|
def taken(latest) = "Installed PRoctolog #{latest}. Restart the desk to run it: one open now still runs #{Release.running}."
|
|
@@ -316,13 +401,55 @@ module Proctolog
|
|
|
316
401
|
Exits 0 when every need is met, 1 when one is not.
|
|
317
402
|
TEXT
|
|
318
403
|
def doctor
|
|
319
|
-
project =
|
|
320
|
-
|
|
404
|
+
project = discovered
|
|
405
|
+
boot_desk!
|
|
406
|
+
Doctor.new(project).tap { puts it }.then { exit(it.fine? ? 0 : 1) }
|
|
407
|
+
end
|
|
408
|
+
|
|
409
|
+
desc "setup", "Set PRoctolog up, a step at a time, from wherever you are to an open desk"
|
|
410
|
+
long_desc <<~TEXT
|
|
411
|
+
First links the proctolog command, zsh's completion and the agent skills to this PRoctolog: from the gem,
|
|
412
|
+
or the checkout, which also gets its launcher in ~/.local/bin. Anything of the same name that is not
|
|
413
|
+
PRoctolog's stays as it is. Then takes doctor's checks in the order you meet them: gh signed in, the
|
|
414
|
+
checkout you review and its GitHub repository, a first burst, what turns features on, sharing the desk
|
|
415
|
+
with your phone, and the desk itself. Each step says why it matters and what to do; Enter checks it again
|
|
416
|
+
once you have, and it is stamped DONE when it is met, or LATER when you leave it. Outside a checkout, its
|
|
417
|
+
step asks for the folder of one. Of anyone else's, nothing is installed for you. The last step opens the
|
|
418
|
+
desk with `proctolog serve`. `install` is another name for it, and `update` runs it for the version it
|
|
419
|
+
installs, which then only links and says what is new.
|
|
420
|
+
|
|
421
|
+
Outside a terminal it asks nothing: it prints each step as it stands, and exits 0 when the desk can
|
|
422
|
+
open, 1 when a step still waits on something the desk needs.
|
|
423
|
+
TEXT
|
|
424
|
+
def setup
|
|
425
|
+
installation = Installation.here
|
|
426
|
+
# An update hands over to the version it installed, to link it and say
|
|
427
|
+
# what is new: the owner asked for an update, not for a setup.
|
|
428
|
+
return puts(installation.install, "", installation.news) if installation.news
|
|
429
|
+
|
|
430
|
+
discovered
|
|
431
|
+
boot_desk!
|
|
432
|
+
setup = Setup.new(installation:, directory: options[:project] || Dir.pwd) { Doctor.new(discovered(it)) }
|
|
433
|
+
exit(setup.show ? 0 : 1) unless $stdin.tty?
|
|
434
|
+
|
|
435
|
+
case setup.walk
|
|
436
|
+
# The command that runs now, as a new terminal may not find proctolog yet.
|
|
437
|
+
in :serve then exec(RbConfig.ruby, $PROGRAM_NAME, "serve", *(["--project", Project.current.root.to_s] if Project.current))
|
|
438
|
+
in :finished then exit 0
|
|
439
|
+
in :stopped then exit 1
|
|
440
|
+
end
|
|
441
|
+
rescue Installation::Refused => e
|
|
442
|
+
abort e.message
|
|
443
|
+
end
|
|
444
|
+
|
|
445
|
+
no_commands do
|
|
446
|
+
# The checkout --project names, or the one this runs in, found again
|
|
447
|
+
# each time: one a setup step made is there the next time it checks.
|
|
448
|
+
def discovered(directory = options[:project] || Dir.pwd)
|
|
449
|
+
Project.current = Project.discover(directory)
|
|
321
450
|
rescue Project::Missing
|
|
322
|
-
nil
|
|
451
|
+
Project.current = nil
|
|
323
452
|
end
|
|
324
|
-
Desk.initialize! unless Desk.initialized?
|
|
325
|
-
Doctor.new(project).tap { puts it }.then { exit(it.fine? ? 0 : 1) }
|
|
326
453
|
end
|
|
327
454
|
end
|
|
328
455
|
end
|
data/completions/_proctolog
CHANGED
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
# where burst files were kept before; for check and burst new, the open pull
|
|
6
6
|
# requests of the desk's last reading, with their titles, and the branches
|
|
7
7
|
# whose pull requests nobody has opened yet; for unopened folder, every
|
|
8
|
-
# branch of the checkout.
|
|
8
|
+
# branch of the checkout; for share, its commands and the ways to share.
|
|
9
9
|
|
|
10
10
|
# "owner/name" of the checkout's GitHub origin.
|
|
11
11
|
_proctolog_repository() {
|
|
@@ -47,9 +47,10 @@ _proctolog_parts() {
|
|
|
47
47
|
|
|
48
48
|
_proctolog() {
|
|
49
49
|
local root own named
|
|
50
|
-
local -a commands burst_commands unopened_commands bursts pull_requests unopened branches parts project files
|
|
50
|
+
local -a commands burst_commands unopened_commands share_commands bursts pull_requests unopened branches parts project port files
|
|
51
51
|
commands=(
|
|
52
|
-
'serve:Open the desk on http://localhost:4040'
|
|
52
|
+
'serve:Open the desk on http://proctolog.localhost:4040'
|
|
53
|
+
'share:Share the desk with your phone or another computer, through Tailscale or ngrok'
|
|
53
54
|
'route:Print the order to take a burst in'
|
|
54
55
|
'handover:Hand what the owner decided so far to the agent that runs it'
|
|
55
56
|
'burst:Start a burst of your own, or list the bursts there are'
|
|
@@ -57,8 +58,9 @@ _proctolog() {
|
|
|
57
58
|
'check:Check a description as the desk reads it, of a pull request or a file'
|
|
58
59
|
'themes:Print the themes a decision may name, with what each means'
|
|
59
60
|
'doctor:Say what this Mac and this checkout lack, a line each with the fix'
|
|
61
|
+
'setup:Set PRoctolog up, a step at a time, from wherever you are to an open desk'
|
|
60
62
|
'version:Print which PRoctolog this is'
|
|
61
|
-
'install:
|
|
63
|
+
'install:Another name for setup: link this PRoctolog, then set it up a step at a time'
|
|
62
64
|
'update:Install the latest PRoctolog, and link to it'
|
|
63
65
|
'tree:Print a tree of all available commands'
|
|
64
66
|
'help:Describe the commands, or one of them'
|
|
@@ -73,7 +75,14 @@ _proctolog() {
|
|
|
73
75
|
'folder:Print the folder a pull request nobody has opened yet is written in'
|
|
74
76
|
'help:Describe the unopened commands, or one of them'
|
|
75
77
|
)
|
|
78
|
+
share_commands=(
|
|
79
|
+
'start:Share the desk through Tailscale or ngrok, and print its address'
|
|
80
|
+
'stop:Stop sharing the desk: it is only on this Mac again'
|
|
81
|
+
'status:Say whether the desk is shared, and what Tailscale and ngrok each need'
|
|
82
|
+
'help:Describe the share commands, or one of them'
|
|
83
|
+
)
|
|
76
84
|
project=('(-C --project)'{-C,--project}'[Git checkout]:directory:_directories')
|
|
85
|
+
port=('(-p --port)'{-p,--port}'[The port of the desk]:port:')
|
|
77
86
|
root=$(git -C "$PWD" rev-parse --show-toplevel 2>/dev/null)
|
|
78
87
|
own=${PROCTOLOG_DATA:-$HOME/.local/share/proctolog}/$(_proctolog_repository "$root")/bursts
|
|
79
88
|
bursts=($own/*.yml(N:t:r) $root/.proctolog/{bursts,reviews}/*.yml(N:t:r))
|
|
@@ -85,7 +94,23 @@ _proctolog() {
|
|
|
85
94
|
command) _describe -t commands 'command' commands ;;
|
|
86
95
|
argument)
|
|
87
96
|
case $words[1] in
|
|
88
|
-
serve) _arguments '
|
|
97
|
+
serve) _arguments $port '--share[Share it too, as proctolog share does]' $project ;;
|
|
98
|
+
share)
|
|
99
|
+
# Without a subcommand, share is start: its options come at once.
|
|
100
|
+
if (( CURRENT == 2 )) && [[ $words[2] != -* ]]; then
|
|
101
|
+
_describe -t commands 'share command' share_commands
|
|
102
|
+
else
|
|
103
|
+
case $words[2] in
|
|
104
|
+
start|-*)
|
|
105
|
+
_arguments $port \
|
|
106
|
+
'--via[The way to share it, remembered for the next time]:way:(tailscale ngrok)' \
|
|
107
|
+
'*--allow[An email that may sign in through ngrok]:email:' \
|
|
108
|
+
'1:: :' ;;
|
|
109
|
+
stop|status) _arguments $port '1:: :' ;;
|
|
110
|
+
help) _describe -t commands 'share command' share_commands ;;
|
|
111
|
+
esac
|
|
112
|
+
fi
|
|
113
|
+
;;
|
|
89
114
|
route) _arguments $project "1:burst:(${bursts})" ;;
|
|
90
115
|
handover)
|
|
91
116
|
named=${${words[2,-1]:#-*}[1]}
|
|
@@ -133,7 +158,7 @@ _proctolog() {
|
|
|
133
158
|
unopened=(${(f)"$(_proctolog_unopened "$root")"})
|
|
134
159
|
_arguments $project '(-f --file)'{-f,--file}'[A local Markdown file to check before it is published]:file:_files' \
|
|
135
160
|
"1:pull request:{_describe -t pull-requests 'open pull request' pull_requests; _describe -t unopened 'not opened yet' unopened}" ;;
|
|
136
|
-
themes|doctor|version|install|update) _arguments $project ;;
|
|
161
|
+
themes|doctor|setup|version|install|update) _arguments $project ;;
|
|
137
162
|
help) _describe -t commands 'command' commands ;;
|
|
138
163
|
esac
|
|
139
164
|
;;
|
data/config/application.rb
CHANGED
|
@@ -53,6 +53,18 @@ module Proctolog
|
|
|
53
53
|
|
|
54
54
|
def self.scratch = @scratch ||= Pathname(Dir.mktmpdir("proctolog")).tap { |made| at_exit { made.rmtree } }
|
|
55
55
|
|
|
56
|
+
def self.port = config.x.port
|
|
57
|
+
|
|
58
|
+
# Where the desk is on this Mac: a name of its own, which a browser's
|
|
59
|
+
# history completes from "procto" where localhost:4040 is one of many.
|
|
60
|
+
def self.address(port = self.port) = "http://proctolog.localhost:#{port}"
|
|
61
|
+
|
|
62
|
+
# A desk on marks of its own (PROCTOLOG_MARKS): a test's, or one an agent
|
|
63
|
+
# starts to try a change, often while the owner is away. It writes nothing
|
|
64
|
+
# to GitHub, and asks 1Password for no keys, which would wait on the owner
|
|
65
|
+
# to unlock it.
|
|
66
|
+
def self.trial? = ENV.key?("PROCTOLOG_MARKS")
|
|
67
|
+
|
|
56
68
|
config.root = File.expand_path("..", __dir__)
|
|
57
69
|
config.load_defaults 8.1
|
|
58
70
|
config.eager_load = false
|
|
@@ -61,8 +73,14 @@ module Proctolog
|
|
|
61
73
|
# Kept outside the installed app so an update does not sign out an open desk.
|
|
62
74
|
config.secret_key_base = kept("secret_key_base") { SecureRandom.hex(64) }
|
|
63
75
|
# Whatever the environment: a page of another site that points a name of
|
|
64
|
-
# its own at this Mac is turned away.
|
|
65
|
-
|
|
76
|
+
# its own at this Mac is turned away. Names under localhost are this
|
|
77
|
+
# Mac's alone, as browsers send them nowhere else: proctolog.localhost is
|
|
78
|
+
# the desk's own. While the desk is shared, it answers to the share's
|
|
79
|
+
# name too (Share).
|
|
80
|
+
config.hosts = [".localhost", IPAddr.new("127.0.0.0/8"), IPAddr.new("::1")]
|
|
81
|
+
config.host_authorization = {exclude: ->(request) { Proctolog::Share.carries?(request) }}
|
|
82
|
+
# The port the desk serves on, which `serve` names.
|
|
83
|
+
config.x.port = 4040
|
|
66
84
|
config.logger = ActiveSupport::Logger.new($stdout)
|
|
67
85
|
# Grouse says at boot what is not set up yet, which its tests need not hear.
|
|
68
86
|
config.log_level = Rails.env.test? ? :error : :warn
|
|
@@ -80,8 +98,9 @@ module Proctolog
|
|
|
80
98
|
config.active_job.queue_adapter = Rails.env.test? ? :test : :async
|
|
81
99
|
# Whether Grouse is given the owner's keys from 1Password (config/keys.yml):
|
|
82
100
|
# when the desk serves, and for Rails' own commands such as grouse:doctor.
|
|
83
|
-
# A `proctolog` command that only prints asks 1Password for nothing
|
|
84
|
-
|
|
101
|
+
# A `proctolog` command that only prints asks 1Password for nothing, and
|
|
102
|
+
# nor does a trial desk.
|
|
103
|
+
config.x.reads_keys = Rails.env.development? && !trial?
|
|
85
104
|
config.active_storage.service = :local
|
|
86
105
|
config.active_storage.variant_processor = :disabled
|
|
87
106
|
# The migrations are the record; the desk brings its database up to them
|
|
@@ -86,7 +86,7 @@ Grouse.configure do |c|
|
|
|
86
86
|
# The owner's keys, read from 1Password by config/keys.yml's references where the desk
|
|
87
87
|
# reads keys at all (config.x.reads_keys). Without one, a voice note arrives as audio only
|
|
88
88
|
# and a note is not written up; `op` says on the terminal why it could not read it. Tests
|
|
89
|
-
# read none, and send nothing to a vendor.
|
|
89
|
+
# and trial desks (PROCTOLOG_MARKS) read none, and send nothing to a vendor.
|
|
90
90
|
c.transcriber = Grouse::Transcribers::ElevenLabs.new(api_key: key.call("elevenlabs"))
|
|
91
91
|
c.structurer = Grouse::Structurers::Anthropic.new(api_key: key.call("anthropic"))
|
|
92
92
|
# c.transcriber = nil # turns a recording into words; with none, voice notes arrive as audio only
|
data/config/routes.rb
CHANGED
|
@@ -17,6 +17,9 @@ Proctolog::Desk.routes.draw do
|
|
|
17
17
|
# The pull requests, of every state, that a # typed in any box may mean.
|
|
18
18
|
resources :mentions, only: :index
|
|
19
19
|
|
|
20
|
+
# The desk shared with the owner's other devices, through Tailscale or ngrok.
|
|
21
|
+
resource :share, only: %i[show create destroy]
|
|
22
|
+
|
|
20
23
|
resources :bursts, only: %i[show create] do
|
|
21
24
|
resource :put_away, only: %i[create destroy], controller: :put_away
|
|
22
25
|
resources :pull_requests, only: :show, param: :number do
|
data/docs/manual/bursts.md
CHANGED
|
@@ -7,8 +7,8 @@ the one waiting for you; `proctolog route` and `proctolog handover` take it too
|
|
|
7
7
|
when no burst is named. With no burst yet, it opens on the repository's open
|
|
8
8
|
pull requests, at `/pull_requests`. "bursts & PRs" in the pane of every page
|
|
9
9
|
goes there: every burst at its top, the newest first, then the pull requests.
|
|
10
|
-
Its own pane says no burst is open, and lists your bursts with how
|
|
11
|
-
one's decisions
|
|
10
|
+
Its own pane says no burst is open, and lists your bursts with how many of
|
|
11
|
+
each one's decisions wait: a bead of each grade, and how many of it. A burst put away says so beside its name in its pane.
|
|
12
12
|
GitHub's stacks come first, each whole from the bottom up, then the pull requests in no
|
|
13
13
|
stack: yours first, then those written for PRoctolog (they have decisions),
|
|
14
14
|
then the newest. Each line says whose it is, what it sits on when that is not
|
|
@@ -27,6 +27,11 @@ Under "Not opened" come the branches with a description written for them that
|
|
|
27
27
|
nobody has opened yet, each with "Review alone", and "Review the chain" for one
|
|
28
28
|
that sits on another's branch ([how](unopened.md)).
|
|
29
29
|
|
|
30
|
+
"Start here" heads the page until you have a burst of your own, and whenever
|
|
31
|
+
there is nothing to decide: it says how many pull requests are written for
|
|
32
|
+
PRoctolog and how to open one, that none is yet and how to have them
|
|
33
|
+
rewritten, or that nothing is open and how to have some written.
|
|
34
|
+
|
|
30
35
|
## When a burst is done
|
|
31
36
|
|
|
32
37
|
A pull request that merges leaves the route, and goes under "Merged" at its
|
data/docs/manual/deciding.md
CHANGED
|
@@ -17,7 +17,10 @@ question's text, “Ask Claude now” opens a new Claude Code session in the
|
|
|
17
17
|
Claude app, in the checkout under review, with a prompt typed in: the decision
|
|
18
18
|
quoted with its pull request, branch and head, the pictures it rests on where
|
|
19
19
|
the desk keeps them on this Mac, and your question last, where you can go on
|
|
20
|
-
typing. It asks the session to answer and change nothing.
|
|
20
|
+
typing. It asks the session to answer and change nothing. A pull request
|
|
21
|
+
nobody has opened yet is named by its branch, its base and the file its
|
|
22
|
+
description is kept in, and the session is told to read it with git, since
|
|
23
|
+
GitHub has nothing of it. Nothing is sent
|
|
21
24
|
until you send it there. “Copy prompt” copies the same prompt, for a session
|
|
22
25
|
already open or another agent. With decisions selected, the prompt quotes each
|
|
23
26
|
of them. Asking leaves the question here a draft and the decision open: once
|
data/docs/manual/handover.md
CHANGED
|
@@ -56,6 +56,12 @@ to response, and Esc goes back to where `v` was pressed. Wherever you are in a
|
|
|
56
56
|
response, `a` confirms it, `q` replies or asks, `c` requests a correction, and
|
|
57
57
|
`l` defers. Ctrl/⌘ + Enter saves the reply; `e` exports the next pass.
|
|
58
58
|
|
|
59
|
+
A note of yours that an agent answers follows its answer in the marks, where
|
|
60
|
+
the page's Notes and an agent reading the files both see it: addressed once
|
|
61
|
+
the answer is in, confirmed once you confirm it here, open again once you
|
|
62
|
+
answer back. Its card says to judge it under Returned work and offers only
|
|
63
|
+
Withdraw. The pane's `↩` follows as you go, without the page loading again.
|
|
64
|
+
|
|
59
65
|
What the agent wrote is drawn as GitHub draws markdown. Under Evidence, the
|
|
60
66
|
pictures it names are shown, read from where they are on this Mac: two named
|
|
61
67
|
alike but for `before` and `after` stand side by side as one, and Space looks
|
data/docs/manual/pr-format.md
CHANGED
|
@@ -21,7 +21,7 @@ which only renders and keeps nothing.
|
|
|
21
21
|
## PR format
|
|
22
22
|
|
|
23
23
|
The skill in `skills/proctolog-pr-writing/` teaches an agent this format, with a
|
|
24
|
-
whole example in its `references/example-description.md`. `proctolog
|
|
24
|
+
whole example in its `references/example-description.md`. `proctolog setup`
|
|
25
25
|
links it into `~/.claude/skills/` and `~/.agents/skills/`. Keep the project's
|
|
26
26
|
template (Summary, Changes, Verification) and add:
|
|
27
27
|
|
data/docs/manual/reading.md
CHANGED
|
@@ -42,18 +42,23 @@ server goes on in the background until it is stopped. When it has stopped
|
|
|
42
42
|
without the desk, the end of its log shows while the pointer is on that line.
|
|
43
43
|
`t` goes to Try it. In a narrower window it waits at the foot of the page.
|
|
44
44
|
|
|
45
|
-
On the Route, `j` / `k` select a PR and Enter opens it. Each line
|
|
46
|
-
|
|
47
|
-
|
|
48
|
-
|
|
49
|
-
|
|
45
|
+
On the Route, `j` / `k` select a PR and Enter opens it. Each line ends with its
|
|
46
|
+
decisions as beads, a bead to each, as deciding draws them: a full-size bead
|
|
47
|
+
for a blocking decision, a small one for a non-blocking, solid once reviewed,
|
|
48
|
+
hollow while it waits. Before them it says **back to you** once the agent's
|
|
49
|
+
answers have come back for you to confirm, or answer, or once it addressed one
|
|
50
|
+
of your notes; otherwise whose it is when it is not yours: with the agent,
|
|
51
|
+
settled or waiting. The line the cursor is on opens out with the rest: its
|
|
52
|
+
layer, what is open (“4 returned, to confirm”), its pictures and words, and
|
|
53
|
+
when it last changed. `N` opens the next PR that asks for you: one with a
|
|
54
|
+
blocking decision left, or something back from the agent. Clicking a title opens that PR. Clicking elsewhere on a row,
|
|
50
55
|
or tabbing to one of its links, selects it and shows its actions. In a narrow
|
|
51
56
|
window those actions wrap under the selected row. On
|
|
52
57
|
a PR's page, `j` / `k` or the up/down arrows move through headings, description
|
|
53
58
|
blocks, pictures, decisions and returned work, as below. `[` / `]` jump between
|
|
54
59
|
sections; Enter or Right opens a section, Left closes it. Home / End reach the
|
|
55
60
|
first / last item. Tab / Shift+Tab reach links, buttons and fields. `J` / `K` open the
|
|
56
|
-
next / previous PR, `N` the next one
|
|
61
|
+
next / previous PR, `N` the next one that asks for you, and `h` returns
|
|
57
62
|
to the Route. `G`, on the Route or a PR's page, opens the PR on GitHub in a tab
|
|
58
63
|
of its own.
|
|
59
64
|
|
|
@@ -93,12 +98,14 @@ part stands apart in the pane. A handover of one part says the rest of the
|
|
|
93
98
|
burst is outside it, and names the chains that sit on it, so a change to it is
|
|
94
99
|
carried up through them.
|
|
95
100
|
|
|
96
|
-
A pane at the left of every page lists the route,
|
|
97
|
-
|
|
98
|
-
|
|
99
|
-
|
|
100
|
-
|
|
101
|
-
|
|
101
|
+
A pane at the left of every page lists the route, and marks the one you are
|
|
102
|
+
on. Each PR ends with the same beads as its line on the Route, smaller, and
|
|
103
|
+
they follow your choices as you make them. A `↩` before it means the agent's
|
|
104
|
+
answers came back on it for you. A `*` before a title means GitHub moved on
|
|
105
|
+
since the desk read it. Hold the pointer on a row and a card beside the pane
|
|
106
|
+
gives its title in full, says each of its marks in words (“back to you: 4
|
|
107
|
+
returned, to confirm”, “1 of 4 blocking decisions reviewed”), and links to it
|
|
108
|
+
on GitHub (and to “read again” when it moved).
|
|
102
109
|
|
|
103
110
|
The desk has no colour. What you are on and what waits for you is white and
|
|
104
111
|
bold; what is settled is grey. A bead says the same of anything that is
|
|
@@ -120,23 +127,43 @@ beside its box with its words: ⌘ Enter saves it written over, in the same
|
|
|
120
127
|
place, and Withdraw takes it back off the picture. Escape puts it away while
|
|
121
128
|
nothing in it is changed. Escape, or the ✕ at the top
|
|
122
129
|
right, closes the viewer and nothing else: you are back on the block, or the
|
|
123
|
-
decision, you opened it from
|
|
124
|
-
|
|
125
|
-
|
|
130
|
+
decision, you opened it from, and the page draws the notes written there
|
|
131
|
+
where it stands. Once the viewer shows only part of the picture, a map in its
|
|
132
|
+
corner shows the whole, with the part on the stage framed; a press on the map
|
|
133
|
+
goes there.
|
|
134
|
+
|
|
135
|
+
A note on a picture of the page itself, a click or a drag on it, is written
|
|
136
|
+
under the picture and saved where it stands too: the page does not load
|
|
137
|
+
again or move, zoomed in or not, and draws the note's box and its card.
|
|
126
138
|
|
|
127
139
|
Two pictures named `NAME-before` and `NAME-after` are one picture of a change:
|
|
128
140
|
the description shows them side by side under one name, and a decision links
|
|
129
|
-
them as `#screenshot-NAME`. In the viewer
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
141
|
+
them as `#screenshot-NAME`. In the viewer a quiet panel beside them lists the
|
|
142
|
+
five ways to look at them, each a button with its key; the one you are in
|
|
143
|
+
says what it shows. `s` side by side, `w` swipe (the after picture over the
|
|
144
|
+
before, up to a divider that a drag or the arrows move), `b` blink (one, then
|
|
145
|
+
the other), `o` onion skin (the after picture half seen through over the
|
|
146
|
+
before) and `d` difference (only what differs is lit). A view's key again, or
|
|
147
|
+
a press on its button, goes back to side by side. Blink is the exception: `b`
|
|
148
|
+
again shows the other half, when you want it, and Shift B sets it blinking by
|
|
149
|
+
itself, or stops it.
|
|
150
|
+
|
|
151
|
+
Under the views the panel counts the changed regions. They are outlined on
|
|
152
|
+
the pictures in black dashed with white, at the same place on each half; a
|
|
153
|
+
note's box is a heavier solid frame, so the two are told apart at a glance.
|
|
154
|
+
`n` and `p` focus on the next region and the one before, as does a press on
|
|
155
|
+
its number: it is tagged with its number and its half. Focusing keeps your
|
|
156
|
+
zoom: seen whole, the region is only outlined; zoomed in, the stage goes near
|
|
157
|
+
enough to see each region whole. `z` goes near to the one in focus, and `0`
|
|
158
|
+
back to the whole. The panel shows the region focused on as the before has it
|
|
159
|
+
and as the after does; Enter writes a note on it in the after, and a press on
|
|
160
|
+
either writes one there, in any view. Until the regions are found the panel
|
|
161
|
+
says “Comparing…”, then how many there are, “Nothing: the same pixels.”,
|
|
162
|
+
“Sizes differ: laid from the top left.”, or that finding them needs
|
|
163
|
+
ImageMagick. A click or a drag marks the half it is made on side by side, and
|
|
164
|
+
the one showing in blink, which holds still while you write; swipe, onion skin
|
|
165
|
+
and difference take a note only on a region the panel shows. Comparisons are
|
|
166
|
+
kept in the cache, by the pictures' pixels.
|
|
140
167
|
|
|
141
168
|
A run of pictures that no decision or other line of the description links
|
|
142
169
|
folds where it stood, with its captions, into one “More pictures (N)”, opened
|
|
@@ -162,7 +189,7 @@ discarding it, then Escape returns to the section heading. `u` undoes the last
|
|
|
162
189
|
choice, wherever you are on the page. Once every blocking decision is
|
|
163
190
|
reviewed, the page shows where the decisions end: what is reviewed and how many
|
|
164
191
|
non-blocking still wait; what to do next, where `g` marks the PR good to go,
|
|
165
|
-
`N` opens the next PR
|
|
192
|
+
`N` opens the next PR that asks for you, `e` hands over and
|
|
166
193
|
`h` goes back to the Route; and every decision on a line under its grade, with
|
|
167
194
|
what you chose on it or what holds it. Each grade says what it asks of you: a
|
|
168
195
|
blocking decision needs your answer before the work goes on, and a non-blocking
|