@prestack/thread 0.0.1 → 0.0.2
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.
- package/dist/core/actor.ts +20 -0
- package/dist/core/asking.d.ts +0 -1
- package/dist/core/asking.js +0 -1
- package/dist/core/asking.ts +1342 -0
- package/dist/core/attach.js +2 -2
- package/dist/core/attach.ts +585 -0
- package/dist/core/chain.js +8 -1
- package/dist/core/chain.ts +148 -0
- package/dist/core/cli.js +97 -18
- package/dist/core/cli.ts +2004 -0
- package/dist/core/condition.ts +227 -0
- package/dist/core/descend.d.ts +1 -0
- package/dist/core/descend.js +36 -0
- package/dist/core/descend.ts +1001 -0
- package/dist/core/finding.ts +38 -0
- package/dist/core/generators/meta.js +6 -0
- package/dist/core/generators/meta.ts +1006 -0
- package/dist/core/init.ts +170 -0
- package/dist/core/invoked.ts +47 -0
- package/dist/core/round.ts +57 -0
- package/dist/core/rows.ts +369 -0
- package/dist/core/scope.ts +18 -0
- package/dist/core/thread.ts +29 -0
- package/dist/core/tree.ts +67 -0
- package/dist/core/types.ts +350 -0
- package/dist/derivation/attach.js +1 -1
- package/dist/derivation/attach.ts +68 -0
- package/dist/derivation/generators/build.ts +232 -0
- package/dist/derivation/generators/check.ts +118 -0
- package/dist/derivation/generators/derivation.ts +347 -0
- package/dist/derivation/generators/determinism.ts +145 -0
- package/dist/derivation/generators/release.js +19 -9
- package/dist/derivation/generators/release.ts +213 -0
- package/dist/derivation/makers/release.ts +135 -0
- package/dist/derivation/release.ts +23 -0
- package/dist/integrations/attach.js +2 -2
- package/dist/integrations/attach.ts +207 -0
- package/dist/integrations/generators/read.ts +127 -0
- package/dist/integrations/generators/secret.ts +289 -0
- package/dist/integrations/generators/target.js +16 -6
- package/dist/integrations/generators/target.ts +944 -0
- package/dist/integrations/generators/vault.js +46 -14
- package/dist/integrations/generators/vault.ts +212 -0
- package/dist/integrations/{sdk.js → module.js} +4 -4
- package/dist/integrations/module.ts +189 -0
- package/dist/integrations/secret.ts +50 -0
- package/dist/integrations/target.d.ts +3 -1
- package/dist/integrations/target.js +14 -11
- package/dist/integrations/target.ts +340 -0
- package/dist/integrations/vault.d.ts +2 -0
- package/dist/integrations/vault.js +4 -2
- package/dist/integrations/vault.ts +136 -0
- package/dist/integrity/attach.ts +234 -0
- package/dist/integrity/check-threads.js +2 -26
- package/dist/integrity/check-threads.ts +166 -0
- package/dist/integrity/constraint.ts +1202 -0
- package/dist/integrity/generators/compiler.ts +161 -0
- package/dist/integrity/generators/gate.ts +201 -0
- package/dist/integrity/generators/issues.ts +160 -0
- package/dist/integrity/generators/publish.js +14 -5
- package/dist/integrity/generators/publish.ts +247 -0
- package/dist/integrity/generators/rule.ts +271 -0
- package/dist/integrity/generators/seal.ts +117 -0
- package/dist/integrity/generators/trial.ts +320 -0
- package/dist/integrity/lint.ts +227 -0
- package/dist/integrity/lsp/bin.ts +42 -0
- package/dist/integrity/lsp/diagnostics.ts +92 -0
- package/dist/integrity/lsp/findings.ts +63 -0
- package/dist/integrity/lsp/lint.ts +140 -0
- package/dist/integrity/lsp/protocol.ts +78 -0
- package/dist/integrity/lsp/server.ts +224 -0
- package/dist/integrity/sealed.js +2 -2
- package/dist/integrity/sealed.ts +56 -0
- package/dist/integrity/slopnator.ts +350 -0
- package/dist/integrity/slopnators/sl_broken_lineage.ts +36 -0
- package/dist/integrity/slopnators/sl_declared_elsewhere.ts +39 -0
- package/dist/integrity/slopnators/sl_derived_drift.ts +98 -0
- package/dist/integrity/slopnators/sl_hardcoded_style.ts +55 -0
- package/dist/integrity/slopnators/sl_inert_island.ts +91 -0
- package/dist/integrity/slopnators/sl_inert_slopnator.ts +93 -0
- package/dist/integrity/slopnators/sl_missing_row.ts +182 -0
- package/dist/integrity/slopnators/sl_no_barrel.ts +48 -0
- package/dist/integrity/slopnators/sl_no_child_process.ts +74 -0
- package/dist/integrity/slopnators/sl_no_comment.ts +43 -0
- package/dist/integrity/slopnators/sl_no_dispatch_bypass.ts +328 -0
- package/dist/integrity/slopnators/sl_no_import_cycle.ts +50 -0
- package/dist/integrity/slopnators/sl_no_such_command.ts +64 -0
- package/dist/integrity/slopnators/sl_only_takes_a_literal.ts +63 -0
- package/dist/integrity/slopnators/sl_secret_literal.ts +100 -0
- package/dist/integrity/slopnators/sl_stale_path.ts +94 -0
- package/dist/integrity/slopnators/sl_twice_meant.ts +31 -0
- package/dist/integrity/slopnators/sl_twice_parented.ts +37 -0
- package/dist/integrity/slopnators/sl_undeclared_crossing.ts +87 -0
- package/dist/integrity/slopnators/sl_undeclared_destination.ts +65 -0
- package/dist/integrity/slopnators/sl_undeclared_element.ts +44 -0
- package/dist/integrity/slopnators/sl_undeclared_node.ts +69 -0
- package/dist/integrity/slopnators/sl_undeclared_operation.ts +147 -0
- package/dist/integrity/slopnators/sl_undeclared_variant.ts +148 -0
- package/dist/integrity/slopnators/sl_undescribed.ts +65 -0
- package/dist/integrity/slopnators/sl_ungoverned_island.ts +61 -0
- package/dist/integrity/slopnators/sl_unkept_rows.ts +111 -0
- package/dist/integrity/slopnators/sl_unnamed_island.ts +41 -0
- package/dist/integrity/slopnators/sl_unpaired_member.ts +131 -0
- package/dist/integrity/slopnators/sl_unpaired_slopnator.ts +58 -0
- package/dist/integrity/slopnators/sl_unprefixed_member.ts +136 -0
- package/dist/integrity/slopnators/sl_unseamed_reach.ts +79 -0
- package/dist/integrity/slopnators/sl_unthreaded.ts +419 -0
- package/dist/integrity/slopnators/sl_untranslated_key.ts +164 -0
- package/dist/integrity/slopnators/sl_unversioned.ts +36 -0
- package/dist/integrity/sweep.ts +307 -0
- package/dist/knowledge/attach.ts +350 -0
- package/dist/knowledge/generators/docsgen.ts +174 -0
- package/dist/knowledge/generators/glossary.ts +73 -0
- package/dist/knowledge/generators/skills.ts +263 -0
- package/dist/knowledge/glossary.ts +6 -0
- package/dist/operations/access.ts +88 -0
- package/dist/operations/attach.ts +475 -0
- package/dist/operations/capabilities.ts +65 -0
- package/dist/operations/cost.ts +58 -0
- package/dist/operations/dispatch.ts +348 -0
- package/dist/operations/error-results.ts +31 -0
- package/dist/operations/errors/catalog.ts +168 -0
- package/dist/operations/errors/classes.ts +51 -0
- package/dist/operations/errors/errors.ts +314 -0
- package/dist/operations/generators/capability.js +29 -19
- package/dist/operations/generators/capability.ts +704 -0
- package/dist/operations/generators/compile.js +20 -1
- package/dist/operations/generators/compile.ts +643 -0
- package/dist/operations/generators/grant.ts +336 -0
- package/dist/operations/generators/operation.ts +972 -0
- package/dist/operations/generators/permits.ts +223 -0
- package/dist/operations/generators/seed.ts +347 -0
- package/dist/operations/generators/trial.js +4 -1
- package/dist/operations/generators/trial.ts +529 -0
- package/dist/operations/http.ts +164 -0
- package/dist/operations/makers/capability.ts +166 -0
- package/dist/operations/makers/trial.ts +520 -0
- package/dist/operations/memory.ts +470 -0
- package/dist/operations/metering.ts +328 -0
- package/dist/operations/nodes.ts +54 -0
- package/dist/operations/operations.ts +32 -0
- package/dist/operations/queue-policy.ts +10 -0
- package/dist/operations/result.ts +28 -0
- package/dist/operations/seams.d.ts +4 -4
- package/dist/operations/seams.ts +364 -0
- package/dist/operations/store.ts +29 -0
- package/package.json +8 -1
- package/dist/integrations/generators/sdk.d.ts +0 -1
- package/dist/integrations/generators/sdk.js +0 -148
- /package/dist/integrations/{sdk.d.ts → module.d.ts} +0 -0
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
import type { Attaching } from '../core/attach.ts'
|
|
2
|
+
import { named } from '../core/scope.ts'
|
|
3
|
+
|
|
4
|
+
const VERSION = 1
|
|
5
|
+
|
|
6
|
+
const DESCRIBES =
|
|
7
|
+
'The system that states every place this app reaches and every credential it reaches with. A target is a destination and the prefix on its id is how it is reached; what it accepts is entities if it keeps them, calls if it is called over https, and the methods or subcommands it answers to if it is an npm package or a program; what it requires is secrets, and where the reach has an address one of them is it. Nothing else in this app names a host, a connection string or an environment variable, so an operation says which place it needs and the rest is worked out from the declaration.'
|
|
8
|
+
|
|
9
|
+
const TARGETS =
|
|
10
|
+
'Where this app states the places it reaches and what each one accepts. Declare one with `thread create target`, which asks how it is reached and puts the matching prefix on its id — `pg_` for a postgres over the network, `pl_` for one in this process, `zu_` for a store in the browser, `ht_` for something called over https, `mod_` for something this app brings in from outside the repository and calls, `cli_` for a program it runs at a command line — and writes what it accepts and the secrets reaching it needs. A place that keeps things accepts entities; a place that is called accepts the calls declared beside it; a package or a program says in `uses` what it is and in `offers` what it responds to, and takes nothing, because a method name belongs to something outside this tree and `takes` is a demand this tree has to be able to meet. This is the one place the split between what reaches a server and what stays local is written down.'
|
|
11
|
+
|
|
12
|
+
const SECRETS =
|
|
13
|
+
'Where this app states every value it must be given and may not hold. A row here is a name and a sentence and never a value: `sec_` for one only a server may ever hold, `pub_` for one that may reach a browser, and that prefix is the whole of what decides where the target requiring it can be reached from. A secret with a `sends` goes into a header on every call; the one without is the address of the target that requires it.'
|
|
14
|
+
|
|
15
|
+
const VAULT =
|
|
16
|
+
'What opens a secret\'s value at the moment of use, written once and then yours. It reads the names off _threads/secrets.ts and opens the values sealed beside the tree, refusing any other name; add a secret with `thread create secret`, which is the only thing that ever takes a value. To open values from somewhere else in production, replace what opens them here.'
|
|
17
|
+
|
|
18
|
+
const SECRET =
|
|
19
|
+
'A value this app must be given and may never hold — a key, a token, a connection string. The row names it and says what it is for, and there is no field on it for the value, because a row is read by everything and a credential is read by one thing at one moment. The prefix says who may ever hold it: `sec_` a server and nothing else, `pub_` anybody including whoever opens the page. That one letter is what refuses a server credential its way into a browser bundle, so it is on the id rather than in a field somebody could forget.'
|
|
20
|
+
|
|
21
|
+
const CALL =
|
|
22
|
+
'One thing a target can be asked to do — the verb and the path, named so that a node names the call and never writes a url. It is a row rather than a field because it is a thing with a name and a sentence like everything else here, and because what a vendor offers is a list that grows: an app reaches one more endpoint by declaring one more of these, not by editing a body.'
|
|
23
|
+
|
|
24
|
+
const REACH =
|
|
25
|
+
'How a target is got at — over a network to a postgres, inside this process, in the browser\'s own memory, over https, as an npm package this app installs and calls directly, or as a program run at a command line. It is the prefix on the target\'s id and never a field, because how a thing is reached decides everything else that may be said about it: what it accepts, whether it can be reached from a page at all, whether anything has to say where it is, and whether anything has to arrive on the machine before it can be reached at all. A field beside the id would be a second answer to a question the id already answers.'
|
|
26
|
+
|
|
27
|
+
export const CONCEPTS: readonly { readonly id: string; readonly describes: string }[] = [
|
|
28
|
+
{ id: 'secret', describes: SECRET },
|
|
29
|
+
{ id: 'call', describes: CALL },
|
|
30
|
+
{ id: 'reach', describes: REACH },
|
|
31
|
+
]
|
|
32
|
+
|
|
33
|
+
import { quoted } from '../core/rows.ts'
|
|
34
|
+
|
|
35
|
+
const concept = (one: { readonly id: string; readonly describes: string }): string =>
|
|
36
|
+
[
|
|
37
|
+
' {',
|
|
38
|
+
` id: '${one.id}',`,
|
|
39
|
+
" at: '_threads/concepts.ts',",
|
|
40
|
+
` describes: ${quoted(one.describes)},`,
|
|
41
|
+
` version: ${String(VERSION)},`,
|
|
42
|
+
' cites: [',
|
|
43
|
+
" { on: '2026-08-29', said: 'SSOT for the integrations, as a whole meta inside thread core' },",
|
|
44
|
+
" { on: '2026-08-29', said: 'seams declare BOTH target AND secrets' },",
|
|
45
|
+
' ],',
|
|
46
|
+
' },',
|
|
47
|
+
].join('\n')
|
|
48
|
+
|
|
49
|
+
const INTEGRATIONS_SOURCE = `import type { Threaded } from '../core/thread.ts'
|
|
50
|
+
|
|
51
|
+
export const INTEGRATIONS: Threaded = {
|
|
52
|
+
id: 'integrations',
|
|
53
|
+
at: '_threads/integrations.ts',
|
|
54
|
+
describes: ${quoted(DESCRIBES)},
|
|
55
|
+
version: ${String(VERSION)},
|
|
56
|
+
declares: [
|
|
57
|
+
{
|
|
58
|
+
id: 'targets',
|
|
59
|
+
at: '_threads/targets.ts',
|
|
60
|
+
describes: ${quoted(TARGETS)},
|
|
61
|
+
version: ${String(VERSION)},
|
|
62
|
+
},
|
|
63
|
+
{
|
|
64
|
+
id: 'secrets',
|
|
65
|
+
at: '_threads/secrets.ts',
|
|
66
|
+
describes: ${quoted(SECRETS)},
|
|
67
|
+
version: ${String(VERSION)},
|
|
68
|
+
},
|
|
69
|
+
],
|
|
70
|
+
}
|
|
71
|
+
`
|
|
72
|
+
|
|
73
|
+
const TARGETS_SOURCE = `import type { Call, Target } from './target.ts'
|
|
74
|
+
|
|
75
|
+
export const TARGETS: readonly Target[] = []
|
|
76
|
+
|
|
77
|
+
export const CALLS: readonly Call[] = []
|
|
78
|
+
`
|
|
79
|
+
|
|
80
|
+
const SECRETS_SOURCE = `import type { Secret } from './secret.ts'
|
|
81
|
+
|
|
82
|
+
export const SECRETS: readonly Secret[] = []
|
|
83
|
+
`
|
|
84
|
+
|
|
85
|
+
const OWN: readonly { readonly into: string; readonly list: string; readonly id: string; readonly source: string }[] = [
|
|
86
|
+
...CONCEPTS.map((one) => ({
|
|
87
|
+
into: 'concepts.ts',
|
|
88
|
+
list: 'CONCEPTS',
|
|
89
|
+
id: one.id,
|
|
90
|
+
source: concept(one),
|
|
91
|
+
})),
|
|
92
|
+
{
|
|
93
|
+
into: 'integrations.ts',
|
|
94
|
+
list: 'declares',
|
|
95
|
+
id: 'vault',
|
|
96
|
+
source: [
|
|
97
|
+
' {',
|
|
98
|
+
" id: 'vault',",
|
|
99
|
+
" at: 'src/vault.ts',",
|
|
100
|
+
` describes: ${quoted(VAULT)},`,
|
|
101
|
+
` version: ${String(VERSION)},`,
|
|
102
|
+
" by: 'thread vault',",
|
|
103
|
+
" runs: 'thread vault --check',",
|
|
104
|
+
' },',
|
|
105
|
+
].join('\n'),
|
|
106
|
+
},
|
|
107
|
+
]
|
|
108
|
+
|
|
109
|
+
const SEEDED = {
|
|
110
|
+
attaches: ['@prestack/thread/derivation/attach'] as const,
|
|
111
|
+
id: 'integrations',
|
|
112
|
+
declares: [
|
|
113
|
+
' {',
|
|
114
|
+
' id: \'integrations\',',
|
|
115
|
+
' at: \'_threads/integrations.ts\',',
|
|
116
|
+
' describes: \'The system that states every place this app reaches and every credential it reaches with. A target is a destination and the prefix on its id is how it is reached; what it accepts is entities if it keeps them, calls if it is called over https, and the methods or subcommands it answers to if it is an npm package or a program; what it requires is secrets, and where the reach has an address one of them is it. Nothing else in this app names a host, a connection string or an environment variable, so an operation says which place it needs and the rest is worked out from the declaration.\',',
|
|
117
|
+
' version: 1,',
|
|
118
|
+
' },',
|
|
119
|
+
].join('\n'),
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
const SHIPPED: readonly { readonly into: string; readonly list: string; readonly id: string; readonly source: string }[] = [
|
|
123
|
+
{
|
|
124
|
+
into: 'derivation.ts',
|
|
125
|
+
list: 'GENERATORS',
|
|
126
|
+
id: 'target-generator',
|
|
127
|
+
source: " {\n id: 'target-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/target.js',\n describes: 'The command that declares a place this app reaches. Asked for nothing it lists every target already here with what it accepts and what it requires, every way a target may be reached and what each way can hold, and every secret this app declares. It then asks what the place is called and how it is reached, puts the prefix on the id, asks which secret is its address and which go into a header, and writes the row — so a target arrives declared with its credential named rather than with an environment variable typed into whatever needed it.',\n version: 1,\n by: 'thread create target',\n },",
|
|
128
|
+
},
|
|
129
|
+
{
|
|
130
|
+
into: 'derivation.ts',
|
|
131
|
+
list: 'GENERATORS',
|
|
132
|
+
id: 'secret-generator',
|
|
133
|
+
source: " {\n id: 'secret-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/secret.js',\n describes: 'The command that declares a value this app must be given, and the only thing that ever takes one. It asks what the secret is for and who may hold it, puts `sec_` or `pub_` on the id accordingly, and writes the row. Then it asks a person for the value, with the terminal\\'s echo off, and seals it into the store beside the tree under a key it generates and never prints. The value goes from the keyboard into the sealed store and nowhere else: not into the row, not into the output, not into whatever transcript is being kept of the session that ran it.',\n version: 1,\n by: 'thread create secret',\n },",
|
|
134
|
+
},
|
|
135
|
+
{
|
|
136
|
+
into: 'derivation.ts',
|
|
137
|
+
list: 'GENERATORS',
|
|
138
|
+
id: 'vault-generator',
|
|
139
|
+
source: " {\n id: 'vault-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/vault.js',\n describes: 'The writer of the file that opens this app\\'s sealed store. It reads the secrets this app declares and writes one function that resolves them by name, refusing any name no row declares — so a value can be asked for only by something that declared it needed it, and a secret taken off the tree stops resolving rather than lingering as a name that still works.',\n version: 1,\n by: 'thread vault',\n runs: 'thread vault --check',\n },",
|
|
140
|
+
},
|
|
141
|
+
{
|
|
142
|
+
into: 'derivation.ts',
|
|
143
|
+
list: 'GENERATORS',
|
|
144
|
+
id: 'inspect-generator',
|
|
145
|
+
source: " {\n id: 'inspect-generator',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/read.js',\n describes: 'The one command for reading what this app declares. Asked for nothing it prints every sheet and what each holds; asked for a word it prints every row whose id or sentence carries it. It reads the tree rather than the files, which is the whole reason it exists: a row is a nested TypeScript literal and a search over lines answers questions about text while the questions people actually ask are about rows.',\n version: 1,\n by: 'thread inspect',\n },",
|
|
146
|
+
},
|
|
147
|
+
{
|
|
148
|
+
into: 'derivation.ts',
|
|
149
|
+
list: 'GENERATORS',
|
|
150
|
+
id: 'call-noun',
|
|
151
|
+
source: " {\n id: 'call-noun',\n at: 'node_modules/@prestack/thread/dist/integrations/generators/target.js',\n describes: 'The command that makes an already-declared call happen. `thread run call <id>` asks for whatever its path leaves to be filled in — `{like_this}` — by name, and for a call that writes (POST, PUT, PATCH) a JSON body, then resolves the target\\'s address and every credential it requires through the vault at the moment of the call and never before. Nothing here ever names a vendor: a call already states its target, its method and its path, and which one runs is read off its own id, exactly as `thread run trial` reads an operation\\'s.',\n version: 1,\n by: 'thread run call',\n },",
|
|
152
|
+
},
|
|
153
|
+
]
|
|
154
|
+
export const ATTACH: Attaching = {
|
|
155
|
+
attaches: SEEDED.attaches,
|
|
156
|
+
id: SEEDED.id,
|
|
157
|
+
declares: SEEDED.declares,
|
|
158
|
+
writes: [
|
|
159
|
+
{ file: 'integrations.ts', source: INTEGRATIONS_SOURCE },
|
|
160
|
+
{ file: 'targets.ts', source: TARGETS_SOURCE },
|
|
161
|
+
{ file: 'secrets.ts', source: SECRETS_SOURCE },
|
|
162
|
+
],
|
|
163
|
+
rows: [...OWN, ...SHIPPED],
|
|
164
|
+
|
|
165
|
+
retires: [
|
|
166
|
+
{
|
|
167
|
+
into: 'derivation.ts',
|
|
168
|
+
list: 'GENERATORS',
|
|
169
|
+
id: 'read-generator',
|
|
170
|
+
why: 'renamed to `inspect-generator`. `thread read` is a verb of the CLI itself now — it reads FILES, and this package reads ROWS, which is `thread inspect`. Both were called read, and only one of them could answer.',
|
|
171
|
+
},
|
|
172
|
+
{
|
|
173
|
+
into: 'integrations.ts',
|
|
174
|
+
list: 'declares',
|
|
175
|
+
id: 'trace',
|
|
176
|
+
made: `src/integrations.trace.${'jsonl'}`,
|
|
177
|
+
why: 'the record of what went out through an integration is gone. It sealed every line against the one before it, and a chain nobody reads guards nothing. What an integration did is what the tool itself reports, at the moment it runs',
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
into: 'derivation.ts',
|
|
181
|
+
list: 'GENERATORS',
|
|
182
|
+
id: 'trace-generator',
|
|
183
|
+
why: 'the record it wrote is gone, so there is nothing for it to make and nothing for `--check` to read',
|
|
184
|
+
},
|
|
185
|
+
|
|
186
|
+
{
|
|
187
|
+
into: 'derivation.ts',
|
|
188
|
+
list: 'GENERATORS',
|
|
189
|
+
id: 'sdk-generator',
|
|
190
|
+
why:
|
|
191
|
+
'`sdk` is a way a target is reached now, not a noun beside one — `thread create target` declares an SDK with ' +
|
|
192
|
+
'`--reach sdk`, which installs it, reads what it answers to, and writes one row instead of three. The noun ' +
|
|
193
|
+
'also raised the collision guard against any app declaring a meta called `sdk`, which made every other ' +
|
|
194
|
+
'withdrawal this package ships undeliverable to the trees that needed them.',
|
|
195
|
+
},
|
|
196
|
+
{
|
|
197
|
+
into: 'concepts.ts',
|
|
198
|
+
list: 'CONCEPTS',
|
|
199
|
+
id: 'sdk',
|
|
200
|
+
why:
|
|
201
|
+
'`sdk` is no longer a noun beside a target — it is one of the ways a target is reached, and its meaning is ' +
|
|
202
|
+
`now \`SAID.module\` in \`${named('integrations')}/target\`, beside \`https\` and every other reach, none of which is ` +
|
|
203
|
+
'coined here either. It also collided with an app declaring a meta called `sdk`, which coins the word: two ' +
|
|
204
|
+
'meanings for one word is what `twice-meant` refuses, and it was holding the glossary and the docs red.',
|
|
205
|
+
},
|
|
206
|
+
],
|
|
207
|
+
}
|
|
@@ -0,0 +1,127 @@
|
|
|
1
|
+
import { stdout } from 'node:process'
|
|
2
|
+
|
|
3
|
+
import { exit } from '../../core/invoked.ts'
|
|
4
|
+
import { wordsIn } from '../../core/asking.ts'
|
|
5
|
+
import { descend, type Row } from '../../core/descend.ts'
|
|
6
|
+
|
|
7
|
+
const words = wordsIn([])
|
|
8
|
+
|
|
9
|
+
const looking = words.join(' ').trim().toLowerCase()
|
|
10
|
+
|
|
11
|
+
const pass = descend()
|
|
12
|
+
|
|
13
|
+
const said = (one: Row): string => (one.describes ?? '').replace(/\s+/g, ' ')
|
|
14
|
+
|
|
15
|
+
const carries = (one: Row, list: string, file: string): boolean => {
|
|
16
|
+
if (looking === '') return true
|
|
17
|
+
const held = [one.id ?? '', one.at ?? '', said(one), one.by ?? '', one.runs ?? '', list, file, ...(one.requires ?? []), ...(one.takes ?? [])]
|
|
18
|
+
return held.some((each) => each.toLowerCase().includes(looking))
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const shown = (one: Row, list: string, pad: string, alone = false): readonly string[] => {
|
|
22
|
+
const marks = [
|
|
23
|
+
...(one.by === undefined ? [] : [`written by \`${one.by}\``]),
|
|
24
|
+
...(one.runs === undefined ? [] : [`checked by \`${one.runs}\``]),
|
|
25
|
+
...((one.requires ?? []).length === 0 ? [] : [`requires ${(one.requires ?? []).join(', ')}`]),
|
|
26
|
+
...((one.takes ?? []).length === 0 ? [] : [`takes ${(one.takes ?? []).join(', ')}`]),
|
|
27
|
+
]
|
|
28
|
+
const sentence = said(one)
|
|
29
|
+
return [
|
|
30
|
+
`${pad}${one.id ?? '(unnamed)'} [${list}]`,
|
|
31
|
+
...(marks.length === 0 ? [] : [`${pad} ${marks.join(' · ')}`]),
|
|
32
|
+
|
|
33
|
+
...(sentence === ''
|
|
34
|
+
? []
|
|
35
|
+
: [`${pad} ${alone || sentence.length <= 160 ? sentence : `${sentence.slice(0, 157)}...`}`]),
|
|
36
|
+
]
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
const files = [...pass.threaded.keys()].sort()
|
|
40
|
+
|
|
41
|
+
const matched =
|
|
42
|
+
files.reduce((sum, file) => sum + (pass.threaded.get(file) ?? []).filter((each) => carries(each.row, each.list, file)).length, 0) +
|
|
43
|
+
[...pass.stated.entries()].filter(
|
|
44
|
+
([, one]) =>
|
|
45
|
+
!files.some((file) => (pass.threaded.get(file) ?? []).some((each) => each.row === one)) && carries(one, 'stated', one.at ?? ''),
|
|
46
|
+
).length
|
|
47
|
+
|
|
48
|
+
const only = matched === 1
|
|
49
|
+
|
|
50
|
+
let rows = 0
|
|
51
|
+
const out: string[] = []
|
|
52
|
+
|
|
53
|
+
if (looking === '') {
|
|
54
|
+
for (const file of files) {
|
|
55
|
+
const held = pass.threaded.get(file) ?? []
|
|
56
|
+
if (held.length === 0) continue
|
|
57
|
+
rows += held.length
|
|
58
|
+
out.push(` ${String(held.length).padStart(4)} ${file}`)
|
|
59
|
+
}
|
|
60
|
+
out.unshift(`${String(files.length)} sheet(s) in this tree, and what each holds:`, '')
|
|
61
|
+
out.push(
|
|
62
|
+
'',
|
|
63
|
+
'Name a word to read the rows themselves — every row whose id, sentence, path, command or',
|
|
64
|
+
'requirement carries it, each with what it means:',
|
|
65
|
+
'',
|
|
66
|
+
' npx thread inspect element',
|
|
67
|
+
' npx thread inspect variant',
|
|
68
|
+
' npx thread inspect <any word above, or any word this tree coins>',
|
|
69
|
+
'',
|
|
70
|
+
)
|
|
71
|
+
stdout.write(`${out.join('\n')}\n`)
|
|
72
|
+
stdout.write(
|
|
73
|
+
`read: ${String(rows)} row(s) across ${String(pass.declarations)} declaration(s), ` +
|
|
74
|
+
`and ${String(pass.findings.length)} finding(s) the gate would report.\n`,
|
|
75
|
+
)
|
|
76
|
+
exit(0)
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
for (const file of files) {
|
|
80
|
+
const held = (pass.threaded.get(file) ?? []).filter((each) => carries(each.row, each.list, file))
|
|
81
|
+
if (held.length === 0) continue
|
|
82
|
+
out.push(file)
|
|
83
|
+
for (const each of held) {
|
|
84
|
+
rows += 1
|
|
85
|
+
out.push(...shown(each.row, each.list, ' ', only))
|
|
86
|
+
}
|
|
87
|
+
out.push('')
|
|
88
|
+
}
|
|
89
|
+
|
|
90
|
+
const loose = [...pass.stated.entries()]
|
|
91
|
+
.filter(([, one]) => !files.some((file) => (pass.threaded.get(file) ?? []).some((each) => each.row === one)))
|
|
92
|
+
.filter(([, one]) => carries(one, 'stated', one.at ?? ''))
|
|
93
|
+
|
|
94
|
+
if (loose.length > 0) {
|
|
95
|
+
out.push('stated outright, in no parent\'s list')
|
|
96
|
+
for (const [, one] of loose) {
|
|
97
|
+
rows += 1
|
|
98
|
+
out.push(...shown(one, 'stated', ' ', only))
|
|
99
|
+
}
|
|
100
|
+
out.push('')
|
|
101
|
+
}
|
|
102
|
+
|
|
103
|
+
if (looking !== '') {
|
|
104
|
+
const coined = [...pass.coined.entries()].filter(([word]) => word.toLowerCase().includes(looking))
|
|
105
|
+
if (coined.length > 0) {
|
|
106
|
+
out.push('words this tree coins')
|
|
107
|
+
for (const [word, held] of coined) out.push(` ${word} — declared under ${held.under}`)
|
|
108
|
+
out.push('')
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
stdout.write(`${out.join('\n')}\n`)
|
|
113
|
+
|
|
114
|
+
if (rows === 0) {
|
|
115
|
+
stdout.write(
|
|
116
|
+
looking === ''
|
|
117
|
+
? 'read: this tree declares nothing yet. Attach a package with `thread attach`.\n'
|
|
118
|
+
: `read: nothing in this tree carries "${looking}" — not an id, a sentence, a path, a command, a requirement or a thing taken.\n`,
|
|
119
|
+
)
|
|
120
|
+
exit(0)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
stdout.write(
|
|
124
|
+
`read: ${String(rows)} row(s)${looking === '' ? '' : ` carrying "${looking}"`} across ` +
|
|
125
|
+
`${String(pass.declarations)} declaration(s), and ${String(pass.findings.length)} finding(s) the gate would report.\n`,
|
|
126
|
+
)
|
|
127
|
+
exit(0)
|
|
@@ -0,0 +1,289 @@
|
|
|
1
|
+
import { spawnSync } from 'node:child_process'
|
|
2
|
+
import { appendFileSync, existsSync, readFileSync, writeFileSync } from 'node:fs'
|
|
3
|
+
import { join, relative } from 'node:path'
|
|
4
|
+
import { argv, stdin, stdout } from 'node:process'
|
|
5
|
+
import { createInterface } from 'node:readline'
|
|
6
|
+
|
|
7
|
+
import { exit } from '../../core/invoked.ts'
|
|
8
|
+
import type { Env } from '../../core/actor.ts'
|
|
9
|
+
import { ELSEWHERE, RECEIPT, answer, asking, asksOf, given, issue, needed, only, ownAnswers, refuse, rowLines, seeding, slug, spend, stating, wordsIn } from '../../core/asking.ts'
|
|
10
|
+
import { holdsRow, listBounds, rowsIn, withRow } from '../../core/rows.ts'
|
|
11
|
+
import { named as packageOf } from '../../core/scope.ts'
|
|
12
|
+
import { findTree, inTree } from '../../core/tree.ts'
|
|
13
|
+
|
|
14
|
+
import { SAID, SECRECY, secrecyOf, type Secret } from '../secret.ts'
|
|
15
|
+
import { IGNORES, KEY_AT, SEALED_AT, keyOf, seal, sealedIn } from '../vault.ts'
|
|
16
|
+
|
|
17
|
+
const TREE = findTree()
|
|
18
|
+
|
|
19
|
+
const FILE = 'secrets.ts'
|
|
20
|
+
|
|
21
|
+
const LIST = 'SECRETS'
|
|
22
|
+
|
|
23
|
+
const NAME = /^[a-z][a-z0-9_]*$/
|
|
24
|
+
|
|
25
|
+
const ENVS: readonly Env[] = ['sandbox', 'production']
|
|
26
|
+
|
|
27
|
+
const WORDS = Object.keys(SECRECY) as (keyof typeof SECRECY)[]
|
|
28
|
+
|
|
29
|
+
const ASKS: readonly { readonly key: string; readonly says: readonly string[]; readonly edits?: true }[] = [
|
|
30
|
+
{ key: 'name', says: ['lower_snake_case, and the prefix is added for you'] },
|
|
31
|
+
{ key: 'holder', says: [`who may hold it — ${WORDS.join(', ')}`] },
|
|
32
|
+
{ key: 'describes', says: ['what it is for, in one sentence'], edits: true },
|
|
33
|
+
{ key: 'header', says: ['blank if this value IS the address of what requires it'], edits: true },
|
|
34
|
+
{ key: 'prefix', says: ['what precedes it in that header, trailing space and all'], edits: true },
|
|
35
|
+
]
|
|
36
|
+
|
|
37
|
+
const TAKES: readonly string[] = ASKS.map((one) => one.key)
|
|
38
|
+
|
|
39
|
+
const EDITS: readonly string[] = ASKS.filter((one) => one.edits === true).map((one) => one.key)
|
|
40
|
+
|
|
41
|
+
const ASKED: readonly string[] = ASKS.flatMap((one) =>
|
|
42
|
+
one.says.map((line, at) => ` ${at === 0 ? `${one.key}:`.padEnd(32) : ' '.repeat(32)}${line}`),
|
|
43
|
+
)
|
|
44
|
+
|
|
45
|
+
const here = (one: string): string => relative(TREE.at, one).split('\\').join('/')
|
|
46
|
+
|
|
47
|
+
const path = inTree(TREE, FILE)
|
|
48
|
+
|
|
49
|
+
const at = here(path)
|
|
50
|
+
|
|
51
|
+
import { quoted } from '../../core/rows.ts'
|
|
52
|
+
|
|
53
|
+
let secrets: readonly Secret[] = []
|
|
54
|
+
try {
|
|
55
|
+
const taken = (await import(new URL(`file://${path}`).href)) as { SECRETS?: readonly Secret[] }
|
|
56
|
+
secrets = taken.SECRETS ?? []
|
|
57
|
+
} catch {
|
|
58
|
+
refuse(`${at} could not be read. Attach \`${packageOf('integrations')}\` first — an app states its secrets there.`)
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
const held = new Map(ENVS.map((env) => [env, new Set(sealedIn(TREE.at, env))]))
|
|
62
|
+
|
|
63
|
+
const setIn = (id: string): string => {
|
|
64
|
+
const found = ENVS.filter((env) => held.get(env)?.has(id) === true)
|
|
65
|
+
return found.length === 0 ? 'no value anywhere yet' : `set for ${found.join(' and ')}`
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
async function sealing(id: string): Promise<number> {
|
|
69
|
+
const hidden = async (question: string): Promise<string> => {
|
|
70
|
+
stdout.write(question)
|
|
71
|
+
const rl = createInterface({ input: stdin, output: stdout, terminal: true })
|
|
72
|
+
;(rl as unknown as { _writeToOutput: (one: string) => void })._writeToOutput = () => {}
|
|
73
|
+
const value = await new Promise<string>((done) => rl.question('', done))
|
|
74
|
+
rl.close()
|
|
75
|
+
stdout.write('\n')
|
|
76
|
+
return value
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
let sealed = 0
|
|
80
|
+
for (const env of ENVS) {
|
|
81
|
+
const value = await hidden(` ${id} for ${env} (paste it, or press enter to skip): `)
|
|
82
|
+
if (value.trim() === '') continue
|
|
83
|
+
seal(TREE.at, id, env, value)
|
|
84
|
+
sealed += 1
|
|
85
|
+
}
|
|
86
|
+
if (sealed === 0) return 0
|
|
87
|
+
|
|
88
|
+
const ignore = join(TREE.at, '.gitignore')
|
|
89
|
+
const already = existsSync(ignore) ? readFileSync(ignore, 'utf8') : ''
|
|
90
|
+
const missing = IGNORES.filter((one) => !already.split('\n').some((line) => line.trim() === one))
|
|
91
|
+
if (missing.length > 0) {
|
|
92
|
+
appendFileSync(ignore, `${already.endsWith('\n') || already === '' ? '' : '\n'}${missing.join('\n')}\n`)
|
|
93
|
+
stdout.write(`secret: added ${missing.join(', ')} to .gitignore — the key is never committed.\n`)
|
|
94
|
+
}
|
|
95
|
+
|
|
96
|
+
stdout.write(`secret: sealed ${id} for ${String(sealed)} environment(s) into ${SEALED_AT}.\n`)
|
|
97
|
+
return sealed
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
const listing =
|
|
101
|
+
secrets.length === 0
|
|
102
|
+
? 'no secrets yet.'
|
|
103
|
+
: [
|
|
104
|
+
`${String(secrets.length)} secret(s):`,
|
|
105
|
+
...secrets.flatMap((one) => [
|
|
106
|
+
` ${one.id} — ${secrecyOf(one.id) ?? 'wearing no prefix, which is refused'}, ${setIn(one.id)}`,
|
|
107
|
+
` ${one.sends === undefined ? 'no `sends`, so it is an address' : `sent as ${one.sends.header}: ${one.sends.prefix ?? ''}<value>`}`,
|
|
108
|
+
]),
|
|
109
|
+
].join('\n')
|
|
110
|
+
|
|
111
|
+
const words = wordsIn(['new', 'set'])
|
|
112
|
+
|
|
113
|
+
const verb = words[0]
|
|
114
|
+
|
|
115
|
+
stdout.write(`${listing}\n`)
|
|
116
|
+
if (keyOf(TREE.at) === undefined && secrets.length > 0) {
|
|
117
|
+
stdout.write(
|
|
118
|
+
`\nthere is no vault key at ${KEY_AT}, so nothing above can be opened here. It is never committed — ` +
|
|
119
|
+
'whoever set this app up has it.\n',
|
|
120
|
+
)
|
|
121
|
+
}
|
|
122
|
+
|
|
123
|
+
const method = (nonce: string): string =>
|
|
124
|
+
[
|
|
125
|
+
'',
|
|
126
|
+
'what this makes:',
|
|
127
|
+
'',
|
|
128
|
+
` thread create secret new <name> ${RECEIPT} ${nonce}`,
|
|
129
|
+
` thread create secret set <id> ${RECEIPT} ${nonce}`,
|
|
130
|
+
'',
|
|
131
|
+
...ELSEWHERE,
|
|
132
|
+
'',
|
|
133
|
+
` ${RECEIPT} is spent by one run and refused if ${at} moved after it was issued.`,
|
|
134
|
+
'',
|
|
135
|
+
'who may hold a value. The word is what you send; the prefix is what the id wears, and the prefix',
|
|
136
|
+
'IS who may hold it — no field says so:',
|
|
137
|
+
'',
|
|
138
|
+
...WORDS.flatMap((one) => [` ${one.padEnd(7)} → ${SECRECY[one]}<name>`, ` ${SAID[one]}`, '']),
|
|
139
|
+
'`new` asks, in this order:',
|
|
140
|
+
'',
|
|
141
|
+
...ASKED,
|
|
142
|
+
'',
|
|
143
|
+
'and then, at a terminal only, it asks for the value itself — echo off, sealed as you press enter.',
|
|
144
|
+
'A piped run declares the row and says so instead, because there is no terminal to read one from.',
|
|
145
|
+
'',
|
|
146
|
+
'`edit` asks the same three, and a blank answer keeps what is already there — except that naming',
|
|
147
|
+
'a DIFFERENT header drops the old prefix unless you give a new one, because a prefix belongs to',
|
|
148
|
+
'the scheme its header names. `-` as the header removes the header and makes this an address.',
|
|
149
|
+
'',
|
|
150
|
+
'It will not change who may hold a value. That is the prefix, the prefix is the id, and an id that',
|
|
151
|
+
'changes is a different secret — every target requiring the old name would go on requiring a name',
|
|
152
|
+
'nothing declares. Declare the other one and move what requires it.',
|
|
153
|
+
'',
|
|
154
|
+
'`set` is the same prompt on its own, for changing a value later or giving one a `new` run skipped.',
|
|
155
|
+
'It takes no answers and cannot be piped. The value goes from the keyboard into the sealed store and',
|
|
156
|
+
`nowhere else — not into a row, not into this output, not into ${SEALED_AT} in any readable form.`,
|
|
157
|
+
'',
|
|
158
|
+
'So an agent may declare a secret and may never give it one. That is the point of the split.',
|
|
159
|
+
].join('\n')
|
|
160
|
+
|
|
161
|
+
const flagged = argv.indexOf(RECEIPT)
|
|
162
|
+
|
|
163
|
+
if (verb === undefined && flagged === -1) {
|
|
164
|
+
const nonce = issue(TREE.at, listing)
|
|
165
|
+
stdout.write(`${method(nonce)}\n`)
|
|
166
|
+
stdout.write(`\nsecret: receipt ${nonce} — spent on use, and stale the moment ${at} changes.\n`)
|
|
167
|
+
exit(0)
|
|
168
|
+
}
|
|
169
|
+
|
|
170
|
+
spend(TREE.at, listing, argv[flagged + 1], at)
|
|
171
|
+
|
|
172
|
+
if (verb !== 'new' && verb !== 'set' && verb !== 'edit') {
|
|
173
|
+
refuse(`there is nothing called "${verb ?? ''}" to do here — say new, edit or set`)
|
|
174
|
+
}
|
|
175
|
+
|
|
176
|
+
const text = readFileSync(path, 'utf8')
|
|
177
|
+
const where = listBounds(text, LIST)
|
|
178
|
+
if (where === undefined) refuse(`${at} states no \`${LIST}\` list to declare a secret in`)
|
|
179
|
+
|
|
180
|
+
if (verb === 'set') {
|
|
181
|
+
const id = words[1] ?? refuse('`set` takes the id of a secret this app already declares')
|
|
182
|
+
const one = secrets.find((each) => each.id === id)
|
|
183
|
+
if (one === undefined) {
|
|
184
|
+
refuse(
|
|
185
|
+
`\`${id}\` is not a secret this app declares. Declare it first with \`thread create secret new\` — a value is ` +
|
|
186
|
+
'given to something that said it needed it, or not at all.',
|
|
187
|
+
)
|
|
188
|
+
}
|
|
189
|
+
if (stdin.isTTY !== true) {
|
|
190
|
+
refuse(
|
|
191
|
+
'a value is typed by a person at a terminal, and this run is piped. Nothing was written. Run ' +
|
|
192
|
+
`\`thread create secret set ${id}\` yourself — an agent may declare a secret and may never give it one.`,
|
|
193
|
+
)
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
if ((await sealing(id)) === 0) {
|
|
197
|
+
stdout.write('secret: nothing typed, so nothing was sealed.\n')
|
|
198
|
+
exit(0)
|
|
199
|
+
}
|
|
200
|
+
|
|
201
|
+
const kept = spawnSync('npx', ['thread', 'vault'], { cwd: TREE.at, encoding: 'utf8' })
|
|
202
|
+
stdout.write(`${`${kept.stdout ?? ''}${kept.stderr ?? ''}`.trim()}\n`)
|
|
203
|
+
exit(kept.status === 0 ? 0 : 1)
|
|
204
|
+
}
|
|
205
|
+
|
|
206
|
+
const piped = given()
|
|
207
|
+
only(piped, TAKES, 'a secret')
|
|
208
|
+
|
|
209
|
+
const { ask, line, shut } = asking(piped)
|
|
210
|
+
|
|
211
|
+
const own = ownAnswers(piped)
|
|
212
|
+
|
|
213
|
+
const name = slug(words[1] ?? (await ask('name', 'name: ')))
|
|
214
|
+
if (name === '') {
|
|
215
|
+
shut()
|
|
216
|
+
refuse('a secret has to be called something')
|
|
217
|
+
}
|
|
218
|
+
if (!NAME.test(name)) {
|
|
219
|
+
shut()
|
|
220
|
+
refuse(`"${name}" cannot be the name of a secret — it is written in lower_snake_case`)
|
|
221
|
+
}
|
|
222
|
+
|
|
223
|
+
const holder = (piped.length > 0 ? needed(own, 'holder', 'a secret') : await ask('holder', `who may hold it — ${WORDS.join(', ')}: `)).trim()
|
|
224
|
+
if (!WORDS.includes(holder as keyof typeof SECRECY)) {
|
|
225
|
+
shut()
|
|
226
|
+
refuse(
|
|
227
|
+
`there is no such holder as "${holder}". A secret is held by a ${WORDS.join(' or a ')}, and that prefix is the ` +
|
|
228
|
+
'only thing standing between a server credential and a browser bundle.',
|
|
229
|
+
)
|
|
230
|
+
}
|
|
231
|
+
const word = holder as keyof typeof SECRECY
|
|
232
|
+
const id = `${SECRECY[word]}${name}`
|
|
233
|
+
|
|
234
|
+
if (secrets.some((one) => one.id === id) || holdsRow(text, where, id)) {
|
|
235
|
+
shut()
|
|
236
|
+
refuse(`\`${id}\` is already declared in ${at}`)
|
|
237
|
+
}
|
|
238
|
+
|
|
239
|
+
const describes = (piped.length > 0 ? needed(own, 'describes', 'a secret') : await ask('describes', 'what is it for? ')).trim()
|
|
240
|
+
if (describes === '') {
|
|
241
|
+
shut()
|
|
242
|
+
refuse('a secret says what it is for — the sentence is what somebody reads before requiring it')
|
|
243
|
+
}
|
|
244
|
+
|
|
245
|
+
const header = (piped.length > 0 ? (answer(own, 'header') ?? '') : await ask('header', 'header (blank if this value IS an address): ')).trim()
|
|
246
|
+
const typed = header === '' ? '' : (piped.length > 0 ? (answer(own, 'prefix') ?? '') : await ask('prefix', 'prefix in that header, e.g. "Bearer ": '))
|
|
247
|
+
|
|
248
|
+
const { stated, kids } = await stating(asksOf('secret', 'Secret', {}, ['id', 'at', 'describes', 'version', 'sends']), piped, piped.length > 0, ask, line, [])
|
|
249
|
+
|
|
250
|
+
shut()
|
|
251
|
+
|
|
252
|
+
const prefix = typed === '' || typed.endsWith(' ') ? typed : `${typed} `
|
|
253
|
+
|
|
254
|
+
const row = [
|
|
255
|
+
' {',
|
|
256
|
+
` id: ${quoted(id)},`,
|
|
257
|
+
` at: '_threads/${FILE}',`,
|
|
258
|
+
` describes: ${quoted(describes)},`,
|
|
259
|
+
' version: 1,',
|
|
260
|
+
...(header === ''
|
|
261
|
+
? []
|
|
262
|
+
: [` sends: { header: ${quoted(header.toLowerCase())}${prefix === '' ? '' : `, prefix: ${quoted(prefix)}`} },`]),
|
|
263
|
+
...rowLines(' ', stated, kids),
|
|
264
|
+
' },',
|
|
265
|
+
].join('\n')
|
|
266
|
+
|
|
267
|
+
writeFileSync(path, withRow(text, where, row), 'utf8')
|
|
268
|
+
|
|
269
|
+
for (const told of seeding(kids, TREE.at)) stdout.write(`secret: ${told}\n`)
|
|
270
|
+
|
|
271
|
+
stdout.write(
|
|
272
|
+
`secret: declared ${id} in ${at}${header === '' ? ' — it has no `sends`, so it is the address of whatever requires it' : ''}.\n`,
|
|
273
|
+
)
|
|
274
|
+
|
|
275
|
+
if (stdin.isTTY === true) {
|
|
276
|
+
stdout.write(`\n${SAID[word]}\n\nThe value is not echoed and is sealed straight into ${SEALED_AT}.\n`)
|
|
277
|
+
if ((await sealing(id)) === 0) {
|
|
278
|
+
stdout.write(`secret: nothing typed, so ${id} has no value yet. Run \`thread create secret set ${id}\` when you have it.\n`)
|
|
279
|
+
}
|
|
280
|
+
} else {
|
|
281
|
+
stdout.write(
|
|
282
|
+
`secret: it has no value anywhere yet, and this run is piped — a value is typed by a person at a terminal. ` +
|
|
283
|
+
`Run \`thread create secret set ${id}\` yourself.\n`,
|
|
284
|
+
)
|
|
285
|
+
}
|
|
286
|
+
|
|
287
|
+
const kept = spawnSync('npx', ['thread', 'vault'], { cwd: TREE.at, encoding: 'utf8' })
|
|
288
|
+
stdout.write(`${`${kept.stdout ?? ''}${kept.stderr ?? ''}`.trim()}\n`)
|
|
289
|
+
exit(kept.status === 0 ? 0 : 1)
|