agentilda 1.0.3 → 2.0.1

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 (90) hide show
  1. checksums.yaml +4 -4
  2. data/agentilda.gemspec +10 -6
  3. data/agents/hansolo-reviewer.md +42 -13
  4. data/agents/lando-broker.md +40 -46
  5. data/agents/leah-researcher.md +36 -58
  6. data/agents/luke-backend.md +49 -61
  7. data/agents/palpatine-planner.md +39 -21
  8. data/agents/rey-frontend.md +44 -86
  9. data/agents/yoda-writer.md +38 -36
  10. data/bin/setup +26 -0
  11. data/exe/agentilda +13 -92
  12. data/exe/tilda-rs +0 -0
  13. data/lib/agentilda/adoption.rb +7 -2
  14. data/lib/agentilda/agent.rb +40 -99
  15. data/lib/agentilda/agents.rb +122 -0
  16. data/lib/agentilda/board.rb +91 -0
  17. data/lib/agentilda/brief.rb +54 -5
  18. data/lib/agentilda/child.rb +92 -0
  19. data/lib/agentilda/cli/agents/subcommands/describe.rb +8 -4
  20. data/lib/agentilda/cli/base.rb +46 -6
  21. data/lib/agentilda/cli/create/create.rb +46 -19
  22. data/lib/agentilda/cli/docs/docs.rb +6 -2
  23. data/lib/agentilda/cli/index/index.rb +6 -4
  24. data/lib/agentilda/cli/linear/linear.rb +3 -2
  25. data/lib/agentilda/cli/linear/subcommands/import.rb +26 -15
  26. data/lib/agentilda/cli/mail/mail.rb +31 -0
  27. data/lib/agentilda/cli/mail/subcommands/read.rb +43 -0
  28. data/lib/agentilda/cli/mail/subcommands/send.rb +43 -0
  29. data/lib/agentilda/cli/resync/subcommands/dirs.rb +6 -4
  30. data/lib/agentilda/cli/resync/subcommands/prs.rb +21 -13
  31. data/lib/agentilda/cli/run/run.rb +207 -99
  32. data/lib/agentilda/cli/unblock/unblock.rb +24 -13
  33. data/lib/agentilda/cli/worktree/worktree.rb +85 -0
  34. data/lib/agentilda/cli.rb +43 -0
  35. data/lib/agentilda/clock.rb +114 -0
  36. data/lib/agentilda/console.rb +157 -0
  37. data/lib/agentilda/control.rb +63 -0
  38. data/lib/agentilda/creator.rb +7 -2
  39. data/lib/agentilda/dashboard.rb +219 -0
  40. data/lib/agentilda/dev_work.rb +2 -0
  41. data/lib/agentilda/diagram.rb +12 -7
  42. data/lib/agentilda/dispatcher.rb +646 -0
  43. data/lib/agentilda/documentation.rb +14 -6
  44. data/lib/agentilda/executor.rb +362 -204
  45. data/lib/agentilda/feature.rb +14 -135
  46. data/lib/agentilda/frontmatter.rb +4 -2
  47. data/lib/agentilda/github.rb +19 -11
  48. data/lib/agentilda/index.rb +11 -11
  49. data/lib/agentilda/keyboard.rb +76 -8
  50. data/lib/agentilda/launcher.rb +69 -0
  51. data/lib/agentilda/ledger.rb +233 -0
  52. data/lib/agentilda/linear/api.rb +14 -10
  53. data/lib/agentilda/linear/attribution.rb +5 -5
  54. data/lib/agentilda/linear/import.rb +30 -14
  55. data/lib/agentilda/linear/issue.rb +6 -3
  56. data/lib/agentilda/linear/mapping.rb +18 -17
  57. data/lib/agentilda/linear/push.rb +19 -12
  58. data/lib/agentilda/linear/survey.rb +3 -2
  59. data/lib/agentilda/linear/unit.rb +12 -12
  60. data/lib/agentilda/linear.rb +5 -6
  61. data/lib/agentilda/mailbox.rb +141 -0
  62. data/lib/agentilda/markdown.rb +1 -1
  63. data/lib/agentilda/progress_log.rb +5 -1
  64. data/lib/agentilda/publisher.rb +6 -3
  65. data/lib/agentilda/pull_request.rb +0 -186
  66. data/lib/agentilda/pull_requests.rb +230 -0
  67. data/lib/agentilda/reporter.rb +9 -3
  68. data/lib/agentilda/resync.rb +120 -52
  69. data/lib/agentilda/roster.rb +31 -48
  70. data/lib/agentilda/runner.rb +72 -389
  71. data/lib/agentilda/screen/ratatui/bar.rb +57 -0
  72. data/lib/agentilda/screen/ratatui/key_translator.rb +29 -0
  73. data/lib/agentilda/screen/ratatui.rb +318 -0
  74. data/lib/agentilda/state_file.rb +174 -0
  75. data/lib/agentilda/state_machine.rb +60 -32
  76. data/lib/agentilda/status.rb +120 -37
  77. data/lib/agentilda/subject.rb +133 -0
  78. data/lib/agentilda/tally.rb +17 -11
  79. data/lib/agentilda/transcript.rb +47 -16
  80. data/lib/agentilda/tree.rb +10 -6
  81. data/lib/agentilda/ui.rb +204 -190
  82. data/lib/agentilda/unblocker.rb +25 -11
  83. data/lib/agentilda/version.rb +1 -1
  84. data/lib/agentilda/worktree.rb +14 -18
  85. data/lib/agentilda.rb +57 -70
  86. metadata +65 -13
  87. data/Gemfile +0 -26
  88. data/Gemfile.lock +0 -261
  89. data/bin/create-plan-folder +0 -125
  90. data/lib/dry/cli/banner.rb +0 -293
