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 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
@@ -0,0 +1,9 @@
1
+ # frozen_string_literal: true
2
+
3
+ begin
4
+ require "terret"
5
+ rescue LoadError
6
+ require_relative "../../../terret-core/lib/terret" # monorepo path source
7
+ end
8
+
9
+ require_relative "fortune/tool"
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: []