@x-otto/env 0.1.0-alpha.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +102 -0
- package/dist/index.d.ts +353 -0
- package/dist/index.js +2 -0
- package/package.json +28 -0
package/README.md
ADDED
|
@@ -0,0 +1,102 @@
|
|
|
1
|
+
# @otto/env
|
|
2
|
+
|
|
3
|
+
> Environment variable standardization, path constants, and resilience configuration. Zero internal dependencies — the foundational layer of the monorepo.
|
|
4
|
+
|
|
5
|
+
`@otto/env` provides the canonical path constants (dot-otto root, auth path, session dirs, etc.), environment variable normalization helpers, Claude compatibility toggles, process enumeration/liveness probes, config-layer resolution, and the unified resilience config contract. Every other package in the monorepo depends on it — it is the leaf of the dependency graph.
|
|
6
|
+
|
|
7
|
+
## Installation
|
|
8
|
+
|
|
9
|
+
```bash
|
|
10
|
+
pnpm add @otto/env
|
|
11
|
+
```
|
|
12
|
+
|
|
13
|
+
## Usage
|
|
14
|
+
|
|
15
|
+
```ts
|
|
16
|
+
import {
|
|
17
|
+
OTTO_HOME,
|
|
18
|
+
OTTO_SESSIONS_DIR,
|
|
19
|
+
AUTH_PATH,
|
|
20
|
+
normalizeEnv,
|
|
21
|
+
normalizePath,
|
|
22
|
+
findWorkspaceRoot,
|
|
23
|
+
resolveConfigLayers,
|
|
24
|
+
listOttoProcesses,
|
|
25
|
+
isProcessAlive,
|
|
26
|
+
} from '@otto/env'
|
|
27
|
+
|
|
28
|
+
// Canonical paths
|
|
29
|
+
console.log(OTTO_HOME) // ~/.otto (overridable via OTTO_HOME env)
|
|
30
|
+
console.log(AUTH_PATH) // ~/.config/otto/auth.json
|
|
31
|
+
|
|
32
|
+
// Environment variable normalization
|
|
33
|
+
const port = normalizeEnv(process.env['MY_PORT'], 8080)
|
|
34
|
+
|
|
35
|
+
// Find monorepo root
|
|
36
|
+
const root = findWorkspaceRoot(process.cwd())
|
|
37
|
+
|
|
38
|
+
// Resolve config layers (priority: project/otto > repository/otto > project/claude > user/otto > user/claude)
|
|
39
|
+
const layers = resolveConfigLayers({ cwd: process.cwd(), homedir: os.homedir() })
|
|
40
|
+
|
|
41
|
+
// Enumerate running otto processes
|
|
42
|
+
const processes = listOttoProcesses()
|
|
43
|
+
console.log(processes.map(p => `${p.pid}: ${p.command} (${p.etimeMs}ms)`))
|
|
44
|
+
```
|
|
45
|
+
|
|
46
|
+
## API
|
|
47
|
+
|
|
48
|
+
### Path Constants
|
|
49
|
+
- `OTTO_ROOT` — `.otto` directory name
|
|
50
|
+
- `OTTO_HOME` — user-level root (`~/.otto`, overridable via `OTTO_HOME` env)
|
|
51
|
+
- `OTTO_SESSIONS_DIR` — session storage root (`OTTO_HOME/sessions`)
|
|
52
|
+
- `OTTO_SESSION_LOCKS_DIR` — write lease lock directory (`OTTO_SESSIONS_DIR/locks`)
|
|
53
|
+
- `OTTO_USER_CONFIG_DIR` — user config (`~/.config/otto`)
|
|
54
|
+
- `OTTO_PROJECT_DIR` — project-level `.otto` (`cwd/.otto`)
|
|
55
|
+
- `OTTO_PROJECT_CONFIG_RELPATH` — project config relative path (`.otto/config.json`)
|
|
56
|
+
- `AUTH_PATH` — auth credentials file path
|
|
57
|
+
- `OTTO_IDENTITY_PATH` — user identity file path
|
|
58
|
+
- `OTTO_USER_AGENT` — `otto/<version>` user agent string
|
|
59
|
+
- `SDK_VERSION`, `BUILD_ID` — version identifiers
|
|
60
|
+
|
|
61
|
+
### Service & Logging
|
|
62
|
+
- `SERVICE_PORT` — default HTTP/WS port (8417)
|
|
63
|
+
- `SERVICE_URL` — default service URL
|
|
64
|
+
- `LOG_LEVEL` — active log level (env-derived)
|
|
65
|
+
- `LOG_FILE` — log file path (empty = stderr only)
|
|
66
|
+
|
|
67
|
+
### Environment Variable Normalization
|
|
68
|
+
- `normalizeEnv(value, default)` — parse uint env var, fallback on invalid
|
|
69
|
+
- `normalizeEnvFraction(value, default)` — parse (0,1] fraction env var
|
|
70
|
+
- `normalizePath(path)` — normalize to forward-slash path
|
|
71
|
+
- `findWorkspaceRoot(startDir)` — walk up to find `pnpm-workspace.yaml`
|
|
72
|
+
|
|
73
|
+
### Claude Compat Toggles
|
|
74
|
+
- `isClaudeCompatEnabled()` — `.claude` directory compat (default on)
|
|
75
|
+
- `isClaudeSettingsCompatEnabled()` — settings translation (default off)
|
|
76
|
+
- `isClaudeHooksCompatEnabled()` — hooks command bridge (default off)
|
|
77
|
+
|
|
78
|
+
### Config Layers
|
|
79
|
+
- `resolveConfigLayers(options)` — enumerate `.otto`/`.claude` × project/user layers
|
|
80
|
+
|
|
81
|
+
### Process Management (RFC-196)
|
|
82
|
+
- `listOttoProcesses(pattern?)` — enumerate otto main processes via `ps`
|
|
83
|
+
- `parseOttoProcessTable(stdout)` — parse `ps` output for testing
|
|
84
|
+
- `isProcessAlive(pid)` — probe process via `process.kill(pid, 0)`
|
|
85
|
+
- `encodeLeaseToken(token, pid, host)` / `decodeLeaseToken(raw)` — lease lock token codec
|
|
86
|
+
- `currentHostname()` — local hostname
|
|
87
|
+
- `isContainerEnvironment()` — detect Docker/k8s
|
|
88
|
+
|
|
89
|
+
### Resilience Config (RFC-230)
|
|
90
|
+
- `DEFAULT_RESILIENCE_CONFIG` — defaults for all 6 subsystems (stream, network, mcp, schedule, error-retry, oauth)
|
|
91
|
+
- `resolveResilienceConfig(explicit?, fromSettings?, warn?)` — merge 4 config layers
|
|
92
|
+
- `parseResilienceEnv(raw, warn?)` — parse `OTTO_RESILIENCE_CONFIG` env
|
|
93
|
+
- `validateResilienceOverride(input, warn)` — validate partial override shape
|
|
94
|
+
|
|
95
|
+
## Dependencies
|
|
96
|
+
|
|
97
|
+
- Internal: none
|
|
98
|
+
- External: none (Node.js builtins only: `node:os`, `node:fs`, `node:path`, `node:child_process`)
|
|
99
|
+
|
|
100
|
+
## Related
|
|
101
|
+
|
|
102
|
+
- [Architecture](./ARCHITECTURE.md)
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1,353 @@
|
|
|
1
|
+
//#region src/build-id.d.ts
|
|
2
|
+
declare const BUILD_ID = 4512;
|
|
3
|
+
//#endregion
|
|
4
|
+
//#region src/config-layers.d.ts
|
|
5
|
+
/**
|
|
6
|
+
* config-layers.ts —— otto/claude 配置层枚举器(RFC-032 §2 单一真源)。
|
|
7
|
+
*
|
|
8
|
+
* 把 `.otto`/`.claude` × project/user 的目录与优先级集中一处(此前散在各轴硬编码)。
|
|
9
|
+
* **职责边界**:只产出层描述(kind/scope/dir),不做文件 IO、不判断 exists、不发现 artifact、
|
|
10
|
+
* 不含信任门。纯函数、确定性、无副作用。
|
|
11
|
+
*
|
|
12
|
+
* 现状(RFC-044 review S1 + RFC-081 终局复审定档):真实职责 = 派生插件根
|
|
13
|
+
* (`@otto/coding discoverPlugins` 取 otto 层得 `<.otto>/plugins`)。**「四轴收编中央源」经复审判定不迁**
|
|
14
|
+
* (不是待办):command/agent 两个目录扫描轴的 precedence 已完全一致
|
|
15
|
+
* (`plugin < user/claude < user/otto < project/claude < project/otto`,各有 RFC-032 测试守护漂移),
|
|
16
|
+
* 强迁会把清晰的本地 layer 数组换成 `resolveConfigLayers().reverse().filter().map()` 徒增耦合;
|
|
17
|
+
* 且 skill/mcp 非目录扫描轴(走 SkillRegistry/McpController),不适用此枚举模型。勿再当 open gap 重做。
|
|
18
|
+
* 插件目录**不经此枚举器**接入(走 discoverPlugins → App.activePlugins() 注入各轴),故此处不含 plugin 层 kind。
|
|
19
|
+
*
|
|
20
|
+
* repository 层(RFC-130):monorepo 根目录的 `.otto/`——当 `workspaceRoot` 与 `cwd` 不同时
|
|
21
|
+
* (如 `cwd = /repo/packages/cli`, `workspaceRoot = /repo`),在 project 与 user 之间插入
|
|
22
|
+
* `repository/otto` 层。去重:若 workspaceRoot === cwd 则 repository 与 project 同目录,跳过插入。
|
|
23
|
+
*/
|
|
24
|
+
type ConfigLayerKind = 'otto' | 'claude';
|
|
25
|
+
type ConfigLayerScope = 'project' | 'repository' | 'user';
|
|
26
|
+
interface ConfigLayer {
|
|
27
|
+
kind: ConfigLayerKind;
|
|
28
|
+
scope: ConfigLayerScope;
|
|
29
|
+
/** 该层根目录(绝对路径,正斜杠归一)。 */
|
|
30
|
+
dir: string;
|
|
31
|
+
}
|
|
32
|
+
interface ResolveConfigLayersOptions {
|
|
33
|
+
cwd: string;
|
|
34
|
+
homedir: string;
|
|
35
|
+
/**
|
|
36
|
+
* 是否纳入 `.claude` 层。缺省取 `isClaudeCompatEnabled()`(env 总开关)。
|
|
37
|
+
* 显式传 false 可在测试/特定场景剔除所有 claude 层。
|
|
38
|
+
*/
|
|
39
|
+
claudeCompat?: boolean;
|
|
40
|
+
/**
|
|
41
|
+
* monorepo 根目录(含 `pnpm-workspace.yaml` 的目录的绝对路径)。
|
|
42
|
+
* 传入且与 `cwd` 不同时,在 project/otto 之后插入 repository/otto 层。
|
|
43
|
+
* 未传入时不插入(向后兼容纯 project+user 模式)。
|
|
44
|
+
*/
|
|
45
|
+
workspaceRoot?: string;
|
|
46
|
+
}
|
|
47
|
+
/**
|
|
48
|
+
* 枚举配置层,数组序即优先级(高→低):
|
|
49
|
+
* project/otto → [repository/otto] → project/claude → user/otto → user/claude
|
|
50
|
+
*
|
|
51
|
+
* `.claude` 关闭时整体剔除其两层。repository 层仅当 `workspaceRoot` 与 `cwd` 不同时插入。
|
|
52
|
+
*/
|
|
53
|
+
declare function resolveConfigLayers(opts: ResolveConfigLayersOptions): ConfigLayer[];
|
|
54
|
+
//#endregion
|
|
55
|
+
//#region src/process-liveness.d.ts
|
|
56
|
+
/** 判断进程是否存活(`process.kill(pid, 0)` 不发信号,仅探测目标是否可达)。 */
|
|
57
|
+
declare function isProcessAlive(pid: number): boolean;
|
|
58
|
+
/** 本机 hostname(便于消费方与 `decodeLeaseToken` 结果比对,避免各处重复 import 'node:os')。 */
|
|
59
|
+
declare function currentHostname(): string;
|
|
60
|
+
/**
|
|
61
|
+
* 编码租约锁 token:`<token>:<pid>:<hostname>`。
|
|
62
|
+
*
|
|
63
|
+
* `token` 由调用方生成(`randomUUID()`),本身不含冒号;`hostname` 由 `os.hostname()` 产出,
|
|
64
|
+
* 系统规范不含冒号;三段冒号分隔天然无歧义。
|
|
65
|
+
*/
|
|
66
|
+
declare function encodeLeaseToken(token: string, pid: number, host: string): string;
|
|
67
|
+
/** 解码结果:`pid`/`host` 为 `undefined` 表示旧格式锁文件(裸 token)或字段非法,安全降级。 */
|
|
68
|
+
interface DecodedLeaseToken {
|
|
69
|
+
readonly token: string;
|
|
70
|
+
readonly pid?: number;
|
|
71
|
+
readonly host?: string;
|
|
72
|
+
}
|
|
73
|
+
/**
|
|
74
|
+
* 解码租约锁 token。向后兼容:解析不出 `<token>:<pid>:<host>` 三段结构(旧格式裸 token,
|
|
75
|
+
* 或内容被截断/损坏)时,整体作为 `token` 返回,`pid`/`host` 置空——消费方据此退化为现有
|
|
76
|
+
* 纯 stale-timeout 判定路径,行为与升级前完全一致,不会误判、不会崩溃。
|
|
77
|
+
*
|
|
78
|
+
* pid 合法性校验(独立评审要求):解析出的 pid 必须是有限正整数(`Number.isFinite(n) && n > 0`),
|
|
79
|
+
* 否则一律视为非法并降级为 `undefined`。原因:
|
|
80
|
+
* - `pid <= 0`:`process.kill(0, 0)` 的语义是探测调用进程自身所在进程组是否存在,
|
|
81
|
+
* 而非探测"pid 0"这个进程是否存活——若不过滤会产生完全错误的存活判定。
|
|
82
|
+
* - `pid` 非数字(`NaN`):`process.kill(NaN, 0)` 直接抛 `TypeError`,会导致 `isProcessAlive`
|
|
83
|
+
* 崩溃而非安全返回 false。
|
|
84
|
+
* 触发场景:锁文件被意外截断、手动编辑、或极端并发下的部分写入残留,均可能产生
|
|
85
|
+
* `token:0:host`、`token:abc:host`、`token::host` 等畸形内容。
|
|
86
|
+
*/
|
|
87
|
+
declare function decodeLeaseToken(raw: string): DecodedLeaseToken;
|
|
88
|
+
//#endregion
|
|
89
|
+
//#region src/process-enumeration.d.ts
|
|
90
|
+
/** 单个 otto 主进程的枚举快照。 */
|
|
91
|
+
interface OttoProcessInfo {
|
|
92
|
+
readonly pid: number;
|
|
93
|
+
readonly ppid: number;
|
|
94
|
+
/** RSS(KB,`ps` 原始口径,含共享库——勿作精确容量计算,见 RFC-196 F10)。 */
|
|
95
|
+
readonly rssKb: number;
|
|
96
|
+
/** 控制终端(`??` = 无 TTY)。 */
|
|
97
|
+
readonly tty: string;
|
|
98
|
+
/** 运行时长(毫秒,由 etime 解析)。 */
|
|
99
|
+
readonly etimeMs: number;
|
|
100
|
+
/** 完整命令行。 */
|
|
101
|
+
readonly command: string;
|
|
102
|
+
/** argv 中 `--continue <sessionId>` 的会话 ID(无则 undefined)。 */
|
|
103
|
+
readonly sessionId: string | undefined;
|
|
104
|
+
/** argv 中 `--restart-generation=<n>` 的代际计数(RFC-196 M3 引入;无则 undefined)。 */
|
|
105
|
+
readonly generation: number | undefined;
|
|
106
|
+
}
|
|
107
|
+
/** `ps` etime 格式:`[[dd-]hh:]mm:ss` → 毫秒。解析失败返回 0。 */
|
|
108
|
+
declare function parseEtimeMs(etime: string): number;
|
|
109
|
+
/**
|
|
110
|
+
* 枚举全机 otto 主进程(argv 含 `bin/otto.js` 的 node 进程)。
|
|
111
|
+
*
|
|
112
|
+
* 匹配谓词刻意收窄到主进程入口脚本——不含插件 server.mjs、node-pty spawn-helper 等
|
|
113
|
+
* 周边进程(它们的生命周期由 RFC-085 process-exit 机制覆盖,RFC-196 非目标)。
|
|
114
|
+
*
|
|
115
|
+
* @param matchPattern 主进程识别子串(默认 `bin/otto.js`;测试注入用)
|
|
116
|
+
*/
|
|
117
|
+
declare function listOttoProcesses(matchPattern?: string): OttoProcessInfo[];
|
|
118
|
+
/** `ps` 输出解析(与 listOttoProcesses 分离供单测直接喂文本)。 */
|
|
119
|
+
declare function parseOttoProcessTable(stdout: string, matchPattern?: string): OttoProcessInfo[];
|
|
120
|
+
/**
|
|
121
|
+
* 容器环境探测(RFC-196 F6):容器内 PID 1 收养是常态,`ppid==1` 不可作为孤儿强信号。
|
|
122
|
+
* 检测 `/.dockerenv` 存在或 `/proc/1/cgroup` 含 docker/kubepods。darwin 恒 false。
|
|
123
|
+
*/
|
|
124
|
+
declare function isContainerEnvironment(): boolean;
|
|
125
|
+
//#endregion
|
|
126
|
+
//#region src/resilience.d.ts
|
|
127
|
+
/**
|
|
128
|
+
* 统一重试/超时/退避配置契约(RFC-230)。
|
|
129
|
+
*
|
|
130
|
+
* 归口在零依赖叶包 `@otto/env`(不新建独立包,量级参照 RFC-157 已判定的 `@otto/invariant`
|
|
131
|
+
* 29 行不该独立成包先例)。四层配置源优先级(右覆盖左):
|
|
132
|
+
*
|
|
133
|
+
* 硬编码默认值 → env(`OTTO_RESILIENCE_CONFIG`,JSON 字符串)→ settings(`.otto/config.json`
|
|
134
|
+
* 的 `resilience` 字段)→ 程序化注入(`AppOptions.resilience`)
|
|
135
|
+
*
|
|
136
|
+
* 本文件只负责契约类型 + 默认值 + 纯函数解析/合并逻辑,不认识 `App`/`SettingsManager` 等
|
|
137
|
+
* 任何上层概念——消费方(`@otto/setting`/`@otto/coding` 等)各自在正确时机调用
|
|
138
|
+
* `resolveResilienceConfig()`,不由本文件决定何时求值(见 RFC-230 §3 D5 的"延迟冻结"设计,
|
|
139
|
+
* 那是消费方 `App` 的职责,不是本包的职责)。
|
|
140
|
+
*
|
|
141
|
+
* 不引入 zod(`@otto/env` 当前零外部依赖,见 package.json),字段级容错走手写递归校验。
|
|
142
|
+
*/
|
|
143
|
+
/** 统一重试/超时/退避配置的完整形状(六个子系统,全部字段均为必填——`DEFAULT_RESILIENCE_CONFIG` 保证完整性)。 */
|
|
144
|
+
interface ResilienceConfig {
|
|
145
|
+
/** Agent 流式请求重试(429/5xx 等服务端响应类,指数退避)。 */
|
|
146
|
+
stream: {
|
|
147
|
+
/** 最大重试次数。默认 5。 */maxRetries: number; /** 首次重试延迟(ms)。默认 500。 */
|
|
148
|
+
initialDelayMs: number; /** 指数退避倍率。默认 2。 */
|
|
149
|
+
backoffFactor: number; /** 单次延迟上限(ms),防止 Retry-After 头失控。默认 60_000。 */
|
|
150
|
+
maxDelayMs: number;
|
|
151
|
+
};
|
|
152
|
+
/** Agent 网络断连重试(ECONNRESET/socket hang up 等,固定间隔,不做指数退避)。 */
|
|
153
|
+
networkDisconnect: {
|
|
154
|
+
/** 最大重试次数。默认 5。 */maxRetries: number; /** 固定重试间隔(ms)。默认 30_000。 */
|
|
155
|
+
intervalMs: number;
|
|
156
|
+
};
|
|
157
|
+
/** MCP 客户端重连(全局默认,per-server `mcpServerSettingSchema` 可覆盖 maxReconnectAttempts/requestTimeoutMs 两项,reconnectDelayMs 只有全局配置)。 */
|
|
158
|
+
mcp: {
|
|
159
|
+
/** 最大重连尝试次数。默认 3。 */maxReconnectAttempts: number; /** 重连退避基准延迟(ms,指数退避)。默认 500。 */
|
|
160
|
+
reconnectDelayMs: number; /** 单次请求超时(ms)。默认 30_000。 */
|
|
161
|
+
requestTimeoutMs: number;
|
|
162
|
+
};
|
|
163
|
+
/** Scheduler 定时任务失败重试(per-task maxRetries 已可配置,本字段只补充退避延迟)。 */
|
|
164
|
+
schedule: {
|
|
165
|
+
/** 每次重试之间的固定延迟(ms)。默认 0(保持现状:不改变既有 per-task 重试节奏,除非显式配置非零值)。 */retryBackoffMs: number;
|
|
166
|
+
};
|
|
167
|
+
/** TUI 对话级错误重试卡片(回合终止性错误的自动重投,RFC-147)。 */
|
|
168
|
+
errorRetry: {
|
|
169
|
+
/** 每个 prompt 的最大自动重试次数。默认 5。 */maxAttempts: number; /** 自动重试倒计时间隔(ms)。默认 30_000。 */
|
|
170
|
+
intervalMs: number;
|
|
171
|
+
};
|
|
172
|
+
/** OAuth 跨进程刷新锁(陈旧锁清理阈值 = 等待超时 = 心跳间隔×3,三者必须同步派生,见 RFC-137 规则9)。 */
|
|
173
|
+
oauth: {
|
|
174
|
+
/** 刷新锁超时(ms)。默认 15_000,下限 1_000(心跳间隔 = timeoutMs/3,过小值会导致 setInterval 忙循环,见 RFC-230 二轮评审 P1)。 */refreshLockTimeoutMs: number;
|
|
175
|
+
};
|
|
176
|
+
}
|
|
177
|
+
declare const DEFAULT_RESILIENCE_CONFIG: ResilienceConfig;
|
|
178
|
+
/**
|
|
179
|
+
* `ResilienceConfig` 的深度可选覆盖形状。
|
|
180
|
+
*
|
|
181
|
+
* 手写局部类型,不引入 `@otto/shared` 的 `DeepPartial`——`@otto/env` 是比 `@otto/shared`
|
|
182
|
+
* 更底层的依赖叶子(`shared` 依赖 `env`,反向引入会成环),见 RFC-230 §3 D2 依赖方向核实。
|
|
183
|
+
*/
|
|
184
|
+
interface ResilienceConfigOverride {
|
|
185
|
+
stream?: Partial<ResilienceConfig['stream']>;
|
|
186
|
+
networkDisconnect?: Partial<ResilienceConfig['networkDisconnect']>;
|
|
187
|
+
mcp?: Partial<ResilienceConfig['mcp']>;
|
|
188
|
+
schedule?: Partial<ResilienceConfig['schedule']>;
|
|
189
|
+
errorRetry?: Partial<ResilienceConfig['errorRetry']>;
|
|
190
|
+
oauth?: Partial<ResilienceConfig['oauth']>;
|
|
191
|
+
}
|
|
192
|
+
/**
|
|
193
|
+
* 手写递归校验:把任意 `unknown`(通常来自 `JSON.parse` 的结果)收窄为合法的
|
|
194
|
+
* `ResilienceConfigOverride` 子集,非法部分被剔除并通过 `warn` 回调上报(不抛异常)。
|
|
195
|
+
*
|
|
196
|
+
* 校验规则(见 RFC-230 §3 D3):
|
|
197
|
+
* - 顶层不是 object,或某个顶层 key 的值不是 object → 该顶层 key 整体剔除。
|
|
198
|
+
* - 顶层 key 不在已知的 6 个 key 之内 → 该未知 key 整体剔除(防止拼写错误静默生效误导用户)。
|
|
199
|
+
* - 子字段存在但不是"合法数值"(见 `isValidFieldValue`)→ 单字段剔除,兄弟字段不受影响。
|
|
200
|
+
* - 子字段名不在已知字段名集合内 → 该未知子字段剔除。
|
|
201
|
+
*/
|
|
202
|
+
declare function validateResilienceOverride(input: unknown, warn: (message: string) => void): ResilienceConfigOverride | undefined;
|
|
203
|
+
/** 解析 `OTTO_RESILIENCE_CONFIG` 环境变量(JSON 字符串),非法值整体回退(无法字段级容错,因为连结构都没解析出来)。 */
|
|
204
|
+
declare function parseResilienceEnv(raw: string | undefined, warn?: (message: string) => void): ResilienceConfigOverride | undefined;
|
|
205
|
+
/**
|
|
206
|
+
* 合并四层配置源,返回完整的 `ResilienceConfig`(永远齐全,不含 undefined 字段)。
|
|
207
|
+
*
|
|
208
|
+
* 合并顺序(右覆盖左):默认值 → env → settings → 程序化注入。
|
|
209
|
+
* 消费方(`@otto/coding` 的 `App`)负责在正确时机(settings 已加载后)传入 `fromSettings`,
|
|
210
|
+
* 本函数是纯合并逻辑,不关心调用时机(见 RFC-230 §3 D5)。
|
|
211
|
+
*
|
|
212
|
+
* 三层输入(env/fromSettings/explicit)**全部**经 `validateResilienceOverride` 校验,不只校验
|
|
213
|
+
* env 层——`fromSettings`/`explicit` 虽然编译期类型是 `ResilienceConfigOverride`,但运行时仍可能
|
|
214
|
+
* 携带越界数值(如 settings 层理论上应已被 `@otto/setting` 的 zod schema 拦下,但那是另一个包
|
|
215
|
+
* 的独立校验实现,不能假设它与本包的规则永远同步;`explicit` 是程序化注入,TS 类型只保证是
|
|
216
|
+
* `number` 不保证范围)。统一在合并入口做一次权威校验,避免"同一条规则两处实现、日后漂移"。
|
|
217
|
+
*/
|
|
218
|
+
declare function resolveResilienceConfig(explicit?: ResilienceConfigOverride, fromSettings?: ResilienceConfigOverride, warn?: (message: string) => void): ResilienceConfig;
|
|
219
|
+
//#endregion
|
|
220
|
+
//#region src/index.d.ts
|
|
221
|
+
/** 清空队列并逐条调 sink。调用方应包一层 `(msg) => process.stderr.write(msg)` 绑定 this。 */
|
|
222
|
+
declare function flushEnvWarnings(sink: (msg: string) => void): void;
|
|
223
|
+
/** 取出并清空队列(测试用,不经过 sink)。 */
|
|
224
|
+
declare function drainEnvWarnings(): string[];
|
|
225
|
+
declare const normalizeEnv: (value: string | undefined, defaultValue: number) => number;
|
|
226
|
+
/**
|
|
227
|
+
* 解析 (0,1] 区间的比例型环境变量(如压缩硬顶占窗口比例 0.90)。
|
|
228
|
+
* normalizeEnv 走 parseInt 只收正整数,无法表达小数比例——本 helper 用 parseFloat 且约束
|
|
229
|
+
* 到 (0,1](比例语义:0 无意义、>1 超出窗口无意义),越界或非数回退默认值(不静默接受错值)。
|
|
230
|
+
*/
|
|
231
|
+
declare const normalizeEnvFraction: (value: string | undefined, defaultValue: number) => number;
|
|
232
|
+
declare function normalizePath(path: string): string;
|
|
233
|
+
/**
|
|
234
|
+
* 从 `startDir` 向上查找最近的 `pnpm-workspace.yaml`(monorepo root 标志文件),返回其所在目录。
|
|
235
|
+
* 找不到(如在非本仓的用户项目里运行)返回 `undefined`。
|
|
236
|
+
*
|
|
237
|
+
* RFC-130:单一真源实现——原先在 `@otto/coding`(`install-plugin.ts`,供 CLI `--repo` 消费)与
|
|
238
|
+
* `@otto/plugin`(`discovery.ts`,供仓库级插件发现消费)各自维护一份逐字相同的实现(copy-paste),
|
|
239
|
+
* 无法互相 import(`@otto/plugin` 是 `@otto/coding` 的依赖,反向 import 会成环)。收敛到零依赖的
|
|
240
|
+
* `@otto/env`(两者共同的底层依赖)消除维护漂移风险(终局审核 finding,2026-07-09)。
|
|
241
|
+
*
|
|
242
|
+
* 终止条件用 `dirname(dir) === dir`(到达文件系统根)而非固定层数上限——后者在深层嵌套路径下
|
|
243
|
+
* 会误截断(旧实现的 `i < 20` 上限已移除,审核 finding:该上限是冗余约束)。
|
|
244
|
+
*/
|
|
245
|
+
declare function findWorkspaceRoot(startDir: string): string | undefined;
|
|
246
|
+
/** SDK 版本号(`@otto/env` package.json version),会话创建时戳入 metadata。 */
|
|
247
|
+
declare const SDK_VERSION: string;
|
|
248
|
+
declare const OTTO_USER_AGENT: string;
|
|
249
|
+
declare const OTTO_ROOT = ".otto";
|
|
250
|
+
/** 用户手写主配置的文件名(env 可覆盖,便于后续迁移改名)。 */
|
|
251
|
+
declare const OTTO_CONFIG_FILENAME: string;
|
|
252
|
+
/** 项目级主配置相对路径(`.otto/config.json`):SettingsManager 按 workspaceDir 解析的唯一来源。 */
|
|
253
|
+
declare const OTTO_PROJECT_CONFIG_RELPATH: string;
|
|
254
|
+
declare const OTTO_HOME: string;
|
|
255
|
+
/** 会话存储根目录(`OTTO_HOME/sessions`):sessions.db、blob、写租约锁的共同父目录。 */
|
|
256
|
+
declare const OTTO_SESSIONS_DIR: string;
|
|
257
|
+
/**
|
|
258
|
+
* 会话写租约锁目录(`OTTO_HOME/sessions/locks`,RFC-159/171)——**跨消费方单源**(RFC-199 D5)。
|
|
259
|
+
*
|
|
260
|
+
* RFC-196 P0 事故(bc37146b):cli.ts 硬编码 `~/.otto` 而 fleet-scan 走 OTTO_HOME,
|
|
261
|
+
* OTTO_HOME 被 env 覆盖时两者锁目录分裂 → `otto ps --sweep` 误判 lease-released 误杀活进程。
|
|
262
|
+
* 教训固化:所有锁/租约目录派生必须经本常量(或 OTTO_HOME 显式派生),禁止各消费方
|
|
263
|
+
* 自行拼接 homedir()。当前消费方:cli.ts(storage.session.dir)、fleet-scan.ts(只读
|
|
264
|
+
* lease 检视)、session-pool-factory.ts(writeLease lockDir = `${dir}/locks` 随 storage 派生)。
|
|
265
|
+
*/
|
|
266
|
+
declare const OTTO_SESSION_LOCKS_DIR: string;
|
|
267
|
+
/**
|
|
268
|
+
* Session Authority daemon 的运行态目录(`OTTO_HOME/daemon`,RFC-335 M2)。
|
|
269
|
+
*
|
|
270
|
+
* 必须经 OTTO_HOME 派生、禁止消费方自行拼 `homedir()`——与 OTTO_SESSION_LOCKS_DIR 同一条
|
|
271
|
+
* 教训(RFC-196 P0 事故 bc37146b:硬编码 `~/.otto` 与 OTTO_HOME 分裂后 `otto ps --sweep`
|
|
272
|
+
* 误判并误杀活进程)。daemon state 同样承载"据此决定杀哪个进程"的语义,路径分裂的后果
|
|
273
|
+
* 是同一类误杀,故沿用单源约束。
|
|
274
|
+
*/
|
|
275
|
+
declare const OTTO_DAEMON_DIR: string;
|
|
276
|
+
declare const OTTO_USER_CONFIG_DIR: string;
|
|
277
|
+
declare const OTTO_PROJECT_DIR: string;
|
|
278
|
+
/** Claude Code 项目级目录名。 */
|
|
279
|
+
declare const CLAUDE_ROOT = ".claude";
|
|
280
|
+
/** Claude Code 用户级目录(`~/.claude`)。 */
|
|
281
|
+
declare const CLAUDE_HOME: string;
|
|
282
|
+
/**
|
|
283
|
+
* `.claude` 目录兼容总开关。默认开;`OTTO_CLAUDE_COMPAT=0|false|no|off` 关闭。
|
|
284
|
+
* 读 env 在调用时求值(便于测试注入)。MVP 仅覆盖 CLAUDE.md + skills,
|
|
285
|
+
* 二者格式同构、不跨 otto 既有信任边界(otto 本就读仓库内 AGENTS.md/.otto/skills),
|
|
286
|
+
* 故默认开安全;agents/commands/settings/hooks/MCP 仍 deferred。
|
|
287
|
+
*/
|
|
288
|
+
declare function isClaudeCompatEnabled(): boolean;
|
|
289
|
+
/**
|
|
290
|
+
* `.claude/settings.json` 翻译(高风险面)总开关。**默认关**——
|
|
291
|
+
* 需 `OTTO_CLAUDE_COMPAT_SETTINGS=1|true|yes|on` 显式启用。权限翻译有损(`Bash(cmd:*)` 不可表达
|
|
292
|
+
* → 降 ask),故 opt-in。亦受总开关 `isClaudeCompatEnabled()` 约束。
|
|
293
|
+
*/
|
|
294
|
+
declare function isClaudeSettingsCompatEnabled(): boolean;
|
|
295
|
+
/**
|
|
296
|
+
* `.claude` hooks 命令桥(最高风险=任意命令执行)总开关。**默认关**——
|
|
297
|
+
* 需 `OTTO_CLAUDE_COMPAT_HOOKS=1|true|yes|on` 显式启用。亦受总开关 + 项目信任门约束。
|
|
298
|
+
*/
|
|
299
|
+
declare function isClaudeHooksCompatEnabled(): boolean;
|
|
300
|
+
/**
|
|
301
|
+
* 日志文件路径。默认空字符串(不写文件,仅 stderr);
|
|
302
|
+
* 设置 OTTO_LOG_FILE 启用文件日志(如 OTTO_LOG_FILE=~/.otto/otto.log)。
|
|
303
|
+
*/
|
|
304
|
+
declare const LOG_FILE: string;
|
|
305
|
+
declare const SERVICE_PORT: number;
|
|
306
|
+
declare const SERVICE_URL: string;
|
|
307
|
+
/**
|
|
308
|
+
* RFC-341 D9:npm 源有限嗅探链——优先嗅探的内网 registry 列表(逗号分隔),缺省空。
|
|
309
|
+
*
|
|
310
|
+
* 公网源码缺省空 = 零内网痕迹(D1 隔离红线);内网构建/用户通过环境变量注入内网 jnpm
|
|
311
|
+
* registry URL 即可让升级检查/安装先查内网 jnpm、404 再回落公网 npm。
|
|
312
|
+
* 解析:逗号分隔、去空白、去空项、去尾部斜杠归一(后续拼 `<src>/<pkg>/latest`)。
|
|
313
|
+
*
|
|
314
|
+
* call-time 解析(非模块加载快照)——嗅探链消费方 `npm-registry-sniff.ts` 每次查表都读
|
|
315
|
+
* 当前 env,既 testable(测试可运行时改 env)又始终反映真实值。
|
|
316
|
+
*/
|
|
317
|
+
declare const resolveNpmSniffRegistries: () => readonly string[];
|
|
318
|
+
declare const LOG_LEVEL: "info" | "warn" | "error" | "debug" | "trace" | "fatal";
|
|
319
|
+
declare const SETTING_OFFLINE_MODE_ENABLED: boolean;
|
|
320
|
+
/**
|
|
321
|
+
* 影子模式(RFC-345):进程级布尔,**调用时求值**(对齐 `isClaudeCompatEnabled` 范式,
|
|
322
|
+
* 而非 offline 的模块级 const)。
|
|
323
|
+
*
|
|
324
|
+
* 为什么必须函数式而非 const:`OTTO_SHADOW` 由 `packages/cli/src/cli.ts` 在**运行时**
|
|
325
|
+
* 解析 `--shadow` flag 后写入 `process.env`,而 `@otto/env` 在 cli.ts 顶部就被 import——
|
|
326
|
+
* 若用模块级 const,常量在 flag 写入前就已冻结为 false。调用时求值规避此时序陷阱,
|
|
327
|
+
* 也便于测试注入。(offline 模式可用 const 是因为 `OTTO_OFFLINE` 由外部 shell 预设、
|
|
328
|
+
* node 启动前就存在——两者注入时机不同,范式不能照搬。)
|
|
329
|
+
*
|
|
330
|
+
* 语义:为真时该进程及其全部子进程/worker 在整个生命周期内不向磁盘写入任何 otto 自有
|
|
331
|
+
* 状态(凭据/会话/记忆/配置/trace/日志),既有磁盘状态照常只读继承(overlay 语义)。
|
|
332
|
+
*
|
|
333
|
+
* 传染面:`OTTO_SHADOW` 走 `OTTO_` 前缀,由 `@otto/shared` 的 `buildAllowedEnv` 白名单
|
|
334
|
+
* 自动透传给全部 spawn 子进程;worker_threads 共享 `process.env` 天然继承——无需额外接线。
|
|
335
|
+
*
|
|
336
|
+
* 判断该开关的位置受 RFC-345 §D1 白名单约束(架构门禁强制),不得在业务层散落 `if (shadow)`。
|
|
337
|
+
*/
|
|
338
|
+
declare function isShadowModeEnabled(): boolean;
|
|
339
|
+
declare const AUTH_PATH: string;
|
|
340
|
+
/** 用户身份文件路径。`/login --service` 写入 user_id + bearer token。 */
|
|
341
|
+
declare const OTTO_IDENTITY_PATH: string;
|
|
342
|
+
/** 远端服务用户身份。login 后持久化到 OTTO_IDENTITY_PATH。 */
|
|
343
|
+
interface UserIdentity {
|
|
344
|
+
readonly userId: string;
|
|
345
|
+
readonly token: string;
|
|
346
|
+
readonly serviceUrl: string;
|
|
347
|
+
readonly loginAt: number;
|
|
348
|
+
}
|
|
349
|
+
/** 从 identity.json 加载 UserIdentity。文件不存在/解析失败返回 null。 */
|
|
350
|
+
declare function loadUserIdentity(path?: string): Promise<UserIdentity | null>;
|
|
351
|
+
//#endregion
|
|
352
|
+
export { AUTH_PATH, BUILD_ID, CLAUDE_HOME, CLAUDE_ROOT, type ConfigLayer, type ConfigLayerKind, type ConfigLayerScope, DEFAULT_RESILIENCE_CONFIG, type DecodedLeaseToken, LOG_FILE, LOG_LEVEL, OTTO_CONFIG_FILENAME, OTTO_DAEMON_DIR, OTTO_HOME, OTTO_IDENTITY_PATH, OTTO_PROJECT_CONFIG_RELPATH, OTTO_PROJECT_DIR, OTTO_ROOT, OTTO_SESSIONS_DIR, OTTO_SESSION_LOCKS_DIR, OTTO_USER_AGENT, OTTO_USER_CONFIG_DIR, type OttoProcessInfo, type ResilienceConfig, type ResilienceConfigOverride, type ResolveConfigLayersOptions, SDK_VERSION, SERVICE_PORT, SERVICE_URL, SETTING_OFFLINE_MODE_ENABLED, UserIdentity, currentHostname, decodeLeaseToken, drainEnvWarnings, encodeLeaseToken, findWorkspaceRoot, flushEnvWarnings, isClaudeCompatEnabled, isClaudeHooksCompatEnabled, isClaudeSettingsCompatEnabled, isContainerEnvironment, isProcessAlive, isShadowModeEnabled, listOttoProcesses, loadUserIdentity, normalizeEnv, normalizeEnvFraction, normalizePath, parseEtimeMs, parseOttoProcessTable, parseResilienceEnv, resolveConfigLayers, resolveNpmSniffRegistries, resolveResilienceConfig, validateResilienceOverride };
|
|
353
|
+
//# sourceMappingURL=index.d.ts.map
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,2 @@
|
|
|
1
|
+
import{homedir as e,hostname as t}from"node:os";import{existsSync as n,readFileSync as r}from"node:fs";import{dirname as i,join as a,normalize as o,sep as s}from"node:path";import{spawnSync as c}from"node:child_process";const l=4512;function u(e){let{cwd:t,homedir:n,workspaceRoot:r}=e,i=e.claudeCompat??q(),o=[];return o.push({kind:`otto`,scope:`project`,dir:j(a(t,`.otto`))}),r&&j(r)!==j(t)&&o.push({kind:`otto`,scope:`repository`,dir:j(a(r,`.otto`))}),i&&o.push({kind:`claude`,scope:`project`,dir:j(a(t,`.claude`))}),n&&o.push({kind:`otto`,scope:`user`,dir:j(a(n,`.otto`))}),i&&n&&o.push({kind:`claude`,scope:`user`,dir:j(a(n,`.claude`))}),o}function d(e){try{return process.kill(e,0),!0}catch{return!1}}function f(){return t()}function p(e,t,n){return`${e}:${t}:${n}`}function m(e){let t=e.indexOf(`:`);if(t===-1)return{token:e};let n=e.indexOf(`:`,t+1);if(n===-1)return{token:e};let r=e.slice(0,t),i=e.slice(t+1,n),a=e.slice(n+1),o=Number(i),s=Number.isFinite(o)&&o>0?o:void 0;return s!==void 0&&a.length>0?{token:r,pid:s,host:a}:{token:r}}function h(e){let t=e.trim().match(/^(?:(\d+)-)?(?:(\d+):)?(\d+):(\d+)$/);if(!t)return 0;let[,n,r,i,a]=t;return(Number(n??0)*86400+Number(r??0)*3600+Number(i)*60+Number(a))*1e3}function ee(e){return e.match(/--continue\s+([0-9a-f-]{36})/)?.[1]}function te(e){let t=e.match(/--restart-generation=(\d+)/);return t?Number(t[1]):void 0}function g(e=`bin/otto.js`){let t=c(`ps`,[`-axo`,`pid=,ppid=,rss=,tty=,etime=,args=`],{encoding:`utf8`,timeout:1e4,maxBuffer:16*1024*1024});return t.status!==0||!t.stdout?[]:_(t.stdout,e)}function _(e,t=`bin/otto.js`){let n=[];for(let r of e.split(`
|
|
2
|
+
`)){if(!r.includes(t))continue;let e=r.match(/^\s*(\d+)\s+(\d+)\s+(\d+)\s+(\S+)\s+(\S+)\s+(.*)$/);if(!e)continue;let[,i,a,o,s,c,l]=e;l.includes(`spawn-helper`)||n.push({pid:Number(i),ppid:Number(a),rssKb:Number(o),tty:s,etimeMs:h(c),command:l,sessionId:ee(l),generation:te(l)})}return n}function v(){if(process.platform===`darwin`)return!1;try{if(n(`/.dockerenv`))return!0;if(n(`/proc/1/cgroup`)){let e=r(`/proc/1/cgroup`,`utf8`);return/docker|kubepods|containerd/.test(e)}}catch{}return!1}const y={stream:{maxRetries:5,initialDelayMs:500,backoffFactor:2,maxDelayMs:6e4},networkDisconnect:{maxRetries:5,intervalMs:3e4},mcp:{maxReconnectAttempts:3,reconnectDelayMs:500,requestTimeoutMs:3e4},schedule:{retryBackoffMs:0},errorRetry:{maxAttempts:5,intervalMs:3e4},oauth:{refreshLockTimeoutMs:15e3}},b={stream:[`maxRetries`,`initialDelayMs`,`backoffFactor`,`maxDelayMs`],networkDisconnect:[`maxRetries`,`intervalMs`],mcp:[`maxReconnectAttempts`,`reconnectDelayMs`,`requestTimeoutMs`],schedule:[`retryBackoffMs`],errorRetry:[`maxAttempts`,`intervalMs`],oauth:[`refreshLockTimeoutMs`]},x=Object.keys(b),S=new Set([`mcp.maxReconnectAttempts`,`schedule.retryBackoffMs`]);function ne(e,t,n){return typeof n!=`number`||!Number.isFinite(n)||(S.has(`${e}.${t}`)?n<0:n<=0)?!1:e===`oauth`&&t===`refreshLockTimeoutMs`?n>=1e3:!0}function C(e,t){if(typeof e!=`object`||!e||Array.isArray(e)){t(`resilience config must be an object, got: ${typeof e}`);return}let n={},r=e;for(let e of Object.keys(r)){if(!x.includes(e)){t(`resilience config has unknown top-level key "${e}", ignored`);continue}let i=e,a=r[e];if(typeof a!=`object`||!a||Array.isArray(a)){t(`resilience.${i} must be an object, got: ${typeof a}, ignored`);continue}let o=a,s={};for(let e of Object.keys(o)){if(!b[i].includes(e)){t(`resilience.${i}.${e} is not a recognized field, ignored`);continue}let n=o[e];if(!ne(i,e,n)){t(`resilience.${i}.${e} has invalid value (${JSON.stringify(n)}), ignored`);continue}s[e]=n}Object.keys(s).length>0&&(n[i]=s)}return n}function w(e,t=e=>console.warn(`[resilience-config] ${e}`)){if(!e)return;let n;try{n=JSON.parse(e)}catch(e){t(`OTTO_RESILIENCE_CONFIG is not valid JSON, ignoring entirely: ${e instanceof Error?e.message:String(e)}`);return}return C(n,t)}function T(e,t){return t?{...e,...t}:e}function re(e,t,n=e=>console.warn(`[resilience-config] ${e}`)){let r=[w(process.env.OTTO_RESILIENCE_CONFIG,n),t?C(t,n):void 0,e?C(e,n):void 0],i={stream:{...y.stream},networkDisconnect:{...y.networkDisconnect},mcp:{...y.mcp},schedule:{...y.schedule},errorRetry:{...y.errorRetry},oauth:{...y.oauth}};for(let e of r)e&&(i={stream:T(i.stream,e.stream),networkDisconnect:T(i.networkDisconnect,e.networkDisconnect),mcp:T(i.mcp,e.mcp),schedule:T(i.schedule,e.schedule),errorRetry:T(i.errorRetry,e.errorRetry),oauth:T(i.oauth,e.oauth)});return i}const E=[];function D(e){let t=E.splice(0);for(let n of t)e(n)}function O(){return E.splice(0)}const k=(e,t)=>{if(e===void 0)return t;let n=parseInt(e,10);return isNaN(n)||n<=0?(E.push(`Invalid environment variable value: ${e}. Using default: ${t}`),t):n},A=(e,t)=>{if(e===void 0)return t;let n=parseFloat(e);return isNaN(n)||n<=0||n>1?(E.push(`Invalid fraction environment variable value: ${e}. Using default: ${t}`),t):n};function j(e){return o(e).replaceAll(s===`\\`?`\\`:s,`/`)}function M(e){let t=e;for(;;){if(n(a(t,`pnpm-workspace.yaml`)))return t;let e=i(t);if(e===t)return;t=e}}function N(){try{let e=j(`${j(new URL(`.`,import.meta.url).pathname)}/../package.json`);return JSON.parse(r(e,`utf-8`)).version||`unknown`}catch(e){return console.warn(`Failed to read package version:`,e),`unknown`}}const P=N(),F=`otto/${P}`,I=`.otto`,L=process.env.OTTO_CONFIG_FILENAME||`config.json`,R=`${I}/${L}`,z=j(process.env.OTTO_HOME||`${e()}/.otto`),B=`${z}/sessions`,V=`${B}/locks`,H=`${z}/daemon`,U=j(e()+`/.config/otto`),W=j(`${process.cwd()}/${I}`),G=`.claude`,K=j(`${e()}/${G}`);function q(){let e=process.env.OTTO_CLAUDE_COMPAT?.toLowerCase();return!(e===`0`||e===`false`||e===`no`||e===`off`)}function J(){if(!q())return!1;let e=process.env.OTTO_CLAUDE_COMPAT_SETTINGS?.toLowerCase();return e===`1`||e===`true`||e===`yes`||e===`on`}function Y(){if(!q())return!1;let e=process.env.OTTO_CLAUDE_COMPAT_HOOKS?.toLowerCase();return e===`1`||e===`true`||e===`yes`||e===`on`}const X=process.env.OTTO_LOG_FILE?j(process.env.OTTO_LOG_FILE):``,Z=k(process.env.OTTO_SERVICE_PORT,8417),ie=process.env.OTTO_SERVICE_URL??`http://127.0.0.1:${Z}`,ae=()=>(process.env.OTTO_NPM_SNIFF_REGISTRIES??``).split(`,`).map(e=>e.trim().replace(/\/+$/,``)).filter(e=>e.length>0),oe=process.env.OTTO_LOG_LEVEL||(process.env.NODE_ENV===`production`?`info`:`trace`),se=process.env.OTTO_OFFLINE===`1`||process.env.OTTO_OFFLINE?.toLowerCase()===`true`||process.env.OTTO_OFFLINE?.toLowerCase()===`yes`;function Q(){let e=process.env.OTTO_SHADOW?.toLowerCase();return e===`1`||e===`true`||e===`yes`||e===`on`}const ce=j(process.env.OTTO_AUTH_PATH||`${U}/auth.json`),$=j(process.env.OTTO_IDENTITY_PATH||`${z}/identity.json`);async function le(e){let t=e??$;try{let{readFile:e}=await import(`node:fs/promises`),n=await e(t,`utf-8`),r=JSON.parse(n);return typeof r.userId==`string`&&typeof r.token==`string`&&typeof r.serviceUrl==`string`&&typeof r.loginAt==`number`?r:null}catch{return null}}export{ce as AUTH_PATH,l as BUILD_ID,K as CLAUDE_HOME,G as CLAUDE_ROOT,y as DEFAULT_RESILIENCE_CONFIG,X as LOG_FILE,oe as LOG_LEVEL,L as OTTO_CONFIG_FILENAME,H as OTTO_DAEMON_DIR,z as OTTO_HOME,$ as OTTO_IDENTITY_PATH,R as OTTO_PROJECT_CONFIG_RELPATH,W as OTTO_PROJECT_DIR,I as OTTO_ROOT,B as OTTO_SESSIONS_DIR,V as OTTO_SESSION_LOCKS_DIR,F as OTTO_USER_AGENT,U as OTTO_USER_CONFIG_DIR,P as SDK_VERSION,Z as SERVICE_PORT,ie as SERVICE_URL,se as SETTING_OFFLINE_MODE_ENABLED,f as currentHostname,m as decodeLeaseToken,O as drainEnvWarnings,p as encodeLeaseToken,M as findWorkspaceRoot,D as flushEnvWarnings,q as isClaudeCompatEnabled,Y as isClaudeHooksCompatEnabled,J as isClaudeSettingsCompatEnabled,v as isContainerEnvironment,d as isProcessAlive,Q as isShadowModeEnabled,g as listOttoProcesses,le as loadUserIdentity,k as normalizeEnv,A as normalizeEnvFraction,j as normalizePath,h as parseEtimeMs,_ as parseOttoProcessTable,w as parseResilienceEnv,u as resolveConfigLayers,ae as resolveNpmSniffRegistries,re as resolveResilienceConfig,C as validateResilienceOverride};
|
package/package.json
ADDED
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "@x-otto/env",
|
|
3
|
+
"version": "0.1.0-alpha.0",
|
|
4
|
+
"files": [
|
|
5
|
+
"dist"
|
|
6
|
+
],
|
|
7
|
+
"type": "module",
|
|
8
|
+
"main": "./dist/index.js",
|
|
9
|
+
"types": "./dist/index.d.ts",
|
|
10
|
+
"exports": {
|
|
11
|
+
".": {
|
|
12
|
+
"types": "./dist/index.d.ts",
|
|
13
|
+
"import": "./dist/index.js"
|
|
14
|
+
}
|
|
15
|
+
},
|
|
16
|
+
"publishConfig": {
|
|
17
|
+
"access": "public",
|
|
18
|
+
"registry": "https://registry.npmjs.org",
|
|
19
|
+
"tag": "alpha"
|
|
20
|
+
},
|
|
21
|
+
"scripts": {
|
|
22
|
+
"build": "tsdown && rm -f dist/*.map && sed -i '' '\\|//# sourceMappingURL=|d' dist/*.js dist/browser/index.js 2>/dev/null; true",
|
|
23
|
+
"typecheck:project": "tsc -p tsconfig.json --noEmit",
|
|
24
|
+
"typecheck:file": "tsc src/index.ts --noEmit --target ES2024 --module ESNext --moduleResolution bundler --esModuleInterop --skipLibCheck",
|
|
25
|
+
"typecheck": "tsc --noEmit",
|
|
26
|
+
"clean": "rm -rf dist"
|
|
27
|
+
}
|
|
28
|
+
}
|