data/Gemfile.lock DELETED
@@ -1,261 +0,0 @@
1
- PATH
2
- remote: .
3
- specs:
4
- agentilda (1.0.3)
5
- aasm
6
- concurrent-ruby
7
- dry-cli
8
- dry-cli-autocomplete
9
- dry-inflector
10
- dry-monads
11
- fuzzy-string-match
12
- parallel
13
- pastel
14
- strings
15
- tty-box
16
- tty-command
17
- tty-progressbar
18
- tty-screen
19
- tty-spinner
20
- unicode-display_width
21
-
22
- GEM
23
- remote: https://rubygems.org/
24
- specs:
25
- RubyInline (3.14.4)
26
- ZenTest (~> 4.3)
27
- ZenTest (4.12.2)
28
- aasm (6.0.0)
29
- concurrent-ruby (~> 1.0)
30
- ast (2.4.3)
31
- bigdecimal (4.1.2)
32
- colored2 (4.0.3)
33
- concurrent-ruby (1.3.8)
34
- coverage-badge (0.1.1)
35
- diff-lcs (1.6.2)
36
- dry-cli (1.4.1)
37
- dry-cli-autocomplete (0.1.3)
38
- dry-cli (>= 1.0)
39
- dry-inflector (>= 1.0)
40
- dry-core (1.2.0)
41
- concurrent-ruby (~> 1.0)
42
- logger
43
- zeitwerk (~> 2.6)
44
- dry-inflector (1.3.1)
45
- dry-monads (1.10.0)
46
- concurrent-ruby (~> 1.0)
47
- dry-core (~> 1.1)
48
- zeitwerk (~> 2.6)
49
- erb (6.0.7)
50
- fuzzy-string-match (1.0.1)
51
- RubyInline (>= 3.8.6)
52
- hana (1.3.7)
53
- io-console (0.9.2)
54
- irb (1.18.0)
55
- pp (>= 0.6.0)
56
- prism (>= 1.3.0)
57
- rdoc (>= 4.0.0)
58
- reline (>= 0.4.2)
59
- json (2.21.2)
60
- json_schemer (2.5.0)
61
- bigdecimal
62
- hana (~> 1.3)
63
- regexp_parser (~> 2.0)
64
- simpleidn (~> 0.2)
65
- language_server-protocol (3.17.0.6)
66
- lint_roller (1.1.0)
67
- logger (1.7.0)
68
- parallel (2.1.0)
69
- parser (3.3.12.0)
70
- ast (~> 2.4.1)
71
- racc
72
- pastel (0.8.0)
73
- tty-color (~> 0.5)
74
- pp (0.6.4)
75
- prettyprint
76
- prettyprint (0.2.0)
77
- prism (1.9.0)
78
- racc (1.8.1)
79
- rainbow (3.1.1)
80
- rake (13.4.2)
81
- rbs (4.2.0)
82
- logger
83
- prism (>= 1.6.0)
84
- tsort
85
- rdoc (8.0.0)
86
- erb
87
- prism (>= 1.6.0)
88
- rbs (>= 4.0.0)
89
- tsort
90
- regexp_parser (2.12.0)
91
- reline (0.7.0)
92
- io-console (~> 0.5)
93
- rspec (3.13.2)
94
- rspec-core (~> 3.13.0)
95
- rspec-expectations (~> 3.13.0)
96
- rspec-mocks (~> 3.13.0)
97
- rspec-core (3.13.6)
98
- rspec-support (~> 3.13.0)
99
- rspec-expectations (3.13.5)
100
- diff-lcs (>= 1.2.0, < 2.0)
101
- rspec-support (~> 3.13.0)
102
- rspec-its (2.0.0)
103
- rspec-core (>= 3.13.0)
104
- rspec-expectations (>= 3.13.0)
105
- rspec-mocks (3.13.8)
106
- diff-lcs (>= 1.2.0, < 2.0)
107
- rspec-support (~> 3.13.0)
108
- rspec-support (3.13.7)
109
- rspec_junit_formatter (0.6.0)
110
- rspec-core (>= 2, < 4, != 2.12.0)
111
- rubocop (1.88.2)
112
- json (~> 2.3)
113
- language_server-protocol (~> 3.17.0.2)
114
- lint_roller (~> 1.1.0)
115
- parallel (>= 1.10)
116
- parser (>= 3.3.0.2)
117
- rainbow (>= 2.2.2, < 4.0)
118
- regexp_parser (>= 2.9.3, < 3.0)
119
- rubocop-ast (>= 1.49.0, < 2.0)
120
- ruby-progressbar (~> 1.7)
121
- unicode-display_width (>= 2.4.0, < 4.0)
122
- rubocop-ast (1.50.0)
123
- parser (>= 3.3.7.2)
124
- prism (~> 1.7)
125
- rubocop-performance (1.26.1)
126
- lint_roller (~> 1.1)
127
- rubocop (>= 1.75.0, < 2.0)
128
- rubocop-ast (>= 1.47.1, < 2.0)
129
- ruby-progressbar (1.13.0)
130
- simplecov (1.1.1)
131
- simpleidn (0.2.3)
132
- standard (1.56.0)
133
- language_server-protocol (~> 3.17.0.2)
134
- lint_roller (~> 1.0)
135
- rubocop (~> 1.88.0)
136
- standard-custom (~> 1.0.0)
137
- standard-performance (~> 1.8)
138
- standard-custom (1.0.2)
139
- lint_roller (~> 1.0)
140
- rubocop (~> 1.50)
141
- standard-performance (1.9.0)
142
- lint_roller (~> 1.1)
143
- rubocop-performance (~> 1.26.0)
144
- strings (0.2.1)
145
- strings-ansi (~> 0.2)
146
- unicode-display_width (>= 1.5, < 3.0)
147
- unicode_utils (~> 1.4)
148
- strings-ansi (0.2.0)
149
- tsort (0.2.0)
150
- tty-box (0.7.0)
151
- pastel (~> 0.8)
152
- strings (~> 0.2.0)
153
- tty-cursor (~> 0.7)
154
- tty-color (0.6.0)
155
- tty-command (0.10.1)
156
- pastel (~> 0.8)
157
- tty-cursor (0.7.1)
158
- tty-progressbar (0.18.3)
159
- strings-ansi (~> 0.2)
160
- tty-cursor (~> 0.7)
161
- tty-screen (~> 0.8)
162
- unicode-display_width (>= 1.6, < 3.0)
163
- tty-screen (0.8.2)
164
- tty-spinner (0.9.3)
165
- tty-cursor (~> 0.7)
166
- unicode-display_width (2.6.0)
167
- unicode_utils (1.4.0)
168
- yard (0.9.45)
169
- zeitwerk (2.8.3)
170
-
171
- PLATFORMS
172
- arm64-darwin-25
173
- ruby
174
-
175
- DEPENDENCIES
176
- agentilda!
177
- colored2
178
- coverage-badge
179
- irb
180
- json_schemer
181
- rake
182
- rspec
183
- rspec-its
184
- rspec_junit_formatter
185
- simplecov
186
- standard
187
- yard
188
-
189
- CHECKSUMS
190
- RubyInline (3.14.4) sha256=75aec9bb7626a56a6c443922d1d299e1f062795f1d7b7195b0b863ca3776d9ab
191
- ZenTest (4.12.2) sha256=814e7478a28f631df8f802aee40bd1faa2a53df1fb50e086eb8655f9ee5d60b9
192
- aasm (6.0.0) sha256=c96ce60ea9574eb194e33f29d801be430b8cade2f31c8a5d137a9870ee39d404
193
- agentilda (1.0.3)
194
- ast (2.4.3) sha256=954615157c1d6a382bc27d690d973195e79db7f55e9765ac7c481c60bdb4d383
195
- bigdecimal (4.1.2) sha256=53d217666027eab4280346fba98e7d5b66baaae1b9c3c1c0ffe89d48188a3fbd
196
- bundler (4.0.19) sha256=b48056e4c77fb3853cc47775b5447ede44aaa4f53c32f168014ab4fe86587d47
197
- colored2 (4.0.3) sha256=63e1038183976287efc43034f5cca17fb180b4deef207da8ba78d051cbce2b37
198
- concurrent-ruby (1.3.8) sha256=b2f1be836e968ccc78ccfce277ea79c72a88633f22306782c16ff23fb415d1e1
199
- coverage-badge (0.1.1) sha256=f4ada4edadfa98cd99a50ab49f04ffc1af926e9f00513a063b774bd683eda131
200
- diff-lcs (1.6.2) sha256=9ae0d2cba7d4df3075fe8cd8602a8604993efc0dfa934cff568969efb1909962
201
- dry-cli (1.4.1) sha256=b8015bb76c708aa8705a36faf694973e75eeeffca39b89c8e172dc6f66a7d874
202
- dry-cli-autocomplete (0.1.3) sha256=14f76447bda0000c71739b585c827dc0a0a763e30a68a8b4b2dff91f640768e4
203
- dry-core (1.2.0) sha256=0cc5a7da88df397f153947eeeae42e876e999c1e30900f3c536fb173854e96a1
204
- dry-inflector (1.3.1) sha256=7fb0c2bb04f67638f25c52e7ba39ab435d922a3a5c3cd196120f63accb682dcc
205
- dry-monads (1.10.0) sha256=68c90d77617c6ce88d60704fc3b233907e6320974152fe75ad947f968006ca39
206
- erb (6.0.7) sha256=c5ca6dc25b0ef974a44dc8f59fe847577122483b1968a38dec305c60bf91ee92
207
- fuzzy-string-match (1.0.1) sha256=0740e515b3d1f7a40bd7f058a67a63682a9f4d98e4a95a9eb670aa562a047549
208
- hana (1.3.7) sha256=5425db42d651fea08859811c29d20446f16af196308162894db208cac5ce9b0d
209
- io-console (0.9.2) sha256=efa74f891dd03c0939a931dfc6e74c2813d904763d456ea9762b0525e748db08
210
- irb (1.18.0) sha256=de9454a0703a54704b9811a5ef31a60c86949fbf4013fcf244fabc7c775248e3
211
- json (2.21.2) sha256=1f1d3b7cf2b3ba1a69beca0bb6db13d5438b80bff3cd54cdaaa620b9b07c1c6a
212
- json_schemer (2.5.0) sha256=2f01fb4cce721a4e08dd068fc2030cffd0702a7f333f1ea2be6e8991f00ae396
213
- language_server-protocol (3.17.0.6) sha256=5ef2c0c138f8267e1bc631d3328347d354f96724b0af22f2c79516120443b7f0
214
- lint_roller (1.1.0) sha256=2c0c845b632a7d172cb849cc90c1bce937a28c5c8ccccb50dfd46a485003cc87
215
- logger (1.7.0) sha256=196edec7cc44b66cfb40f9755ce11b392f21f7967696af15d274dde7edff0203
216
- parallel (2.1.0) sha256=b35258865c2e31134c5ecb708beaaf6772adf9d5efae28e93e99260877b09356
217
- parser (3.3.12.0) sha256=21a6d7f755d5a24dfbdc6e6b772e4e879a52e7631a88bc5a3a134606052c9828
218
- pastel (0.8.0) sha256=481da9fb7d2f6e6b1a08faf11fa10363172dc40fd47848f096ae21209f805a75
219
- pp (0.6.4) sha256=dfcb0fce700c41456265922884f9fe195d7fbb0674a3578e6c0f69588e82b570
220
- prettyprint (0.2.0) sha256=2bc9e15581a94742064a3cc8b0fb9d45aae3d03a1baa6ef80922627a0766f193
221
- prism (1.9.0) sha256=7b530c6a9f92c24300014919c9dcbc055bf4cdf51ec30aed099b06cd6674ef85
222
- racc (1.8.1) sha256=4a7f6929691dbec8b5209a0b373bc2614882b55fc5d2e447a21aaa691303d62f
223
- rainbow (3.1.1) sha256=039491aa3a89f42efa1d6dec2fc4e62ede96eb6acd95e52f1ad581182b79bc6a
224
- rake (13.4.2) sha256=cb825b2bd5f1f8e91ca37bddb4b9aaf345551b4731da62949be002fa89283701
225
- rbs (4.2.0) sha256=51f7b886dcc05bc09e10b901daa6a81829f6adc03101d6ca9ea4aac6103e0674
226
- rdoc (8.0.0) sha256=03bf8c08a9639658855a0cfd77c0abca8325c227693f7f33f82957811348c469
227
- regexp_parser (2.12.0) sha256=35a916a1d63190ab5c9009457136ae5f3c0c7512d60291d0d1378ba18ce08ebb
228
- reline (0.7.0) sha256=5b012d8e55dbf9d450f12bde2cf7d15ff546ae80b3f8f3b30e570d431815583d
229
- rspec (3.13.2) sha256=206284a08ad798e61f86d7ca3e376718d52c0bc944626b2349266f239f820587
230
- rspec-core (3.13.6) sha256=a8823c6411667b60a8bca135364351dda34cd55e44ff94c4be4633b37d828b2d
231
- rspec-expectations (3.13.5) sha256=33a4d3a1d95060aea4c94e9f237030a8f9eae5615e9bd85718fe3a09e4b58836
232
- rspec-its (2.0.0) sha256=a88e8bc38149f2835e93533591ec4f5c829aacbfd41269a2e6f9f5b82f5260df
233
- rspec-mocks (3.13.8) sha256=086ad3d3d17533f4237643de0b5c42f04b66348c28bf6b9c2d3f4a3b01af1d47
234
- rspec-support (3.13.7) sha256=0640e5570872aafefd79867901deeeeb40b0c9875a36b983d85f54fb7381c47c
235
- rspec_junit_formatter (0.6.0) sha256=40dde674e6ae4e6cc0ff560da25497677e34fefd2338cc467a8972f602b62b15
236
- rubocop (1.88.2) sha256=8def251c90cd955feb4daa3edc0ab56893250c4ce90ef81e6c80c03f9a939bbf
237
- rubocop-ast (1.50.0) sha256=b9ca88300da0803ee222ad20cdb30494c0a784eed06fdc35d254b06d662788db
238
- rubocop-performance (1.26.1) sha256=cd19b936ff196df85829d264b522fd4f98b6c89ad271fa52744a8c11b8f71834
239
- ruby-progressbar (1.13.0) sha256=80fc9c47a9b640d6834e0dc7b3c94c9df37f08cb072b7761e4a71e22cff29b33
240
- simplecov (1.1.1) sha256=25825ef13f0b2e74694d769817dad6ab8e90131dabdaa666e522fea105521e78
241
- simpleidn (0.2.3) sha256=08ce96f03fa1605286be22651ba0fc9c0b2d6272c9b27a260bc88be05b0d2c29
242
- standard (1.56.0) sha256=ae2af4d9669589162ac69ed5ef59dcf9f346d4afc81f7e62b84339310dfcb787
243
- standard-custom (1.0.2) sha256=424adc84179a074f1a2a309bb9cf7cd6bfdb2b6541f20c6bf9436c0ba22a652b
244
- standard-performance (1.9.0) sha256=49483d31be448292951d80e5e67cdcb576c2502103c7b40aec6f1b6e9c88e3f2
245
- strings (0.2.1) sha256=933293b3c95cf85b81eb44b3cf673e3087661ba739bbadfeadf442083158d6fb
246
- strings-ansi (0.2.0) sha256=90262d760ea4a94cc2ae8d58205277a343409c288cbe7c29416b1826bd511c88
247
- tsort (0.2.0) sha256=9650a793f6859a43b6641671278f79cfead60ac714148aabe4e3f0060480089f
248
- tty-box (0.7.0) sha256=17f453a0c1df43871fdb6de461ee797b78574dacc652372fa2ae98a00785df8b
249
- tty-color (0.6.0) sha256=6f9c37ca3a4e2367fb2e6d09722762647d6f455c111f05b59f35730eeb24332a
250
- tty-command (0.10.1) sha256=0c6c471fcb932d55518734eb4e2e07e9efdd2918713cc39bb7393ba862471192
251
- tty-cursor (0.7.1) sha256=79534185e6a777888d88628b14b6a1fdf5154a603f285f80b1753e1908e0bf48
252
- tty-progressbar (0.18.3) sha256=6cbb4260e55e74a9180d502143eb6a467d2c8e51bf5f3c9509fb5cacc3d4a5f6
253
- tty-screen (0.8.2) sha256=c090652115beae764336c28802d633f204fb84da93c6a968aa5d8e319e819b50
254
- tty-spinner (0.9.3) sha256=0e036f047b4ffb61f2aa45f5a770ec00b4d04130531558a94bfc5b192b570542
255
- unicode-display_width (2.6.0) sha256=12279874bba6d5e4d2728cef814b19197dbb10d7a7837a869bab65da943b7f5a
256
- unicode_utils (1.4.0) sha256=b922d0cf2313b6b7136ada6645ce7154ffc86418ca07d53b058efe9eb72f2a40
257
- yard (0.9.45) sha256=52e211493f7cb8a3ebf7e104a25a1e73937a3103092545d34cb88fafebb3dc51
258
- zeitwerk (2.8.3) sha256=2c85125a8467ce069e20123d1e709a08955c9d29c118c25b46b7b7fafdbb92e5
259
-
260
- BUNDLED WITH
261
- 4.0.19
@@ -1,125 +0,0 @@
1
- #!/usr/bin/env bash
2
- #
3
- # WARNING: deprecated in favor of "tilda create <words>" or
4
- # "tilda create --from <file.md>"
5
- #
6
- # create-plan-folder — create the next numbered plan folder
7
- #
8
- # create-plan-folder [-D <dir>] <status> <topic words...>
9
- #
10
- # Prints the created path on stdout (so `cd "$(create-plan-folder ...)"` works);
11
- # all chatter goes to stderr.
12
- #
13
- # Portable to bash 3.2 — the one Apple still ships in /bin/bash.
14
-
15
- set -eo pipefail
16
-
17
- usage() {
18
- echo -e "
19
- ${bldylw}DEPRECATED:${bldred}
20
- This file is deprecated for use in creating directories. Partly because
21
- it does not auto-generate the spec.md inside, while 'agentilda' executable
22
- does.
23
-
24
- Please use: ${txgrn}agentilda create [ word word ... ] | [ --from file.md ]
25
-
26
- —————————————————————————————————————————————————————————————————————————————
27
-
28
- ${bldcyn}USAGE:
29
- ${txtylw}create-plan-folder [-D <dir>] <status> <topic words...>${clr}
30
-
31
- ${bldcyn}WHERE:
32
- -D <dir> Enclosing directory (default: ".")
33
-
34
- ${txtgren}DESCRIPTION:${clr}
35
- Status is a name or the emoji itself:
36
-
37
- white | spec ⚪️ spec.md only, not yet planned
38
- blue | planned | plan 🔵 spec.md + plan.md
39
- yellow | open | wip 🟡 PR raised, not yet merged
40
- green | done 🟢 all PRs merged
41
- red | declined 🔴 decided never
42
- hole | blocked ⭕️ needs a human decision (write blockers.md)
43
- brown | later | defer 🟤 deliberately deferred (state the trigger)
44
- purple | merged 🟣 PR-level status; see note below
45
-
46
- ${bldcyn}EXAMPLES:${clr}
47
- ${bldylw}create-plan-folder blue update sopsy with two new commands
48
- create-plan-folder -D .plans white identity subcommand group${clr}
49
- "
50
- exit 64
51
- }
52
-
53
- [ $# -eq 0 ] && usage
54
- case "${1:-}" in -h|--help|help) usage ;; esac
55
-
56
- export dir="./.plans"
57
- if [ "${1:-}" = "-D" ]; then
58
- [ $# -ge 2 ] || usage
59
- dir="$2"
60
- shift 2
61
- fi
62
-
63
- [ $# -ge 2 ] || usage
64
-
65
- # ---------------------------------------------------------------- status ----
66
- # LC_ALL=C keeps tr byte-safe so multi-byte emoji pass through untouched.
67
- case "$(printf '%s' "$1" | LC_ALL=C tr 'A-Z' 'a-z')" in
68
- white|spec|'⚪️'|'⚪') emoji='⚪️' ;;
69
- blue|planned|plan|'🔵') emoji='🔵' ;;
70
- yellow|open|wip|'🟡') emoji='🟡' ;;
71
- green|done|'🟢') emoji='🟢' ;;
72
- red|declined|'🔴') emoji='🔴' ;;
73
- hole|blocked|'⭕️'|'⭕') emoji='⭕️' ;;
74
- brown|later|defer|deferred|'🟤') emoji='🟤' ;;
75
- purple|merged|'🟣') emoji='🟣' ;;
76
- *)
77
- printf 'create-plan-folder: unknown status %s\n\n' "$1" >&2
78
- usage
79
- ;;
80
- esac
81
- shift
82
-
83
- # ------------------------------------------------------------- next index ----
84
- [ -d "$dir" ] || {
85
- printf 'create-plan-folder: not a directory: %s\n' "$dir" >&2
86
- exit 66
87
- }
88
-
89
- last=0
90
- for path in "$dir"/[0-9][0-9][0-9]*; do
91
- [ -d "$path" ] || continue # also skips the unexpanded glob
92
- base=${path##*/}
93
- digits=${base%%[!0-9]*} # leading run of digits, nothing else
94
- [ -n "$digits" ] || continue
95
- n=$((10#$digits)) # 10# so 008 isn't read as octal
96
- if [ "$n" -gt "$last" ]; then last=$n; fi
97
- done
98
-
99
- next=$(printf '%03d' $((last + 1)))
100
-
101
- # ------------------------------------------------------------------ slug ----
102
- slug=$(
103
- printf '%s' "$*" \
104
- | LC_ALL=C tr 'A-Z' 'a-z' \
105
- | LC_ALL=C tr -c 'a-z0-9' '-' \
106
- | LC_ALL=C tr -s '-' \
107
- | sed -e 's/^-*//' -e 's/-*$//'
108
- )
109
-
110
- [ -n "$slug" ] || {
111
- printf 'create-plan-folder: topic produced an empty slug\n' >&2
112
- exit 65
113
- }
114
-
115
- # --------------------------------------------------------------- create -----
116
- target="$dir/${next}-${emoji}-${slug}"
117
-
118
- if [ -e "$target" ]; then
119
- printf 'create-plan-folder: already exists: %s\n' "$target" >&2
120
- exit 73
121
- fi
122
-
123
- mkdir -p "$target"
124
- # printf 'created %s\n' "$target" >&2
125
- printf '%s\n' "$target"
@@ -1,293 +0,0 @@
1
- # frozen_string_literal: true
2
-
3
- require "dry/cli/program_name"
4
- require "pastel"
5
-
6
- module Dry
7
- class CLI
8
- # Utility module for wrapping text to a given width
9
- module WordWrap
10
- def wrap_description(text, width: 55, prefix: " ")
11
- text.split("\n", -1).map { |para|
12
- para.scan(/\S.{0,#{width - 1}}(?=\s|\z)|\S{#{width},}/).join("\n#{prefix}")
13
- }.join("\n")
14
- end
15
-
16
- alias_method :wrap, :wrap_description
17
- end
18
-
19
- # The command list `agentilda -h` prints.
20
- #
21
- # Upstream renders a description on one line however long it is, so a
22
- # long `desc` runs off the terminal and a two-line one drops its second
23
- # line to column zero. Descriptions are wrapped here instead, hung under
24
- # the `#` the way {Banner} already hangs an option's.
25
- module Usage
26
- extend WordWrap
27
-
28
- # Narrowest description column worth wrapping to. Below this the
29
- # hanging indent leaves no room for words.
30
- MIN_DESCRIPTION_WIDTH = 24
31
-
32
- # @param result [Dry::CLI::CommandRegistry::LookupResult]
33
- # @return [String]
34
- def self.call(result)
35
- max_length, commands = commands_and_arguments(result)
36
- column = max_length + max_length / 2
37
-
38
- by_depth(commands).filter_map { |banner, node|
39
- next if node.hidden
40
-
41
- usage = description(node.command, column) if node.leaf?
42
- "#{justify(banner, max_length, usage)}#{usage}"
43
- }.unshift("Commands:").join("\n")
44
- end
45
-
46
- # A command that owns subcommands is the heading for a whole area of the
47
- # tool, so it belongs above the flat commands rather than filed
48
- # alphabetically among them. Alphabetical order is kept inside each group.
49
- #
50
- # `children?` is the direct question, `children.any?`. `leaf?` asks
51
- # something else — whether the node carries a runnable command — and the
52
- # two are independent: a command that also takes subcommands is both, and
53
- # sorting on `leaf?` would file it with the flat commands.
54
- #
55
- # @param commands [Hash{String => Dry::CLI::CommandRegistry::Node}]
56
- # @return [Array<Array(String, Dry::CLI::CommandRegistry::Node)>]
57
- def self.by_depth(commands)
58
- commands.sort_by { |banner, node| [node.children? ? 0 : 1, banner] }
59
- end
60
-
61
- # @param command [Dry::CLI::Command]
62
- # @param column [Integer] the width the banner is padded to; the `#`
63
- # sits one past it, so continuation lines do too
64
- # @return [String, nil]
65
- def self.description(command, column = 0)
66
- return unless CLI.command?(command)
67
- return if command.description.nil?
68
-
69
- prefix = "#{" " * (column + 1)}# "
70
- width = [86 - prefix.length, MIN_DESCRIPTION_WIDTH].max
71
- text = command.description.gsub(/\s+/, " ").strip
72
-
73
- " # #{wrap_description(text, width: width, prefix: prefix)}"
74
- end
75
- end
76
-
77
- # Command banner
78
- #
79
- # @since 0.1.0
80
- # @api private
81
- module Banner
82
- extend WordWrap
83
-
84
- @color_enabled = true
85
-
86
- module ColorMethods
87
- @pastel = nil
88
-
89
- COLOR_METHODS = %i[
90
- yellow
91
- green
92
- red
93
- blue
94
- magenta
95
- cyan
96
- white
97
- black
98
- gray
99
- bright_white
100
- bright_black
101
- bright_red
102
- bright_green
103
- bright_blue
104
- bright_magenta
105
- bright_cyan
106
- bright_gray
107
- ].freeze
108
-
109
- # Pastel answers a colour two ways: `yellow("text")` decorates in one
110
- # call, `yellow.bold("text")` returns a chain to decorate further. The
111
- # colourless stand-in has to answer both shapes, so given text it hands
112
- # the text straight back and given nothing it hands back itself.
113
- DECORATIONS = %i[bold dim italic underline inverse strikethrough blink].freeze
114
-
115
- class NoColorPastel
116
- (COLOR_METHODS + DECORATIONS).each do |name|
117
- define_method(name) { |*args| args.empty? ? self : args.join }
118
- end
119
- end
120
-
121
- def pastel
122
- @pastel ||= if Banner.color_enabled?
123
- Pastel.new
124
- else
125
- NoColorPastel.new
126
- end
127
- end
128
-
129
- extend Forwardable
130
-
131
- def_delegators :pastel, *COLOR_METHODS
132
- end
133
-
134
- extend ColorMethods
135
-
136
- def self.color_enabled?
137
- @color_enabled
138
- end
139
-
140
- def self.enable_color!
141
- @pastel = nil
142
- @color_enabled = true
143
- end
144
-
145
- def self.disable_color!
146
- @pastel = nil
147
- @color_enabled = false
148
- end
149
-
150
- def self.color_header(string)
151
- color_enabled? ? bright_blue.bold(string.upcase) : string.upcase
152
- end
153
-
154
- def self.color_command(string)
155
- color_enabled? ? green.bold(string) : string
156
- end
157
-
158
- def self.color_arguments(string)
159
- color_enabled? ? yellow.italic(string) : string
160
- end
161
-
162
- # Prints command banner
163
- #
164
- # @param command [Dry::CLI::Command] the command
165
- # @param out [IO] standard output
166
- #
167
- # @since 0.1.0
168
- # @api private
169
- def self.call(command, name)
170
- [
171
- command_name(name),
172
- command_name_and_arguments(command, name),
173
- command_description(command),
174
- command_subcommands(command),
175
- command_arguments(command),
176
- command_options(command),
177
- command_examples(command, name)
178
- ].compact.join("\n")
179
- end
180
-
181
- # @since 0.1.0
182
- # @api private
183
- def self.command_name(name)
184
- color_header("Command:\n") + " #{yellow(name)}"
185
- end
186
-
187
- # @since 0.1.0
188
- # @api private
189
- def self.command_name_and_arguments(command, name)
190
- usage = "\n#{color_header("Usage")}:\n #{color_command(name)}#{color_arguments(arguments(command))}"
191
-
192
- return usage + " | #{name} SUBCOMMAND" if command.subcommands.any?
193
-
194
- usage
195
- end
196
-
197
- # @since 0.1.0
198
- # @api private
199
- def self.command_examples(command, name)
200
- return if command.examples.empty?
201
-
202
- "\n#{color_header("Examples")}:\n" + command.examples.map do |example|
203
- args, desc = example.split("#")
204
- comment_line = (desc.nil? || desc.empty?) ? "" : " #{bright_black("##{desc}")}\n"
205
- comment_line + " #{color_command(name)} #{color_arguments(args)}"
206
- end.join("\n\n")
207
- end
208
-
209
- # @since 0.1.0
210
- # @api private
211
- def self.command_description(command)
212
- return if command.description.nil?
213
-
214
- "\n#{color_header("Description")}:\n #{command.description}"
215
- end
216
-
217
- def self.command_subcommands(command)
218
- return if command.subcommands.empty?
219
-
220
- "\n#{color_header("Subcommands")}:\n#{build_subcommands_list(command.subcommands)}"
221
- end
222
-
223
- # @since 0.1.0
224
- # @api private
225
- def self.command_arguments(command)
226
- return if command.arguments.empty?
227
-
228
- "\n#{color_header("Arguments")}:\n#{extended_command_arguments(command)}"
229
- end
230
-
231
- # @since 0.1.0
232
- # @api private
233
- def self.command_options(command)
234
- "\n#{color_header("Options")}:\n#{extended_command_options(command)}"
235
- end
236
-
237
- # @since 0.1.0
238
- # @api private
239
- def self.arguments(command)
240
- required_arguments = command.required_arguments
241
- optional_arguments = command.optional_arguments
242
-
243
- required = required_arguments.map { |arg| arg.name.upcase }.join(" ") if required_arguments.any?
244
- optional = optional_arguments.map { |arg| "[#{arg.name.upcase}]" }.join(" ") if optional_arguments.any?
245
- result = [required, optional].compact
246
-
247
- " #{result.join(" ")}" unless result.empty?
248
- end
249
-
250
- DESCRIPTION_START = 25
251
-
252
- # @since 0.1.0
253
- # @api private
254
- def self.extended_command_arguments(command)
255
- command.arguments.map do |argument|
256
- " #{argument.name.to_s.upcase.ljust(DESCRIPTION_START)} # #{"REQUIRED " if argument.required?}#{wrap_description(argument.desc, prefix: " " * 31 + "# ")}"
257
- end.join("\n")
258
- end
259
-
260
- # @since 0.1.0
261
- # @api private
262
- #
263
- def self.extended_command_options(command)
264
- result = command.options.map do |option|
265
- name = Inflector.dasherize(option.name)
266
- name = if option.boolean?
267
- "[no-]#{name}"
268
- elsif option.flag?
269
- name
270
- elsif option.array?
271
- "#{name}=VALUE1,VALUE2,.."
272
- else
273
- "#{name}=VALUE"
274
- end
275
- name = "#{name}, #{option.alias_names.join(", ")}" if option.aliases.any?
276
- name = " --#{name.ljust(DESCRIPTION_START)}"
277
- name = "#{name} # #{wrap_description(option.desc, prefix: " " * 31 + "# ")}"
278
- name = "#{name}, default: #{option.default.inspect}" unless option.default.nil?
279
- name
280
- end
281
-
282
- result << " --#{"help, -h".ljust(DESCRIPTION_START)} # Print this help"
283
- result.join("\n")
284
- end
285
-
286
- def self.build_subcommands_list(subcommands)
287
- subcommands.map do |subcommand_name, subcommand|
288
- " #{yellow(subcommand_name.ljust(12))} # #{red(wrap_description(subcommand.command.description))}`"
289
- end.join("\n")
290
- end
291
- end
292
- end
293
- end