typeclaw 0.1.1 → 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 +16 -12
- 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/doctor.ts +173 -0
- package/src/agent/subagents.ts +24 -2
- 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/backup/README.md +81 -0
- package/src/bundled-plugins/backup/index.ts +209 -0
- package/src/bundled-plugins/backup/runner.ts +231 -0
- package/src/bundled-plugins/backup/subagents.ts +200 -0
- package/src/bundled-plugins/memory/index.ts +42 -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/channels/router.ts +29 -0
- package/src/cli/channel.ts +3 -3
- package/src/cli/compose.ts +92 -1
- package/src/cli/doctor.ts +100 -0
- package/src/cli/index.ts +4 -0
- package/src/cli/init.ts +2 -1
- package/src/cli/ui.ts +11 -0
- package/src/compose/doctor.ts +141 -0
- package/src/compose/index.ts +8 -0
- package/src/compose/logs.ts +32 -19
- package/src/config/config.ts +31 -0
- package/src/container/log-colors.ts +75 -0
- package/src/container/log-timestamps.ts +84 -0
- package/src/container/logs.ts +71 -5
- package/src/container/start.ts +113 -9
- package/src/cron/consumer.ts +29 -7
- package/src/doctor/checks.ts +426 -0
- package/src/doctor/commit.ts +71 -0
- package/src/doctor/index.ts +287 -0
- package/src/doctor/plugin-bridge.ts +147 -0
- package/src/doctor/report.ts +142 -0
- package/src/doctor/types.ts +87 -0
- 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/cli-version.ts +81 -0
- package/src/init/dockerfile.ts +234 -25
- package/src/init/index.ts +141 -87
- package/src/init/kakaotalk-auth.ts +9 -3
- package/src/init/run-bun-install.ts +34 -0
- package/src/plugin/hooks.ts +32 -0
- package/src/plugin/index.ts +7 -0
- package/src/plugin/manager.ts +2 -0
- package/src/plugin/registry.ts +32 -3
- package/src/plugin/types.ts +65 -0
- package/src/run/bundled-plugins.ts +15 -0
- package/src/run/index.ts +19 -5
- 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/server/index.ts +103 -5
- package/src/shared/index.ts +3 -0
- package/src/shared/protocol.ts +22 -0
- package/src/skills/typeclaw-config/SKILL.md +48 -9
- package/typeclaw.schema.json +84 -0
- package/src/secrets/env.ts +0 -43
package/src/server/index.ts
CHANGED
|
@@ -6,6 +6,7 @@ import {
|
|
|
6
6
|
type CreateSessionOptions,
|
|
7
7
|
type CreateSessionResult,
|
|
8
8
|
} from '@/agent'
|
|
9
|
+
import { runPluginDoctorChecks, runPluginDoctorFix } from '@/agent/doctor'
|
|
9
10
|
import type { SessionOrigin } from '@/agent/session-origin'
|
|
10
11
|
import type { ChannelRouter } from '@/channels/router'
|
|
11
12
|
import type { HookBus } from '@/plugin'
|
|
@@ -159,7 +160,7 @@ export function createServer({
|
|
|
159
160
|
|
|
160
161
|
if (stream) {
|
|
161
162
|
state.unsubPrompts = stream.subscribe({ target: { kind: 'session', sessionId: sessionFileId } }, (msg) =>
|
|
162
|
-
enqueuePrompt(ws, state, msg),
|
|
163
|
+
enqueuePrompt(ws, state, msg, agentDir),
|
|
163
164
|
)
|
|
164
165
|
|
|
165
166
|
state.unsubBroadcast = stream.subscribe({ target: { kind: 'broadcast' } }, (msg) => {
|
|
@@ -190,6 +191,16 @@ export function createServer({
|
|
|
190
191
|
return
|
|
191
192
|
}
|
|
192
193
|
|
|
194
|
+
if (msg.type === 'doctor') {
|
|
195
|
+
await handleDoctor(ws, msg.requestId, pluginRuntime, agentDir)
|
|
196
|
+
return
|
|
197
|
+
}
|
|
198
|
+
|
|
199
|
+
if (msg.type === 'doctor_fix') {
|
|
200
|
+
await handleDoctorFix(ws, msg.requestId, msg.checkId, pluginRuntime, agentDir)
|
|
201
|
+
return
|
|
202
|
+
}
|
|
203
|
+
|
|
193
204
|
if (msg.type === 'abort') {
|
|
194
205
|
if (!state) return
|
|
195
206
|
await state.session.abort()
|
|
@@ -215,13 +226,27 @@ export function createServer({
|
|
|
215
226
|
return
|
|
216
227
|
}
|
|
217
228
|
send(ws, { type: 'prompt_started', messageId: `local-${crypto.randomUUID()}`, text: msg.text })
|
|
229
|
+
const fallbackHooks = state.runtimeSnapshot?.hooks
|
|
230
|
+
if (fallbackHooks !== undefined && agentDir !== undefined) {
|
|
231
|
+
await fallbackHooks.runSessionTurnStart({
|
|
232
|
+
sessionId: state.sessionFileId,
|
|
233
|
+
agentDir,
|
|
234
|
+
origin: state.origin,
|
|
235
|
+
})
|
|
236
|
+
}
|
|
218
237
|
try {
|
|
219
238
|
await state.session.prompt(msg.text)
|
|
220
239
|
send(ws, { type: 'done' })
|
|
221
240
|
} catch (err) {
|
|
222
241
|
send(ws, { type: 'error', message: err instanceof Error ? err.message : String(err) })
|
|
223
242
|
}
|
|
224
|
-
|
|
243
|
+
if (fallbackHooks !== undefined && agentDir !== undefined) {
|
|
244
|
+
await fallbackHooks.runSessionTurnEnd({
|
|
245
|
+
sessionId: state.sessionFileId,
|
|
246
|
+
agentDir,
|
|
247
|
+
origin: state.origin,
|
|
248
|
+
})
|
|
249
|
+
}
|
|
225
250
|
if (fallbackHooks !== undefined) {
|
|
226
251
|
await fallbackHooks.runSessionIdle({
|
|
227
252
|
sessionId: state.sessionFileId,
|
|
@@ -323,7 +348,7 @@ function forwardAssistantError(ws: Ws, message: unknown): void {
|
|
|
323
348
|
send(ws, { type: 'error', message: text })
|
|
324
349
|
}
|
|
325
350
|
|
|
326
|
-
function enqueuePrompt(ws: Ws, state: SessionState, msg: StreamMessage): void {
|
|
351
|
+
function enqueuePrompt(ws: Ws, state: SessionState, msg: StreamMessage, agentDir: string | undefined): void {
|
|
327
352
|
const payload = msg.payload as { kind?: string; text?: string; delivery?: PromptDelivery }
|
|
328
353
|
if (payload?.kind !== 'prompt' || typeof payload.text !== 'string') return
|
|
329
354
|
const delivery: PromptDelivery = payload.delivery ?? 'queue'
|
|
@@ -339,7 +364,7 @@ function enqueuePrompt(ws: Ws, state: SessionState, msg: StreamMessage): void {
|
|
|
339
364
|
ts: msg.ts,
|
|
340
365
|
})
|
|
341
366
|
pushQueueState(ws, state)
|
|
342
|
-
void drain(ws, state)
|
|
367
|
+
void drain(ws, state, agentDir)
|
|
343
368
|
}
|
|
344
369
|
|
|
345
370
|
// `session.idle` semantically means "the agent finished a prompt and is now
|
|
@@ -360,10 +385,26 @@ function makeIdleHookCaller(state: SessionState): () => Promise<void> {
|
|
|
360
385
|
}
|
|
361
386
|
}
|
|
362
387
|
|
|
363
|
-
|
|
388
|
+
function makeTurnHookCallers(
|
|
389
|
+
state: SessionState,
|
|
390
|
+
agentDir: string | undefined,
|
|
391
|
+
): { fireTurnStart: () => Promise<void>; fireTurnEnd: () => Promise<void> } {
|
|
392
|
+
const hooks: HookBus | undefined = state.runtimeSnapshot?.hooks
|
|
393
|
+
if (hooks === undefined || agentDir === undefined) {
|
|
394
|
+
return { fireTurnStart: async () => {}, fireTurnEnd: async () => {} }
|
|
395
|
+
}
|
|
396
|
+
const event = { sessionId: state.sessionFileId, agentDir, origin: state.origin }
|
|
397
|
+
return {
|
|
398
|
+
fireTurnStart: () => hooks.runSessionTurnStart(event),
|
|
399
|
+
fireTurnEnd: () => hooks.runSessionTurnEnd(event),
|
|
400
|
+
}
|
|
401
|
+
}
|
|
402
|
+
|
|
403
|
+
async function drain(ws: Ws, state: SessionState, agentDir: string | undefined): Promise<void> {
|
|
364
404
|
if (state.draining) return
|
|
365
405
|
state.draining = true
|
|
366
406
|
const fireIdle = makeIdleHookCaller(state)
|
|
407
|
+
const { fireTurnStart, fireTurnEnd } = makeTurnHookCallers(state, agentDir)
|
|
367
408
|
try {
|
|
368
409
|
while (state.drainQueue.length > 0) {
|
|
369
410
|
const item = state.drainQueue.shift()
|
|
@@ -371,12 +412,14 @@ async function drain(ws: Ws, state: SessionState): Promise<void> {
|
|
|
371
412
|
pushQueueState(ws, state)
|
|
372
413
|
send(ws, { type: 'prompt_started', messageId: item.streamMessageId, text: item.text })
|
|
373
414
|
|
|
415
|
+
await fireTurnStart()
|
|
374
416
|
try {
|
|
375
417
|
await state.session.prompt(item.text)
|
|
376
418
|
send(ws, { type: 'done' })
|
|
377
419
|
} catch (err) {
|
|
378
420
|
send(ws, { type: 'error', message: err instanceof Error ? err.message : String(err) })
|
|
379
421
|
}
|
|
422
|
+
await fireTurnEnd()
|
|
380
423
|
await fireIdle()
|
|
381
424
|
}
|
|
382
425
|
} finally {
|
|
@@ -393,6 +436,61 @@ function pushQueueState(ws: Ws, state: SessionState): void {
|
|
|
393
436
|
send(ws, { type: 'queue_state', pending })
|
|
394
437
|
}
|
|
395
438
|
|
|
439
|
+
async function handleDoctor(
|
|
440
|
+
ws: Ws,
|
|
441
|
+
requestId: string,
|
|
442
|
+
pluginRuntime: PluginRuntime | undefined,
|
|
443
|
+
agentDir: string | undefined,
|
|
444
|
+
): Promise<void> {
|
|
445
|
+
if (pluginRuntime === undefined || agentDir === undefined) {
|
|
446
|
+
send(ws, { type: 'doctor_result', requestId, checks: [] })
|
|
447
|
+
return
|
|
448
|
+
}
|
|
449
|
+
const snapshot = pluginRuntime.get()
|
|
450
|
+
if (snapshot === undefined) {
|
|
451
|
+
send(ws, { type: 'doctor_result', requestId, checks: [] })
|
|
452
|
+
return
|
|
453
|
+
}
|
|
454
|
+
try {
|
|
455
|
+
const checks = await runPluginDoctorChecks({ registry: snapshot.registry, agentDir })
|
|
456
|
+
send(ws, { type: 'doctor_result', requestId, checks })
|
|
457
|
+
} catch (err) {
|
|
458
|
+
send(ws, { type: 'error', message: err instanceof Error ? err.message : String(err) })
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
async function handleDoctorFix(
|
|
463
|
+
ws: Ws,
|
|
464
|
+
requestId: string,
|
|
465
|
+
checkId: string,
|
|
466
|
+
pluginRuntime: PluginRuntime | undefined,
|
|
467
|
+
agentDir: string | undefined,
|
|
468
|
+
): Promise<void> {
|
|
469
|
+
if (pluginRuntime === undefined || agentDir === undefined) {
|
|
470
|
+
send(ws, {
|
|
471
|
+
type: 'doctor_fix_result',
|
|
472
|
+
requestId,
|
|
473
|
+
result: { ok: false, checkId, error: 'plugin runtime not configured' },
|
|
474
|
+
})
|
|
475
|
+
return
|
|
476
|
+
}
|
|
477
|
+
const snapshot = pluginRuntime.get()
|
|
478
|
+
if (snapshot === undefined) {
|
|
479
|
+
send(ws, {
|
|
480
|
+
type: 'doctor_fix_result',
|
|
481
|
+
requestId,
|
|
482
|
+
result: { ok: false, checkId, error: 'plugin runtime not configured' },
|
|
483
|
+
})
|
|
484
|
+
return
|
|
485
|
+
}
|
|
486
|
+
const outcome = await runPluginDoctorFix({ registry: snapshot.registry, agentDir, checkId })
|
|
487
|
+
const result =
|
|
488
|
+
outcome.ok === true
|
|
489
|
+
? { ok: true as const, checkId, summary: outcome.summary, changedPaths: outcome.changedPaths }
|
|
490
|
+
: { ok: false as const, checkId, error: outcome.error }
|
|
491
|
+
send(ws, { type: 'doctor_fix_result', requestId, result })
|
|
492
|
+
}
|
|
493
|
+
|
|
396
494
|
async function handleReload(
|
|
397
495
|
ws: Ws,
|
|
398
496
|
reloadAll: ReloadAllFn | undefined,
|
package/src/shared/index.ts
CHANGED
package/src/shared/protocol.ts
CHANGED
|
@@ -4,11 +4,31 @@ export type ReloadResultPayload =
|
|
|
4
4
|
|
|
5
5
|
export type PromptDelivery = 'queue' | 'steer' | 'interrupt'
|
|
6
6
|
|
|
7
|
+
export type DoctorRequestId = string
|
|
8
|
+
|
|
9
|
+
export type DoctorCheckPayload = {
|
|
10
|
+
id: string
|
|
11
|
+
pluginName: string
|
|
12
|
+
checkName: string
|
|
13
|
+
description: string
|
|
14
|
+
category: string
|
|
15
|
+
status: 'ok' | 'warning' | 'error'
|
|
16
|
+
message: string
|
|
17
|
+
details?: string[]
|
|
18
|
+
fix?: { description: string; hasApply: boolean }
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
export type DoctorFixPayload =
|
|
22
|
+
| { ok: true; checkId: string; summary: string; changedPaths: string[] }
|
|
23
|
+
| { ok: false; checkId: string; error: string }
|
|
24
|
+
|
|
7
25
|
export type ClientMessage =
|
|
8
26
|
| { type: 'prompt'; text: string; delivery?: PromptDelivery }
|
|
9
27
|
| { type: 'reload'; scope?: string }
|
|
10
28
|
| { type: 'abort' }
|
|
11
29
|
| { type: 'queue_cancel'; messageId: string }
|
|
30
|
+
| { type: 'doctor'; requestId: DoctorRequestId }
|
|
31
|
+
| { type: 'doctor_fix'; requestId: DoctorRequestId; checkId: string }
|
|
12
32
|
|
|
13
33
|
export type QueueStateItem = { id: string; text: string; ts: number }
|
|
14
34
|
|
|
@@ -23,3 +43,5 @@ export type ServerMessage =
|
|
|
23
43
|
| { type: 'notification'; payload: unknown; replyTo?: string; meta?: Record<string, string> }
|
|
24
44
|
| { type: 'queue_state'; pending: QueueStateItem[] }
|
|
25
45
|
| { type: 'prompt_started'; messageId: string; text: string }
|
|
46
|
+
| { type: 'doctor_result'; requestId: DoctorRequestId; checks: DoctorCheckPayload[] }
|
|
47
|
+
| { type: 'doctor_fix_result'; requestId: DoctorRequestId; result: DoctorFixPayload }
|
|
@@ -403,7 +403,7 @@ RUN apt-get install ... <baseline + enabled toggle packages> ← toggles fan o
|
|
|
403
403
|
ENV NODE_ENV=production
|
|
404
404
|
# Custom lines from typeclaw.json#dockerfile.append. ← only emitted when append is non-empty
|
|
405
405
|
<your appended lines>
|
|
406
|
-
ENTRYPOINT ["
|
|
406
|
+
ENTRYPOINT ["/usr/local/bin/typeclaw-entrypoint"]
|
|
407
407
|
CMD ["run"]
|
|
408
408
|
```
|
|
409
409
|
|
|
@@ -539,17 +539,56 @@ Do **not** edit `typeclaw.json` to a model the registry doesn't know, even if th
|
|
|
539
539
|
|
|
540
540
|
`typeclaw.json` does **not** hold API keys or OAuth tokens. Credentials live in two gitignored files:
|
|
541
541
|
|
|
542
|
-
- **`./.env`** (API
|
|
543
|
-
- `OPENAI_API_KEY` —
|
|
544
|
-
- `FIREWORKS_API_KEY` —
|
|
545
|
-
- **`./secrets.json`** (
|
|
546
|
-
- `
|
|
542
|
+
- **`./.env`** (any environment variable, including API keys): plain `KEY=value` lines, loaded by Docker via `--env-file` at container start. The canonical env-var names per provider:
|
|
543
|
+
- `OPENAI_API_KEY` — for any `openai/...` model.
|
|
544
|
+
- `FIREWORKS_API_KEY` — for any `fireworks/...` model.
|
|
545
|
+
- **`./secrets.json`** (structured store): a `v2` envelope managed by `SecretsBackend` (wraps `pi-coding-agent`'s `AuthStorage`). Two top-level slices:
|
|
546
|
+
- `providers.*` — per-provider credentials. API-key providers store `{ type: 'api_key', key: <Secret> }`. OAuth providers store the `pi-coding-agent` token blob `{ type: 'oauth', access_token, refresh_token, expires_at, ... }`. The container auto-refreshes OAuth tokens with file locking; api-key writes only happen on explicit user-driven rotation.
|
|
547
|
+
- `channels.*` — per-adapter credentials, with named fields per adapter:
|
|
548
|
+
- `discord-bot: { token: <Secret> }`
|
|
549
|
+
- `slack-bot: { botToken: <Secret>, appToken: <Secret> }`
|
|
550
|
+
- `telegram-bot: { token: <Secret> }`
|
|
547
551
|
|
|
548
|
-
|
|
552
|
+
(Pre-v2 agent folders carry the older `llm` slice and channel-env-var-keyed shape; they are upgraded transparently on first read. Pre-rename folders may even carry the file as `auth.json`; it is renamed to `secrets.json` on the next boot.)
|
|
549
553
|
|
|
550
|
-
|
|
554
|
+
### The `Secret` shape and env-wins resolution
|
|
551
555
|
|
|
552
|
-
|
|
556
|
+
Every secret-bearing field in `secrets.json` is a **`Secret`**: either a plain string or an object `{ value?, env? }`.
|
|
557
|
+
|
|
558
|
+
```json
|
|
559
|
+
{
|
|
560
|
+
"version": 2,
|
|
561
|
+
"providers": {
|
|
562
|
+
"fireworks": { "type": "api_key", "key": "fw_xxx" },
|
|
563
|
+
"openai-codex": { "type": "oauth", "access_token": "...", "refresh_token": "...", "expires_at": 99 }
|
|
564
|
+
},
|
|
565
|
+
"channels": {
|
|
566
|
+
"slack-bot": {
|
|
567
|
+
"botToken": "xoxb-...",
|
|
568
|
+
"appToken": { "value": "xapp-...", "env": "MY_CUSTOM_SLACK_APP_TOKEN" }
|
|
569
|
+
}
|
|
570
|
+
}
|
|
571
|
+
}
|
|
572
|
+
```
|
|
573
|
+
|
|
574
|
+
**Resolution at boot, in order:**
|
|
575
|
+
|
|
576
|
+
1. `process.env[secret.env]` — explicit binding wins (the `env` field on the object form).
|
|
577
|
+
2. `process.env[<canonical env name>]` — canonical-env fallback (`SLACK_BOT_TOKEN`, `FIREWORKS_API_KEY`, etc.).
|
|
578
|
+
3. `secret.value` — the on-disk value.
|
|
579
|
+
4. Otherwise the field is treated as missing.
|
|
580
|
+
|
|
581
|
+
**Env wins, the file is never auto-mutated.** When the env var is set, that value is used in-memory via `setRuntimeApiKey` (api-keys) or `process.env` injection (channels) — `secrets.json` is **not** rewritten to capture the env value. The user's file stays user-owned.
|
|
582
|
+
|
|
583
|
+
**Custom env-var binding** — the optional `env` field on the object form lets the user route a credential through an env var of their choosing (e.g., a CI system that exposes `MY_PROD_SLACK_TOKEN` instead of `SLACK_BOT_TOKEN`).
|
|
584
|
+
|
|
585
|
+
### Switching credentials
|
|
586
|
+
|
|
587
|
+
If a user wants to switch from API key to OAuth (or vice versa) for a provider that supports both, the easiest path is to delete the relevant entry from `.env` / `secrets.json#providers` and re-run `typeclaw init` from inside the agent folder — it'll prompt for the auth method again.
|
|
588
|
+
|
|
589
|
+
If the user wants to rotate an api-key, edit either `.env` (env-wins picks it up immediately) or `secrets.json#providers.<provider>.key` (rewrite the `value` field, or remove the entry if the env var should take over). After either, `typeclaw restart` on the host stage.
|
|
590
|
+
|
|
591
|
+
Never echo, log, or commit values from `.env` or `secrets.json`. Both are gitignored by default — keep them that way.
|
|
553
592
|
|
|
554
593
|
## Editing `typeclaw.json` safely
|
|
555
594
|
|
package/typeclaw.schema.json
CHANGED
|
@@ -706,6 +706,18 @@
|
|
|
706
706
|
"allow"
|
|
707
707
|
]
|
|
708
708
|
},
|
|
709
|
+
"network": {
|
|
710
|
+
"default": {
|
|
711
|
+
"blockInternal": true
|
|
712
|
+
},
|
|
713
|
+
"type": "object",
|
|
714
|
+
"properties": {
|
|
715
|
+
"blockInternal": {
|
|
716
|
+
"default": true,
|
|
717
|
+
"type": "boolean"
|
|
718
|
+
}
|
|
719
|
+
}
|
|
720
|
+
},
|
|
709
721
|
"dockerfile": {
|
|
710
722
|
"default": {
|
|
711
723
|
"ffmpeg": false,
|
|
@@ -780,6 +792,40 @@
|
|
|
780
792
|
}
|
|
781
793
|
}
|
|
782
794
|
},
|
|
795
|
+
"tool-result-cap": {
|
|
796
|
+
"default": {
|
|
797
|
+
"enabled": true,
|
|
798
|
+
"imageMaxBytes": 262144,
|
|
799
|
+
"textMaxBytes": 65536,
|
|
800
|
+
"exemptTools": []
|
|
801
|
+
},
|
|
802
|
+
"type": "object",
|
|
803
|
+
"properties": {
|
|
804
|
+
"enabled": {
|
|
805
|
+
"default": true,
|
|
806
|
+
"type": "boolean"
|
|
807
|
+
},
|
|
808
|
+
"imageMaxBytes": {
|
|
809
|
+
"default": 262144,
|
|
810
|
+
"type": "integer",
|
|
811
|
+
"minimum": 1024,
|
|
812
|
+
"maximum": 9007199254740991
|
|
813
|
+
},
|
|
814
|
+
"textMaxBytes": {
|
|
815
|
+
"default": 65536,
|
|
816
|
+
"type": "integer",
|
|
817
|
+
"minimum": 1024,
|
|
818
|
+
"maximum": 9007199254740991
|
|
819
|
+
},
|
|
820
|
+
"exemptTools": {
|
|
821
|
+
"default": [],
|
|
822
|
+
"type": "array",
|
|
823
|
+
"items": {
|
|
824
|
+
"type": "string"
|
|
825
|
+
}
|
|
826
|
+
}
|
|
827
|
+
}
|
|
828
|
+
},
|
|
783
829
|
"memory": {
|
|
784
830
|
"default": {
|
|
785
831
|
"idleMs": 10000,
|
|
@@ -816,6 +862,44 @@
|
|
|
816
862
|
}
|
|
817
863
|
}
|
|
818
864
|
}
|
|
865
|
+
},
|
|
866
|
+
"backup": {
|
|
867
|
+
"default": {
|
|
868
|
+
"enabled": true,
|
|
869
|
+
"idleMs": 30000,
|
|
870
|
+
"pushToOrigin": true,
|
|
871
|
+
"commitTimeoutMs": 30000,
|
|
872
|
+
"networkTimeoutMs": 60000
|
|
873
|
+
},
|
|
874
|
+
"type": "object",
|
|
875
|
+
"properties": {
|
|
876
|
+
"enabled": {
|
|
877
|
+
"default": true,
|
|
878
|
+
"type": "boolean"
|
|
879
|
+
},
|
|
880
|
+
"idleMs": {
|
|
881
|
+
"default": 30000,
|
|
882
|
+
"type": "integer",
|
|
883
|
+
"minimum": 1000,
|
|
884
|
+
"maximum": 9007199254740991
|
|
885
|
+
},
|
|
886
|
+
"pushToOrigin": {
|
|
887
|
+
"default": true,
|
|
888
|
+
"type": "boolean"
|
|
889
|
+
},
|
|
890
|
+
"commitTimeoutMs": {
|
|
891
|
+
"default": 30000,
|
|
892
|
+
"type": "integer",
|
|
893
|
+
"minimum": 1,
|
|
894
|
+
"maximum": 9007199254740991
|
|
895
|
+
},
|
|
896
|
+
"networkTimeoutMs": {
|
|
897
|
+
"default": 60000,
|
|
898
|
+
"type": "integer",
|
|
899
|
+
"minimum": 1,
|
|
900
|
+
"maximum": 9007199254740991
|
|
901
|
+
}
|
|
902
|
+
}
|
|
819
903
|
}
|
|
820
904
|
},
|
|
821
905
|
"additionalProperties": {}
|
package/src/secrets/env.ts
DELETED
|
@@ -1,43 +0,0 @@
|
|
|
1
|
-
import { readFileSync, writeFileSync } from 'node:fs'
|
|
2
|
-
|
|
3
|
-
// No-op when the file is missing or the key is absent: the caller has
|
|
4
|
-
// already persisted to `secrets.json` and just wants `.env` to stop being a
|
|
5
|
-
// second source of truth. Parsing matches `parseEnvKeys` in
|
|
6
|
-
// `src/init/index.ts` — line-based, trim, skip blanks/comments, split on the
|
|
7
|
-
// first `=`. Duplicate assignments to the same key are all removed because
|
|
8
|
-
// dotenv resolves "last wins" so every duplicate carries the value we just
|
|
9
|
-
// promoted.
|
|
10
|
-
export function stripEnvKey(path: string, key: string): void {
|
|
11
|
-
let original: string
|
|
12
|
-
try {
|
|
13
|
-
original = readFileSync(path, 'utf8')
|
|
14
|
-
} catch (error) {
|
|
15
|
-
if ((error as NodeJS.ErrnoException).code === 'ENOENT') return
|
|
16
|
-
throw error
|
|
17
|
-
}
|
|
18
|
-
|
|
19
|
-
const next = removeKeyFromEnvText(original, key)
|
|
20
|
-
if (next === original) return
|
|
21
|
-
writeFileSync(path, next)
|
|
22
|
-
}
|
|
23
|
-
|
|
24
|
-
export function removeKeyFromEnvText(content: string, key: string): string {
|
|
25
|
-
const lines = content.split('\n')
|
|
26
|
-
const kept: string[] = []
|
|
27
|
-
for (const line of lines) {
|
|
28
|
-
const trimmed = line.trim()
|
|
29
|
-
if (trimmed === '' || trimmed.startsWith('#')) {
|
|
30
|
-
kept.push(line)
|
|
31
|
-
continue
|
|
32
|
-
}
|
|
33
|
-
const eq = trimmed.indexOf('=')
|
|
34
|
-
if (eq <= 0) {
|
|
35
|
-
kept.push(line)
|
|
36
|
-
continue
|
|
37
|
-
}
|
|
38
|
-
const lineKey = trimmed.slice(0, eq).trim()
|
|
39
|
-
if (lineKey === key) continue
|
|
40
|
-
kept.push(line)
|
|
41
|
-
}
|
|
42
|
-
return kept.join('\n')
|
|
43
|
-
}
|