adde-acp 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/LICENSE +21 -0
- package/README.ko.md +88 -0
- package/README.md +88 -0
- package/dist/backend/acp/client.d.ts +149 -0
- package/dist/backend/acp/client.js +538 -0
- package/dist/backend/acp/client.js.map +1 -0
- package/dist/backend/acp/index.d.ts +8 -0
- package/dist/backend/acp/index.js +7 -0
- package/dist/backend/acp/index.js.map +1 -0
- package/dist/backend/acp/lifecycle.d.ts +15 -0
- package/dist/backend/acp/lifecycle.js +56 -0
- package/dist/backend/acp/lifecycle.js.map +1 -0
- package/dist/backend/acp/perm-diff.d.ts +37 -0
- package/dist/backend/acp/perm-diff.js +58 -0
- package/dist/backend/acp/perm-diff.js.map +1 -0
- package/dist/backend/acp/spawn.d.ts +20 -0
- package/dist/backend/acp/spawn.js +70 -0
- package/dist/backend/acp/spawn.js.map +1 -0
- package/dist/cli/adde.d.ts +2 -0
- package/dist/cli/adde.js +11 -0
- package/dist/cli/adde.js.map +1 -0
- package/dist/cli/alias.d.ts +45 -0
- package/dist/cli/alias.js +94 -0
- package/dist/cli/alias.js.map +1 -0
- package/dist/cli/completion.d.ts +4 -0
- package/dist/cli/completion.js +209 -0
- package/dist/cli/completion.js.map +1 -0
- package/dist/cli/init.d.ts +3 -0
- package/dist/cli/init.js +114 -0
- package/dist/cli/init.js.map +1 -0
- package/dist/cli/lane.d.ts +20 -0
- package/dist/cli/lane.js +350 -0
- package/dist/cli/lane.js.map +1 -0
- package/dist/cli/ops.d.ts +5 -0
- package/dist/cli/ops.js +230 -0
- package/dist/cli/ops.js.map +1 -0
- package/dist/cli/prompt.d.ts +15 -0
- package/dist/cli/prompt.js +41 -0
- package/dist/cli/prompt.js.map +1 -0
- package/dist/cli/run.d.ts +5 -0
- package/dist/cli/run.js +216 -0
- package/dist/cli/run.js.map +1 -0
- package/dist/cli/spec.d.ts +48 -0
- package/dist/cli/spec.js +98 -0
- package/dist/cli/spec.js.map +1 -0
- package/dist/core/diagnostics.d.ts +73 -0
- package/dist/core/diagnostics.js +333 -0
- package/dist/core/diagnostics.js.map +1 -0
- package/dist/core/index.d.ts +11 -0
- package/dist/core/index.js +9 -0
- package/dist/core/index.js.map +1 -0
- package/dist/core/injector.d.ts +27 -0
- package/dist/core/injector.js +297 -0
- package/dist/core/injector.js.map +1 -0
- package/dist/core/lane-config.d.ts +80 -0
- package/dist/core/lane-config.js +303 -0
- package/dist/core/lane-config.js.map +1 -0
- package/dist/core/launchd.d.ts +81 -0
- package/dist/core/launchd.js +216 -0
- package/dist/core/launchd.js.map +1 -0
- package/dist/core/messages.d.ts +31 -0
- package/dist/core/messages.js +71 -0
- package/dist/core/messages.js.map +1 -0
- package/dist/core/queue.d.ts +74 -0
- package/dist/core/queue.js +227 -0
- package/dist/core/queue.js.map +1 -0
- package/dist/core/runtime-state.d.ts +52 -0
- package/dist/core/runtime-state.js +90 -0
- package/dist/core/runtime-state.js.map +1 -0
- package/dist/core/session-ledger.d.ts +25 -0
- package/dist/core/session-ledger.js +89 -0
- package/dist/core/session-ledger.js.map +1 -0
- package/dist/core/supervisor.d.ts +41 -0
- package/dist/core/supervisor.js +315 -0
- package/dist/core/supervisor.js.map +1 -0
- package/dist/core/transcript.d.ts +22 -0
- package/dist/core/transcript.js +93 -0
- package/dist/core/transcript.js.map +1 -0
- package/dist/core/update-check.d.ts +25 -0
- package/dist/core/update-check.js +142 -0
- package/dist/core/update-check.js.map +1 -0
- package/dist/core/version.d.ts +7 -0
- package/dist/core/version.js +32 -0
- package/dist/core/version.js.map +1 -0
- package/dist/gate/gate.d.ts +41 -0
- package/dist/gate/gate.js +28 -0
- package/dist/gate/gate.js.map +1 -0
- package/dist/gate/index.d.ts +6 -0
- package/dist/gate/index.js +6 -0
- package/dist/gate/index.js.map +1 -0
- package/dist/shared/conf.d.ts +54 -0
- package/dist/shared/conf.js +85 -0
- package/dist/shared/conf.js.map +1 -0
- package/dist/shared/deny-match.d.ts +19 -0
- package/dist/shared/deny-match.js +122 -0
- package/dist/shared/deny-match.js.map +1 -0
- package/dist/shared/envelope.d.ts +37 -0
- package/dist/shared/envelope.js +91 -0
- package/dist/shared/envelope.js.map +1 -0
- package/dist/shared/errors.d.ts +8 -0
- package/dist/shared/errors.js +23 -0
- package/dist/shared/errors.js.map +1 -0
- package/dist/shared/fs-atomic.d.ts +17 -0
- package/dist/shared/fs-atomic.js +31 -0
- package/dist/shared/fs-atomic.js.map +1 -0
- package/dist/shared/i18n.d.ts +23 -0
- package/dist/shared/i18n.js +53 -0
- package/dist/shared/i18n.js.map +1 -0
- package/dist/shared/locales/en.d.ts +393 -0
- package/dist/shared/locales/en.js +447 -0
- package/dist/shared/locales/en.js.map +1 -0
- package/dist/shared/locales/ko.d.ts +389 -0
- package/dist/shared/locales/ko.js +443 -0
- package/dist/shared/locales/ko.js.map +1 -0
- package/dist/shared/mask.d.ts +6 -0
- package/dist/shared/mask.js +28 -0
- package/dist/shared/mask.js.map +1 -0
- package/dist/shared/notify.d.ts +15 -0
- package/dist/shared/notify.js +20 -0
- package/dist/shared/notify.js.map +1 -0
- package/dist/shared/paths.d.ts +42 -0
- package/dist/shared/paths.js +83 -0
- package/dist/shared/paths.js.map +1 -0
- package/dist/src-adapters/index.d.ts +8 -0
- package/dist/src-adapters/index.js +6 -0
- package/dist/src-adapters/index.js.map +1 -0
- package/dist/src-adapters/markdown.d.ts +80 -0
- package/dist/src-adapters/markdown.js +794 -0
- package/dist/src-adapters/markdown.js.map +1 -0
- package/dist/src-adapters/source.d.ts +33 -0
- package/dist/src-adapters/source.js +3 -0
- package/dist/src-adapters/source.js.map +1 -0
- package/dist/src-adapters/telegram.d.ts +48 -0
- package/dist/src-adapters/telegram.js +412 -0
- package/dist/src-adapters/telegram.js.map +1 -0
- package/package.json +62 -0
|
@@ -0,0 +1,303 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* 레인 .conf 설정 관리(생성·조회·삭제) — `adde lane` 서브커맨드의 코어.
|
|
3
|
+
* 파일 1개 = 레인 1개 (~/.config/adde/<proj>/lanes.d/<lane>.conf).
|
|
4
|
+
* 모든 검증을 통과한 뒤에만 디스크에 쓴다(validate-then-commit). 쓰기는 tmp→rename 원자적.
|
|
5
|
+
*/
|
|
6
|
+
import { t, SUPPORTED_LOCALES } from "../shared/i18n.js";
|
|
7
|
+
import { readdir, readFile, mkdir, unlink, stat } from "node:fs/promises";
|
|
8
|
+
import { join, dirname, resolve } from "node:path";
|
|
9
|
+
import { parseLaneConf, serializeLaneConf } from "../shared/conf.js";
|
|
10
|
+
import { lanePaths, defaultBase, expandTilde, isPathInside, normCasePath, pathsOverlap, } from "../shared/paths.js";
|
|
11
|
+
import { parseDenyEntry, DEFAULT_AUTOPASS_DENYLIST } from "../shared/deny-match.js";
|
|
12
|
+
import { atomicWrite, secureLaneDirs } from "../shared/fs-atomic.js";
|
|
13
|
+
/** proj/lane 식별자 — 디렉터리·파일명이 되므로 안전 문자만 허용. */
|
|
14
|
+
const NAME_RE = /^[A-Za-z0-9_-]+$/;
|
|
15
|
+
/** telegram chat id — 그룹은 음수일 수 있음. */
|
|
16
|
+
const CHAT_ID_RE = /^-?\d+$/;
|
|
17
|
+
/** allowlist/denylist 도구명 — 도구 식별자 안전 문자셋(C). */
|
|
18
|
+
const ALLOWLIST_ITEM_RE = /^[A-Za-z0-9_.-]+$/;
|
|
19
|
+
/** allow_from 항목 — telegram user/chat id(그룹은 음수). */
|
|
20
|
+
const ALLOW_FROM_RE = /^-?\d+$/;
|
|
21
|
+
/** 파일 권한 모드 허용값. 미지정 시 private(secure-by-default). */
|
|
22
|
+
export const KNOWN_FILE_MODES = ["private", "shared"];
|
|
23
|
+
/** 구현된 perm_tier 값. 오타 시 acp 처럼 동작(안전 방향)하지만 의도와 다르므로 생성 시 경고. */
|
|
24
|
+
export const KNOWN_PERM_TIERS = ["acp", "autopass"];
|
|
25
|
+
const SUPPORTED_SOURCES = ["telegram", "markdown"];
|
|
26
|
+
/** 검증 실패를 식별하기 위한 전용 에러(흡수 금지 — 호출자가 메시지를 사용자에게 전달). */
|
|
27
|
+
export class LaneConfigError extends Error {
|
|
28
|
+
name = "LaneConfigError";
|
|
29
|
+
}
|
|
30
|
+
/** 봇 토큰 대략 형식: <숫자id>:<영숫자/_-> (형식 오타 조기 발견용 휴리스틱). */
|
|
31
|
+
const TELEGRAM_TOKEN_RE = /^\d+:[A-Za-z0-9_-]+$/;
|
|
32
|
+
/**
|
|
33
|
+
* 쓰기를 막지 않는 사전 검증 경고 수집 — cwd/markdown root 부재·telegram 토큰 형식.
|
|
34
|
+
* 하드 오류(이름·source·chat_id 형식·중복)는 laneAdd 본문에서 throw 로 차단한다(여기 아님).
|
|
35
|
+
*/
|
|
36
|
+
async function collectAddWarnings(conf, token) {
|
|
37
|
+
const warnings = [];
|
|
38
|
+
if (conf.cwd) {
|
|
39
|
+
const p = expandTilde(conf.cwd);
|
|
40
|
+
if (!(await exists(p))) {
|
|
41
|
+
warnings.push(t("laneConfig.warn.cwdMissing", { path: p }));
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
if (conf.source === "markdown") {
|
|
45
|
+
if (!conf.root) {
|
|
46
|
+
warnings.push(t("laneConfig.warn.mdRootMissingConf"));
|
|
47
|
+
}
|
|
48
|
+
else if (!(await exists(expandTilde(conf.root)))) {
|
|
49
|
+
warnings.push(t("laneConfig.warn.mdRootNotFound", { path: expandTilde(conf.root) }));
|
|
50
|
+
}
|
|
51
|
+
// 경로 상호 배타 사전 경고 — 기동 시 fail-closed 거부되는 조합을 생성 시점에 미리 안내.
|
|
52
|
+
// 해석 규칙은 markdown 어댑터 resolvePaths·기동 가드와 동일(미지정 시 inbox 형제, darwin 대소문자 정규화).
|
|
53
|
+
if (conf.root && conf.inbox) {
|
|
54
|
+
const root = expandTilde(conf.root);
|
|
55
|
+
const inboxPath = resolve(join(root, conf.inbox));
|
|
56
|
+
const inboxDir = dirname(inboxPath);
|
|
57
|
+
const approvalsDir = resolve(conf.approvals ? join(root, conf.approvals) : join(inboxDir, "approvals"));
|
|
58
|
+
const outboxDir = resolve(conf.outbox ? join(root, conf.outbox) : join(inboxDir, "out"));
|
|
59
|
+
const quarantineDir = resolve(join(inboxDir, ".conflicts"));
|
|
60
|
+
const insideNorm = (child, parent) => isPathInside(normCasePath(child), normCasePath(parent));
|
|
61
|
+
if (pathsOverlap(outboxDir, approvalsDir) ||
|
|
62
|
+
pathsOverlap(approvalsDir, quarantineDir) ||
|
|
63
|
+
pathsOverlap(outboxDir, quarantineDir) ||
|
|
64
|
+
insideNorm(inboxPath, approvalsDir) ||
|
|
65
|
+
insideNorm(inboxPath, outboxDir)) {
|
|
66
|
+
warnings.push(t("laneConfig.warn.mdPathOverlap", {
|
|
67
|
+
inbox: inboxPath,
|
|
68
|
+
approvals: approvalsDir,
|
|
69
|
+
outbox: outboxDir,
|
|
70
|
+
}));
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
if (conf.source === "telegram" && token !== undefined && !TELEGRAM_TOKEN_RE.test(token)) {
|
|
75
|
+
warnings.push(t("laneConfig.warn.tokenFormat"));
|
|
76
|
+
}
|
|
77
|
+
// 인바운드 인증 앵커 부재 → 기동 시 전 인바운드 fail-closed 무시. 생성 시점에 미리 안내.
|
|
78
|
+
// 자기 인증은 개인 chat(양수 chat_id)만 성립 — 그룹(음수) chat_id 는 회신 대상일 뿐 멤버를
|
|
79
|
+
// 인증하지 않으므로, 그룹만 있고 allow_from 이 없으면 여전히 전부 거부된다.
|
|
80
|
+
if (conf.source === "telegram") {
|
|
81
|
+
const chatIdNum = conf.chat_id ? Number(conf.chat_id) : NaN;
|
|
82
|
+
const hasSelfAuth = Number.isFinite(chatIdNum) && chatIdNum > 0;
|
|
83
|
+
if (!hasSelfAuth && !conf.allow_from) {
|
|
84
|
+
warnings.push(t("laneConfig.warn.telegramNoAuth"));
|
|
85
|
+
}
|
|
86
|
+
}
|
|
87
|
+
if (conf.lang && !SUPPORTED_LOCALES.includes(conf.lang)) {
|
|
88
|
+
warnings.push(t("laneConfig.warn.badLang", { lang: conf.lang, supported: SUPPORTED_LOCALES.join("|") }));
|
|
89
|
+
}
|
|
90
|
+
if (!KNOWN_PERM_TIERS.includes(conf.perm_tier)) {
|
|
91
|
+
warnings.push(t("laneConfig.warn.permTierUnknown", {
|
|
92
|
+
tier: conf.perm_tier,
|
|
93
|
+
known: KNOWN_PERM_TIERS.join("|"),
|
|
94
|
+
}));
|
|
95
|
+
}
|
|
96
|
+
if (conf.perm_tier === "autopass") {
|
|
97
|
+
warnings.push(t("laneConfig.warn.autopassBanner"));
|
|
98
|
+
if (conf.denylist.length === 0) {
|
|
99
|
+
warnings.push(t("laneConfig.warn.autopassEmptyDeny"));
|
|
100
|
+
}
|
|
101
|
+
const overlap = conf.denylist.filter((t) => conf.allowlist.includes(t));
|
|
102
|
+
if (overlap.length > 0) {
|
|
103
|
+
warnings.push(t("laneConfig.warn.allowDenyOverlap", { tools: overlap.join(", ") }));
|
|
104
|
+
}
|
|
105
|
+
}
|
|
106
|
+
return warnings;
|
|
107
|
+
}
|
|
108
|
+
/** CSV 를 트림·빈값 제거해 항목 배열로. allow_from 등 목록 문자열 파싱 공용. */
|
|
109
|
+
export function parseCsv(raw) {
|
|
110
|
+
return raw
|
|
111
|
+
.split(",")
|
|
112
|
+
.map((s) => s.trim())
|
|
113
|
+
.filter((s) => s.length > 0);
|
|
114
|
+
}
|
|
115
|
+
/** conf.file_mode → 정규화된 권한 모드. 미지정/미지값은 private(secure-by-default). */
|
|
116
|
+
export function resolveFileMode(mode) {
|
|
117
|
+
return mode === "shared" ? "shared" : "private";
|
|
118
|
+
}
|
|
119
|
+
function assertName(kind, value) {
|
|
120
|
+
if (!value)
|
|
121
|
+
throw new LaneConfigError(t("laneConfig.err.emptyIdent", { kind }));
|
|
122
|
+
if (!NAME_RE.test(value)) {
|
|
123
|
+
throw new LaneConfigError(t("laneConfig.err.badIdent", { kind, value }));
|
|
124
|
+
}
|
|
125
|
+
}
|
|
126
|
+
/** tmp 파일에 쓴 뒤 rename 으로 원자적 교체(shared 위임 — mode 시그니처 유지). */
|
|
127
|
+
async function writeAtomic(path, content, mode) {
|
|
128
|
+
await atomicWrite(path, content, mode === undefined ? undefined : { mode });
|
|
129
|
+
}
|
|
130
|
+
async function exists(path) {
|
|
131
|
+
try {
|
|
132
|
+
await stat(path);
|
|
133
|
+
return true;
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
return false;
|
|
137
|
+
}
|
|
138
|
+
}
|
|
139
|
+
/**
|
|
140
|
+
* `adde lane add` — 레인 conf 생성. 검증 통과 후 원자적 기록.
|
|
141
|
+
* 기존 conf 가 있으면 force 없이는 거부한다.
|
|
142
|
+
*/
|
|
143
|
+
export async function laneAdd(proj, lane, opts = {}) {
|
|
144
|
+
assertName("proj", proj);
|
|
145
|
+
assertName("lane", lane);
|
|
146
|
+
const source = (opts.source ?? "telegram");
|
|
147
|
+
if (!SUPPORTED_SOURCES.includes(source)) {
|
|
148
|
+
throw new LaneConfigError(t("laneConfig.err.badSource", { source, supported: SUPPORTED_SOURCES.join(" | ") }));
|
|
149
|
+
}
|
|
150
|
+
const src = source;
|
|
151
|
+
if (opts.chat_id !== undefined && opts.chat_id !== "" && !CHAT_ID_RE.test(opts.chat_id)) {
|
|
152
|
+
throw new LaneConfigError(t("laneConfig.err.badChatId", { chatId: opts.chat_id }));
|
|
153
|
+
}
|
|
154
|
+
if (opts.token !== undefined && src !== "telegram") {
|
|
155
|
+
throw new LaneConfigError(t("laneConfig.err.tokenOnlyTelegram"));
|
|
156
|
+
}
|
|
157
|
+
if (opts.allow_from !== undefined && opts.allow_from !== "") {
|
|
158
|
+
if (src !== "telegram") {
|
|
159
|
+
throw new LaneConfigError(t("laneConfig.err.allowFromOnlyTelegram"));
|
|
160
|
+
}
|
|
161
|
+
for (const id of parseCsv(opts.allow_from)) {
|
|
162
|
+
if (!ALLOW_FROM_RE.test(id)) {
|
|
163
|
+
throw new LaneConfigError(t("laneConfig.err.badAllowFrom", { id }));
|
|
164
|
+
}
|
|
165
|
+
}
|
|
166
|
+
}
|
|
167
|
+
if (opts.file_mode !== undefined &&
|
|
168
|
+
!KNOWN_FILE_MODES.includes(opts.file_mode)) {
|
|
169
|
+
throw new LaneConfigError(t("laneConfig.err.badFileMode", {
|
|
170
|
+
mode: opts.file_mode,
|
|
171
|
+
known: KNOWN_FILE_MODES.join("|"),
|
|
172
|
+
}));
|
|
173
|
+
}
|
|
174
|
+
for (const tool of opts.allowlist ?? []) {
|
|
175
|
+
if (!ALLOWLIST_ITEM_RE.test(tool)) {
|
|
176
|
+
throw new LaneConfigError(t("laneConfig.err.badAllowTool", { tool }));
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
// denylist 는 `Tool` 또는 `Tool(글롭)` 형식. 콤마는 목록 구분자라 항목 내 금지.
|
|
180
|
+
for (const entry of opts.denylist ?? []) {
|
|
181
|
+
if (entry.includes(",") || !parseDenyEntry(entry)) {
|
|
182
|
+
throw new LaneConfigError(t("laneConfig.err.badDenyEntry", { entry }));
|
|
183
|
+
}
|
|
184
|
+
}
|
|
185
|
+
// hard_deny 도 denylist 와 동일한 항목 형식.
|
|
186
|
+
for (const entry of opts.hard_deny ?? []) {
|
|
187
|
+
if (entry.includes(",") || !parseDenyEntry(entry)) {
|
|
188
|
+
throw new LaneConfigError(t("laneConfig.err.badDenyEntry", { entry }));
|
|
189
|
+
}
|
|
190
|
+
}
|
|
191
|
+
const permTier = opts.perm_tier ?? "acp";
|
|
192
|
+
const conf = {
|
|
193
|
+
source: src,
|
|
194
|
+
backend: opts.backend ?? "acp",
|
|
195
|
+
engine: opts.engine ?? "claude-code-acp",
|
|
196
|
+
channel: opts.channel ?? src,
|
|
197
|
+
perm_tier: permTier,
|
|
198
|
+
acp_version: opts.acp_version ?? "v1",
|
|
199
|
+
allowlist: opts.allowlist ?? [],
|
|
200
|
+
// autopass 인데 denylist 미지정 → 내장 기본 denylist. conf 에 구체 목록을
|
|
201
|
+
// 명시 기록한다(암묵 기본값이 파일에 안 보이는 상태 회피). 명시 지정(빈 배열 포함)이 우선.
|
|
202
|
+
denylist: opts.denylist ?? (permTier === "autopass" ? [...DEFAULT_AUTOPASS_DENYLIST] : []),
|
|
203
|
+
// 방어심화 하드-거부 — safe_defaults 면 내장 위험 목록에 explicit hard_deny 를 합집합(중복 제거).
|
|
204
|
+
hard_deny: opts.safe_defaults
|
|
205
|
+
? [...new Set([...DEFAULT_AUTOPASS_DENYLIST, ...(opts.hard_deny ?? [])])]
|
|
206
|
+
: (opts.hard_deny ?? []),
|
|
207
|
+
};
|
|
208
|
+
// exactOptionalPropertyTypes: undefined 대입 금지 — 값이 있을 때만 설정.
|
|
209
|
+
if (opts.cwd)
|
|
210
|
+
conf.cwd = opts.cwd;
|
|
211
|
+
if (opts.chat_id)
|
|
212
|
+
conf.chat_id = opts.chat_id;
|
|
213
|
+
if (opts.root)
|
|
214
|
+
conf.root = opts.root;
|
|
215
|
+
if (opts.inbox)
|
|
216
|
+
conf.inbox = opts.inbox;
|
|
217
|
+
if (opts.approvals)
|
|
218
|
+
conf.approvals = opts.approvals;
|
|
219
|
+
if (opts.outbox)
|
|
220
|
+
conf.outbox = opts.outbox;
|
|
221
|
+
if (opts.lang)
|
|
222
|
+
conf.lang = opts.lang;
|
|
223
|
+
if (opts.allow_from)
|
|
224
|
+
conf.allow_from = opts.allow_from;
|
|
225
|
+
if (opts.file_mode)
|
|
226
|
+
conf.file_mode = opts.file_mode;
|
|
227
|
+
const base = opts.base ?? defaultBase();
|
|
228
|
+
const paths = lanePaths(base, proj, lane);
|
|
229
|
+
if (!opts.force && (await exists(paths.confFile))) {
|
|
230
|
+
throw new LaneConfigError(t("laneConfig.err.laneExists", { lane, confFile: paths.confFile }));
|
|
231
|
+
}
|
|
232
|
+
await mkdir(paths.lanesDir, { recursive: true });
|
|
233
|
+
await writeAtomic(paths.confFile, serializeLaneConf(conf));
|
|
234
|
+
// conf 는 chat_id·allow_from·cwd 등 메타를 담는다 — private 모드에서 lanes.d 도 잠근다(0700).
|
|
235
|
+
await secureLaneDirs([paths.lanesDir], resolveFileMode(conf.file_mode));
|
|
236
|
+
const result = { lane, confPath: paths.confFile, conf, warnings: [] };
|
|
237
|
+
if (opts.token !== undefined) {
|
|
238
|
+
const token = opts.token.trim();
|
|
239
|
+
if (!token)
|
|
240
|
+
throw new LaneConfigError(t("laneConfig.err.tokenEmpty"));
|
|
241
|
+
if (!opts.force && (await exists(paths.envFile))) {
|
|
242
|
+
const existing = (await readFile(paths.envFile, "utf8")).trim();
|
|
243
|
+
if (existing) {
|
|
244
|
+
throw new LaneConfigError(t("laneConfig.err.envHasToken", { envFile: paths.envFile }));
|
|
245
|
+
}
|
|
246
|
+
}
|
|
247
|
+
await mkdir(paths.stateDir, { recursive: true });
|
|
248
|
+
// 토큰이 사는 state 디렉터리를 권한 모드대로 잠근다(private=0700). .env 자체도 0600.
|
|
249
|
+
await secureLaneDirs([paths.stateDir], resolveFileMode(conf.file_mode));
|
|
250
|
+
await writeAtomic(paths.envFile, `TELEGRAM_BOT_TOKEN=${token}\n`, 0o600);
|
|
251
|
+
result.envPath = paths.envFile;
|
|
252
|
+
}
|
|
253
|
+
result.warnings = await collectAddWarnings(conf, opts.token);
|
|
254
|
+
return result;
|
|
255
|
+
}
|
|
256
|
+
/** `adde lane ls` — proj 의 레인 ID 목록(정렬). lanes.d 부재 시 빈 배열. */
|
|
257
|
+
export async function laneList(proj, opts = {}) {
|
|
258
|
+
assertName("proj", proj);
|
|
259
|
+
const base = opts.base ?? defaultBase();
|
|
260
|
+
const lanesDir = join(base, proj, "lanes.d");
|
|
261
|
+
let files;
|
|
262
|
+
try {
|
|
263
|
+
files = await readdir(lanesDir);
|
|
264
|
+
}
|
|
265
|
+
catch {
|
|
266
|
+
return { lanes: [] };
|
|
267
|
+
}
|
|
268
|
+
const lanes = files
|
|
269
|
+
.filter((f) => f.endsWith(".conf"))
|
|
270
|
+
.map((f) => f.replace(/\.conf$/, ""))
|
|
271
|
+
.sort();
|
|
272
|
+
return { lanes };
|
|
273
|
+
}
|
|
274
|
+
/** `adde lane show` — 레인 conf 의 파싱 결과와 원본 텍스트. */
|
|
275
|
+
export async function laneShow(proj, lane, opts = {}) {
|
|
276
|
+
assertName("proj", proj);
|
|
277
|
+
assertName("lane", lane);
|
|
278
|
+
const base = opts.base ?? defaultBase();
|
|
279
|
+
const paths = lanePaths(base, proj, lane);
|
|
280
|
+
let text;
|
|
281
|
+
try {
|
|
282
|
+
text = await readFile(paths.confFile, "utf8");
|
|
283
|
+
}
|
|
284
|
+
catch {
|
|
285
|
+
throw new LaneConfigError(t("laneConfig.err.laneNotFound", { lane, confFile: paths.confFile }));
|
|
286
|
+
}
|
|
287
|
+
return { lane, confPath: paths.confFile, conf: parseLaneConf(text), text };
|
|
288
|
+
}
|
|
289
|
+
/** `adde lane rm` — 레인 conf 삭제. 부재 시 에러. state/queue 등 부수 데이터는 보존. */
|
|
290
|
+
export async function laneRemove(proj, lane, opts = {}) {
|
|
291
|
+
assertName("proj", proj);
|
|
292
|
+
assertName("lane", lane);
|
|
293
|
+
const base = opts.base ?? defaultBase();
|
|
294
|
+
const paths = lanePaths(base, proj, lane);
|
|
295
|
+
try {
|
|
296
|
+
await unlink(paths.confFile);
|
|
297
|
+
}
|
|
298
|
+
catch {
|
|
299
|
+
throw new LaneConfigError(t("laneConfig.err.laneNotFound", { lane, confFile: paths.confFile }));
|
|
300
|
+
}
|
|
301
|
+
return { lane, confPath: paths.confFile };
|
|
302
|
+
}
|
|
303
|
+
//# sourceMappingURL=lane-config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"lane-config.js","sourceRoot":"","sources":["../../src/core/lane-config.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,81 @@
|
|
|
1
|
+
/** launchctl 실행자 타입 — 테스트에서 fake 로 주입 가능(CI 실 launchctl 미접촉). */
|
|
2
|
+
export type LaunchctlExec = (args: string[]) => Promise<{
|
|
3
|
+
stdout: string;
|
|
4
|
+
code: number;
|
|
5
|
+
}>;
|
|
6
|
+
/** 주입 가능한 의존성 — exec(fake launchctl), home(테스트용 홈 경로). */
|
|
7
|
+
export interface LaunchdDeps {
|
|
8
|
+
exec?: LaunchctlExec;
|
|
9
|
+
home?: string;
|
|
10
|
+
/** 플랫폼 가드 주입(테스트 전용 — 미지정 시 process.platform). */
|
|
11
|
+
platform?: NodeJS.Platform;
|
|
12
|
+
/** node 바이너리 경로 override(테스트 전용 — 미지정 시 process.execPath). */
|
|
13
|
+
nodeBin?: string;
|
|
14
|
+
/** 데몬 실행 파일 경로 override(테스트 전용 — 미지정 시 import.meta.url 기준 dist/cli/adde.js). */
|
|
15
|
+
addeBin?: string;
|
|
16
|
+
/** 데몬 PATH override(테스트 전용 — 미지정 시 node 디렉터리를 앞에 붙인 process.env.PATH). */
|
|
17
|
+
pathEnv?: string;
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* macOS(darwin) 이 아닌 환경에서 actionable throw.
|
|
21
|
+
* launchd 코드 경로의 SSOT 가드 — 개별 함수에서 직접 process.platform 체크 금지.
|
|
22
|
+
*/
|
|
23
|
+
export declare function assertMacOS(platform?: NodeJS.Platform): void;
|
|
24
|
+
/**
|
|
25
|
+
* launchd Label: "com.qwertygeon.adde.<proj>".
|
|
26
|
+
* proj 안전성은 assertSafeSegment 로 검증(Label/파일명 인젝션 차단).
|
|
27
|
+
*/
|
|
28
|
+
export declare function plistLabel(proj: string): string;
|
|
29
|
+
/**
|
|
30
|
+
* plist 파일 경로: ~/Library/LaunchAgents/com.qwertygeon.adde.<proj>.plist.
|
|
31
|
+
* deps.home 미지정 시 os.homedir() 사용(테스트 주입 가능).
|
|
32
|
+
*/
|
|
33
|
+
export declare function plistPath(proj: string, deps?: LaunchdDeps): string;
|
|
34
|
+
export interface RenderPlistOpts {
|
|
35
|
+
nodeBin: string;
|
|
36
|
+
addeBin: string;
|
|
37
|
+
logPath: string;
|
|
38
|
+
/**
|
|
39
|
+
* 데몬 PATH. launchd 는 기본적으로 최소 PATH(/usr/bin:/bin:/usr/sbin:/sbin)만 주는데,
|
|
40
|
+
* ACP 엔진 어댑터가 `claude` CLI 를 `#!/usr/bin/env node` 로 스폰하므로 node·claude 가
|
|
41
|
+
* 이 PATH 에 있어야 한다. 미지정 시 EnvironmentVariables 를 생략(구 동작).
|
|
42
|
+
*/
|
|
43
|
+
pathEnv?: string;
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* launchd plist XML 생성.
|
|
47
|
+
* - KeepAlive=true, RunAtLoad=true.
|
|
48
|
+
* - ProgramArguments=[nodeBin, addeBin, "__daemon", proj] — 시크릿 미포함.
|
|
49
|
+
* - EnvironmentVariables: PATH 만 주입(pathEnv 지정 시). 토큰 등 시크릿은 넣지 않는다
|
|
50
|
+
* (데몬이 .env 파일에서 로드). PATH 는 시크릿이 아니다.
|
|
51
|
+
*/
|
|
52
|
+
export declare function renderPlist(proj: string, opts: RenderPlistOpts): string;
|
|
53
|
+
/**
|
|
54
|
+
* 데몬 워커가 실행할 adde 진입 파일(절대 경로) — import.meta.url 기준 `dist/cli/adde.js`.
|
|
55
|
+
* 빌드본에선 dist/cli/adde.js(존재), tsx dev 에선 src/cli/adde.js(부재)로 해석된다 —
|
|
56
|
+
* loadDaemon 가드·doctor 사전점검의 SSOT. launchd 워커는 분리 프로세스라 이 파일이 실재해야 한다.
|
|
57
|
+
*/
|
|
58
|
+
export declare function daemonEntryPath(): string;
|
|
59
|
+
/**
|
|
60
|
+
* plist 생성 후 launchctl load 로 데몬 등록.
|
|
61
|
+
* exit code ≠ 0 이면 actionable throw.
|
|
62
|
+
*/
|
|
63
|
+
export declare function loadDaemon(proj: string, deps?: LaunchdDeps): Promise<void>;
|
|
64
|
+
/**
|
|
65
|
+
* launchctl unload 후 plist 제거 — 멱등(실패 흡수).
|
|
66
|
+
* 순서: unload 먼저(KeepAlive 재기동 차단) → plist rm.
|
|
67
|
+
*/
|
|
68
|
+
export declare function unloadDaemon(proj: string, deps?: LaunchdDeps): Promise<void>;
|
|
69
|
+
/** 데몬 등록 상태 — doctor·정합성 점검용. */
|
|
70
|
+
export interface DaemonRegState {
|
|
71
|
+
/** plist 파일이 ~/Library/LaunchAgents/ 에 존재하는가. */
|
|
72
|
+
plistExists: boolean;
|
|
73
|
+
/** launchctl list 에 Label(com.qwertygeon.adde.<proj>)이 등록되어 있는가. */
|
|
74
|
+
launchctlRegistered: boolean;
|
|
75
|
+
}
|
|
76
|
+
/**
|
|
77
|
+
* plist 파일 존재 + launchctl list 등록 여부를 독립적으로 확인.
|
|
78
|
+
* PID/status 컬럼 파싱 회피 — Label 부분문자열 매칭만(견고성).
|
|
79
|
+
* exec 주입으로 CI 실 launchctl 미접촉(테스트 부작용 방지).
|
|
80
|
+
*/
|
|
81
|
+
export declare function daemonRegState(proj: string, deps?: LaunchdDeps): Promise<DaemonRegState>;
|
|
@@ -0,0 +1,216 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* macOS launchd LaunchAgent 상호작용 전담 모듈.
|
|
3
|
+
* plist 생성·경로·launchctl 호출을 단일 소스로 관리한다.
|
|
4
|
+
* 비-macOS 환경에서는 assertMacOS() 가 actionable throw(침묵 실패 금지).
|
|
5
|
+
*/
|
|
6
|
+
import { t } from "../shared/i18n.js";
|
|
7
|
+
import { execFile as nodeExecFile } from "node:child_process";
|
|
8
|
+
import { writeFile, unlink, mkdir, stat } from "node:fs/promises";
|
|
9
|
+
import { homedir } from "node:os";
|
|
10
|
+
import { join, dirname } from "node:path";
|
|
11
|
+
import { fileURLToPath } from "node:url";
|
|
12
|
+
import { formatBlock } from "../shared/notify.js";
|
|
13
|
+
import { assertSafeSegment } from "../shared/paths.js";
|
|
14
|
+
// ── macOS 가드 ──────────────────────────────────────────────────────────────
|
|
15
|
+
/**
|
|
16
|
+
* macOS(darwin) 이 아닌 환경에서 actionable throw.
|
|
17
|
+
* launchd 코드 경로의 SSOT 가드 — 개별 함수에서 직접 process.platform 체크 금지.
|
|
18
|
+
*/
|
|
19
|
+
export function assertMacOS(platform = process.platform) {
|
|
20
|
+
if (platform !== "darwin") {
|
|
21
|
+
throw new Error(formatBlock({
|
|
22
|
+
situation: t("launchd.macOnly.situation", { platform }),
|
|
23
|
+
action: t("launchd.macOnly.action"),
|
|
24
|
+
}));
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
// ── plist 경로·Label 헬퍼 ───────────────────────────────────────────────────
|
|
28
|
+
/**
|
|
29
|
+
* launchd Label: "com.qwertygeon.adde.<proj>".
|
|
30
|
+
* proj 안전성은 assertSafeSegment 로 검증(Label/파일명 인젝션 차단).
|
|
31
|
+
*/
|
|
32
|
+
export function plistLabel(proj) {
|
|
33
|
+
assertSafeSegment("proj", proj);
|
|
34
|
+
return `com.qwertygeon.adde.${proj}`;
|
|
35
|
+
}
|
|
36
|
+
/**
|
|
37
|
+
* plist 파일 경로: ~/Library/LaunchAgents/com.qwertygeon.adde.<proj>.plist.
|
|
38
|
+
* deps.home 미지정 시 os.homedir() 사용(테스트 주입 가능).
|
|
39
|
+
*/
|
|
40
|
+
export function plistPath(proj, deps) {
|
|
41
|
+
assertSafeSegment("proj", proj);
|
|
42
|
+
const base = deps?.home ?? homedir();
|
|
43
|
+
return join(base, "Library", "LaunchAgents", `${plistLabel(proj)}.plist`);
|
|
44
|
+
}
|
|
45
|
+
/** plist 문자열 값의 XML 특수문자 이스케이프(경로에 &·< 등이 있어도 유효한 plist 유지). */
|
|
46
|
+
function xmlEscape(s) {
|
|
47
|
+
return s.replace(/&/g, "&").replace(/</g, "<").replace(/>/g, ">");
|
|
48
|
+
}
|
|
49
|
+
/**
|
|
50
|
+
* launchd plist XML 생성.
|
|
51
|
+
* - KeepAlive=true, RunAtLoad=true.
|
|
52
|
+
* - ProgramArguments=[nodeBin, addeBin, "__daemon", proj] — 시크릿 미포함.
|
|
53
|
+
* - EnvironmentVariables: PATH 만 주입(pathEnv 지정 시). 토큰 등 시크릿은 넣지 않는다
|
|
54
|
+
* (데몬이 .env 파일에서 로드). PATH 는 시크릿이 아니다.
|
|
55
|
+
*/
|
|
56
|
+
export function renderPlist(proj, opts) {
|
|
57
|
+
assertSafeSegment("proj", proj);
|
|
58
|
+
const label = plistLabel(proj);
|
|
59
|
+
const { nodeBin, addeBin, logPath, pathEnv } = opts;
|
|
60
|
+
const envBlock = pathEnv
|
|
61
|
+
? ` <key>EnvironmentVariables</key>
|
|
62
|
+
<dict>
|
|
63
|
+
<key>PATH</key>
|
|
64
|
+
<string>${xmlEscape(pathEnv)}</string>
|
|
65
|
+
</dict>
|
|
66
|
+
`
|
|
67
|
+
: "";
|
|
68
|
+
return `<?xml version="1.0" encoding="UTF-8"?>
|
|
69
|
+
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
|
70
|
+
<plist version="1.0">
|
|
71
|
+
<dict>
|
|
72
|
+
<key>Label</key>
|
|
73
|
+
<string>${label}</string>
|
|
74
|
+
<key>ProgramArguments</key>
|
|
75
|
+
<array>
|
|
76
|
+
<string>${nodeBin}</string>
|
|
77
|
+
<string>${addeBin}</string>
|
|
78
|
+
<string>__daemon</string>
|
|
79
|
+
<string>${proj}</string>
|
|
80
|
+
</array>
|
|
81
|
+
${envBlock} <key>RunAtLoad</key>
|
|
82
|
+
<true/>
|
|
83
|
+
<key>KeepAlive</key>
|
|
84
|
+
<true/>
|
|
85
|
+
<key>StandardOutPath</key>
|
|
86
|
+
<string>${logPath}.out.log</string>
|
|
87
|
+
<key>StandardErrorPath</key>
|
|
88
|
+
<string>${logPath}.err.log</string>
|
|
89
|
+
</dict>
|
|
90
|
+
</plist>
|
|
91
|
+
`;
|
|
92
|
+
}
|
|
93
|
+
// ── launchctl 실행자 기본 구현 ────────────────────────────────────────────
|
|
94
|
+
/** 기본 LaunchctlExec 구현 — node:child_process.execFile("launchctl", args). */
|
|
95
|
+
function defaultExec(args) {
|
|
96
|
+
return new Promise((resolve) => {
|
|
97
|
+
nodeExecFile("launchctl", args, { encoding: "utf8" }, (err, stdout, stderr) => {
|
|
98
|
+
if (err) {
|
|
99
|
+
const code = typeof err.code === "number" ? err.code : 1;
|
|
100
|
+
resolve({ stdout: stdout + stderr, code });
|
|
101
|
+
}
|
|
102
|
+
else {
|
|
103
|
+
resolve({ stdout: stdout + stderr, code: 0 });
|
|
104
|
+
}
|
|
105
|
+
});
|
|
106
|
+
});
|
|
107
|
+
}
|
|
108
|
+
// ── loadDaemon / unloadDaemon ────────────────────────────────────────────────
|
|
109
|
+
/**
|
|
110
|
+
* 데몬 워커가 실행할 adde 진입 파일(절대 경로) — import.meta.url 기준 `dist/cli/adde.js`.
|
|
111
|
+
* 빌드본에선 dist/cli/adde.js(존재), tsx dev 에선 src/cli/adde.js(부재)로 해석된다 —
|
|
112
|
+
* loadDaemon 가드·doctor 사전점검의 SSOT. launchd 워커는 분리 프로세스라 이 파일이 실재해야 한다.
|
|
113
|
+
*/
|
|
114
|
+
export function daemonEntryPath() {
|
|
115
|
+
// fileURLToPath — .pathname 은 공백·특수문자를 퍼센트 인코딩해(예: "John%20Doe") 실경로와
|
|
116
|
+
// 어긋난다. 가드 stat·plist ProgramArguments 양쪽이 실경로를 써야 하므로 디코딩 변환.
|
|
117
|
+
return fileURLToPath(new URL("../cli/adde.js", import.meta.url));
|
|
118
|
+
}
|
|
119
|
+
/**
|
|
120
|
+
* plist 생성 후 launchctl load 로 데몬 등록.
|
|
121
|
+
* exit code ≠ 0 이면 actionable throw.
|
|
122
|
+
*/
|
|
123
|
+
export async function loadDaemon(proj, deps) {
|
|
124
|
+
assertMacOS(deps?.platform);
|
|
125
|
+
const exec = deps?.exec ?? defaultExec;
|
|
126
|
+
const nodeBin = deps?.nodeBin ?? process.execPath;
|
|
127
|
+
// launchd 가 워커를 기동할 때 동일 Node 바이너리 + 동일 adde.js 를 사용한다.
|
|
128
|
+
const addeBin = deps?.addeBin ?? daemonEntryPath();
|
|
129
|
+
// 데몬 실행 파일 존재 가드 — launchd 워커는 분리 프로세스라 tsx 트랜스파일을 못 쓴다.
|
|
130
|
+
// `pnpm run dev up`(tsx) 은 addeBin 이 src/cli/adde.js(부재)로 해석돼 데몬이 MODULE_NOT_FOUND
|
|
131
|
+
// 로 크래시루프한다 → 빌드 산출물/전역 설치가 필요함을 여기서 명시 거부한다.
|
|
132
|
+
try {
|
|
133
|
+
await stat(addeBin);
|
|
134
|
+
}
|
|
135
|
+
catch {
|
|
136
|
+
throw new Error(formatBlock({
|
|
137
|
+
situation: t("launchd.binMissing.situation", { path: addeBin }),
|
|
138
|
+
action: t("launchd.binMissing.action"),
|
|
139
|
+
}));
|
|
140
|
+
}
|
|
141
|
+
// 데몬 PATH: node 디렉터리를 앞에 두고 현재 PATH 를 승계(중복 제거).
|
|
142
|
+
// launchd 최소 PATH 로는 엔진 어댑터의 `env node`/`claude` 스폰이 실패하므로,
|
|
143
|
+
// up 실행 시점(사용자 셸)의 PATH 를 plist 에 구워 넣어 재부팅 후에도 유지한다.
|
|
144
|
+
const pathEnv = deps?.pathEnv ??
|
|
145
|
+
(() => {
|
|
146
|
+
const parts = [dirname(nodeBin), ...(process.env.PATH ?? "").split(":")];
|
|
147
|
+
return parts.filter((p, i) => p && parts.indexOf(p) === i).join(":");
|
|
148
|
+
})();
|
|
149
|
+
const targetPlist = plistPath(proj, deps);
|
|
150
|
+
// 데몬 stdout/stderr 로그 경로: ~/Library/Logs/adde/<proj>
|
|
151
|
+
const baseHome = deps?.home ?? homedir();
|
|
152
|
+
const logPath = join(baseHome, "Library", "Logs", "adde", proj);
|
|
153
|
+
const plistContent = renderPlist(proj, { nodeBin, addeBin, logPath, pathEnv });
|
|
154
|
+
// LaunchAgents 디렉터리 생성(존재하면 noop).
|
|
155
|
+
await mkdir(dirname(targetPlist), { recursive: true });
|
|
156
|
+
await writeFile(targetPlist, plistContent, "utf8");
|
|
157
|
+
const { stdout, code } = await exec(["load", targetPlist]);
|
|
158
|
+
if (code !== 0) {
|
|
159
|
+
throw new Error(formatBlock({
|
|
160
|
+
situation: t("launchd.loadFail.situation", { code, output: stdout.trim() }),
|
|
161
|
+
action: t("launchd.loadFail.action", { proj }),
|
|
162
|
+
}));
|
|
163
|
+
}
|
|
164
|
+
}
|
|
165
|
+
/**
|
|
166
|
+
* launchctl unload 후 plist 제거 — 멱등(실패 흡수).
|
|
167
|
+
* 순서: unload 먼저(KeepAlive 재기동 차단) → plist rm.
|
|
168
|
+
*/
|
|
169
|
+
export async function unloadDaemon(proj, deps) {
|
|
170
|
+
assertMacOS(deps?.platform);
|
|
171
|
+
const exec = deps?.exec ?? defaultExec;
|
|
172
|
+
const targetPlist = plistPath(proj, deps);
|
|
173
|
+
// unload 실패는 멱등 — 이미 미등록이거나 plist 없는 경우를 정상 취급.
|
|
174
|
+
await exec(["unload", targetPlist]).catch(() => {
|
|
175
|
+
// 오류 흡수 — 미등록 상태도 down 의 의도(종료)와 일치.
|
|
176
|
+
});
|
|
177
|
+
// plist 파일 제거(ENOENT 흡수 — 멱등).
|
|
178
|
+
try {
|
|
179
|
+
await unlink(targetPlist);
|
|
180
|
+
}
|
|
181
|
+
catch (err) {
|
|
182
|
+
if (err.code !== "ENOENT")
|
|
183
|
+
throw err;
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
/**
|
|
187
|
+
* plist 파일 존재 + launchctl list 등록 여부를 독립적으로 확인.
|
|
188
|
+
* PID/status 컬럼 파싱 회피 — Label 부분문자열 매칭만(견고성).
|
|
189
|
+
* exec 주입으로 CI 실 launchctl 미접촉(테스트 부작용 방지).
|
|
190
|
+
*/
|
|
191
|
+
export async function daemonRegState(proj, deps) {
|
|
192
|
+
assertSafeSegment("proj", proj);
|
|
193
|
+
const exec = deps?.exec ?? defaultExec;
|
|
194
|
+
const targetPlist = plistPath(proj, deps);
|
|
195
|
+
const label = plistLabel(proj);
|
|
196
|
+
// plist 파일 존재 여부.
|
|
197
|
+
let plistExists = false;
|
|
198
|
+
try {
|
|
199
|
+
await stat(targetPlist);
|
|
200
|
+
plistExists = true;
|
|
201
|
+
}
|
|
202
|
+
catch {
|
|
203
|
+
// stat 실패 = plist 부재(초기값 유지)
|
|
204
|
+
}
|
|
205
|
+
// launchctl list 에 Label 등록 여부 — 부분문자열 매칭.
|
|
206
|
+
let launchctlRegistered = false;
|
|
207
|
+
try {
|
|
208
|
+
const { stdout } = await exec(["list"]);
|
|
209
|
+
launchctlRegistered = stdout.includes(label);
|
|
210
|
+
}
|
|
211
|
+
catch {
|
|
212
|
+
// launchctl 실행 실패 = 등록 확인 불가 → 미등록 취급(초기값 유지)
|
|
213
|
+
}
|
|
214
|
+
return { plistExists, launchctlRegistered };
|
|
215
|
+
}
|
|
216
|
+
//# sourceMappingURL=launchd.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"launchd.js","sourceRoot":"","sources":["../../src/core/launchd.ts"],"names":[],"mappings":"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"}
|