terret-fortune 0.1.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 +7 -0
- data/LICENSE +21 -0
- data/README.md +139 -0
- data/config/bundle.yml +14 -0
- data/lib/terret/fortune/fortunes.txt +10 -0
- data/lib/terret/fortune/tool.rb +58 -0
- data/lib/terret/fortune.rb +9 -0
- metadata +72 -0
checksums.yaml
ADDED
|
@@ -0,0 +1,7 @@
|
|
|
1
|
+
---
|
|
2
|
+
SHA256:
|
|
3
|
+
metadata.gz: bad4f22d3f92f0e9454dec8d24aec32d3c9879f88bbc22359f5fa8ae73ccd02c
|
|
4
|
+
data.tar.gz: a2987aeffe819aab4fb6fae17e4bcb03b091c5e00c457124629975a5aac524fb
|
|
5
|
+
SHA512:
|
|
6
|
+
metadata.gz: 589284cdbcd52f2904a10901f1a563b9766a36eff877a912935b12add8fc6ae4c4790409f161d40abab6f546d72865ddbe59e2afd19923e53b89bb1e91f3a365
|
|
7
|
+
data.tar.gz: ccf7610b1258fa44b30e4ce5fbbd8b74599b14c0db9d1ad31328fc38b766ac554829485ebebc8df55121548d7e65fffe8f6ec8defafe8b7bbb0632d3e9f30811
|
data/LICENSE
ADDED
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
MIT License
|
|
2
|
+
|
|
3
|
+
Copyright (c) 2026 Obie Fernandez
|
|
4
|
+
|
|
5
|
+
Permission is hereby granted, free of charge, to any person obtaining a copy
|
|
6
|
+
of this software and associated documentation files (the "Software"), to deal
|
|
7
|
+
in the Software without restriction, including without limitation the rights
|
|
8
|
+
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
|
9
|
+
copies of the Software, and to permit persons to whom the Software is
|
|
10
|
+
furnished to do so, subject to the following conditions:
|
|
11
|
+
|
|
12
|
+
The above copyright notice and this permission notice shall be included in all
|
|
13
|
+
copies or substantial portions of the Software.
|
|
14
|
+
|
|
15
|
+
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
|
16
|
+
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
|
17
|
+
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
|
18
|
+
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
|
19
|
+
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
|
20
|
+
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
|
21
|
+
SOFTWARE.
|
data/README.md
ADDED
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
# terret-fortune
|
|
2
|
+
|
|
3
|
+
A worked example of the [Terret](https://terret.org) extension story: a
|
|
4
|
+
third-party gem that ships **one tool** and joins an agent's boot by shipping
|
|
5
|
+
normally. Install it, name it in a profile, permit the tool, and a Terret agent
|
|
6
|
+
can call `fortune` — one short, pithy line from a vendored list.
|
|
7
|
+
|
|
8
|
+
This gem is the running example of Terret's `docs/cookbook/adding-a-tool.md` and
|
|
9
|
+
`docs/cookbook/adding-a-bundle.md`, kept in lockstep with those pages: what they
|
|
10
|
+
describe generically, this repository is concretely.
|
|
11
|
+
|
|
12
|
+
## What it is
|
|
13
|
+
|
|
14
|
+
`fortune` is the smallest honest tool there is: a pure read with no side
|
|
15
|
+
effect. Its metadata says so — `mutating: false`, `approval: :never`,
|
|
16
|
+
`concurrency: :parallel` — and those three fields are what the loop's tool
|
|
17
|
+
barrier and the approvals gate act on, so they are the truth about what calling
|
|
18
|
+
it does, not a convenience.
|
|
19
|
+
|
|
20
|
+
```
|
|
21
|
+
terret-fortune/
|
|
22
|
+
├── terret-fortune.gemspec # declares the gem a bundle (metadata["terret"])
|
|
23
|
+
├── config/
|
|
24
|
+
│ └── bundle.yml # one row mounting Terret::Fortune::Tool
|
|
25
|
+
├── lib/
|
|
26
|
+
│ └── terret/
|
|
27
|
+
│ ├── fortune.rb # entry: requires terret-core, then the service
|
|
28
|
+
│ └── fortune/
|
|
29
|
+
│ ├── tool.rb # the Hames service that registers `fortune`
|
|
30
|
+
│ └── fortunes.txt # the vendored list
|
|
31
|
+
└── test/
|
|
32
|
+
└── fortune_test.rb # boots the service, drives the real tool pipeline
|
|
33
|
+
```
|
|
34
|
+
|
|
35
|
+
## The extension story, end to end
|
|
36
|
+
|
|
37
|
+
### 1. Write the tool
|
|
38
|
+
|
|
39
|
+
A tool provider is an ordinary Hames service: it `inject`s `ctx[:tools]` and
|
|
40
|
+
registers a `Definition` in `start`. `lib/terret/fortune/tool.rb` is the whole
|
|
41
|
+
of it — the same shape `gems/terret-tools-std` uses for the standard roster.
|
|
42
|
+
|
|
43
|
+
### 2. Declare the gem a bundle
|
|
44
|
+
|
|
45
|
+
One line of gemspec metadata makes the gem discoverable. The value is the
|
|
46
|
+
**string** path to the bundle file — RubyGems validates every metadata value as
|
|
47
|
+
a String, so a nested hash does not build at all:
|
|
48
|
+
|
|
49
|
+
```ruby
|
|
50
|
+
# terret-fortune.gemspec
|
|
51
|
+
s.metadata = { "terret" => "config/bundle.yml" }
|
|
52
|
+
s.add_dependency "terret-core", "~> 0.1" # the code the row mounts
|
|
53
|
+
```
|
|
54
|
+
|
|
55
|
+
The bundle file is an ordered list of rows plus the requires its constants
|
|
56
|
+
need:
|
|
57
|
+
|
|
58
|
+
```yaml
|
|
59
|
+
# config/bundle.yml
|
|
60
|
+
name: terret-fortune
|
|
61
|
+
requires:
|
|
62
|
+
- terret/fortune
|
|
63
|
+
rows:
|
|
64
|
+
- id: fortune
|
|
65
|
+
plugin: Terret::Fortune::Tool
|
|
66
|
+
```
|
|
67
|
+
|
|
68
|
+
### 3. Install it
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
gem install terret-fortune
|
|
72
|
+
# or, in a Gemfile:
|
|
73
|
+
# gem "terret-fortune"
|
|
74
|
+
```
|
|
75
|
+
|
|
76
|
+
`gem install` is the whole registration mechanism. Terret's discovery walks
|
|
77
|
+
every gemspec it can see, reads the `terret` metadata key, and parses the file
|
|
78
|
+
it points at — so a third-party gem becomes discoverable by shipping normally.
|
|
79
|
+
Nothing to register, no directory to drop a file into.
|
|
80
|
+
|
|
81
|
+
### 4. Stack it in a profile
|
|
82
|
+
|
|
83
|
+
A profile names bundles by gem name, in stack order, `terret` (terret-base)
|
|
84
|
+
always layer one:
|
|
85
|
+
|
|
86
|
+
```yaml
|
|
87
|
+
# ~/.terret/profiles/headless/profile.yml
|
|
88
|
+
bundles:
|
|
89
|
+
- terret # terret-base, always first
|
|
90
|
+
- terret-fortune # this gem
|
|
91
|
+
```
|
|
92
|
+
|
|
93
|
+
### 5. Permit the tool
|
|
94
|
+
|
|
95
|
+
This is the step people forget: **mounting the tool does not make it
|
|
96
|
+
callable.** Terret's allow list is deny-by-default, and the base floor names
|
|
97
|
+
exactly the standard roster and nothing else. `fortune` is denied until a
|
|
98
|
+
profile permits it — and because a patch replaces a row's config **wholesale**,
|
|
99
|
+
you restate the whole list, not just the new name:
|
|
100
|
+
|
|
101
|
+
```yaml
|
|
102
|
+
# ~/.terret/profiles/headless/patch.yml
|
|
103
|
+
rows:
|
|
104
|
+
- id: allow_list
|
|
105
|
+
config:
|
|
106
|
+
patterns:
|
|
107
|
+
- Read
|
|
108
|
+
- Write
|
|
109
|
+
- Edit
|
|
110
|
+
- Glob
|
|
111
|
+
- Grep
|
|
112
|
+
- Bash
|
|
113
|
+
- fortune # the new tool, now permitted
|
|
114
|
+
# ... restate the rest of the roster you still want
|
|
115
|
+
```
|
|
116
|
+
|
|
117
|
+
### 6. Boot
|
|
118
|
+
|
|
119
|
+
```ruby
|
|
120
|
+
ctx = Terret.boot(profile: "headless")
|
|
121
|
+
```
|
|
122
|
+
|
|
123
|
+
That resolves the layers, discovers `terret-fortune`, requires its code, mounts
|
|
124
|
+
its row, and registers the tool. From here the agent can call `fortune`.
|
|
125
|
+
|
|
126
|
+
## Running the tests
|
|
127
|
+
|
|
128
|
+
```bash
|
|
129
|
+
# Against a published terret-core:
|
|
130
|
+
bundle install
|
|
131
|
+
bundle exec rake test
|
|
132
|
+
|
|
133
|
+
# Against a local Terret monorepo checkout (no publish needed):
|
|
134
|
+
TERRET_CHECKOUT=/path/to/terret bundle exec rake test
|
|
135
|
+
```
|
|
136
|
+
|
|
137
|
+
## License
|
|
138
|
+
|
|
139
|
+
MIT. See `LICENSE`.
|
data/config/bundle.yml
ADDED
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
# terret-fortune -- a one-row bundle so a profile can stack this gem by name.
|
|
2
|
+
#
|
|
3
|
+
# A bundle ships rows; the code the row names comes from the gem this bundle
|
|
4
|
+
# depends on (terret-core, in terret-fortune.gemspec) plus this gem's own lib.
|
|
5
|
+
# `requires:` is the working half of "make the code available": a load path is
|
|
6
|
+
# not a require, so boot loads this file before the row's constant resolves.
|
|
7
|
+
name: terret-fortune
|
|
8
|
+
|
|
9
|
+
requires:
|
|
10
|
+
- terret/fortune
|
|
11
|
+
|
|
12
|
+
rows:
|
|
13
|
+
- id: fortune
|
|
14
|
+
plugin: Terret::Fortune::Tool
|
|
@@ -0,0 +1,10 @@
|
|
|
1
|
+
Fortune favors the prepared mind.
|
|
2
|
+
A ship in harbor is safe, but that is not what ships are for.
|
|
3
|
+
The best way out is always through.
|
|
4
|
+
Small deeds done are better than great deeds planned.
|
|
5
|
+
What is not started today is never finished tomorrow.
|
|
6
|
+
Simplicity is the ultimate sophistication.
|
|
7
|
+
Well begun is half done.
|
|
8
|
+
Make it work, make it right, make it fast.
|
|
9
|
+
The obstacle is the way.
|
|
10
|
+
Slow is smooth, and smooth is fast.
|
|
@@ -0,0 +1,58 @@
|
|
|
1
|
+
# frozen_string_literal: true
|
|
2
|
+
|
|
3
|
+
module Terret
|
|
4
|
+
module Fortune
|
|
5
|
+
# Registers the `fortune` tool: one random line from a vendored list.
|
|
6
|
+
# An ordinary tool provider -- a Hames service that injects ctx[:tools]
|
|
7
|
+
# and registers a Definition in `start`, the shape gems/terret-tools-std
|
|
8
|
+
# uses for the whole standard roster.
|
|
9
|
+
class Tool < Hames::Service
|
|
10
|
+
service_key :fortune
|
|
11
|
+
inject :tools
|
|
12
|
+
config_schema path: { type: String,
|
|
13
|
+
doc: "path to a newline-delimited fortune file; " \
|
|
14
|
+
"defaults to the vendored list" }
|
|
15
|
+
|
|
16
|
+
DEFAULT_FILE = File.expand_path("fortunes.txt", __dir__)
|
|
17
|
+
|
|
18
|
+
DESCRIPTION = "Return a single fortune: one short, pithy line chosen at random."
|
|
19
|
+
|
|
20
|
+
def start(ctx)
|
|
21
|
+
@ctx = ctx
|
|
22
|
+
@fortunes = load_fortunes(config[:path] || DEFAULT_FILE)
|
|
23
|
+
register_fortune
|
|
24
|
+
end
|
|
25
|
+
|
|
26
|
+
# The list is read once at mount. A swapped `path:` needs a remount to
|
|
27
|
+
# apply, and saying so beats letting the base class warn that a service
|
|
28
|
+
# it thinks is stateful needs one (docs/hames-primer.md §5).
|
|
29
|
+
def reconfigure(config)
|
|
30
|
+
@fortunes = load_fortunes(config[:path] || DEFAULT_FILE)
|
|
31
|
+
end
|
|
32
|
+
|
|
33
|
+
private
|
|
34
|
+
|
|
35
|
+
def register_fortune
|
|
36
|
+
# `ctx:` is passed explicitly, exactly as the std roster does it. The
|
|
37
|
+
# registry defaults it to the context the service was started in; a
|
|
38
|
+
# provider mounted into a forked agent scope wants the registration
|
|
39
|
+
# owned by that fork, so disposing the agent reaps the tool rather
|
|
40
|
+
# than leaving it live on the root (gems/terret-core tools.rb).
|
|
41
|
+
@ctx[:tools].register(name: "fortune", description: DESCRIPTION, params: {},
|
|
42
|
+
mutating: false, approval: :never, concurrency: :parallel,
|
|
43
|
+
ctx: @ctx) do
|
|
44
|
+
@fortunes.sample
|
|
45
|
+
end
|
|
46
|
+
end
|
|
47
|
+
|
|
48
|
+
def load_fortunes(path)
|
|
49
|
+
lines = File.readlines(path, chomp: true).map(&:strip).reject(&:empty?)
|
|
50
|
+
# A tool that would return nil on every call is a bug worth failing
|
|
51
|
+
# at mount, not one call at a time.
|
|
52
|
+
raise "no fortunes in #{path}" if lines.empty?
|
|
53
|
+
|
|
54
|
+
lines
|
|
55
|
+
end
|
|
56
|
+
end
|
|
57
|
+
end
|
|
58
|
+
end
|
metadata
ADDED
|
@@ -0,0 +1,72 @@
|
|
|
1
|
+
--- !ruby/object:Gem::Specification
|
|
2
|
+
name: terret-fortune
|
|
3
|
+
version: !ruby/object:Gem::Version
|
|
4
|
+
version: 0.1.0
|
|
5
|
+
platform: ruby
|
|
6
|
+
authors:
|
|
7
|
+
- Obie Fernandez
|
|
8
|
+
autorequire:
|
|
9
|
+
bindir: bin
|
|
10
|
+
cert_chain: []
|
|
11
|
+
date: 2026-08-20 00:00:00.000000000 Z
|
|
12
|
+
dependencies:
|
|
13
|
+
- !ruby/object:Gem::Dependency
|
|
14
|
+
name: terret-core
|
|
15
|
+
requirement: !ruby/object:Gem::Requirement
|
|
16
|
+
requirements:
|
|
17
|
+
- - "~>"
|
|
18
|
+
- !ruby/object:Gem::Version
|
|
19
|
+
version: '0.1'
|
|
20
|
+
type: :runtime
|
|
21
|
+
prerelease: false
|
|
22
|
+
version_requirements: !ruby/object:Gem::Requirement
|
|
23
|
+
requirements:
|
|
24
|
+
- - "~>"
|
|
25
|
+
- !ruby/object:Gem::Version
|
|
26
|
+
version: '0.1'
|
|
27
|
+
description: 'A worked example of the Terret extension story: a third-party gem that
|
|
28
|
+
ships one tool (`fortune`, one line from a vendored list) and declares itself a
|
|
29
|
+
bundle in a line of gemspec metadata, so a profile can stack it by name. Stdlib-only
|
|
30
|
+
beyond terret-core. The running example of docs/cookbook/adding-a-tool.md, kept
|
|
31
|
+
in lockstep with that page.'
|
|
32
|
+
email:
|
|
33
|
+
- obiefernandez@gmail.com
|
|
34
|
+
executables: []
|
|
35
|
+
extensions: []
|
|
36
|
+
extra_rdoc_files: []
|
|
37
|
+
files:
|
|
38
|
+
- LICENSE
|
|
39
|
+
- README.md
|
|
40
|
+
- config/bundle.yml
|
|
41
|
+
- lib/terret/fortune.rb
|
|
42
|
+
- lib/terret/fortune/fortunes.txt
|
|
43
|
+
- lib/terret/fortune/tool.rb
|
|
44
|
+
homepage: https://terret.org
|
|
45
|
+
licenses:
|
|
46
|
+
- MIT
|
|
47
|
+
metadata:
|
|
48
|
+
homepage_uri: https://terret.org
|
|
49
|
+
source_code_uri: https://github.com/terret-org/terret-fortune
|
|
50
|
+
bug_tracker_uri: https://github.com/terret-org/terret-fortune/issues
|
|
51
|
+
rubygems_mfa_required: 'true'
|
|
52
|
+
terret: config/bundle.yml
|
|
53
|
+
post_install_message:
|
|
54
|
+
rdoc_options: []
|
|
55
|
+
require_paths:
|
|
56
|
+
- lib
|
|
57
|
+
required_ruby_version: !ruby/object:Gem::Requirement
|
|
58
|
+
requirements:
|
|
59
|
+
- - ">="
|
|
60
|
+
- !ruby/object:Gem::Version
|
|
61
|
+
version: '4.0'
|
|
62
|
+
required_rubygems_version: !ruby/object:Gem::Requirement
|
|
63
|
+
requirements:
|
|
64
|
+
- - ">="
|
|
65
|
+
- !ruby/object:Gem::Version
|
|
66
|
+
version: '0'
|
|
67
|
+
requirements: []
|
|
68
|
+
rubygems_version: 3.5.11
|
|
69
|
+
signing_key:
|
|
70
|
+
specification_version: 4
|
|
71
|
+
summary: A fortune tool for Terret
|
|
72
|
+
test_files: []
|