typeclaw 0.1.2 → 0.1.3
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/README.md +4 -0
- package/auth.schema.json +238 -7
- package/package.json +1 -1
- package/secrets.schema.json +238 -7
- package/src/agent/auth.ts +19 -38
- package/src/agent/tools/channel-fetch-attachment.ts +6 -0
- package/src/agent/tools/channel-history.ts +10 -1
- package/src/agent/tools/channel-log.ts +32 -0
- package/src/agent/tools/channel-reply.ts +18 -1
- package/src/agent/tools/channel-send.ts +13 -1
- package/src/bundled-plugins/tool-result-cap/README.md +67 -0
- package/src/bundled-plugins/tool-result-cap/cap-result.ts +56 -0
- package/src/bundled-plugins/tool-result-cap/index.ts +51 -0
- package/src/channels/adapters/kakaotalk.ts +25 -16
- package/src/channels/manager.ts +47 -38
- package/src/cli/channel.ts +3 -3
- package/src/cli/index.ts +3 -0
- package/src/cli/init.ts +2 -1
- package/src/cli/ui.ts +11 -0
- package/src/config/config.ts +15 -4
- package/src/container/start.ts +90 -1
- package/src/hostd/daemon.ts +28 -3
- package/src/hostd/protocol.ts +7 -0
- package/src/init/auto-upgrade.ts +368 -0
- package/src/init/dockerfile.ts +25 -14
- package/src/init/index.ts +123 -77
- package/src/init/kakaotalk-auth.ts +9 -3
- package/src/init/run-bun-install.ts +34 -0
- package/src/run/bundled-plugins.ts +7 -0
- package/src/run/index.ts +9 -0
- package/src/secrets/defaults.ts +67 -0
- package/src/secrets/hydrate.ts +99 -0
- package/src/secrets/index.ts +6 -12
- package/src/secrets/kakao-store.ts +129 -0
- package/src/secrets/migrate-kakaotalk.ts +82 -0
- package/src/secrets/migrate.ts +5 -4
- package/src/secrets/resolve.ts +57 -0
- package/src/secrets/schema.ts +162 -42
- package/src/secrets/storage.ts +253 -47
- package/src/skills/typeclaw-config/SKILL.md +47 -8
- package/typeclaw.schema.json +36 -2
- package/src/secrets/env.ts +0 -43
package/README.md
CHANGED
|
@@ -110,6 +110,10 @@ my-agent/
|
|
|
110
110
|
|
|
111
111
|
`Dockerfile` and `.gitignore` are owned by TypeClaw and rewritten on every `start` — edit `src/init/dockerfile.ts` and re-run `start --build` to ship template changes.
|
|
112
112
|
|
|
113
|
+
### Secrets
|
|
114
|
+
|
|
115
|
+
Credentials live in two gitignored files: `.env` (plain `KEY=value` lines, injected into the container via `--env-file`) and `secrets.json` (a structured store managed by TypeClaw). **Env-wins**: when a credential's canonical env var (e.g. `FIREWORKS_API_KEY`, `SLACK_BOT_TOKEN`) is set, that value is used at runtime — `secrets.json` is never auto-mutated to capture it. Every secret-bearing field in `secrets.json` is a `Secret` (`string | { value?, env? }`), so the file can rebind a credential to a custom env-var name on demand. See [AGENTS.md § Secrets](./AGENTS.md#secrets) for the full contract.
|
|
116
|
+
|
|
113
117
|
## Development
|
|
114
118
|
|
|
115
119
|
```sh
|
package/auth.schema.json
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
},
|
|
8
8
|
"version": {
|
|
9
9
|
"type": "number",
|
|
10
|
-
"const":
|
|
10
|
+
"const": 2
|
|
11
11
|
},
|
|
12
|
-
"
|
|
13
|
-
"default": {},
|
|
12
|
+
"providers": {
|
|
14
13
|
"type": "object",
|
|
15
14
|
"propertyNames": {
|
|
16
15
|
"type": "string"
|
|
@@ -25,8 +24,25 @@
|
|
|
25
24
|
"const": "api_key"
|
|
26
25
|
},
|
|
27
26
|
"key": {
|
|
28
|
-
"
|
|
29
|
-
|
|
27
|
+
"anyOf": [
|
|
28
|
+
{
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"value": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"minLength": 1
|
|
38
|
+
},
|
|
39
|
+
"env": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
30
46
|
}
|
|
31
47
|
},
|
|
32
48
|
"required": [
|
|
@@ -51,9 +67,224 @@
|
|
|
51
67
|
}
|
|
52
68
|
},
|
|
53
69
|
"channels": {
|
|
54
|
-
"default": {},
|
|
55
70
|
"type": "object",
|
|
56
|
-
"properties": {
|
|
71
|
+
"properties": {
|
|
72
|
+
"slack-bot": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"botToken": {
|
|
76
|
+
"anyOf": [
|
|
77
|
+
{
|
|
78
|
+
"type": "string",
|
|
79
|
+
"minLength": 1
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"value": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"minLength": 1
|
|
87
|
+
},
|
|
88
|
+
"env": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"minLength": 1
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"appToken": {
|
|
97
|
+
"anyOf": [
|
|
98
|
+
{
|
|
99
|
+
"type": "string",
|
|
100
|
+
"minLength": 1
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"type": "object",
|
|
104
|
+
"properties": {
|
|
105
|
+
"value": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"minLength": 1
|
|
108
|
+
},
|
|
109
|
+
"env": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"minLength": 1
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"discord-bot": {
|
|
120
|
+
"type": "object",
|
|
121
|
+
"properties": {
|
|
122
|
+
"token": {
|
|
123
|
+
"anyOf": [
|
|
124
|
+
{
|
|
125
|
+
"type": "string",
|
|
126
|
+
"minLength": 1
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"type": "object",
|
|
130
|
+
"properties": {
|
|
131
|
+
"value": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"minLength": 1
|
|
134
|
+
},
|
|
135
|
+
"env": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"minLength": 1
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"telegram-bot": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"token": {
|
|
149
|
+
"anyOf": [
|
|
150
|
+
{
|
|
151
|
+
"type": "string",
|
|
152
|
+
"minLength": 1
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"type": "object",
|
|
156
|
+
"properties": {
|
|
157
|
+
"value": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"minLength": 1
|
|
160
|
+
},
|
|
161
|
+
"env": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"minLength": 1
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"kakaotalk": {
|
|
172
|
+
"type": "object",
|
|
173
|
+
"properties": {
|
|
174
|
+
"currentAccount": {
|
|
175
|
+
"anyOf": [
|
|
176
|
+
{
|
|
177
|
+
"type": "string"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"type": "null"
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
"accounts": {
|
|
185
|
+
"type": "object",
|
|
186
|
+
"propertyNames": {
|
|
187
|
+
"type": "string"
|
|
188
|
+
},
|
|
189
|
+
"additionalProperties": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"properties": {
|
|
192
|
+
"account_id": {
|
|
193
|
+
"type": "string"
|
|
194
|
+
},
|
|
195
|
+
"oauth_token": {
|
|
196
|
+
"type": "string"
|
|
197
|
+
},
|
|
198
|
+
"user_id": {
|
|
199
|
+
"type": "string"
|
|
200
|
+
},
|
|
201
|
+
"refresh_token": {
|
|
202
|
+
"type": "string"
|
|
203
|
+
},
|
|
204
|
+
"device_uuid": {
|
|
205
|
+
"type": "string"
|
|
206
|
+
},
|
|
207
|
+
"device_type": {
|
|
208
|
+
"anyOf": [
|
|
209
|
+
{
|
|
210
|
+
"type": "string",
|
|
211
|
+
"const": "pc"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"type": "string",
|
|
215
|
+
"const": "tablet"
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
"auth_method": {
|
|
220
|
+
"anyOf": [
|
|
221
|
+
{
|
|
222
|
+
"type": "string",
|
|
223
|
+
"const": "login"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"type": "string",
|
|
227
|
+
"const": "extract"
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
"created_at": {
|
|
232
|
+
"type": "string"
|
|
233
|
+
},
|
|
234
|
+
"updated_at": {
|
|
235
|
+
"type": "string"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"required": [
|
|
239
|
+
"account_id",
|
|
240
|
+
"oauth_token",
|
|
241
|
+
"user_id",
|
|
242
|
+
"device_uuid",
|
|
243
|
+
"device_type",
|
|
244
|
+
"created_at",
|
|
245
|
+
"updated_at"
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
"pendingLogin": {
|
|
250
|
+
"type": "object",
|
|
251
|
+
"properties": {
|
|
252
|
+
"device_uuid": {
|
|
253
|
+
"type": "string"
|
|
254
|
+
},
|
|
255
|
+
"device_type": {
|
|
256
|
+
"anyOf": [
|
|
257
|
+
{
|
|
258
|
+
"type": "string",
|
|
259
|
+
"const": "pc"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"type": "string",
|
|
263
|
+
"const": "tablet"
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
"email": {
|
|
268
|
+
"type": "string"
|
|
269
|
+
},
|
|
270
|
+
"created_at": {
|
|
271
|
+
"type": "string"
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
"required": [
|
|
275
|
+
"device_uuid",
|
|
276
|
+
"device_type",
|
|
277
|
+
"email",
|
|
278
|
+
"created_at"
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"required": [
|
|
283
|
+
"currentAccount",
|
|
284
|
+
"accounts"
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
},
|
|
57
288
|
"additionalProperties": {}
|
|
58
289
|
}
|
|
59
290
|
},
|
package/package.json
CHANGED
package/secrets.schema.json
CHANGED
|
@@ -7,10 +7,9 @@
|
|
|
7
7
|
},
|
|
8
8
|
"version": {
|
|
9
9
|
"type": "number",
|
|
10
|
-
"const":
|
|
10
|
+
"const": 2
|
|
11
11
|
},
|
|
12
|
-
"
|
|
13
|
-
"default": {},
|
|
12
|
+
"providers": {
|
|
14
13
|
"type": "object",
|
|
15
14
|
"propertyNames": {
|
|
16
15
|
"type": "string"
|
|
@@ -25,8 +24,25 @@
|
|
|
25
24
|
"const": "api_key"
|
|
26
25
|
},
|
|
27
26
|
"key": {
|
|
28
|
-
"
|
|
29
|
-
|
|
27
|
+
"anyOf": [
|
|
28
|
+
{
|
|
29
|
+
"type": "string",
|
|
30
|
+
"minLength": 1
|
|
31
|
+
},
|
|
32
|
+
{
|
|
33
|
+
"type": "object",
|
|
34
|
+
"properties": {
|
|
35
|
+
"value": {
|
|
36
|
+
"type": "string",
|
|
37
|
+
"minLength": 1
|
|
38
|
+
},
|
|
39
|
+
"env": {
|
|
40
|
+
"type": "string",
|
|
41
|
+
"minLength": 1
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
]
|
|
30
46
|
}
|
|
31
47
|
},
|
|
32
48
|
"required": [
|
|
@@ -51,9 +67,224 @@
|
|
|
51
67
|
}
|
|
52
68
|
},
|
|
53
69
|
"channels": {
|
|
54
|
-
"default": {},
|
|
55
70
|
"type": "object",
|
|
56
|
-
"properties": {
|
|
71
|
+
"properties": {
|
|
72
|
+
"slack-bot": {
|
|
73
|
+
"type": "object",
|
|
74
|
+
"properties": {
|
|
75
|
+
"botToken": {
|
|
76
|
+
"anyOf": [
|
|
77
|
+
{
|
|
78
|
+
"type": "string",
|
|
79
|
+
"minLength": 1
|
|
80
|
+
},
|
|
81
|
+
{
|
|
82
|
+
"type": "object",
|
|
83
|
+
"properties": {
|
|
84
|
+
"value": {
|
|
85
|
+
"type": "string",
|
|
86
|
+
"minLength": 1
|
|
87
|
+
},
|
|
88
|
+
"env": {
|
|
89
|
+
"type": "string",
|
|
90
|
+
"minLength": 1
|
|
91
|
+
}
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
]
|
|
95
|
+
},
|
|
96
|
+
"appToken": {
|
|
97
|
+
"anyOf": [
|
|
98
|
+
{
|
|
99
|
+
"type": "string",
|
|
100
|
+
"minLength": 1
|
|
101
|
+
},
|
|
102
|
+
{
|
|
103
|
+
"type": "object",
|
|
104
|
+
"properties": {
|
|
105
|
+
"value": {
|
|
106
|
+
"type": "string",
|
|
107
|
+
"minLength": 1
|
|
108
|
+
},
|
|
109
|
+
"env": {
|
|
110
|
+
"type": "string",
|
|
111
|
+
"minLength": 1
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
}
|
|
115
|
+
]
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
},
|
|
119
|
+
"discord-bot": {
|
|
120
|
+
"type": "object",
|
|
121
|
+
"properties": {
|
|
122
|
+
"token": {
|
|
123
|
+
"anyOf": [
|
|
124
|
+
{
|
|
125
|
+
"type": "string",
|
|
126
|
+
"minLength": 1
|
|
127
|
+
},
|
|
128
|
+
{
|
|
129
|
+
"type": "object",
|
|
130
|
+
"properties": {
|
|
131
|
+
"value": {
|
|
132
|
+
"type": "string",
|
|
133
|
+
"minLength": 1
|
|
134
|
+
},
|
|
135
|
+
"env": {
|
|
136
|
+
"type": "string",
|
|
137
|
+
"minLength": 1
|
|
138
|
+
}
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
]
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
},
|
|
145
|
+
"telegram-bot": {
|
|
146
|
+
"type": "object",
|
|
147
|
+
"properties": {
|
|
148
|
+
"token": {
|
|
149
|
+
"anyOf": [
|
|
150
|
+
{
|
|
151
|
+
"type": "string",
|
|
152
|
+
"minLength": 1
|
|
153
|
+
},
|
|
154
|
+
{
|
|
155
|
+
"type": "object",
|
|
156
|
+
"properties": {
|
|
157
|
+
"value": {
|
|
158
|
+
"type": "string",
|
|
159
|
+
"minLength": 1
|
|
160
|
+
},
|
|
161
|
+
"env": {
|
|
162
|
+
"type": "string",
|
|
163
|
+
"minLength": 1
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
]
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
},
|
|
171
|
+
"kakaotalk": {
|
|
172
|
+
"type": "object",
|
|
173
|
+
"properties": {
|
|
174
|
+
"currentAccount": {
|
|
175
|
+
"anyOf": [
|
|
176
|
+
{
|
|
177
|
+
"type": "string"
|
|
178
|
+
},
|
|
179
|
+
{
|
|
180
|
+
"type": "null"
|
|
181
|
+
}
|
|
182
|
+
]
|
|
183
|
+
},
|
|
184
|
+
"accounts": {
|
|
185
|
+
"type": "object",
|
|
186
|
+
"propertyNames": {
|
|
187
|
+
"type": "string"
|
|
188
|
+
},
|
|
189
|
+
"additionalProperties": {
|
|
190
|
+
"type": "object",
|
|
191
|
+
"properties": {
|
|
192
|
+
"account_id": {
|
|
193
|
+
"type": "string"
|
|
194
|
+
},
|
|
195
|
+
"oauth_token": {
|
|
196
|
+
"type": "string"
|
|
197
|
+
},
|
|
198
|
+
"user_id": {
|
|
199
|
+
"type": "string"
|
|
200
|
+
},
|
|
201
|
+
"refresh_token": {
|
|
202
|
+
"type": "string"
|
|
203
|
+
},
|
|
204
|
+
"device_uuid": {
|
|
205
|
+
"type": "string"
|
|
206
|
+
},
|
|
207
|
+
"device_type": {
|
|
208
|
+
"anyOf": [
|
|
209
|
+
{
|
|
210
|
+
"type": "string",
|
|
211
|
+
"const": "pc"
|
|
212
|
+
},
|
|
213
|
+
{
|
|
214
|
+
"type": "string",
|
|
215
|
+
"const": "tablet"
|
|
216
|
+
}
|
|
217
|
+
]
|
|
218
|
+
},
|
|
219
|
+
"auth_method": {
|
|
220
|
+
"anyOf": [
|
|
221
|
+
{
|
|
222
|
+
"type": "string",
|
|
223
|
+
"const": "login"
|
|
224
|
+
},
|
|
225
|
+
{
|
|
226
|
+
"type": "string",
|
|
227
|
+
"const": "extract"
|
|
228
|
+
}
|
|
229
|
+
]
|
|
230
|
+
},
|
|
231
|
+
"created_at": {
|
|
232
|
+
"type": "string"
|
|
233
|
+
},
|
|
234
|
+
"updated_at": {
|
|
235
|
+
"type": "string"
|
|
236
|
+
}
|
|
237
|
+
},
|
|
238
|
+
"required": [
|
|
239
|
+
"account_id",
|
|
240
|
+
"oauth_token",
|
|
241
|
+
"user_id",
|
|
242
|
+
"device_uuid",
|
|
243
|
+
"device_type",
|
|
244
|
+
"created_at",
|
|
245
|
+
"updated_at"
|
|
246
|
+
]
|
|
247
|
+
}
|
|
248
|
+
},
|
|
249
|
+
"pendingLogin": {
|
|
250
|
+
"type": "object",
|
|
251
|
+
"properties": {
|
|
252
|
+
"device_uuid": {
|
|
253
|
+
"type": "string"
|
|
254
|
+
},
|
|
255
|
+
"device_type": {
|
|
256
|
+
"anyOf": [
|
|
257
|
+
{
|
|
258
|
+
"type": "string",
|
|
259
|
+
"const": "pc"
|
|
260
|
+
},
|
|
261
|
+
{
|
|
262
|
+
"type": "string",
|
|
263
|
+
"const": "tablet"
|
|
264
|
+
}
|
|
265
|
+
]
|
|
266
|
+
},
|
|
267
|
+
"email": {
|
|
268
|
+
"type": "string"
|
|
269
|
+
},
|
|
270
|
+
"created_at": {
|
|
271
|
+
"type": "string"
|
|
272
|
+
}
|
|
273
|
+
},
|
|
274
|
+
"required": [
|
|
275
|
+
"device_uuid",
|
|
276
|
+
"device_type",
|
|
277
|
+
"email",
|
|
278
|
+
"created_at"
|
|
279
|
+
]
|
|
280
|
+
}
|
|
281
|
+
},
|
|
282
|
+
"required": [
|
|
283
|
+
"currentAccount",
|
|
284
|
+
"accounts"
|
|
285
|
+
]
|
|
286
|
+
}
|
|
287
|
+
},
|
|
57
288
|
"additionalProperties": {}
|
|
58
289
|
}
|
|
59
290
|
},
|
package/src/agent/auth.ts
CHANGED
|
@@ -10,7 +10,7 @@ import {
|
|
|
10
10
|
supportsOAuth,
|
|
11
11
|
type KnownProviderId,
|
|
12
12
|
} from '@/config/providers'
|
|
13
|
-
import { createSecretsStoreForAgent
|
|
13
|
+
import { createSecretsStoreForAgent } from '@/secrets'
|
|
14
14
|
|
|
15
15
|
type Auth = {
|
|
16
16
|
authStorage: AuthStorage
|
|
@@ -19,10 +19,6 @@ type Auth = {
|
|
|
19
19
|
|
|
20
20
|
const TEST_DUMMY_API_KEY = 'test_dummy_key'
|
|
21
21
|
|
|
22
|
-
// In container stage, /agent is the bind-mounted agent folder; in host stage
|
|
23
|
-
// (only used by `typeclaw init` itself), it falls back to process.cwd(). The
|
|
24
|
-
// host writes secrets.json at init time and the container reads + refreshes
|
|
25
|
-
// it at runtime — both paths point at the same file on the host filesystem.
|
|
26
22
|
function secretsJsonPath(): string {
|
|
27
23
|
return join(process.cwd(), 'secrets.json')
|
|
28
24
|
}
|
|
@@ -35,10 +31,6 @@ export function getAuth(): Auth {
|
|
|
35
31
|
const providerId = providerForModelRef(getConfig().model)
|
|
36
32
|
const provider = KNOWN_PROVIDERS[providerId]
|
|
37
33
|
|
|
38
|
-
// Bun sets NODE_ENV=test automatically under `bun test`. The dummy path
|
|
39
|
-
// bypasses both secrets.json and process.env so suites that build sessions
|
|
40
|
-
// but never hit the LLM don't need real credentials; production still
|
|
41
|
-
// hard-exits to surface misconfiguration.
|
|
42
34
|
if (process.env.NODE_ENV === 'test' && !hasAnyCredentialInEnv(provider.apiKeyEnv)) {
|
|
43
35
|
const authStorage = AuthStorage.inMemory()
|
|
44
36
|
if (supportsApiKey(provider)) {
|
|
@@ -51,42 +43,31 @@ export function getAuth(): Auth {
|
|
|
51
43
|
|
|
52
44
|
const authStorage = createSecretsStoreForAgent(secretsJsonPath())
|
|
53
45
|
|
|
54
|
-
//
|
|
55
|
-
//
|
|
56
|
-
//
|
|
57
|
-
//
|
|
58
|
-
// unless a credential is materialized into AuthStorage's data map. Before
|
|
59
|
-
// this migration the code used `setRuntimeApiKey`, which papered over the
|
|
60
|
-
// gap in-memory but never wrote secrets.json — leaving `llm` empty for every
|
|
61
|
-
// downstream consumer (rotation, audit, transport over the daemon
|
|
62
|
-
// boundary) that treats the file as authoritative.
|
|
46
|
+
// Env-wins for api-key providers: when the canonical env var is set, layer
|
|
47
|
+
// that value in via setRuntimeApiKey so AuthStorage's hasAuth resolves
|
|
48
|
+
// true without persisting anything to secrets.json. This is the explicit
|
|
49
|
+
// reversal of the pre-v2 auto-migrate-to-file behaviour.
|
|
63
50
|
//
|
|
64
|
-
//
|
|
65
|
-
//
|
|
66
|
-
//
|
|
67
|
-
//
|
|
68
|
-
// key
|
|
51
|
+
// setRuntimeApiKey is in-memory only (it writes to runtimeOverrides, never
|
|
52
|
+
// through withLock), so the file remains untouched even when the env var
|
|
53
|
+
// is set. OAuth credentials in the file still take precedence on read
|
|
54
|
+
// because AuthStorage's hasAuth checks runtimeOverrides first only for
|
|
55
|
+
// api-key-shaped credentials — OAuth on disk wins on its own.
|
|
56
|
+
//
|
|
57
|
+
// The previous code branch that wrote the env value into secrets.json and
|
|
58
|
+
// stripped the matching `.env` line has been removed. Env values stay in
|
|
59
|
+
// env; the file stays user-owned. See src/secrets/hydrate.ts for the same
|
|
60
|
+
// policy on the channels side.
|
|
69
61
|
if (supportsApiKey(provider) && provider.apiKeyEnv) {
|
|
70
62
|
const envKey = process.env[provider.apiKeyEnv]
|
|
71
|
-
if (envKey) {
|
|
63
|
+
if (envKey !== undefined && envKey !== '') {
|
|
72
64
|
const existing = authStorage.get(provider.id)
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
if (existing === undefined || existing.key !== envKey) {
|
|
76
|
-
authStorage.set(provider.id, { type: 'api_key', key: envKey })
|
|
77
|
-
}
|
|
78
|
-
// secrets.json is now authoritative for this provider's api-key credential.
|
|
79
|
-
// Strip the value from `.env` so the next boot does not silently revive a
|
|
80
|
-
// stale or rotated-away key, and so users have a single place to edit.
|
|
81
|
-
stripEnvKey(join(process.cwd(), '.env'), provider.apiKeyEnv)
|
|
65
|
+
if (existing === undefined || existing.type === 'api_key') {
|
|
66
|
+
authStorage.setRuntimeApiKey(provider.id, envKey)
|
|
82
67
|
}
|
|
83
68
|
}
|
|
84
69
|
}
|
|
85
70
|
|
|
86
|
-
// OAuth providers persist via `oauth-login.ts` at init time; api-key
|
|
87
|
-
// providers persist via the migration block above. By this point
|
|
88
|
-
// secrets.json is authoritative — a missing entry means the user skipped
|
|
89
|
-
// login at init, deleted the file, or never set the provider's env var.
|
|
90
71
|
if (!authStorage.hasAuth(provider.id)) {
|
|
91
72
|
console.error(missingCredentialMessage(providerId))
|
|
92
73
|
process.exit(1)
|
|
@@ -119,7 +100,7 @@ function missingCredentialMessage(providerId: KnownProviderId): string {
|
|
|
119
100
|
return `No credentials for ${provider.name}. Run \`typeclaw init\` and pick "OAuth" to log in to ${modelName}.`
|
|
120
101
|
}
|
|
121
102
|
if (apiKeyOnly && provider.apiKeyEnv) {
|
|
122
|
-
return `Set ${provider.apiKeyEnv} in .env to use ${modelName} via ${provider.name}.`
|
|
103
|
+
return `Set ${provider.apiKeyEnv} in .env (or secrets.json#providers.${provider.id}.key.value) to use ${modelName} via ${provider.name}.`
|
|
123
104
|
}
|
|
124
105
|
return `No credentials for ${provider.name}. Either set ${provider.apiKeyEnv ?? '<api-key-env>'} in .env or run \`typeclaw init\` and pick "OAuth".`
|
|
125
106
|
}
|