sayfirst-cli 0.2.0__py3-none-any.whl

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.
@@ -0,0 +1,297 @@
1
+ Metadata-Version: 2.5
2
+ Name: sayfirst-cli
3
+ Version: 0.2.0
4
+ Summary: The product command-line interface of the sayfirst control plane
5
+ Project-URL: Repository, https://github.com/fredaime/sayfirst-cli
6
+ Project-URL: Documentation, https://github.com/fredaime/sayfirst-cli/blob/main/README.md
7
+ Project-URL: Changelog, https://github.com/fredaime/sayfirst-cli/blob/main/CHANGELOG.md
8
+ Author-email: fredaime <frederic.aime@gmail.com>
9
+ License-Expression: Apache-2.0
10
+ License-File: LICENSE
11
+ License-File: NOTICE
12
+ Classifier: Development Status :: 4 - Beta
13
+ Classifier: Programming Language :: Python :: 3
14
+ Classifier: Programming Language :: Python :: 3.12
15
+ Classifier: Programming Language :: Python :: 3.13
16
+ Classifier: Programming Language :: Python :: 3.14
17
+ Requires-Python: <3.15,>=3.12
18
+ Requires-Dist: sayfirst-boundary==0.2.0
19
+ Requires-Dist: sayfirst-contract==0.2.0
20
+ Description-Content-Type: text/markdown
21
+
22
+ <!-- SPDX-License-Identifier: Apache-2.0 -->
23
+ # `sayfirst` command-line interface
24
+
25
+ The open-source command-line interface of the `sayfirst` control plane: the
26
+ product CLI a user installs to connect an existing program to the boundary, to
27
+ read what was decided, and to verify it afterwards. [`QUICKSTART.md`](QUICKSTART.md)
28
+ installs it beside its daemon and walks one governed decision end to end — every
29
+ command on that page was run before it was written down.
30
+
31
+ Its content arrives as the partition of the private CLI, by copy with a
32
+ provenance review, slice by slice. The first slice is here and it runs:
33
+ [`docs/PARTITION.md`](docs/PARTITION.md) says what crosses and what is still
34
+ open, and [`docs/PROVENANCE.md`](docs/PROVENANCE.md) says what arrived by copy
35
+ — today, nothing, and why that is the right answer rather than an omission.
36
+
37
+ ## Three directions
38
+
39
+ A client of this control plane does **three** things, and this repository ships
40
+ all three.
41
+
42
+ **Before an effect, it asks.** One question — a capability, a scope — put to the
43
+ daemon over a socket whose peer it verified, and one answer rendered as it was
44
+ given: allow, deny or suspend, and "could not ask" when the daemon could not be
45
+ reached. That is `sayfirst ask`, and it is what the first slice implements.
46
+
47
+ **It puts the boundary in front of somebody else's program.** `sayfirst
48
+ instrument run --pack DIR … -- <program>` runs a program with the named effects
49
+ asked about first, reversibly and with nothing written anywhere; `sayfirst
50
+ instrument verify` runs it again under the interpreter's own audit hook and
51
+ proves, from that and the scope's evidence chain alone, that every effect of a
52
+ named kind was preceded by a decision; `sayfirst instrument apply` is reserved
53
+ for the committed code modification and refuses, saying so. `sayfirst packs
54
+ list` prints the convenience packs this distribution ships, one line each with
55
+ the path `--pack` accepts, and `sayfirst packs check PATH` reads one the way the
56
+ engine will before anything runs with it.
57
+ [`docs/PACKS.md`](docs/PACKS.md) is the whole of it.
58
+
59
+ **Afterwards, it reads and verifies.** Every read *from the daemon* names its
60
+ scope explicitly (`--scope`) — the writer's `local` default does not apply. The
61
+ two offline checks name a path instead and take no scope at all: `evidence audit
62
+ --file` checks one saved bundle and `evidence exports` a directory of them, and
63
+ both refuse `--scope`, because the file says which scope it holds. This is a
64
+ surface rather than a list, and it grows by slices; what it answers today is
65
+ below, and `sayfirst --help` is the claim that is kept in the dispatch itself.
66
+
67
+ `sayfirst trace` reads back the record of one decision and follows it into the
68
+ evidence that holds it: the record itself, and where it sits in the chain,
69
+ bounded to the pages the read walks.
70
+
71
+ `sayfirst explain` reads the reason the control plane gave for a decision — the
72
+ rule it applied and the policy version it ran under — in the plane's own words,
73
+ adding none of its own.
74
+
75
+ `sayfirst evidence history` and `sayfirst evidence audit` page through scoped
76
+ evidence: `history` renders what each page holds, `audit` puts the served
77
+ verdict beside a local check and names a finding wherever the two disagree;
78
+ `--file` checks an already-saved bundle offline, without opening a socket.
79
+
80
+ `sayfirst evidence export` saves a bundle a third party can verify with the
81
+ contract distribution alone, offline, no server code and no daemon; `--out`
82
+ names where it is saved. A bundle taken while the daemon's current epoch is
83
+ still open verifies with coverage `unknown` rather than `complete` — the honest
84
+ answer, not a failure. `sayfirst evidence exports` lists and re-verifies every
85
+ `*.json` bundle saved in a directory.
86
+
87
+ `sayfirst approvals show` reads where one suspended wait stands: its state,
88
+ when it was asked, when it ends, and — once a person has acted — when and why.
89
+ `sayfirst approvals approve` and `sayfirst approvals reject` end that wait
90
+ exactly once, an optional `--reason` and all, and render the same record
91
+ `show` would: one person's act, and nothing that counts signatures or names a
92
+ designation (article 12). These three follow the exit codes below, with no case
93
+ of their own: an approval already resolved, asked again, answers
94
+ `approval_resolved`, which the registry classifies as a refusal — so it is the
95
+ `3` below, like every other refusal, and this client special-cases nothing.
96
+
97
+ `trace`, `explain` and `evidence history` exit `0` on a read, whatever the
98
+ record's own outcome. `evidence audit`, `evidence export` and `evidence exports`
99
+ exit with the local check's result instead: `0` when it holds, `6` when it finds
100
+ what the plane did not say, `7` when it cannot conclude — which is the ordinary
101
+ answer for a bundle taken while the epoch is still open, and why
102
+ `sayfirst evidence export && …` is not how to script one. A chain the verifier
103
+ could not judge — an entry declaring a recipe this generation holds no reader
104
+ for, a range assembled out of order, an instant outside the calendar — is `7`
105
+ as well, with the sequence it stopped at: the answer arrived and the check could
106
+ not conclude, which is neither a failure to obtain the answer nor a finding
107
+ against the chain. Every command that reads from the control plane exits `3`
108
+ when the request was refused, `4` when the plane could not be read from or the
109
+ answer itself could not be read at all, and `64` on a misused invocation; a
110
+ usage error the parser catches exits `2` everywhere.
111
+
112
+ `sayfirst instrument verify` uses the same two local-check codes for the proof
113
+ it makes: `0` only when every point of every designated pack was `governed` and
114
+ no effect went unjudged, `6` for an effect no decision preceded — a finding this
115
+ client made, never a denial the plane gave — and `7` when the check could not
116
+ conclude, which includes a path the run never walked and an effect this proof
117
+ could not judge. A run that concluded nothing at all says which of its endings
118
+ happened, and answers with that ending's own code: `4` when the chain could not
119
+ be read, before the program started or while it ran, and when the verifier never
120
+ saw the program's own code start; `64` when the invocation was refused before it
121
+ began; `7` when findings arrived and would not read. `sayfirst instrument run`
122
+ owns none of these: it passes the governed program's own ending through
123
+ untouched, and answers `64` only for mistakes in the invocation. `sayfirst packs
124
+ list` exits `64` when a pack this distribution ships will not read — it names it,
125
+ prints the rest of the list anyway, and never ends in a traceback.
126
+
127
+ The operator closed Q-A on 2026-09-05: this repository owed the evidence
128
+ surface, and its promise to read and verify became a commitment with a date on
129
+ it — kept, as of this slice. [`docs/PARTITION.md`](docs/PARTITION.md) records
130
+ the decision; [`docs/EVIDENCE-SURFACE.md`](docs/EVIDENCE-SURFACE.md) records
131
+ what shipped and the contract support it used. Export verification uses the
132
+ recipe and vectors from the contract distribution, without importing server
133
+ code.
134
+
135
+ None of the three defines control semantics or derives a decision. A client
136
+ "explains and invokes" control semantics; it "never defines them, never keeps a
137
+ decision past the lifetime the
138
+ control plane gave it, and never derives an answer the control plane did not
139
+ give" (article 1). Asking, instrumenting and reading are three ways of carrying
140
+ an answer someone else made — the third of them, the verifier, reads the
141
+ interpreter and the chain and reaches no verdict of its own about policy — and
142
+ that is why all three belong to a client.
143
+
144
+ ## The first slice
145
+
146
+ One command, end to end, against the open control plane's contract
147
+ distribution: `sayfirst ask` puts one question to the daemon and renders its
148
+ answer.
149
+
150
+ ```console
151
+ $ sayfirst ask --capability example.effect --scope local --socket /run/user/1000/sayfirst.sock
152
+ verified: true (server_uid 1000, expected 1000)
153
+ outcome: allow
154
+ reason: policy_allows
155
+ capability: example.effect in scope local
156
+ decision: decision-1 at 2026-09-04T20:11:45.929523+00:00
157
+ policy version: sha256:44d91909ffe6283e67c73bad39d698448aa5e8bbdc1b5aa64a635a604e12f7e1
158
+ ```
159
+
160
+ When the daemon cannot be reached, it says that, and it says it as its own
161
+ result rather than as a refusal:
162
+
163
+ ```console
164
+ $ sayfirst ask --capability example.effect --socket /run/user/1000/absent.sock
165
+ verified: false (server_uid not stated, expected not stated)
166
+ could not ask: unreachable: [Errno 2] No such file or directory
167
+ retryable: true
168
+ $ echo $?
169
+ 4
170
+ ```
171
+
172
+ It exits `0` on `allow`, `1` on `deny`, `5` on `suspend`, `3` when the request
173
+ was refused and `4` when the control plane could not be asked — because
174
+ article 1 requires that "denied" and "could not ask" never read as each other.
175
+ There is no `--url` and nothing that takes a token: the boundary says who the
176
+ caller is, and it says so from the socket (article 6).
177
+
178
+ ## The gate
179
+
180
+ `scripts/gate.sh` is the whole gate: format, lint, tests, and the guard that
181
+ installs this distribution into an empty environment and reads back everything
182
+ that arrived with it. That last one is article 13 made mechanical rather than
183
+ promised — and it proves on every run that it can fail, by planting a web
184
+ framework into the environment that has just passed and requiring the check to
185
+ reject it.
186
+
187
+ `sayfirst-contract` is on no index, because article 0 forbids publishing
188
+ anything until the marks are filed. So the gate is told where a checkout of the
189
+ control plane's repository is:
190
+
191
+ ```console
192
+ $ SAYFIRST_CONTRACT_SOURCE=../sf-control-plane-lt ./scripts/gate.sh
193
+ ```
194
+
195
+ It fails, rather than skipping, when it is not told. A skipped guard is a guard
196
+ that cannot fail.
197
+
198
+ ## What binds here
199
+
200
+ The constitution of the open control plane —
201
+ [`CONSTITUTION.md`](https://github.com/fredaime/sayfirst-control-plane/blob/main/CONSTITUTION.md),
202
+ in that repository — binds this repository too. Its article 0 says so: it binds
203
+ "this repository and every other open repository of the project", and an open
204
+ repository adopts it **by pointer, never by copy, because a copy drifts**.
205
+
206
+ **That pointer became a link in the act that published this repository, and was
207
+ not one before.** The open control plane is published as a repository created
208
+ fresh (article 0), and until it existed the only URL this document could have
209
+ carried was the one repository the project has decided never to publish — a link
210
+ that would have resolved for nobody from the first public minute, in the
211
+ document a newcomer reads first. `docs/publication-checklist.md` orders the
212
+ control plane before this client for that reason, and
213
+ `tests/test_pointers_survive_publication.py` still reddens on any reader-facing
214
+ link to the repository that is never published — it is what kept the promise
215
+ from being made early. The same applies to `TRADEMARKS.md`, which is why this
216
+ repository carries one that points rather than one that copies.
217
+
218
+ The articles this repository will answer to first:
219
+
220
+ - **article 1** — a client explains and invokes control semantics; it never
221
+ defines them, never keeps a decision past the lifetime it was given, and never
222
+ derives an answer the control plane did not give;
223
+ - **article 13** — the client depends on the contract distribution and never on
224
+ the server distribution, so installing it never installs a web framework or a
225
+ database layer;
226
+ - **article 14** — nothing here imports, names or is shaped by a private
227
+ product; files that arrive from a private repository arrive by copy with a
228
+ provenance review;
229
+ - **article 15** — Apache-2.0, contributions under the Developer Certificate of
230
+ Origin, read on every pull request by
231
+ `scripts/check_developer_certificate_of_origin.py`, which refuses a commit
232
+ carrying no well-formed sign-off and refuses a range it cannot read rather
233
+ than reporting it as passing.
234
+
235
+ ## The name
236
+
237
+ The product and the command are **`sayfirst`**, decided by the operator on
238
+ 2026-09-04. Every
239
+ distribution the open side publishes takes it as a prefix, the way the retired
240
+ placeholder always said they would: this CLI as `sayfirst-cli`, and the plane as
241
+ `sayfirst-control-plane` where the plane is named. *Which* distributions this
242
+ repository ships is a different question — that is the partition, and it is not
243
+ decided.
244
+
245
+ ### Who holds the command — decided, 2026-09-05
246
+
247
+ This repository claims the distribution `sayfirst-cli`, the import package
248
+ `sayfirst_cli` and the console script `sayfirst`. Until 2026-09-05 that was an
249
+ assumption, stated as one: the control plane's repository claimed the same three
250
+ names for its operator surface, each repository's guard read its own manifest
251
+ and passed, and no test anywhere could see the other tree. The operator settled
252
+ it on 2026-09-05, and all three names in all three forms belong to the product
253
+ command-line interface, which is this repository.
254
+
255
+ The control plane's repository is giving them up: its operator surface — the
256
+ commands that inspect its daemon — takes the daemon's own form of the product
257
+ name instead, the conventional Unix shape in which the daemon and the commands
258
+ that inspect it share one binary. That change is on a branch there, dated
259
+ 2026-09-05 and not merged, so until it merges both trees still declare the three
260
+ names. Nothing had been published under either claim (article 0), so the
261
+ collision was a fact about two source trees and never about an installed
262
+ environment. [`docs/PARTITION.md`](docs/PARTITION.md) records
263
+ the question and its answer as Q-D.
264
+
265
+ Deciding the name did **not** unblock publishing on its own. Article 0 forbids
266
+ publishing anything under it — no package on an index, no public repository, no
267
+ announcement — until three conditions hold together:
268
+
269
+ 1. **the name is decided** — met, 2026-09-04;
270
+ 2. **the marks are filed** — no file here can read a registry, so this
271
+ repository asserts nothing about it and a session reading this file must not
272
+ read the decided name as evidence of it. The date the filing was made and the
273
+ reference it produced are recorded on `docs/publication-checklist.md`, and
274
+ article 0 puts that act before any public repository of this project;
275
+ 3. **every occurrence of the placeholder is replaced** — done here; the
276
+ condition is project-wide, and the other repositories' occurrences are not
277
+ this repository's to change.
278
+
279
+ ## What is still not decided
280
+
281
+ - **The partition.** Which parts of the private CLI cross, and what is renamed
282
+ on the way, is measured privately and executed slice by slice.
283
+ [`docs/PARTITION.md`](docs/PARTITION.md) proposes the open command surface,
284
+ names the three questions it does not close, and says plainly which of its
285
+ lines is a decision and which is a choice it made to keep moving. Two of its
286
+ questions are now answered — Q-D, the name collision, and Q-A, the evidence
287
+ surface — and both stay in the document, marked closed and dated.
288
+
289
+ ## Boundary with the control plane
290
+
291
+ The control plane's operator surface keeps `status`, `doctor`,
292
+ `policy show|history` and `plugins list`, which inspect its daemon's own state.
293
+ `systems {register,retire}` also stays there pending its separate question.
294
+ The 2026-09-05 decision assigns `trace`, `explain` and
295
+ `evidence {audit,history,exports,export}` to this product client: they read the
296
+ decisions and evidence the plane supplied. Q-B, Q-C and Q-E remain open; Q-D
297
+ was already closed.
@@ -0,0 +1,35 @@
1
+ sayfirst_cli/__init__.py,sha256=8zgvziBPj_nkJGMyux6TNzilJB98tKmFWxMvdfI8ywA,112
2
+ sayfirst_cli/approvals.py,sha256=RDhFSO9OtJcJW1jEoZiUCXsnh_mIHBDCix8-bWyOY08,5486
3
+ sayfirst_cli/ask.py,sha256=wMvFQ5OF4AzKQ4m3Yx-c_UBJ2DgwlPrXIDZo9ZisD9A,7024
4
+ sayfirst_cli/evidence.py,sha256=ce5z98Jk32W5snUx9NL8QMKOdz6F86LeFhw7Y4XNgcM,30378
5
+ sayfirst_cli/exit_codes.py,sha256=8OswtauKZRSzFbsWBM3uRMbeSC3pRfjElbDLgaO1AJY,4269
6
+ sayfirst_cli/explain.py,sha256=dMS-2--dbakoqGrvftFNU4aYoa6F5roiZ2B-bbLOOhU,1206
7
+ sayfirst_cli/main.py,sha256=oumnv_oM0PDVxA-DMgFE7HlO3ybsmdaTUliRQZFPCAk,3185
8
+ sayfirst_cli/packs_cmd.py,sha256=CU-oWDNzPHiNbpQZ7-_qAQXPcBDL7UIx32YvPjFGHe0,4936
9
+ sayfirst_cli/pages.py,sha256=u5q8AiSAYA0FRZHj03lUtxkJokyzBQCcckk8pwUX0FU,4135
10
+ sayfirst_cli/reads.py,sha256=cxuJ55woWlk5w33098ZofFpuMxlML9ITkB-_7YsH-N0,10511
11
+ sayfirst_cli/render.py,sha256=3iokEciq6GYbF3aSBFkldUTU4EAB71WRuc2QB_P-ztc,5725
12
+ sayfirst_cli/trace.py,sha256=W25lDI2ms55EIhT3EGavvBnJR5f_driZj-NAvcgLZMc,3733
13
+ sayfirst_cli/instrument/__init__.py,sha256=XaUxOjGVni0ky3TMUAz0wVsbYajYX1GqBVzXj82aDas,778
14
+ sayfirst_cli/instrument/commands.py,sha256=tXeAxFzMTLuIjF_97IcsSO0Ue9nfI4ZTERP7urXt0dI,8722
15
+ sayfirst_cli/instrument/engine.py,sha256=ZjGKneNTo2vi8grrRVLZ4VmWJovYV0uiLZ9aIAg28tU,18300
16
+ sayfirst_cli/instrument/harness.py,sha256=5sOw3eo_ZtIcWUh9MFj75jtoWc_RK_oEs7lcBpbSQh0,55202
17
+ sayfirst_cli/instrument/launch.py,sha256=1O5DhLFMJIVQrXDH6pVg9gpxzvXVgeeEDdZjwQ7pjrA,19935
18
+ sayfirst_cli/instrument/manifest.py,sha256=ESMCZQUmQIAa9cV7QtAjvs6jgBc677vKI6S9z8LHqOI,18367
19
+ sayfirst_cli/instrument/verify.py,sha256=VLRoZw8hgr6GBsAOlFjIZLOWCmVB2FzahHO5uK0hC8Y,24651
20
+ sayfirst_cli/packs/__init__.py,sha256=BKoZs2tFDTLr6vHx_jX0-jPKURL47lQt3ZxILtPz8CU,579
21
+ sayfirst_cli/packs/database/NOTE.md,sha256=EDV00TSw6YR5USwmyCXWZAbQIWaZPNTDuSoHZQlOlhA,264
22
+ sayfirst_cli/packs/database/interpose.py,sha256=gQwjIzbXSn7vg6XBt9IfGuPr9wJLocv3bu-rA3GMM4I,4308
23
+ sayfirst_cli/packs/database/pack.toml,sha256=yJI5For50MJjMwMIkBWzvTblbbQr1jW19NYpTOm-ZYo,259
24
+ sayfirst_cli/packs/http-client/NOTE.md,sha256=VmHzo9bfolFrf6YClRLk_Uc_q6W4e4_sz53-dH4nS1Q,271
25
+ sayfirst_cli/packs/http-client/interpose.py,sha256=egTSO1lyFxv0cIULV9PmMBeyuutYTYfcVGbYOjMbJrg,3221
26
+ sayfirst_cli/packs/http-client/pack.toml,sha256=3zFrhCZB9dwHnzFBIpUEYSSluoSMz0UZ5QXSOOOozkw,260
27
+ sayfirst_cli/packs/subprocess/NOTE.md,sha256=nTwLjZqUNNoNB-pPye0bDARFWtVC2BpF26KjM-vH10o,278
28
+ sayfirst_cli/packs/subprocess/interpose.py,sha256=tsbiz5kyxZkVL_NlQ4uz1iRDKIf_5x4L1TGp_Yc91ho,3955
29
+ sayfirst_cli/packs/subprocess/pack.toml,sha256=2aKVEL2G_VSoH-frHPBcw--vL-3p23vsY3jIIdv0EBw,259
30
+ sayfirst_cli-0.2.0.dist-info/METADATA,sha256=1gQvQ37L55XYGu8Ck4xuKl_NvJ7Qg3K6yN4RrP55GZI,16243
31
+ sayfirst_cli-0.2.0.dist-info/WHEEL,sha256=zOwg4jB6zX2kU910N-cMawjivD6tO8NEWvE12je1bVk,87
32
+ sayfirst_cli-0.2.0.dist-info/entry_points.txt,sha256=U5lrZxS9GCMN19c-MiO6TaHgVCiS-6XF51WnQ0JTAm4,51
33
+ sayfirst_cli-0.2.0.dist-info/licenses/LICENSE,sha256=z8d0m5b2O9McPEK1xHG_dWgUBT6EfBDz6wA0F7xSPTA,11358
34
+ sayfirst_cli-0.2.0.dist-info/licenses/NOTICE,sha256=MCkBvwpULbgh40rntVwPX_vjsx9LZIDYA8t6wGIXiMc,620
35
+ sayfirst_cli-0.2.0.dist-info/RECORD,,
@@ -0,0 +1,4 @@
1
+ Wheel-Version: 1.0
2
+ Generator: hatchling 1.32.0
3
+ Root-Is-Purelib: true
4
+ Tag: py3-none-any
@@ -0,0 +1,2 @@
1
+ [console_scripts]
2
+ sayfirst = sayfirst_cli.main:run
@@ -0,0 +1,202 @@
1
+
2
+ Apache License
3
+ Version 2.0, January 2004
4
+ http://www.apache.org/licenses/
5
+
6
+ TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
7
+
8
+ 1. Definitions.
9
+
10
+ "License" shall mean the terms and conditions for use, reproduction,
11
+ and distribution as defined by Sections 1 through 9 of this document.
12
+
13
+ "Licensor" shall mean the copyright owner or entity authorized by
14
+ the copyright owner that is granting the License.
15
+
16
+ "Legal Entity" shall mean the union of the acting entity and all
17
+ other entities that control, are controlled by, or are under common
18
+ control with that entity. For the purposes of this definition,
19
+ "control" means (i) the power, direct or indirect, to cause the
20
+ direction or management of such entity, whether by contract or
21
+ otherwise, or (ii) ownership of fifty percent (50%) or more of the
22
+ outstanding shares, or (iii) beneficial ownership of such entity.
23
+
24
+ "You" (or "Your") shall mean an individual or Legal Entity
25
+ exercising permissions granted by this License.
26
+
27
+ "Source" form shall mean the preferred form for making modifications,
28
+ including but not limited to software source code, documentation
29
+ source, and configuration files.
30
+
31
+ "Object" form shall mean any form resulting from mechanical
32
+ transformation or translation of a Source form, including but
33
+ not limited to compiled object code, generated documentation,
34
+ and conversions to other media types.
35
+
36
+ "Work" shall mean the work of authorship, whether in Source or
37
+ Object form, made available under the License, as indicated by a
38
+ copyright notice that is included in or attached to the work
39
+ (an example is provided in the Appendix below).
40
+
41
+ "Derivative Works" shall mean any work, whether in Source or Object
42
+ form, that is based on (or derived from) the Work and for which the
43
+ editorial revisions, annotations, elaborations, or other modifications
44
+ represent, as a whole, an original work of authorship. For the purposes
45
+ of this License, Derivative Works shall not include works that remain
46
+ separable from, or merely link (or bind by name) to the interfaces of,
47
+ the Work and Derivative Works thereof.
48
+
49
+ "Contribution" shall mean any work of authorship, including
50
+ the original version of the Work and any modifications or additions
51
+ to that Work or Derivative Works thereof, that is intentionally
52
+ submitted to Licensor for inclusion in the Work by the copyright owner
53
+ or by an individual or Legal Entity authorized to submit on behalf of
54
+ the copyright owner. For the purposes of this definition, "submitted"
55
+ means any form of electronic, verbal, or written communication sent
56
+ to the Licensor or its representatives, including but not limited to
57
+ communication on electronic mailing lists, source code control systems,
58
+ and issue tracking systems that are managed by, or on behalf of, the
59
+ Licensor for the purpose of discussing and improving the Work, but
60
+ excluding communication that is conspicuously marked or otherwise
61
+ designated in writing by the copyright owner as "Not a Contribution."
62
+
63
+ "Contributor" shall mean Licensor and any individual or Legal Entity
64
+ on behalf of whom a Contribution has been received by Licensor and
65
+ subsequently incorporated within the Work.
66
+
67
+ 2. Grant of Copyright License. Subject to the terms and conditions of
68
+ this License, each Contributor hereby grants to You a perpetual,
69
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
70
+ copyright license to reproduce, prepare Derivative Works of,
71
+ publicly display, publicly perform, sublicense, and distribute the
72
+ Work and such Derivative Works in Source or Object form.
73
+
74
+ 3. Grant of Patent License. Subject to the terms and conditions of
75
+ this License, each Contributor hereby grants to You a perpetual,
76
+ worldwide, non-exclusive, no-charge, royalty-free, irrevocable
77
+ (except as stated in this section) patent license to make, have made,
78
+ use, offer to sell, sell, import, and otherwise transfer the Work,
79
+ where such license applies only to those patent claims licensable
80
+ by such Contributor that are necessarily infringed by their
81
+ Contribution(s) alone or by combination of their Contribution(s)
82
+ with the Work to which such Contribution(s) was submitted. If You
83
+ institute patent litigation against any entity (including a
84
+ cross-claim or counterclaim in a lawsuit) alleging that the Work
85
+ or a Contribution incorporated within the Work constitutes direct
86
+ or contributory patent infringement, then any patent licenses
87
+ granted to You under this License for that Work shall terminate
88
+ as of the date such litigation is filed.
89
+
90
+ 4. Redistribution. You may reproduce and distribute copies of the
91
+ Work or Derivative Works thereof in any medium, with or without
92
+ modifications, and in Source or Object form, provided that You
93
+ meet the following conditions:
94
+
95
+ (a) You must give any other recipients of the Work or
96
+ Derivative Works a copy of this License; and
97
+
98
+ (b) You must cause any modified files to carry prominent notices
99
+ stating that You changed the files; and
100
+
101
+ (c) You must retain, in the Source form of any Derivative Works
102
+ that You distribute, all copyright, patent, trademark, and
103
+ attribution notices from the Source form of the Work,
104
+ excluding those notices that do not pertain to any part of
105
+ the Derivative Works; and
106
+
107
+ (d) If the Work includes a "NOTICE" text file as part of its
108
+ distribution, then any Derivative Works that You distribute must
109
+ include a readable copy of the attribution notices contained
110
+ within such NOTICE file, excluding those notices that do not
111
+ pertain to any part of the Derivative Works, in at least one
112
+ of the following places: within a NOTICE text file distributed
113
+ as part of the Derivative Works; within the Source form or
114
+ documentation, if provided along with the Derivative Works; or,
115
+ within a display generated by the Derivative Works, if and
116
+ wherever such third-party notices normally appear. The contents
117
+ of the NOTICE file are for informational purposes only and
118
+ do not modify the License. You may add Your own attribution
119
+ notices within Derivative Works that You distribute, alongside
120
+ or as an addendum to the NOTICE text from the Work, provided
121
+ that such additional attribution notices cannot be construed
122
+ as modifying the License.
123
+
124
+ You may add Your own copyright statement to Your modifications and
125
+ may provide additional or different license terms and conditions
126
+ for use, reproduction, or distribution of Your modifications, or
127
+ for any such Derivative Works as a whole, provided Your use,
128
+ reproduction, and distribution of the Work otherwise complies with
129
+ the conditions stated in this License.
130
+
131
+ 5. Submission of Contributions. Unless You explicitly state otherwise,
132
+ any Contribution intentionally submitted for inclusion in the Work
133
+ by You to the Licensor shall be under the terms and conditions of
134
+ this License, without any additional terms or conditions.
135
+ Notwithstanding the above, nothing herein shall supersede or modify
136
+ the terms of any separate license agreement you may have executed
137
+ with Licensor regarding such Contributions.
138
+
139
+ 6. Trademarks. This License does not grant permission to use the trade
140
+ names, trademarks, service marks, or product names of the Licensor,
141
+ except as required for reasonable and customary use in describing the
142
+ origin of the Work and reproducing the content of the NOTICE file.
143
+
144
+ 7. Disclaimer of Warranty. Unless required by applicable law or
145
+ agreed to in writing, Licensor provides the Work (and each
146
+ Contributor provides its Contributions) on an "AS IS" BASIS,
147
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
148
+ implied, including, without limitation, any warranties or conditions
149
+ of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
150
+ PARTICULAR PURPOSE. You are solely responsible for determining the
151
+ appropriateness of using or redistributing the Work and assume any
152
+ risks associated with Your exercise of permissions under this License.
153
+
154
+ 8. Limitation of Liability. In no event and under no legal theory,
155
+ whether in tort (including negligence), contract, or otherwise,
156
+ unless required by applicable law (such as deliberate and grossly
157
+ negligent acts) or agreed to in writing, shall any Contributor be
158
+ liable to You for damages, including any direct, indirect, special,
159
+ incidental, or consequential damages of any character arising as a
160
+ result of this License or out of the use or inability to use the
161
+ Work (including but not limited to damages for loss of goodwill,
162
+ work stoppage, computer failure or malfunction, or any and all
163
+ other commercial damages or losses), even if such Contributor
164
+ has been advised of the possibility of such damages.
165
+
166
+ 9. Accepting Warranty or Additional Liability. While redistributing
167
+ the Work or Derivative Works thereof, You may choose to offer,
168
+ and charge a fee for, acceptance of support, warranty, indemnity,
169
+ or other liability obligations and/or rights consistent with this
170
+ License. However, in accepting such obligations, You may act only
171
+ on Your own behalf and on Your sole responsibility, not on behalf
172
+ of any other Contributor, and only if You agree to indemnify,
173
+ defend, and hold each Contributor harmless for any liability
174
+ incurred by, or claims asserted against, such Contributor by reason
175
+ of your accepting any such warranty or additional liability.
176
+
177
+ END OF TERMS AND CONDITIONS
178
+
179
+ APPENDIX: How to apply the Apache License to your work.
180
+
181
+ To apply the Apache License to your work, attach the following
182
+ boilerplate notice, with the fields enclosed by brackets "[]"
183
+ replaced with your own identifying information. (Don't include
184
+ the brackets!) The text should be enclosed in the appropriate
185
+ comment syntax for the file format. We also recommend that a
186
+ file or class name and description of purpose be included on the
187
+ same "printed page" as the copyright notice for easier
188
+ identification within third-party archives.
189
+
190
+ Copyright [yyyy] [name of copyright owner]
191
+
192
+ Licensed under the Apache License, Version 2.0 (the "License");
193
+ you may not use this file except in compliance with the License.
194
+ You may obtain a copy of the License at
195
+
196
+ http://www.apache.org/licenses/LICENSE-2.0
197
+
198
+ Unless required by applicable law or agreed to in writing, software
199
+ distributed under the License is distributed on an "AS IS" BASIS,
200
+ WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
201
+ See the License for the specific language governing permissions and
202
+ limitations under the License.
@@ -0,0 +1,13 @@
1
+ sayfirst command-line interface
2
+ Copyright 2026 Frédéric Aime
3
+
4
+ This product is licensed under the Apache License, Version 2.0 (the "License");
5
+ you may not use these files except in compliance with the License. A copy of the
6
+ License is in the LICENSE file at the root of this repository.
7
+
8
+ Distributions that incorporate this software must reproduce the attribution
9
+ notices this file carries (Apache License 2.0, section 4(d)).
10
+
11
+ The Apache License grants no rights in the names and marks of this project; see
12
+ the TRADEMARKS.md file at the root of this repository, which states where the
13
+ project's one marks policy lives.