agent-work-loop 0.0.0 → 0.6.22
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 +272 -12
- package/dist/brief-Z3JKXEUP.js +181 -0
- package/dist/changelog-R7BNBF2C.js +62 -0
- package/dist/chunk-4OCSYHYB.js +274 -0
- package/dist/chunk-6E7XEQOH.js +27 -0
- package/dist/chunk-7SYRDDTX.js +516 -0
- package/dist/chunk-BUWGQVHT.js +1243 -0
- package/dist/chunk-C7TN3LM3.js +448 -0
- package/dist/chunk-CCMG377E.js +771 -0
- package/dist/chunk-D5OINC3G.js +52 -0
- package/dist/chunk-F5LHXBH7.js +209 -0
- package/dist/chunk-FBEUJR2P.js +446 -0
- package/dist/chunk-I77CXOEX.js +693 -0
- package/dist/chunk-LMWAVN7B.js +904 -0
- package/dist/chunk-O7GRZZPJ.js +347 -0
- package/dist/chunk-TKPHC32G.js +96 -0
- package/dist/chunk-UOPWVM2H.js +727 -0
- package/dist/chunk-VU6IPRRM.js +166 -0
- package/dist/chunk-X5LMP5J7.js +307 -0
- package/dist/chunk-ZLTOL3D3.js +286 -0
- package/dist/cli.js +373 -11
- package/dist/commit-APXIVOSD.js +411 -0
- package/dist/config-TFMW7O4T.js +34 -0
- package/dist/doctor-BU6HWXE4.js +29 -0
- package/dist/evolve-NNQX2A43.js +38 -0
- package/dist/feedback-KAXNFMUY.js +125 -0
- package/dist/gotchas-CW4KZDEF.js +43 -0
- package/dist/hold-recheck-SLI7NDLU.js +133 -0
- package/dist/init-UDM5AXKI.js +79 -0
- package/dist/lane-MIVLTDEU.js +41 -0
- package/dist/loop-summary-XAI6KOGB.js +361 -0
- package/dist/metrics-WLRZZRTK.js +25 -0
- package/dist/record-UKDIUJ5T.js +68 -0
- package/dist/review-NTZ3HY3N.js +118 -0
- package/dist/rules-4VDP5LZW.js +33 -0
- package/dist/state-XM7NZ2HA.js +37 -0
- package/dist/status-I3MQOCQM.js +40 -0
- package/dist/uninstall-MOHJDMQH.js +545 -0
- package/dist/update-AYTBYAHI.js +61 -0
- package/dist/verify-6YF5FXWM.js +37 -0
- package/dist/version-check-G27JYMTZ.js +14 -0
- package/dist/work-6MMIQMUC.js +50 -0
- package/engine/skills/claude/awl-loop/SKILL.md +292 -0
- package/engine/skills/claude/awl-loop/reference.md +131 -0
- package/engine/skills/claude/awl-pipeline/SKILL.md +83 -0
- package/engine/skills/claude/awl-pipeline-exec/SKILL.md +200 -0
- package/engine/skills/claude/awl-pipeline-plan/SKILL.md +71 -0
- package/engine/skills/claude/awl-pipeline-review/SKILL.md +149 -0
- package/engine/skills/codex/AGENTS.awl.md +117 -0
- package/engine/templates/block-publish.mjs +9 -0
- package/engine/templates/pre-push.sample +7 -0
- package/engine/templates/related-cmd-examples.md +37 -0
- package/engine/version.json +2 -2
- package/package.json +9 -3
|
@@ -0,0 +1,1243 @@
|
|
|
1
|
+
import {
|
|
2
|
+
caps,
|
|
3
|
+
card,
|
|
4
|
+
engineDir,
|
|
5
|
+
globalRoot,
|
|
6
|
+
installedEngineVersion,
|
|
7
|
+
makeColors,
|
|
8
|
+
makeSymbols,
|
|
9
|
+
makeTokens,
|
|
10
|
+
padEndDisplay,
|
|
11
|
+
projectsFile,
|
|
12
|
+
rawModeCapable,
|
|
13
|
+
signal,
|
|
14
|
+
stringWidth
|
|
15
|
+
} from "./chunk-7SYRDDTX.js";
|
|
16
|
+
|
|
17
|
+
// src/commands/init.ts
|
|
18
|
+
import fs from "fs";
|
|
19
|
+
import path from "path";
|
|
20
|
+
import readline from "readline";
|
|
21
|
+
import { fileURLToPath } from "url";
|
|
22
|
+
|
|
23
|
+
// src/core/select.ts
|
|
24
|
+
function initSelectState(defaultIndex, defaultChecked = []) {
|
|
25
|
+
return { index: defaultIndex, checked: new Set(defaultChecked), done: false, cancelled: false };
|
|
26
|
+
}
|
|
27
|
+
function advanceSelect(state, key, count, multi, selectAllIndex) {
|
|
28
|
+
if (state.done) {
|
|
29
|
+
return state;
|
|
30
|
+
}
|
|
31
|
+
switch (key) {
|
|
32
|
+
case "up":
|
|
33
|
+
return { ...state, index: (state.index - 1 + count) % count };
|
|
34
|
+
case "down":
|
|
35
|
+
return { ...state, index: (state.index + 1) % count };
|
|
36
|
+
case "home":
|
|
37
|
+
return { ...state, index: 0 };
|
|
38
|
+
case "end":
|
|
39
|
+
return { ...state, index: count - 1 };
|
|
40
|
+
case "space": {
|
|
41
|
+
if (!multi) {
|
|
42
|
+
return state;
|
|
43
|
+
}
|
|
44
|
+
const next = new Set(state.checked);
|
|
45
|
+
if (state.index === selectAllIndex) {
|
|
46
|
+
if (next.has(selectAllIndex)) {
|
|
47
|
+
return { ...state, checked: /* @__PURE__ */ new Set() };
|
|
48
|
+
}
|
|
49
|
+
return { ...state, checked: new Set(Array.from({ length: count }, (_, i) => i)) };
|
|
50
|
+
}
|
|
51
|
+
if (selectAllIndex !== void 0) {
|
|
52
|
+
next.delete(selectAllIndex);
|
|
53
|
+
}
|
|
54
|
+
if (next.has(state.index)) {
|
|
55
|
+
next.delete(state.index);
|
|
56
|
+
} else {
|
|
57
|
+
next.add(state.index);
|
|
58
|
+
}
|
|
59
|
+
return { ...state, checked: next };
|
|
60
|
+
}
|
|
61
|
+
case "enter":
|
|
62
|
+
return { ...state, done: true };
|
|
63
|
+
case "escape":
|
|
64
|
+
return { ...state, done: true, cancelled: true };
|
|
65
|
+
default:
|
|
66
|
+
return state;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
function renderSelectOptions(options, state, multi, c) {
|
|
70
|
+
const sym = makeSymbols(c);
|
|
71
|
+
const color = makeColors(c.color);
|
|
72
|
+
const lines = [];
|
|
73
|
+
for (let i = 0; i < options.length; i++) {
|
|
74
|
+
const marker = multi ? state.checked.has(i) ? sym.checkOn : sym.checkOff : i === state.index ? sym.radioOn : sym.radioOff;
|
|
75
|
+
const cursor = i === state.index ? c.unicode ? "\u276F " : "> " : " ";
|
|
76
|
+
const line = `${cursor}${marker} ${options[i]}`;
|
|
77
|
+
lines.push(i === state.index ? color.cyan(line) : line);
|
|
78
|
+
}
|
|
79
|
+
return lines.join("\n");
|
|
80
|
+
}
|
|
81
|
+
function readKey() {
|
|
82
|
+
return new Promise((resolve) => {
|
|
83
|
+
process.stdin.once("data", (buf) => {
|
|
84
|
+
const s = buf.toString("utf8");
|
|
85
|
+
if (s === "\r" || s === "\n") {
|
|
86
|
+
resolve("enter");
|
|
87
|
+
} else if (s === " ") {
|
|
88
|
+
resolve("space");
|
|
89
|
+
} else if (s === "\x1B[A" || s === "k") {
|
|
90
|
+
resolve("up");
|
|
91
|
+
} else if (s === "\x1B[B" || s === "j") {
|
|
92
|
+
resolve("down");
|
|
93
|
+
} else if (s === "\x1B[H" || s === "\x1BOH" || s === "g") {
|
|
94
|
+
resolve("home");
|
|
95
|
+
} else if (s === "\x1B[F" || s === "\x1BOF" || s === "G") {
|
|
96
|
+
resolve("end");
|
|
97
|
+
} else if (s === "\x1B" || s === "") {
|
|
98
|
+
resolve("escape");
|
|
99
|
+
} else {
|
|
100
|
+
resolve("other");
|
|
101
|
+
}
|
|
102
|
+
});
|
|
103
|
+
});
|
|
104
|
+
}
|
|
105
|
+
function moveCursorUp(n) {
|
|
106
|
+
return n > 0 ? `\x1B[${n}A` : "";
|
|
107
|
+
}
|
|
108
|
+
function renderInteractiveSelect(options, state, multi, c, presentation) {
|
|
109
|
+
const color = makeColors(c.color);
|
|
110
|
+
const lines = renderSelectOptions(options, state, multi, c).split("\n");
|
|
111
|
+
if (presentation.hint) {
|
|
112
|
+
lines.push("");
|
|
113
|
+
lines.push(color.dim(presentation.hint));
|
|
114
|
+
}
|
|
115
|
+
const text = card(presentation.title ?? "\uC120\uD0DD", lines, c);
|
|
116
|
+
return { text, lineCount: text.split("\n").length };
|
|
117
|
+
}
|
|
118
|
+
async function runInteractiveSelect(options, defaultIndex, multi, c, defaultChecked = [], presentation = {}) {
|
|
119
|
+
let state = initSelectState(defaultIndex, defaultChecked);
|
|
120
|
+
process.stdin.setRawMode(true);
|
|
121
|
+
process.stdin.resume();
|
|
122
|
+
let rendered = renderInteractiveSelect(options, state, multi, c, presentation);
|
|
123
|
+
process.stdout.write(`${rendered.text}
|
|
124
|
+
`);
|
|
125
|
+
try {
|
|
126
|
+
while (!state.done) {
|
|
127
|
+
const key = await readKey();
|
|
128
|
+
state = advanceSelect(state, key, options.length, multi, presentation.selectAllIndex);
|
|
129
|
+
rendered = renderInteractiveSelect(options, state, multi, c, presentation);
|
|
130
|
+
process.stdout.write(`${moveCursorUp(rendered.lineCount)}${rendered.text}
|
|
131
|
+
`);
|
|
132
|
+
}
|
|
133
|
+
} finally {
|
|
134
|
+
process.stdin.setRawMode(false);
|
|
135
|
+
process.stdin.pause();
|
|
136
|
+
}
|
|
137
|
+
return state.cancelled ? null : { index: state.index, checked: [...state.checked] };
|
|
138
|
+
}
|
|
139
|
+
|
|
140
|
+
// src/commands/init.ts
|
|
141
|
+
function exists(p) {
|
|
142
|
+
try {
|
|
143
|
+
return fs.existsSync(p);
|
|
144
|
+
} catch {
|
|
145
|
+
return false;
|
|
146
|
+
}
|
|
147
|
+
}
|
|
148
|
+
function readJson(p) {
|
|
149
|
+
try {
|
|
150
|
+
return JSON.parse(fs.readFileSync(p, "utf8"));
|
|
151
|
+
} catch {
|
|
152
|
+
return null;
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
function writeFileEnsuringDir(p, content) {
|
|
156
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
157
|
+
fs.writeFileSync(p, content);
|
|
158
|
+
}
|
|
159
|
+
function hasTypescriptDependency(pkg) {
|
|
160
|
+
if (typeof pkg !== "object" || pkg === null) {
|
|
161
|
+
return false;
|
|
162
|
+
}
|
|
163
|
+
const o = pkg;
|
|
164
|
+
for (const field of ["dependencies", "devDependencies"]) {
|
|
165
|
+
const deps = o[field];
|
|
166
|
+
if (deps && typeof deps === "object" && "typescript" in deps) {
|
|
167
|
+
return true;
|
|
168
|
+
}
|
|
169
|
+
}
|
|
170
|
+
return false;
|
|
171
|
+
}
|
|
172
|
+
function stripYamlComment(line) {
|
|
173
|
+
let quote = null;
|
|
174
|
+
for (let i = 0; i < line.length; i++) {
|
|
175
|
+
const c = line[i];
|
|
176
|
+
if (quote) {
|
|
177
|
+
if (c === quote) {
|
|
178
|
+
quote = null;
|
|
179
|
+
}
|
|
180
|
+
} else if (c === '"' || c === "'") {
|
|
181
|
+
quote = c;
|
|
182
|
+
} else if (c === "#") {
|
|
183
|
+
return line.slice(0, i);
|
|
184
|
+
}
|
|
185
|
+
}
|
|
186
|
+
return line;
|
|
187
|
+
}
|
|
188
|
+
function pnpmWorkspaceGlobs(cwd) {
|
|
189
|
+
const p = path.join(cwd, "pnpm-workspace.yaml");
|
|
190
|
+
if (!exists(p)) {
|
|
191
|
+
return [];
|
|
192
|
+
}
|
|
193
|
+
const globs = [];
|
|
194
|
+
try {
|
|
195
|
+
const text = fs.readFileSync(p, "utf8");
|
|
196
|
+
for (const rawLine of text.split("\n")) {
|
|
197
|
+
const line = stripYamlComment(rawLine);
|
|
198
|
+
const flow = /^\s*packages\s*:\s*\[([^\]]*)\]\s*$/.exec(line);
|
|
199
|
+
if (flow) {
|
|
200
|
+
for (const item of (flow[1] ?? "").split(",")) {
|
|
201
|
+
const v = item.trim().replace(/^['"]|['"]$/g, "");
|
|
202
|
+
if (v) {
|
|
203
|
+
globs.push(v);
|
|
204
|
+
}
|
|
205
|
+
}
|
|
206
|
+
continue;
|
|
207
|
+
}
|
|
208
|
+
const bullet = /^\s*-\s*['"]?([^'"]+?)['"]?\s*$/.exec(line);
|
|
209
|
+
if (bullet?.[1]) {
|
|
210
|
+
globs.push(bullet[1].trim());
|
|
211
|
+
}
|
|
212
|
+
}
|
|
213
|
+
} catch {
|
|
214
|
+
}
|
|
215
|
+
return globs;
|
|
216
|
+
}
|
|
217
|
+
function workspaceGlobs(cwd, pkg) {
|
|
218
|
+
const globs = [];
|
|
219
|
+
if (pkg && typeof pkg === "object") {
|
|
220
|
+
const w = pkg.workspaces;
|
|
221
|
+
if (Array.isArray(w)) {
|
|
222
|
+
globs.push(...w.filter((x) => typeof x === "string"));
|
|
223
|
+
} else if (w && typeof w === "object") {
|
|
224
|
+
const packages = w.packages;
|
|
225
|
+
if (Array.isArray(packages)) {
|
|
226
|
+
globs.push(...packages.filter((x) => typeof x === "string"));
|
|
227
|
+
}
|
|
228
|
+
}
|
|
229
|
+
}
|
|
230
|
+
globs.push(...pnpmWorkspaceGlobs(cwd));
|
|
231
|
+
return globs;
|
|
232
|
+
}
|
|
233
|
+
var MAX_GLOB_DEPTH = 6;
|
|
234
|
+
function listSubdirs(dirs) {
|
|
235
|
+
const next = [];
|
|
236
|
+
for (const d of dirs) {
|
|
237
|
+
try {
|
|
238
|
+
for (const entry of fs.readdirSync(d, { withFileTypes: true })) {
|
|
239
|
+
if (entry.isDirectory() && !entry.name.startsWith(".") && entry.name !== "node_modules") {
|
|
240
|
+
next.push(path.join(d, entry.name));
|
|
241
|
+
}
|
|
242
|
+
}
|
|
243
|
+
} catch {
|
|
244
|
+
}
|
|
245
|
+
}
|
|
246
|
+
return next;
|
|
247
|
+
}
|
|
248
|
+
function listSelfAndDescendants(dirs) {
|
|
249
|
+
const all = [...dirs];
|
|
250
|
+
let frontier = dirs;
|
|
251
|
+
for (let depth = 0; depth < MAX_GLOB_DEPTH && frontier.length > 0; depth++) {
|
|
252
|
+
frontier = listSubdirs(frontier);
|
|
253
|
+
all.push(...frontier);
|
|
254
|
+
}
|
|
255
|
+
return all;
|
|
256
|
+
}
|
|
257
|
+
var GIT_SCAN_MAX_DEPTH = 3;
|
|
258
|
+
var GIT_SCAN_LIMIT = 20;
|
|
259
|
+
function scanGitProjects(root, maxDepth = GIT_SCAN_MAX_DEPTH) {
|
|
260
|
+
const found = [];
|
|
261
|
+
const walk = (dir, depth) => {
|
|
262
|
+
if (depth > maxDepth) {
|
|
263
|
+
return;
|
|
264
|
+
}
|
|
265
|
+
let entries;
|
|
266
|
+
try {
|
|
267
|
+
entries = fs.readdirSync(dir, { withFileTypes: true });
|
|
268
|
+
} catch {
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
if (depth > 0 && entries.some((e) => e.name === ".git")) {
|
|
272
|
+
let mtimeMs = 0;
|
|
273
|
+
try {
|
|
274
|
+
mtimeMs = fs.statSync(dir).mtimeMs;
|
|
275
|
+
} catch {
|
|
276
|
+
}
|
|
277
|
+
found.push({ path: dir, name: path.basename(dir), mtimeMs });
|
|
278
|
+
return;
|
|
279
|
+
}
|
|
280
|
+
for (const e of entries) {
|
|
281
|
+
if (e.isDirectory() && !e.name.startsWith(".") && e.name !== "node_modules") {
|
|
282
|
+
walk(path.join(dir, e.name), depth + 1);
|
|
283
|
+
}
|
|
284
|
+
}
|
|
285
|
+
};
|
|
286
|
+
walk(root, 0);
|
|
287
|
+
found.sort((a, b) => b.mtimeMs - a.mtimeMs);
|
|
288
|
+
return found.slice(0, GIT_SCAN_LIMIT);
|
|
289
|
+
}
|
|
290
|
+
function expandSimpleGlob(cwd, glob) {
|
|
291
|
+
const parts = glob.split("/").filter(Boolean);
|
|
292
|
+
let dirs = [cwd];
|
|
293
|
+
for (const part of parts) {
|
|
294
|
+
if (part === "**") {
|
|
295
|
+
dirs = listSelfAndDescendants(dirs);
|
|
296
|
+
} else if (part === "*") {
|
|
297
|
+
dirs = listSubdirs(dirs);
|
|
298
|
+
} else {
|
|
299
|
+
dirs = dirs.map((d) => path.join(d, part)).filter((d) => exists(d));
|
|
300
|
+
}
|
|
301
|
+
}
|
|
302
|
+
return [...new Set(dirs)];
|
|
303
|
+
}
|
|
304
|
+
function anyWorkspaceMemberHasTsconfig(cwd, pkg) {
|
|
305
|
+
for (const glob of workspaceGlobs(cwd, pkg)) {
|
|
306
|
+
for (const dir of expandSimpleGlob(cwd, glob)) {
|
|
307
|
+
if (exists(path.join(dir, "tsconfig.json"))) {
|
|
308
|
+
return true;
|
|
309
|
+
}
|
|
310
|
+
}
|
|
311
|
+
}
|
|
312
|
+
return false;
|
|
313
|
+
}
|
|
314
|
+
function detectLanguage(cwd) {
|
|
315
|
+
if (exists(path.join(cwd, "tsconfig.json"))) {
|
|
316
|
+
return "typescript";
|
|
317
|
+
}
|
|
318
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
319
|
+
if (exists(pkgPath)) {
|
|
320
|
+
const pkg = readJson(pkgPath);
|
|
321
|
+
if (hasTypescriptDependency(pkg) || anyWorkspaceMemberHasTsconfig(cwd, pkg)) {
|
|
322
|
+
return "typescript";
|
|
323
|
+
}
|
|
324
|
+
return "javascript";
|
|
325
|
+
}
|
|
326
|
+
if (exists(path.join(cwd, "pyproject.toml")) || exists(path.join(cwd, "setup.py")) || exists(path.join(cwd, "requirements.txt"))) {
|
|
327
|
+
return "python";
|
|
328
|
+
}
|
|
329
|
+
return null;
|
|
330
|
+
}
|
|
331
|
+
function splitEnv(script) {
|
|
332
|
+
const env = {};
|
|
333
|
+
let rest = script.trim();
|
|
334
|
+
const re = /^(\w+)=(\S+)\s+/;
|
|
335
|
+
let m = re.exec(rest);
|
|
336
|
+
while (m !== null) {
|
|
337
|
+
env[m[1]] = m[2];
|
|
338
|
+
rest = rest.slice(m[0].length);
|
|
339
|
+
m = re.exec(rest);
|
|
340
|
+
}
|
|
341
|
+
return Object.keys(env).length > 0 ? { cmd: rest, env } : { cmd: rest };
|
|
342
|
+
}
|
|
343
|
+
function detectVerify(cwd) {
|
|
344
|
+
const pkg = readJson(path.join(cwd, "package.json"));
|
|
345
|
+
const scripts = pkg && typeof pkg === "object" && typeof pkg.scripts === "object" ? pkg.scripts ?? {} : {};
|
|
346
|
+
const pick = (names) => {
|
|
347
|
+
for (const n of names) {
|
|
348
|
+
const v = scripts[n];
|
|
349
|
+
if (typeof v === "string" && v.trim() !== "") {
|
|
350
|
+
return splitEnv(v);
|
|
351
|
+
}
|
|
352
|
+
}
|
|
353
|
+
return null;
|
|
354
|
+
};
|
|
355
|
+
const hasTsconfig = exists(path.join(cwd, "tsconfig.json"));
|
|
356
|
+
return {
|
|
357
|
+
typecheck: pick(["typecheck", "type-check", "tsc"]) ?? (hasTsconfig ? { cmd: "tsc --noEmit" } : null),
|
|
358
|
+
lint: pick(["lint"]),
|
|
359
|
+
test: pick(["test"]),
|
|
360
|
+
e2e: pick(["e2e", "test:e2e"])
|
|
361
|
+
};
|
|
362
|
+
}
|
|
363
|
+
function detectWorkspacePackages(cwd) {
|
|
364
|
+
const pkgPath = path.join(cwd, "package.json");
|
|
365
|
+
if (!exists(pkgPath)) {
|
|
366
|
+
return [];
|
|
367
|
+
}
|
|
368
|
+
const pkg = readJson(pkgPath);
|
|
369
|
+
const dirs = /* @__PURE__ */ new Set();
|
|
370
|
+
for (const glob of workspaceGlobs(cwd, pkg)) {
|
|
371
|
+
for (const dir of expandSimpleGlob(cwd, glob)) {
|
|
372
|
+
if (exists(path.join(dir, "package.json"))) {
|
|
373
|
+
dirs.add(path.relative(cwd, dir));
|
|
374
|
+
}
|
|
375
|
+
}
|
|
376
|
+
}
|
|
377
|
+
return [...dirs].sort();
|
|
378
|
+
}
|
|
379
|
+
function isVerifyEmpty(v) {
|
|
380
|
+
return !v.typecheck && !v.lint && !v.test && !v.e2e;
|
|
381
|
+
}
|
|
382
|
+
function buildConfig(inputs, engineVersion) {
|
|
383
|
+
return {
|
|
384
|
+
project: inputs.project,
|
|
385
|
+
mainLanguage: inputs.mainLanguage,
|
|
386
|
+
character: inputs.character,
|
|
387
|
+
engineVersion,
|
|
388
|
+
verify: {
|
|
389
|
+
typecheck: inputs.verify.typecheck,
|
|
390
|
+
lint: inputs.verify.lint,
|
|
391
|
+
test: inputs.verify.test,
|
|
392
|
+
e2e: inputs.verify.e2e
|
|
393
|
+
}
|
|
394
|
+
};
|
|
395
|
+
}
|
|
396
|
+
function packageEngineDir() {
|
|
397
|
+
const here = path.dirname(fileURLToPath(import.meta.url));
|
|
398
|
+
for (const up of ["..", "../.."]) {
|
|
399
|
+
const candidate = path.join(here, up, "engine");
|
|
400
|
+
if (exists(path.join(candidate, "version.json"))) {
|
|
401
|
+
return candidate;
|
|
402
|
+
}
|
|
403
|
+
}
|
|
404
|
+
return path.join(here, "..", "engine");
|
|
405
|
+
}
|
|
406
|
+
function isGlobalInstalled() {
|
|
407
|
+
return exists(globalRoot());
|
|
408
|
+
}
|
|
409
|
+
function scaffoldGlobal() {
|
|
410
|
+
const root = globalRoot();
|
|
411
|
+
const created = !exists(root);
|
|
412
|
+
for (const dir of [
|
|
413
|
+
"records",
|
|
414
|
+
"gotchas",
|
|
415
|
+
"rules",
|
|
416
|
+
path.join("rules", "active"),
|
|
417
|
+
"templates",
|
|
418
|
+
"generations"
|
|
419
|
+
]) {
|
|
420
|
+
fs.mkdirSync(path.join(root, dir), { recursive: true });
|
|
421
|
+
}
|
|
422
|
+
if (!exists(path.join(root, "rules", "index.json"))) {
|
|
423
|
+
writeFileEnsuringDir(path.join(root, "rules", "index.json"), "[]\n");
|
|
424
|
+
}
|
|
425
|
+
if (!exists(path.join(root, "rules", "graduated.md"))) {
|
|
426
|
+
writeFileEnsuringDir(path.join(root, "rules", "graduated.md"), "");
|
|
427
|
+
}
|
|
428
|
+
if (!exists(projectsFile())) {
|
|
429
|
+
writeFileEnsuringDir(projectsFile(), "[]\n");
|
|
430
|
+
}
|
|
431
|
+
fs.cpSync(packageEngineDir(), engineDir(), { recursive: true });
|
|
432
|
+
return { created, engineVersion: installedEngineVersion() ?? "unknown" };
|
|
433
|
+
}
|
|
434
|
+
function writeConfig(projectRoot, config) {
|
|
435
|
+
const p = path.join(projectRoot, ".awl", "config.json");
|
|
436
|
+
writeFileEnsuringDir(p, `${JSON.stringify(config, null, 2)}
|
|
437
|
+
`);
|
|
438
|
+
return p;
|
|
439
|
+
}
|
|
440
|
+
function writeState(projectRoot, now) {
|
|
441
|
+
const p = path.join(projectRoot, ".awl", "state.json");
|
|
442
|
+
const state = { generation: 1, createdAt: now, loop: null };
|
|
443
|
+
writeFileEnsuringDir(p, `${JSON.stringify(state, null, 2)}
|
|
444
|
+
`);
|
|
445
|
+
return p;
|
|
446
|
+
}
|
|
447
|
+
function ensureGitignore(projectRoot) {
|
|
448
|
+
const gi = path.join(projectRoot, ".gitignore");
|
|
449
|
+
const targets = [
|
|
450
|
+
".awl/state.json",
|
|
451
|
+
".awl/verify-baseline.json",
|
|
452
|
+
".awl/state.lock",
|
|
453
|
+
".awl-worktrees/",
|
|
454
|
+
".awl/home/"
|
|
455
|
+
];
|
|
456
|
+
let content = exists(gi) ? fs.readFileSync(gi, "utf8") : "";
|
|
457
|
+
const has = (t) => content.split(/\r?\n/).some((line) => line.trim() === t);
|
|
458
|
+
const missing = targets.filter((t) => !has(t));
|
|
459
|
+
if (missing.length === 0) {
|
|
460
|
+
return "exists";
|
|
461
|
+
}
|
|
462
|
+
for (const target of missing) {
|
|
463
|
+
const prefix = content.length > 0 && !content.endsWith("\n") ? "\n" : "";
|
|
464
|
+
content = `${content}${prefix}${target}
|
|
465
|
+
`;
|
|
466
|
+
}
|
|
467
|
+
fs.writeFileSync(gi, content);
|
|
468
|
+
return "added";
|
|
469
|
+
}
|
|
470
|
+
function installSafetyHook(projectRoot) {
|
|
471
|
+
try {
|
|
472
|
+
const hook = path.join(projectRoot, ".git", "hooks", "pre-push");
|
|
473
|
+
if (exists(hook))
|
|
474
|
+
return { installed: false, warning: "\uAE30\uC874 pre-push \uD6C5\uC774 \uC788\uC5B4 awl \uD6C5\uC744 \uB36E\uC5B4\uC4F0\uC9C0 \uC54A\uC558\uC2B5\uB2C8\uB2E4." };
|
|
475
|
+
const template = path.join(packageEngineDir(), "templates", "pre-push.sample");
|
|
476
|
+
fs.mkdirSync(path.dirname(hook), { recursive: true });
|
|
477
|
+
fs.cpSync(template, hook);
|
|
478
|
+
fs.chmodSync(hook, 493);
|
|
479
|
+
return { installed: true };
|
|
480
|
+
} catch (error) {
|
|
481
|
+
return { installed: false, warning: `push \uCC28\uB2E8 \uD6C5\uC744 \uC124\uCE58\uD558\uC9C0 \uBABB\uD588\uC2B5\uB2C8\uB2E4: ${String(error)}` };
|
|
482
|
+
}
|
|
483
|
+
}
|
|
484
|
+
function registerProject(entry) {
|
|
485
|
+
const raw = readJson(projectsFile());
|
|
486
|
+
const list = Array.isArray(raw) ? raw : [];
|
|
487
|
+
const idx = list.findIndex((p) => p.path === entry.path);
|
|
488
|
+
if (idx >= 0) {
|
|
489
|
+
list[idx] = { ...list[idx], ...entry };
|
|
490
|
+
} else {
|
|
491
|
+
list.push(entry);
|
|
492
|
+
}
|
|
493
|
+
writeFileEnsuringDir(projectsFile(), `${JSON.stringify(list, null, 2)}
|
|
494
|
+
`);
|
|
495
|
+
return list.length;
|
|
496
|
+
}
|
|
497
|
+
function claudeSkillNames() {
|
|
498
|
+
const dir = path.join(engineDir(), "skills", "claude");
|
|
499
|
+
try {
|
|
500
|
+
return fs.readdirSync(dir, { withFileTypes: true }).filter((e) => e.isDirectory()).map((e) => e.name).sort();
|
|
501
|
+
} catch {
|
|
502
|
+
return [];
|
|
503
|
+
}
|
|
504
|
+
}
|
|
505
|
+
function copyClaudeSkills(projectRoot, filter) {
|
|
506
|
+
const copied = [];
|
|
507
|
+
for (const name of claudeSkillNames()) {
|
|
508
|
+
if (!filter(name)) {
|
|
509
|
+
continue;
|
|
510
|
+
}
|
|
511
|
+
const src = path.join(engineDir(), "skills", "claude", name);
|
|
512
|
+
const dest = path.join(projectRoot, ".claude", "skills", name);
|
|
513
|
+
fs.mkdirSync(path.dirname(dest), { recursive: true });
|
|
514
|
+
fs.cpSync(src, dest, { recursive: true });
|
|
515
|
+
copied.push(name);
|
|
516
|
+
}
|
|
517
|
+
return copied;
|
|
518
|
+
}
|
|
519
|
+
function installClaudeSkill(projectRoot) {
|
|
520
|
+
return copyClaudeSkills(projectRoot, () => true).length > 0;
|
|
521
|
+
}
|
|
522
|
+
function claudeSkillLabel(names = claudeSkillNames()) {
|
|
523
|
+
return `Claude Code (.claude/skills/ \uC5D0 ${names.length}\uAC1C \uC2A4\uD0AC \uC124\uCE58)`;
|
|
524
|
+
}
|
|
525
|
+
function installCodexSkill(projectRoot) {
|
|
526
|
+
const src = path.join(engineDir(), "skills", "codex", "AGENTS.awl.md");
|
|
527
|
+
if (!exists(src)) {
|
|
528
|
+
return false;
|
|
529
|
+
}
|
|
530
|
+
const snippet = fs.readFileSync(src, "utf8");
|
|
531
|
+
const agents = path.join(projectRoot, "AGENTS.md");
|
|
532
|
+
const current = exists(agents) ? fs.readFileSync(agents, "utf8") : "";
|
|
533
|
+
if (current.includes("awl-loop:start")) {
|
|
534
|
+
return true;
|
|
535
|
+
}
|
|
536
|
+
const prefix = current.length > 0 && !current.endsWith("\n") ? "\n" : "";
|
|
537
|
+
fs.writeFileSync(agents, `${current}${prefix}${current.length > 0 ? "\n" : ""}${snippet}`);
|
|
538
|
+
return true;
|
|
539
|
+
}
|
|
540
|
+
function skillsVersionPath(projectRoot) {
|
|
541
|
+
return path.join(projectRoot, ".awl", "skills-version.json");
|
|
542
|
+
}
|
|
543
|
+
function writeSkillsVersionStamp(projectRoot, installed, engineVersion) {
|
|
544
|
+
if (!installed.claude && !installed.codex) {
|
|
545
|
+
return;
|
|
546
|
+
}
|
|
547
|
+
const p = skillsVersionPath(projectRoot);
|
|
548
|
+
let current = {};
|
|
549
|
+
try {
|
|
550
|
+
current = JSON.parse(fs.readFileSync(p, "utf8"));
|
|
551
|
+
} catch {
|
|
552
|
+
}
|
|
553
|
+
const next = { ...current };
|
|
554
|
+
if (installed.claude) {
|
|
555
|
+
next.claude = engineVersion;
|
|
556
|
+
}
|
|
557
|
+
if (installed.codex) {
|
|
558
|
+
next.codex = engineVersion;
|
|
559
|
+
}
|
|
560
|
+
fs.mkdirSync(path.dirname(p), { recursive: true });
|
|
561
|
+
fs.writeFileSync(p, `${JSON.stringify(next, null, 2)}
|
|
562
|
+
`);
|
|
563
|
+
}
|
|
564
|
+
function syncExistingInstall(projectRoot, engineVersion) {
|
|
565
|
+
let configUpdated = false;
|
|
566
|
+
const configPath = path.join(projectRoot, ".awl", "config.json");
|
|
567
|
+
const raw = readJson(configPath);
|
|
568
|
+
if (raw && raw.engineVersion !== engineVersion) {
|
|
569
|
+
writeFileEnsuringDir(configPath, `${JSON.stringify({ ...raw, engineVersion }, null, 2)}
|
|
570
|
+
`);
|
|
571
|
+
configUpdated = true;
|
|
572
|
+
}
|
|
573
|
+
const skills = [];
|
|
574
|
+
const claudeInUse = claudeSkillNames().some(
|
|
575
|
+
(name) => exists(path.join(projectRoot, ".claude", "skills", name))
|
|
576
|
+
);
|
|
577
|
+
if (claudeInUse && installClaudeSkill(projectRoot)) {
|
|
578
|
+
skills.push("claude");
|
|
579
|
+
}
|
|
580
|
+
const agentsMd = path.join(projectRoot, "AGENTS.md");
|
|
581
|
+
const codexInstalled = exists(agentsMd) && fs.readFileSync(agentsMd, "utf8").includes("awl-loop:start");
|
|
582
|
+
if (codexInstalled && installCodexSkill(projectRoot)) {
|
|
583
|
+
skills.push("codex");
|
|
584
|
+
}
|
|
585
|
+
writeSkillsVersionStamp(
|
|
586
|
+
projectRoot,
|
|
587
|
+
{ claude: skills.includes("claude"), codex: skills.includes("codex") },
|
|
588
|
+
engineVersion
|
|
589
|
+
);
|
|
590
|
+
return { configUpdated, skills };
|
|
591
|
+
}
|
|
592
|
+
function countEntries(dir) {
|
|
593
|
+
try {
|
|
594
|
+
return fs.readdirSync(dir).filter((f) => !f.startsWith(".")).length;
|
|
595
|
+
} catch {
|
|
596
|
+
return 0;
|
|
597
|
+
}
|
|
598
|
+
}
|
|
599
|
+
function detectAgents(projectRoot) {
|
|
600
|
+
return {
|
|
601
|
+
claude: exists(path.join(projectRoot, ".claude")),
|
|
602
|
+
codex: exists(path.join(projectRoot, "AGENTS.md"))
|
|
603
|
+
};
|
|
604
|
+
}
|
|
605
|
+
function nonInteractiveInputs(projectRoot) {
|
|
606
|
+
const detected = detectAgents(projectRoot);
|
|
607
|
+
const skills = detected.claude || detected.codex ? detected : { claude: true, codex: false };
|
|
608
|
+
return {
|
|
609
|
+
project: path.basename(projectRoot),
|
|
610
|
+
mainLanguage: detectLanguage(projectRoot) ?? "",
|
|
611
|
+
character: "",
|
|
612
|
+
verify: detectVerify(projectRoot),
|
|
613
|
+
skills
|
|
614
|
+
};
|
|
615
|
+
}
|
|
616
|
+
function applyInit(projectRoot, inputs, now) {
|
|
617
|
+
const g = scaffoldGlobal();
|
|
618
|
+
const config = buildConfig(inputs, g.engineVersion);
|
|
619
|
+
const configPath = writeConfig(projectRoot, config);
|
|
620
|
+
const statePath = writeState(projectRoot, now);
|
|
621
|
+
const gitignore = ensureGitignore(projectRoot);
|
|
622
|
+
const safetyHook = installSafetyHook(projectRoot);
|
|
623
|
+
const projectCount = registerProject({
|
|
624
|
+
name: inputs.project,
|
|
625
|
+
path: projectRoot,
|
|
626
|
+
mainLanguage: inputs.mainLanguage,
|
|
627
|
+
character: inputs.character,
|
|
628
|
+
registeredAt: now
|
|
629
|
+
});
|
|
630
|
+
const skills = [];
|
|
631
|
+
const claudeInstalled = inputs.skills.claude && installClaudeSkill(projectRoot);
|
|
632
|
+
if (claudeInstalled) {
|
|
633
|
+
skills.push("claude");
|
|
634
|
+
}
|
|
635
|
+
const codexInstalled = inputs.skills.codex && installCodexSkill(projectRoot);
|
|
636
|
+
if (codexInstalled) {
|
|
637
|
+
skills.push("codex");
|
|
638
|
+
}
|
|
639
|
+
writeSkillsVersionStamp(
|
|
640
|
+
projectRoot,
|
|
641
|
+
{ claude: claudeInstalled, codex: codexInstalled },
|
|
642
|
+
g.engineVersion
|
|
643
|
+
);
|
|
644
|
+
return {
|
|
645
|
+
globalCreated: g.created,
|
|
646
|
+
engineVersion: g.engineVersion,
|
|
647
|
+
configPath,
|
|
648
|
+
statePath,
|
|
649
|
+
gitignore,
|
|
650
|
+
skills,
|
|
651
|
+
projectCount,
|
|
652
|
+
ruleCount: countEntries(path.join(globalRoot(), "rules", "active")),
|
|
653
|
+
lessonCount: countEntries(path.join(globalRoot(), "lessons")),
|
|
654
|
+
safetyHook
|
|
655
|
+
};
|
|
656
|
+
}
|
|
657
|
+
var WIDTH = 64;
|
|
658
|
+
function stepBox(step, title, lines, c) {
|
|
659
|
+
return card(`${step}${c.unicode ? " \xB7 " : " - "}${title}`, lines, c, WIDTH - 4);
|
|
660
|
+
}
|
|
661
|
+
var VERIFY_LABELS = {
|
|
662
|
+
typecheck: "\uD0C0\uC785\uCCB4\uD06C",
|
|
663
|
+
lint: "\uB9B0\uD2B8",
|
|
664
|
+
test: "\uD14C\uC2A4\uD2B8",
|
|
665
|
+
e2e: "E2E"
|
|
666
|
+
};
|
|
667
|
+
function verifyLines(v) {
|
|
668
|
+
const keys = Object.keys(VERIFY_LABELS);
|
|
669
|
+
const labelWidth = Math.max(...keys.map((k) => stringWidth(VERIFY_LABELS[k]))) + 2;
|
|
670
|
+
return keys.map((k) => {
|
|
671
|
+
const entry = v[k];
|
|
672
|
+
return ` ${padEndDisplay(VERIFY_LABELS[k], labelWidth)}${entry ? entry.cmd : "(\uC5C6\uC74C)"}`;
|
|
673
|
+
});
|
|
674
|
+
}
|
|
675
|
+
function verifyStepLines(v) {
|
|
676
|
+
return [
|
|
677
|
+
"package.json \uB4F1\uC5D0\uC11C \uCC3E\uC558\uC2B5\uB2C8\uB2E4. \uB9DE\uC73C\uBA74 Enter, \uACE0\uCE58\uB824\uBA74 \uC0C8\uB85C \uC785\uB825.",
|
|
678
|
+
"",
|
|
679
|
+
...verifyLines(v),
|
|
680
|
+
"",
|
|
681
|
+
"\uC774 \uBA85\uB839\uC5B4\uB4E4\uC774 \uC720\uC77C\uD55C \uC2EC\uD310\uC785\uB2C8\uB2E4.",
|
|
682
|
+
'AI \uAC00 "\uB2E4 \uD588\uC2B5\uB2C8\uB2E4"\uB77C\uACE0 \uB9D0\uD560 \uC218 \uC5C6\uAC8C \uB9CC\uB4DC\uB294 \uC7A5\uCE58\uC785\uB2C8\uB2E4.'
|
|
683
|
+
];
|
|
684
|
+
}
|
|
685
|
+
function renderNonTtyNotice() {
|
|
686
|
+
return [
|
|
687
|
+
"",
|
|
688
|
+
" awl init \uC740 \uD654\uBA74\uC5D0\uC11C \uBA87 \uAC00\uC9C0\uB97C \uBB3C\uC5B4\uBD05\uB2C8\uB2E4.",
|
|
689
|
+
" \uC9C0\uAE08\uC740 \uB300\uD654\uD615 \uD654\uBA74\uC744 \uB744\uC6B8 \uC218 \uC5C6\uB294 \uD658\uACBD\uC785\uB2C8\uB2E4(\uD30C\uC774\uD504/CI).",
|
|
690
|
+
"",
|
|
691
|
+
" \uC790\uB3D9\uC73C\uB85C \uC9C4\uD589\uD558\uB824\uBA74 --yes \uB97C \uBD99\uC774\uC138\uC694. \uAC10\uC9C0\uB41C \uAC12\uC73C\uB85C \uC124\uC815\uD569\uB2C8\uB2E4.",
|
|
692
|
+
"",
|
|
693
|
+
" awl init --yes",
|
|
694
|
+
""
|
|
695
|
+
].join("\n");
|
|
696
|
+
}
|
|
697
|
+
function renderResult(result, inputs, c) {
|
|
698
|
+
const color = makeColors(c.color);
|
|
699
|
+
const t = makeTokens(c);
|
|
700
|
+
const line = (name, value, note = "") => ` ${padEndDisplay(name, 20)}${t.emphasis(value)}${note ? ` ${color.dim(note)}` : ""}`;
|
|
701
|
+
const setupLines = [];
|
|
702
|
+
setupLines.push(line("~/.awl", result.globalCreated ? "\uC0DD\uC131\uB428" : "\uC774\uBBF8 \uC788\uC74C"));
|
|
703
|
+
setupLines.push(line("~/.awl/engine", result.engineVersion));
|
|
704
|
+
setupLines.push(line(".awl/config.json", "\uC0DD\uC131\uB428", "<- \uCEE4\uBC0B\uD558\uC138\uC694. \uD300\uC6D0\uC740 \uC774 \uD30C\uC77C\uC744 \uC501\uB2C8\uB2E4"));
|
|
705
|
+
setupLines.push(
|
|
706
|
+
line(
|
|
707
|
+
".awl/state.json",
|
|
708
|
+
result.gitignore === "added" ? "gitignore \uC5D0 \uCD94\uAC00\uD568" : "\uC774\uBBF8 gitignore \uC5D0 \uC788\uC74C",
|
|
709
|
+
result.safetyHook.warning ? `${signal(c, "warn")} ${result.safetyHook.warning}` : result.safetyHook.installed ? `${signal(c, "ok")} git push \uCC28\uB2E8 \uD6C5 \uC124\uCE58` : "git push \uCC28\uB2E8 \uD6C5 \uC774\uBBF8 \uC124\uCE58\uB428"
|
|
710
|
+
)
|
|
711
|
+
);
|
|
712
|
+
setupLines.push(
|
|
713
|
+
color.dim(
|
|
714
|
+
`\uADDC\uCE59 ${result.ruleCount}\uAC1C \xB7 \uAD50\uD6C8 ${result.lessonCount}\uAC1C \xB7 \uB4F1\uB85D\uB41C \uD504\uB85C\uC81D\uD2B8 ${result.projectCount}\uAC1C \xB7 1\uC138\uB300`
|
|
715
|
+
)
|
|
716
|
+
);
|
|
717
|
+
const nextLines = [];
|
|
718
|
+
if (inputs.skills.claude) {
|
|
719
|
+
nextLines.push("Claude Code \uB97C \uC5F4\uACE0 \uC774\uB807\uAC8C \uB9D0\uD558\uC138\uC694.");
|
|
720
|
+
nextLines.push("");
|
|
721
|
+
nextLines.push(`${color.bold("/awl-loop")} \uD398\uC774\uC9C0 \uD3B8\uC9D1\uAE30\uC5D0 \uC5EC\uBC31 \uC2DC\uC2A4\uD15C\uC744 \uB123\uACE0 \uC2F6\uC5B4`);
|
|
722
|
+
} else if (inputs.skills.codex) {
|
|
723
|
+
nextLines.push("Codex \uC5D0\uAC8C \uC774\uB807\uAC8C \uB9D0\uD558\uC138\uC694.");
|
|
724
|
+
nextLines.push("");
|
|
725
|
+
nextLines.push(`${color.bold("/awl-loop")} \uD398\uC774\uC9C0 \uD3B8\uC9D1\uAE30\uC5D0 \uC5EC\uBC31 \uC2DC\uC2A4\uD15C\uC744 \uB123\uACE0 \uC2F6\uC5B4`);
|
|
726
|
+
} else {
|
|
727
|
+
nextLines.push("\uB098\uC911\uC5D0 \uC2A4\uD0AC\uC744 \uC124\uCE58\uD558\uB824\uBA74 awl init \uC744 \uB2E4\uC2DC \uC2E4\uD589\uD558\uC138\uC694.");
|
|
728
|
+
}
|
|
729
|
+
return [
|
|
730
|
+
"",
|
|
731
|
+
card("\uC124\uC815 \uC644\uB8CC", setupLines, c, WIDTH - 4),
|
|
732
|
+
"",
|
|
733
|
+
card("\uB2E4\uC74C \uB2E8\uACC4", nextLines, c, WIDTH - 4)
|
|
734
|
+
].join("\n");
|
|
735
|
+
}
|
|
736
|
+
var LANG_OPTIONS = ["TypeScript", "JavaScript", "Python", "\uC9C1\uC811 \uC785\uB825"];
|
|
737
|
+
var LANG_VALUES = ["typescript", "javascript", "python", ""];
|
|
738
|
+
function langDefaultIndex(projectRoot) {
|
|
739
|
+
const detected = detectLanguage(projectRoot);
|
|
740
|
+
const idx = detected ? LANG_VALUES.indexOf(detected) : 0;
|
|
741
|
+
return idx < 0 ? 0 : idx;
|
|
742
|
+
}
|
|
743
|
+
function buildScreens(projectRoot, hasGlobal, c) {
|
|
744
|
+
const project = path.basename(projectRoot);
|
|
745
|
+
const defLang = langDefaultIndex(projectRoot);
|
|
746
|
+
const verify = detectVerify(projectRoot);
|
|
747
|
+
const agents = detectAgents(projectRoot);
|
|
748
|
+
const welcome = hasGlobal ? null : [
|
|
749
|
+
"",
|
|
750
|
+
" ~/.awl \uC774 \uC5C6\uC2B5\uB2C8\uB2E4. \uCC98\uC74C \uC624\uC168\uAD70\uC694.",
|
|
751
|
+
"",
|
|
752
|
+
" \uC5EC\uB7EC \uD504\uB85C\uC81D\uD2B8\uB97C \uB4F1\uB85D\uD574\uC11C Agent Work Loop \uB97C \uB3CC\uB9B4 \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
753
|
+
" \uC774 \uD504\uB85C\uC81D\uD2B8\uB97C \uCCAB \uBC88\uC9F8\uB85C \uB4F1\uB85D\uD569\uB2C8\uB2E4.",
|
|
754
|
+
"",
|
|
755
|
+
` ${project}`,
|
|
756
|
+
` ${projectRoot}`,
|
|
757
|
+
"",
|
|
758
|
+
" \uB2E4\uB978 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C\uB3C4 awl init \uC744 \uC2E4\uD589\uD558\uBA74 \uADF8\uB54C \uB4F1\uB85D\uB429\uB2C8\uB2E4.",
|
|
759
|
+
" \uADDC\uCE59\uACFC \uAD50\uD6C8\uC740 \uD504\uB85C\uC81D\uD2B8\uAC00 \uC544\uB2C8\uB77C \uB2F9\uC2E0\uC5D0\uAC8C \uC313\uC774\uACE0,",
|
|
760
|
+
" \uB4F1\uB85D\uB41C \uBAA8\uB4E0 \uD504\uB85C\uC81D\uD2B8\uC5D0\uC11C \uD568\uAED8 \uC4F0\uC785\uB2C8\uB2E4."
|
|
761
|
+
].join("\n");
|
|
762
|
+
const langLines = [
|
|
763
|
+
`\uC790\uB3D9 \uAC10\uC9C0: ${LANG_OPTIONS[defLang] ?? "TypeScript"}`,
|
|
764
|
+
"",
|
|
765
|
+
"\uBC14\uB85C \uC544\uB798\uC758 \uC120\uD0DD\uAE30\uC5D0\uC11C \uACE0\uB985\uB2C8\uB2E4.",
|
|
766
|
+
"\uD654\uC0B4\uD45C \uB610\uB294 j/k \uB85C \uC774\uB3D9\uD558\uACE0 Enter \uB85C \uD655\uC815\uD558\uC138\uC694.",
|
|
767
|
+
"\uD0A4 \uC785\uB825\uC744 \uC9C0\uC6D0\uD558\uC9C0 \uC54A\uB294 \uD130\uBBF8\uB110\uC5D0\uC11C\uB294 \uBC88\uD638\uB97C \uC785\uB825\uD560 \uC218 \uC788\uC2B5\uB2C8\uB2E4."
|
|
768
|
+
];
|
|
769
|
+
return {
|
|
770
|
+
welcome,
|
|
771
|
+
lang: stepBox("1/3", "\uC8FC \uC5B8\uC5B4", langLines, c),
|
|
772
|
+
verify: stepBox("2/3", "\uAC80\uC99D \uBA85\uB839\uC5B4", verifyStepLines(verify), c),
|
|
773
|
+
character: stepBox(
|
|
774
|
+
"3/3",
|
|
775
|
+
"\uADDC\uCE59\uACFC \uC774 \uD504\uB85C\uC81D\uD2B8\uC758 \uC131\uACA9",
|
|
776
|
+
[
|
|
777
|
+
"\uAC19\uC740 \uC2E4\uD328\uAC00 \uC313\uC774\uBA74 \uADDC\uCE59\uC774 \uB418\uACE0, \uB2E4\uC74C \uD504\uB85C\uC81D\uD2B8\uC5D0\uB3C4 \uC804\uD30C\uB429\uB2C8\uB2E4.",
|
|
778
|
+
"\uD504\uB85C\uC81D\uD2B8\uC758 \uC131\uACA9\uC740 \uADF8 \uADDC\uCE59\uC744 \uC5EC\uAE30\uC5D0\uB3C4 \uC801\uC6A9\uD560\uC9C0 \uD310\uB2E8\uD558\uB294 \uADFC\uAC70\uC785\uB2C8\uB2E4.",
|
|
779
|
+
"",
|
|
780
|
+
"\uC774 \uD504\uB85C\uC81D\uD2B8\uB294 \uC5B4\uB5A4 \uACF3\uC785\uB2C8\uAE4C?",
|
|
781
|
+
'(\uC608\uC2DC: "React + TailwindCSS \uC6F9 \uD504\uB860\uD2B8\uC5D4\uB4DC", "Python Fast API \uBD84\uC11D \uC11C\uBC84",',
|
|
782
|
+
' "TypeScript \uB77C\uC774\uBE0C\uB7EC\uB9AC \uD328\uD0A4\uC9C0")'
|
|
783
|
+
],
|
|
784
|
+
c
|
|
785
|
+
),
|
|
786
|
+
skills: stepBox(
|
|
787
|
+
"\uC2A4\uD0AC",
|
|
788
|
+
"\uC2A4\uD0AC \uC124\uCE58",
|
|
789
|
+
[
|
|
790
|
+
"awl \uC740 \uD310\uB2E8\uD558\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uD30C\uC77C\uACFC \uC0C1\uD0DC\uB9CC \uAD00\uB9AC\uD569\uB2C8\uB2E4.",
|
|
791
|
+
"\uD310\uB2E8\uC740 \uC774\uBBF8 \uC4F0\uACE0 \uACC4\uC2E0 \uC5D0\uC774\uC804\uD2B8\uAC00 \uD569\uB2C8\uB2E4.",
|
|
792
|
+
"",
|
|
793
|
+
"\uBC14\uB85C \uC544\uB798\uC5D0\uC11C \uC124\uCE58\uD560 \uC5D0\uC774\uC804\uD2B8\uB97C \uACE0\uB985\uB2C8\uB2E4.",
|
|
794
|
+
"\uCCAB \uBC88\uC9F8 \u201C\uBAA8\uB450 \uC124\uCE58\u201D\uB97C \uACE0\uB974\uAC70\uB098, Space \uB85C \uD544\uC694\uD55C \uD56D\uBAA9\uB9CC \uACE0\uB97C \uC218 \uC788\uC2B5\uB2C8\uB2E4.",
|
|
795
|
+
`\uAC10\uC9C0\uB428: Claude Code ${agents.claude ? "\uC788\uC74C" : "\uC5C6\uC74C"} \xB7 Codex ${agents.codex ? "\uC788\uC74C" : "\uC5C6\uC74C"}`
|
|
796
|
+
],
|
|
797
|
+
c
|
|
798
|
+
)
|
|
799
|
+
};
|
|
800
|
+
}
|
|
801
|
+
function ask(rl, q) {
|
|
802
|
+
return new Promise((resolve) => rl.question(q, (a) => resolve(a)));
|
|
803
|
+
}
|
|
804
|
+
async function promptNumber(rl, defaultIndex, count) {
|
|
805
|
+
const answer = (await ask(rl, ` \uBC88\uD638 \uC120\uD0DD (\uAE30\uBCF8 ${defaultIndex + 1}): `)).trim();
|
|
806
|
+
if (answer === "") {
|
|
807
|
+
return defaultIndex;
|
|
808
|
+
}
|
|
809
|
+
const n = Number(answer);
|
|
810
|
+
return Number.isInteger(n) && n >= 1 && n <= count ? n - 1 : defaultIndex;
|
|
811
|
+
}
|
|
812
|
+
async function selectSingle(rl, options, defaultIndex, c, useRawMode, title = "\uC120\uD0DD") {
|
|
813
|
+
if (useRawMode) {
|
|
814
|
+
const result = await runInteractiveSelect(options, defaultIndex, false, c, [], {
|
|
815
|
+
title,
|
|
816
|
+
hint: "\u2191\u2193 \uB610\uB294 j/k \uC774\uB3D9 \xB7 Enter \uC120\uD0DD \xB7 Esc \uAE30\uBCF8\uAC12 \uC720\uC9C0"
|
|
817
|
+
});
|
|
818
|
+
return result?.index ?? defaultIndex;
|
|
819
|
+
}
|
|
820
|
+
for (let i = 0; i < options.length; i++) {
|
|
821
|
+
process.stdout.write(` ${i + 1}. ${options[i]}${i === defaultIndex ? " (\uAE30\uBCF8)" : ""}
|
|
822
|
+
`);
|
|
823
|
+
}
|
|
824
|
+
return promptNumber(rl, defaultIndex, options.length);
|
|
825
|
+
}
|
|
826
|
+
async function selectMulti(rl, options, defaultChecked, c, useRawMode, title = "\uC120\uD0DD", selectAllIndex) {
|
|
827
|
+
if (useRawMode) {
|
|
828
|
+
const result = await runInteractiveSelect(options, 0, true, c, defaultChecked, {
|
|
829
|
+
title,
|
|
830
|
+
hint: "\u2191\u2193 \uB610\uB294 j/k \uC774\uB3D9 \xB7 Space \uC120\uD0DD \xB7 Enter \uD655\uC815 \xB7 Esc \uAE30\uBCF8\uAC12 \uC720\uC9C0",
|
|
831
|
+
selectAllIndex
|
|
832
|
+
});
|
|
833
|
+
return result?.checked ?? defaultChecked;
|
|
834
|
+
}
|
|
835
|
+
for (let i = 0; i < options.length; i++) {
|
|
836
|
+
process.stdout.write(
|
|
837
|
+
` ${i + 1}. ${options[i]}${defaultChecked.includes(i) ? " (\uAE30\uBCF8 \uC120\uD0DD)" : ""}
|
|
838
|
+
`
|
|
839
|
+
);
|
|
840
|
+
}
|
|
841
|
+
const def = defaultChecked.slice().sort((a, b) => a - b).map((i) => String(i + 1)).join(",");
|
|
842
|
+
const answer = (await ask(rl, ` \uD3EC\uD568\uD560 \uBC88\uD638\uB97C \uC27C\uD45C\uB85C (\uAE30\uBCF8 ${def || "\uC5C6\uC74C"}): `)).trim();
|
|
843
|
+
const chosen = (answer === "" ? def : answer).split(",").map((s) => s.trim()).filter(Boolean).map((s) => Number(s) - 1).filter((n) => Number.isInteger(n) && n >= 0 && n < options.length);
|
|
844
|
+
return chosen;
|
|
845
|
+
}
|
|
846
|
+
async function promptVerifyLocation(rl, projectRoot, rootVerify, color) {
|
|
847
|
+
const packages = detectWorkspacePackages(projectRoot);
|
|
848
|
+
if (packages.length === 0) {
|
|
849
|
+
return { verify: rootVerify };
|
|
850
|
+
}
|
|
851
|
+
if (!isVerifyEmpty(rootVerify)) {
|
|
852
|
+
process.stdout.write(
|
|
853
|
+
`
|
|
854
|
+
${color.dim(`\uBAA8\uB178\uB808\uD3EC\uC785\uB2C8\uB2E4(${packages.length}\uAC1C \uD328\uD0A4\uC9C0). \uD2B9\uC815 \uD328\uD0A4\uC9C0\uB9CC \uAC80\uC99D\uD558\uB824\uBA74 \uB098\uC911\uC5D0 awl config set verify.*.cwd \uB85C \uC9C0\uC815\uD558\uC138\uC694.`)}
|
|
855
|
+
`
|
|
856
|
+
);
|
|
857
|
+
return { verify: rootVerify };
|
|
858
|
+
}
|
|
859
|
+
process.stdout.write(
|
|
860
|
+
"\n \uBAA8\uB178\uB808\uD3EC\uB85C \uBCF4\uC774\uB294\uB370 \uB8E8\uD2B8\uC5D0\uC11C \uAC80\uC99D \uBA85\uB839\uC744 \uBABB \uCC3E\uC558\uC2B5\uB2C8\uB2E4. \uC5B4\uB290 \uD328\uD0A4\uC9C0\uB97C \uAC80\uC99D\uD560\uAE4C\uC694?\n\n"
|
|
861
|
+
);
|
|
862
|
+
const options = ["\uB8E8\uD2B8(\uC804\uCCB4, \uAC80\uC99D \uBA85\uB839 \uC5C6\uC774 \uB460)", ...packages];
|
|
863
|
+
for (let i = 0; i < options.length; i++) {
|
|
864
|
+
process.stdout.write(` ${i + 1} ${options[i]}
|
|
865
|
+
`);
|
|
866
|
+
}
|
|
867
|
+
const idx = await promptNumber(rl, 0, options.length);
|
|
868
|
+
if (idx === 0) {
|
|
869
|
+
return { verify: rootVerify };
|
|
870
|
+
}
|
|
871
|
+
const chosen = packages[idx - 1];
|
|
872
|
+
return { verify: detectVerify(path.join(projectRoot, chosen)), cwd: chosen };
|
|
873
|
+
}
|
|
874
|
+
function applyVerifyCwd(verify, cwd) {
|
|
875
|
+
if (!cwd) {
|
|
876
|
+
return verify;
|
|
877
|
+
}
|
|
878
|
+
for (const k of Object.keys(verify)) {
|
|
879
|
+
const entry = verify[k];
|
|
880
|
+
if (entry) {
|
|
881
|
+
entry.cwd = cwd;
|
|
882
|
+
}
|
|
883
|
+
}
|
|
884
|
+
return verify;
|
|
885
|
+
}
|
|
886
|
+
async function interactiveInputs(projectRoot, hasGlobal, c) {
|
|
887
|
+
const screens = buildScreens(projectRoot, hasGlobal, c);
|
|
888
|
+
const project = path.basename(projectRoot);
|
|
889
|
+
const useRawMode = rawModeCapable();
|
|
890
|
+
const session = { rl: null };
|
|
891
|
+
const prompt = () => {
|
|
892
|
+
if (!session.rl) {
|
|
893
|
+
session.rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
894
|
+
}
|
|
895
|
+
return session.rl;
|
|
896
|
+
};
|
|
897
|
+
try {
|
|
898
|
+
if (screens.welcome) {
|
|
899
|
+
process.stdout.write(`${screens.welcome}
|
|
900
|
+
`);
|
|
901
|
+
}
|
|
902
|
+
process.stdout.write(`
|
|
903
|
+
${screens.lang}
|
|
904
|
+
`);
|
|
905
|
+
const rawLanguage = useRawMode ? await runInteractiveSelect(LANG_OPTIONS, langDefaultIndex(projectRoot), false, c, [], {
|
|
906
|
+
title: "\uC8FC \uC5B8\uC5B4",
|
|
907
|
+
hint: "\u2191\u2193 \uB610\uB294 j/k \uC774\uB3D9 \xB7 Enter \uC120\uD0DD \xB7 Esc \uAE30\uBCF8\uAC12 \uC720\uC9C0"
|
|
908
|
+
}) : null;
|
|
909
|
+
const langIdx = rawLanguage?.index ?? (useRawMode ? langDefaultIndex(projectRoot) : await selectSingle(
|
|
910
|
+
prompt(),
|
|
911
|
+
LANG_OPTIONS,
|
|
912
|
+
langDefaultIndex(projectRoot),
|
|
913
|
+
c,
|
|
914
|
+
false,
|
|
915
|
+
"\uC8FC \uC5B8\uC5B4"
|
|
916
|
+
));
|
|
917
|
+
let mainLanguage = LANG_VALUES[langIdx] ?? "";
|
|
918
|
+
if (langIdx === LANG_OPTIONS.length - 1) {
|
|
919
|
+
mainLanguage = (await ask(prompt(), " \uC8FC \uC5B8\uC5B4\uB97C \uC785\uB825\uD558\uC138\uC694: ")).trim();
|
|
920
|
+
}
|
|
921
|
+
const rootVerify = detectVerify(projectRoot);
|
|
922
|
+
const located = await promptVerifyLocation(
|
|
923
|
+
prompt(),
|
|
924
|
+
projectRoot,
|
|
925
|
+
rootVerify,
|
|
926
|
+
makeColors(c.color)
|
|
927
|
+
);
|
|
928
|
+
const verify = located.verify;
|
|
929
|
+
if (located.cwd) {
|
|
930
|
+
process.stdout.write(`
|
|
931
|
+
${stepBox("2/3", "\uAC80\uC99D \uBA85\uB839\uC5B4", verifyStepLines(verify), c)}
|
|
932
|
+
`);
|
|
933
|
+
} else {
|
|
934
|
+
process.stdout.write(`
|
|
935
|
+
${screens.verify}
|
|
936
|
+
`);
|
|
937
|
+
}
|
|
938
|
+
for (const k of Object.keys(VERIFY_LABELS)) {
|
|
939
|
+
const cur = verify[k];
|
|
940
|
+
const shown = cur ? cur.cmd : "(\uC5C6\uC74C)";
|
|
941
|
+
const answer = (await ask(prompt(), ` ${VERIFY_LABELS[k]} [${shown}]: `)).trim();
|
|
942
|
+
if (answer !== "") {
|
|
943
|
+
verify[k] = answer.toLowerCase() === "\uC5C6\uC74C" || answer === "-" ? null : splitEnv(answer);
|
|
944
|
+
}
|
|
945
|
+
}
|
|
946
|
+
applyVerifyCwd(verify, located.cwd);
|
|
947
|
+
process.stdout.write(`
|
|
948
|
+
${screens.character}
|
|
949
|
+
`);
|
|
950
|
+
const character = (await ask(prompt(), " > ")).trim();
|
|
951
|
+
process.stdout.write(`
|
|
952
|
+
${screens.skills}
|
|
953
|
+
`);
|
|
954
|
+
const agents = detectAgents(projectRoot);
|
|
955
|
+
const skillOptions = [
|
|
956
|
+
"\uBAA8\uB450 \uC124\uCE58 (Claude Code + Codex)",
|
|
957
|
+
claudeSkillLabel(),
|
|
958
|
+
"Codex (AGENTS.md \uC5D0 \uCD94\uAC00)"
|
|
959
|
+
];
|
|
960
|
+
const defaultChecked = agents.claude && agents.codex ? [0, 1, 2] : [agents.claude ? 1 : -1, agents.codex ? 2 : -1].filter((i) => i >= 0);
|
|
961
|
+
if (useRawMode && session.rl) {
|
|
962
|
+
session.rl.close();
|
|
963
|
+
session.rl = null;
|
|
964
|
+
}
|
|
965
|
+
const rawSkills = useRawMode ? await runInteractiveSelect(skillOptions, 0, true, c, defaultChecked, {
|
|
966
|
+
title: "\uC124\uCE58\uD560 \uC5D0\uC774\uC804\uD2B8 \uC2A4\uD0AC",
|
|
967
|
+
hint: "\u2191\u2193 \uB610\uB294 j/k \uC774\uB3D9 \xB7 Space \uC120\uD0DD \xB7 Enter \uD655\uC815 \xB7 Esc \uAE30\uBCF8\uAC12 \uC720\uC9C0",
|
|
968
|
+
selectAllIndex: 0
|
|
969
|
+
}) : null;
|
|
970
|
+
const checked = rawSkills?.checked ?? (useRawMode ? defaultChecked : await selectMulti(
|
|
971
|
+
prompt(),
|
|
972
|
+
skillOptions,
|
|
973
|
+
defaultChecked,
|
|
974
|
+
c,
|
|
975
|
+
false,
|
|
976
|
+
"\uC124\uCE58\uD560 \uC5D0\uC774\uC804\uD2B8 \uC2A4\uD0AC",
|
|
977
|
+
0
|
|
978
|
+
));
|
|
979
|
+
const installAll = checked.includes(0);
|
|
980
|
+
const skills = {
|
|
981
|
+
claude: installAll || checked.includes(1),
|
|
982
|
+
codex: installAll || checked.includes(2)
|
|
983
|
+
};
|
|
984
|
+
return { project, mainLanguage, character, verify, skills };
|
|
985
|
+
} finally {
|
|
986
|
+
session.rl?.close();
|
|
987
|
+
}
|
|
988
|
+
}
|
|
989
|
+
async function handleExistingConfig(rl, projectRoot, c, now) {
|
|
990
|
+
const raw = readJson(path.join(projectRoot, ".awl", "config.json"));
|
|
991
|
+
const config = raw;
|
|
992
|
+
scaffoldGlobal();
|
|
993
|
+
const installedVer = installedEngineVersion();
|
|
994
|
+
process.stdout.write("\n .awl/config.json \uC774 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4. \uD300\uC6D0\uC774 \uC124\uC815\uD574\uB450\uC5C8\uAD70\uC694.\n\n");
|
|
995
|
+
process.stdout.write(` \uD504\uB85C\uC81D\uD2B8 ${config?.project ?? "(\uC5C6\uC74C)"}
|
|
996
|
+
`);
|
|
997
|
+
process.stdout.write(` \uC8FC \uC5B8\uC5B4 ${config?.mainLanguage ?? "(\uC5C6\uC74C)"}
|
|
998
|
+
`);
|
|
999
|
+
process.stdout.write(` \uC131\uACA9 ${config?.character || "(\uC5C6\uC74C)"}
|
|
1000
|
+
`);
|
|
1001
|
+
const engineNote = installedVer && config?.engineVersion ? installedVer === config.engineVersion ? `(\uC124\uCE58\uB428: ${installedVer} \uC77C\uCE58)` : `(\uC124\uCE58\uB428: ${installedVer} \uBD88\uC77C\uCE58 -> '\uADF8\uB300\uB85C \uC4F4\uB2E4'\uB97C \uACE0\uB974\uBA74 \uB3D9\uAE30\uD654\uB429\uB2C8\uB2E4)` : "";
|
|
1002
|
+
process.stdout.write(` \uC5D4\uC9C4 ${config?.engineVersion ?? "(\uC5C6\uC74C)"} ${engineNote}
|
|
1003
|
+
|
|
1004
|
+
`);
|
|
1005
|
+
if (config?.verify) {
|
|
1006
|
+
for (const line of verifyLines(config.verify)) {
|
|
1007
|
+
process.stdout.write(` \uAC80\uC99D ${line.trim()}
|
|
1008
|
+
`);
|
|
1009
|
+
}
|
|
1010
|
+
process.stdout.write("\n");
|
|
1011
|
+
}
|
|
1012
|
+
process.stdout.write(" \uC774 \uC124\uC815\uC744 \uADF8\uB300\uB85C \uC4F0\uC2DC\uACA0\uC2B5\uB2C8\uAE4C?\n\n");
|
|
1013
|
+
const options = ["\uADF8\uB300\uB85C \uC4F4\uB2E4", "\uAC80\uC99D \uBA85\uB839\uC5B4\uB9CC \uACE0\uCE5C\uB2E4", "\uCC98\uC74C\uBD80\uD130 \uB2E4\uC2DC"];
|
|
1014
|
+
process.stdout.write(
|
|
1015
|
+
`${card(
|
|
1016
|
+
"\uAE30\uC874 \uC124\uC815",
|
|
1017
|
+
options.map((option, i) => `${i + 1}. ${option}`),
|
|
1018
|
+
c
|
|
1019
|
+
)}
|
|
1020
|
+
`
|
|
1021
|
+
);
|
|
1022
|
+
const choice = await promptNumber(rl, 0, options.length);
|
|
1023
|
+
process.stdout.write(
|
|
1024
|
+
`
|
|
1025
|
+
${makeColors(c.color).dim("\uADDC\uCE59\uACFC \uAD50\uD6C8\uC740 \uACF5\uC720\uB418\uC9C0 \uC54A\uC2B5\uB2C8\uB2E4. \uC800 \uC124\uC815\uB9CC \uD300\uC6D0\uACFC \uAC19\uACE0, \uC313\uC774\uB294 \uAC83\uC740 \uB2F9\uC2E0 \uAC83\uC785\uB2C8\uB2E4.")}
|
|
1026
|
+
`
|
|
1027
|
+
);
|
|
1028
|
+
if (choice === 0) {
|
|
1029
|
+
const synced = syncExistingInstall(projectRoot, installedVer ?? "unknown");
|
|
1030
|
+
if (synced.configUpdated || synced.skills.length > 0) {
|
|
1031
|
+
process.stdout.write(
|
|
1032
|
+
`
|
|
1033
|
+
\uC124\uC815\uC744 \uADF8\uB300\uB85C \uC501\uB2C8\uB2E4. \uBC84\uC804 \uB9C8\uCEE4\uB97C ${installedVer ?? "\uC5D4\uC9C4"} \uB85C \uB3D9\uAE30\uD654\uD588\uC2B5\uB2C8\uB2E4${synced.skills.length ? ` (\uC2A4\uD0AC: ${synced.skills.join(", ")})` : ""}.
|
|
1034
|
+
`
|
|
1035
|
+
);
|
|
1036
|
+
} else {
|
|
1037
|
+
process.stdout.write("\n \uC124\uC815\uC744 \uADF8\uB300\uB85C \uC501\uB2C8\uB2E4. \uC774\uBBF8 \uCD5C\uC2E0\uC785\uB2C8\uB2E4.\n");
|
|
1038
|
+
}
|
|
1039
|
+
return;
|
|
1040
|
+
}
|
|
1041
|
+
if (choice === 1) {
|
|
1042
|
+
const verify = config?.verify ?? detectVerify(projectRoot);
|
|
1043
|
+
for (const k of Object.keys(VERIFY_LABELS)) {
|
|
1044
|
+
const cur = verify[k];
|
|
1045
|
+
const shown = cur ? cur.cmd : "(\uC5C6\uC74C)";
|
|
1046
|
+
const answer = (await ask(rl, ` ${VERIFY_LABELS[k]} [${shown}]: `)).trim();
|
|
1047
|
+
if (answer !== "") {
|
|
1048
|
+
verify[k] = answer.toLowerCase() === "\uC5C6\uC74C" || answer === "-" ? null : splitEnv(answer);
|
|
1049
|
+
}
|
|
1050
|
+
}
|
|
1051
|
+
const merged = {
|
|
1052
|
+
project: config?.project ?? path.basename(projectRoot),
|
|
1053
|
+
mainLanguage: config?.mainLanguage ?? "",
|
|
1054
|
+
character: config?.character ?? "",
|
|
1055
|
+
verify,
|
|
1056
|
+
skills: { claude: false, codex: false }
|
|
1057
|
+
};
|
|
1058
|
+
writeConfig(projectRoot, buildConfig(merged, installedVer ?? "unknown"));
|
|
1059
|
+
process.stdout.write("\n \uAC80\uC99D \uBA85\uB839\uC5B4\uB97C \uAC31\uC2E0\uD588\uC2B5\uB2C8\uB2E4.\n");
|
|
1060
|
+
return;
|
|
1061
|
+
}
|
|
1062
|
+
rl.close();
|
|
1063
|
+
const inputs = await interactiveInputs(projectRoot, isGlobalInstalled(), c);
|
|
1064
|
+
const result = applyInit(projectRoot, inputs, now);
|
|
1065
|
+
process.stdout.write(`
|
|
1066
|
+
${renderResult(result, inputs, c)}
|
|
1067
|
+
`);
|
|
1068
|
+
}
|
|
1069
|
+
function resolveProjectChoice(idx, candidates) {
|
|
1070
|
+
if (idx >= 0 && idx < candidates.length) {
|
|
1071
|
+
return { kind: "path", path: candidates[idx]?.path ?? "" };
|
|
1072
|
+
}
|
|
1073
|
+
if (idx === candidates.length) {
|
|
1074
|
+
return { kind: "type" };
|
|
1075
|
+
}
|
|
1076
|
+
return { kind: "cancel" };
|
|
1077
|
+
}
|
|
1078
|
+
async function pickProjectRoot(cwd, c) {
|
|
1079
|
+
const raw = rawModeCapable();
|
|
1080
|
+
const session = { rl: null };
|
|
1081
|
+
const prompt = () => {
|
|
1082
|
+
if (!session.rl) {
|
|
1083
|
+
session.rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1084
|
+
}
|
|
1085
|
+
return session.rl;
|
|
1086
|
+
};
|
|
1087
|
+
const select = async (options, title) => {
|
|
1088
|
+
if (raw) {
|
|
1089
|
+
if (session.rl) {
|
|
1090
|
+
session.rl.close();
|
|
1091
|
+
session.rl = null;
|
|
1092
|
+
}
|
|
1093
|
+
const r = await runInteractiveSelect(options, 0, false, c, [], {
|
|
1094
|
+
title,
|
|
1095
|
+
hint: "\u2191\u2193 \uB610\uB294 j/k \uC774\uB3D9 \xB7 Enter \uC120\uD0DD \xB7 Esc \uAE30\uBCF8\uAC12 \uC720\uC9C0"
|
|
1096
|
+
});
|
|
1097
|
+
return r?.index ?? 0;
|
|
1098
|
+
}
|
|
1099
|
+
return selectSingle(prompt(), options, 0, c, false, title);
|
|
1100
|
+
};
|
|
1101
|
+
try {
|
|
1102
|
+
if (exists(path.join(cwd, ".git"))) {
|
|
1103
|
+
const idx = await select(
|
|
1104
|
+
[`\uC774 \uD504\uB85C\uC81D\uD2B8\uB85C \uC9C4\uD589 (${cwd})`, "\uB2E4\uB978 \uD504\uB85C\uC81D\uD2B8 \uACE0\uB974\uAE30", "\uCDE8\uC18C"],
|
|
1105
|
+
"\uC5B4\uB290 \uD504\uB85C\uC81D\uD2B8\uC5D0 awl \uC744 \uBD99\uC77C\uAE4C\uC694?"
|
|
1106
|
+
);
|
|
1107
|
+
if (idx === 0) {
|
|
1108
|
+
return cwd;
|
|
1109
|
+
}
|
|
1110
|
+
if (idx !== 1) {
|
|
1111
|
+
return null;
|
|
1112
|
+
}
|
|
1113
|
+
}
|
|
1114
|
+
const candidates = scanGitProjects(cwd);
|
|
1115
|
+
const labels = [...candidates.map((p) => `${p.name} (${p.path})`), "\uC9C1\uC811 \uACBD\uB85C \uC785\uB825", "\uCDE8\uC18C"];
|
|
1116
|
+
const title = candidates.length > 0 ? "\uD504\uB85C\uC81D\uD2B8\uB97C \uACE0\uB974\uC138\uC694 (\uCD5C\uADFC \uC218\uC815\uC21C, \uCD5C\uB300 20)" : "\uD558\uC704\uC5D0 git \uD504\uB85C\uC81D\uD2B8\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4 \u2014 \uC9C1\uC811 \uC785\uB825\uD558\uAC70\uB098 \uCDE8\uC18C";
|
|
1117
|
+
const choice = resolveProjectChoice(await select(labels, title), candidates);
|
|
1118
|
+
if (choice.kind === "path") {
|
|
1119
|
+
return choice.path;
|
|
1120
|
+
}
|
|
1121
|
+
if (choice.kind === "cancel") {
|
|
1122
|
+
return null;
|
|
1123
|
+
}
|
|
1124
|
+
const typed = (await ask(prompt(), " \uD504\uB85C\uC81D\uD2B8 \uACBD\uB85C\uB97C \uC785\uB825\uD558\uC138\uC694: ")).trim();
|
|
1125
|
+
if (!typed) {
|
|
1126
|
+
return null;
|
|
1127
|
+
}
|
|
1128
|
+
const resolved = path.resolve(typed);
|
|
1129
|
+
if (!exists(resolved)) {
|
|
1130
|
+
process.stdout.write(`
|
|
1131
|
+
\uADF8 \uACBD\uB85C\uAC00 \uC5C6\uC2B5\uB2C8\uB2E4: ${resolved}
|
|
1132
|
+
`);
|
|
1133
|
+
return null;
|
|
1134
|
+
}
|
|
1135
|
+
return resolved;
|
|
1136
|
+
} finally {
|
|
1137
|
+
if (session.rl) {
|
|
1138
|
+
session.rl.close();
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
}
|
|
1142
|
+
async function runInit(opts) {
|
|
1143
|
+
const projectRoot = process.cwd();
|
|
1144
|
+
const interactive = process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
1145
|
+
if (!opts.yes && !interactive) {
|
|
1146
|
+
process.stderr.write(renderNonTtyNotice());
|
|
1147
|
+
process.exit(1);
|
|
1148
|
+
}
|
|
1149
|
+
const c = caps();
|
|
1150
|
+
const now = (/* @__PURE__ */ new Date()).toISOString();
|
|
1151
|
+
const configExists = exists(path.join(projectRoot, ".awl", "config.json"));
|
|
1152
|
+
if (opts.yes) {
|
|
1153
|
+
if (configExists) {
|
|
1154
|
+
const engine = scaffoldGlobal();
|
|
1155
|
+
const hook = installSafetyHook(projectRoot);
|
|
1156
|
+
const synced = syncExistingInstall(projectRoot, engine.engineVersion);
|
|
1157
|
+
const syncNote = synced.configUpdated || synced.skills.length > 0 ? `
|
|
1158
|
+
${signal(c, "ok")} \uBC84\uC804 \uB9C8\uCEE4\uB97C ${engine.engineVersion} \uB85C \uB3D9\uAE30\uD654\uD588\uC2B5\uB2C8\uB2E4${synced.skills.length ? ` (\uC2A4\uD0AC: ${synced.skills.join(", ")})` : ""}.` : "";
|
|
1159
|
+
process.stdout.write(
|
|
1160
|
+
`
|
|
1161
|
+
.awl/config.json \uC774 \uC774\uBBF8 \uC788\uC2B5\uB2C8\uB2E4. \uADF8\uB300\uB85C \uC501\uB2C8\uB2E4.
|
|
1162
|
+
${signal(c, "ok")} \uC5D4\uC9C4 \uD15C\uD50C\uB9BF\uC744 ${engine.created ? "\uC124\uCE58\uD588\uC2B5\uB2C8\uB2E4." : "\uAC31\uC2E0\uD588\uC2B5\uB2C8\uB2E4."}${syncNote}${hook.warning ? `
|
|
1163
|
+
${signal(c, "warn")} ${hook.warning}` : hook.installed ? `
|
|
1164
|
+
${signal(c, "ok")} git push \uCC28\uB2E8 \uD6C5 \uC124\uCE58` : ""}
|
|
1165
|
+
`
|
|
1166
|
+
);
|
|
1167
|
+
return;
|
|
1168
|
+
}
|
|
1169
|
+
const inputs2 = nonInteractiveInputs(projectRoot);
|
|
1170
|
+
const result2 = applyInit(projectRoot, inputs2, now);
|
|
1171
|
+
process.stdout.write(`${renderResult(result2, inputs2, c)}
|
|
1172
|
+
`);
|
|
1173
|
+
return;
|
|
1174
|
+
}
|
|
1175
|
+
if (configExists) {
|
|
1176
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1177
|
+
try {
|
|
1178
|
+
await handleExistingConfig(rl, projectRoot, c, now);
|
|
1179
|
+
} finally {
|
|
1180
|
+
rl.close();
|
|
1181
|
+
}
|
|
1182
|
+
return;
|
|
1183
|
+
}
|
|
1184
|
+
const chosenRoot = await pickProjectRoot(projectRoot, c);
|
|
1185
|
+
if (chosenRoot === null) {
|
|
1186
|
+
process.stdout.write("\n \uCDE8\uC18C\uD588\uC2B5\uB2C8\uB2E4.\n");
|
|
1187
|
+
return;
|
|
1188
|
+
}
|
|
1189
|
+
if (exists(path.join(chosenRoot, ".awl", "config.json"))) {
|
|
1190
|
+
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
1191
|
+
try {
|
|
1192
|
+
await handleExistingConfig(rl, chosenRoot, c, now);
|
|
1193
|
+
} finally {
|
|
1194
|
+
rl.close();
|
|
1195
|
+
}
|
|
1196
|
+
return;
|
|
1197
|
+
}
|
|
1198
|
+
const inputs = await interactiveInputs(chosenRoot, isGlobalInstalled(), c);
|
|
1199
|
+
const result = applyInit(chosenRoot, inputs, now);
|
|
1200
|
+
process.stdout.write(`
|
|
1201
|
+
${renderResult(result, inputs, c)}
|
|
1202
|
+
`);
|
|
1203
|
+
}
|
|
1204
|
+
|
|
1205
|
+
export {
|
|
1206
|
+
scanGitProjects,
|
|
1207
|
+
detectLanguage,
|
|
1208
|
+
splitEnv,
|
|
1209
|
+
detectVerify,
|
|
1210
|
+
detectWorkspacePackages,
|
|
1211
|
+
buildConfig,
|
|
1212
|
+
packageEngineDir,
|
|
1213
|
+
isGlobalInstalled,
|
|
1214
|
+
scaffoldGlobal,
|
|
1215
|
+
writeConfig,
|
|
1216
|
+
writeState,
|
|
1217
|
+
ensureGitignore,
|
|
1218
|
+
installSafetyHook,
|
|
1219
|
+
registerProject,
|
|
1220
|
+
installClaudeSkill,
|
|
1221
|
+
claudeSkillLabel,
|
|
1222
|
+
installCodexSkill,
|
|
1223
|
+
skillsVersionPath,
|
|
1224
|
+
writeSkillsVersionStamp,
|
|
1225
|
+
syncExistingInstall,
|
|
1226
|
+
detectAgents,
|
|
1227
|
+
nonInteractiveInputs,
|
|
1228
|
+
applyInit,
|
|
1229
|
+
verifyStepLines,
|
|
1230
|
+
renderNonTtyNotice,
|
|
1231
|
+
renderResult,
|
|
1232
|
+
LANG_OPTIONS,
|
|
1233
|
+
LANG_VALUES,
|
|
1234
|
+
buildScreens,
|
|
1235
|
+
ask,
|
|
1236
|
+
promptNumber,
|
|
1237
|
+
selectSingle,
|
|
1238
|
+
selectMulti,
|
|
1239
|
+
promptVerifyLocation,
|
|
1240
|
+
applyVerifyCwd,
|
|
1241
|
+
resolveProjectChoice,
|
|
1242
|
+
runInit
|
|
1243
|
+
};
|