agentic-dev 0.2.8 → 0.2.9
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 +1 -0
- package/bin/agentic-dev.mjs +110 -16
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -19,6 +19,7 @@ npx agentic-dev init my-app --template template-web --yes --skip-bootstrap
|
|
|
19
19
|
|
|
20
20
|
1. GitHub에서 공개 `say828/template-*` 레포 목록을 조회한다.
|
|
21
21
|
2. 사용자가 프로젝트 디렉터리와 템플릿 레포를 고른다.
|
|
22
|
+
- interactive TTY에서는 템플릿 레포를 `↑/↓ + Enter`로 선택한다.
|
|
22
23
|
3. 선택한 레포를 새 디렉터리로 복제한다.
|
|
23
24
|
4. `.env.example`이 있으면 `.env`를 자동 생성한다.
|
|
24
25
|
5. `pnpm install`을 자동 실행한다.
|
package/bin/agentic-dev.mjs
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
#!/usr/bin/env node
|
|
2
2
|
import path from "node:path";
|
|
3
3
|
import process from "node:process";
|
|
4
|
+
import * as readline from "node:readline";
|
|
4
5
|
import { createInterface } from "node:readline/promises";
|
|
5
6
|
import {
|
|
6
7
|
ensureTargetDir,
|
|
@@ -12,6 +13,114 @@ import {
|
|
|
12
13
|
usage,
|
|
13
14
|
} from "../lib/scaffold.mjs";
|
|
14
15
|
|
|
16
|
+
function clearMenu(lines) {
|
|
17
|
+
if (lines <= 0) {
|
|
18
|
+
return;
|
|
19
|
+
}
|
|
20
|
+
readline.moveCursor(process.stdout, 0, -lines);
|
|
21
|
+
readline.clearScreenDown(process.stdout);
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
function renderRepoSelect(label, repos, cursor) {
|
|
25
|
+
const lines = [`${label} Use ↑/↓ and Enter.`];
|
|
26
|
+
for (let index = 0; index < repos.length; index += 1) {
|
|
27
|
+
const pointer = index === cursor ? ">" : " ";
|
|
28
|
+
const summary = repos[index].description ? ` - ${repos[index].description}` : "";
|
|
29
|
+
lines.push(`${pointer} ${repos[index].name}${summary}`);
|
|
30
|
+
}
|
|
31
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
32
|
+
return lines.length;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
async function runArrowMenu(render, onInput) {
|
|
36
|
+
const stdin = process.stdin;
|
|
37
|
+
const stdout = process.stdout;
|
|
38
|
+
const previousRawMode = typeof stdin.setRawMode === "function" ? stdin.isRaw : undefined;
|
|
39
|
+
|
|
40
|
+
if (typeof stdin.setRawMode === "function") {
|
|
41
|
+
stdin.setRawMode(true);
|
|
42
|
+
}
|
|
43
|
+
stdin.resume();
|
|
44
|
+
stdin.setEncoding("utf8");
|
|
45
|
+
|
|
46
|
+
return new Promise((resolve, reject) => {
|
|
47
|
+
let renderedLines = render();
|
|
48
|
+
|
|
49
|
+
const cleanup = () => {
|
|
50
|
+
stdin.removeListener("data", handleData);
|
|
51
|
+
if (typeof stdin.setRawMode === "function") {
|
|
52
|
+
stdin.setRawMode(Boolean(previousRawMode));
|
|
53
|
+
}
|
|
54
|
+
stdout.write("\n");
|
|
55
|
+
};
|
|
56
|
+
|
|
57
|
+
const rerender = () => {
|
|
58
|
+
clearMenu(renderedLines);
|
|
59
|
+
renderedLines = render();
|
|
60
|
+
};
|
|
61
|
+
|
|
62
|
+
const handleData = (chunk) => {
|
|
63
|
+
try {
|
|
64
|
+
const result = onInput(chunk, rerender);
|
|
65
|
+
if (result !== undefined) {
|
|
66
|
+
clearMenu(renderedLines);
|
|
67
|
+
cleanup();
|
|
68
|
+
resolve(result);
|
|
69
|
+
}
|
|
70
|
+
} catch (error) {
|
|
71
|
+
clearMenu(renderedLines);
|
|
72
|
+
cleanup();
|
|
73
|
+
reject(error);
|
|
74
|
+
}
|
|
75
|
+
};
|
|
76
|
+
|
|
77
|
+
stdin.on("data", handleData);
|
|
78
|
+
});
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
async function promptForTemplateRepo(rl, repos, owner) {
|
|
82
|
+
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
83
|
+
console.log("");
|
|
84
|
+
let cursor = 0;
|
|
85
|
+
return runArrowMenu(
|
|
86
|
+
() => renderRepoSelect(`Public template repos from ${owner}:`, repos, cursor),
|
|
87
|
+
(chunk, rerender) => {
|
|
88
|
+
if (chunk === "\u0003") {
|
|
89
|
+
throw new Error("Prompt cancelled");
|
|
90
|
+
}
|
|
91
|
+
if (chunk === "\r" || chunk === "\n") {
|
|
92
|
+
return repos[cursor].name;
|
|
93
|
+
}
|
|
94
|
+
if (chunk === "\u001b[A") {
|
|
95
|
+
cursor = (cursor - 1 + repos.length) % repos.length;
|
|
96
|
+
rerender();
|
|
97
|
+
} else if (chunk === "\u001b[B") {
|
|
98
|
+
cursor = (cursor + 1) % repos.length;
|
|
99
|
+
rerender();
|
|
100
|
+
}
|
|
101
|
+
return undefined;
|
|
102
|
+
},
|
|
103
|
+
);
|
|
104
|
+
}
|
|
105
|
+
|
|
106
|
+
console.log("");
|
|
107
|
+
console.log(`Public template repos from ${owner}:`);
|
|
108
|
+
repos.forEach((repo, index) => {
|
|
109
|
+
const summary = repo.description ? ` - ${repo.description}` : "";
|
|
110
|
+
console.log(` ${index + 1}. ${repo.name}${summary}`);
|
|
111
|
+
});
|
|
112
|
+
console.log("");
|
|
113
|
+
|
|
114
|
+
while (true) {
|
|
115
|
+
const answer = await rl.question("Select template repo (number or repo name): ");
|
|
116
|
+
try {
|
|
117
|
+
return selectTemplateRepo(answer, repos).name;
|
|
118
|
+
} catch (error) {
|
|
119
|
+
console.log(error.message);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
}
|
|
123
|
+
|
|
15
124
|
async function promptForMissing(options, repos) {
|
|
16
125
|
if (options.yes && (!options.targetDir || !options.template)) {
|
|
17
126
|
throw new Error("`--yes` requires both target directory and template repo.");
|
|
@@ -36,22 +145,7 @@ async function promptForMissing(options, repos) {
|
|
|
36
145
|
}
|
|
37
146
|
|
|
38
147
|
if (!options.template) {
|
|
39
|
-
|
|
40
|
-
console.log(`Public template repos from ${options.owner}:`);
|
|
41
|
-
repos.forEach((repo, index) => {
|
|
42
|
-
const summary = repo.description ? ` - ${repo.description}` : "";
|
|
43
|
-
console.log(` ${index + 1}. ${repo.name}${summary}`);
|
|
44
|
-
});
|
|
45
|
-
console.log("");
|
|
46
|
-
while (!options.template) {
|
|
47
|
-
const answer = await rl.question("Select template repo (number or repo name): ");
|
|
48
|
-
try {
|
|
49
|
-
options.template = selectTemplateRepo(answer, repos).name;
|
|
50
|
-
} catch (error) {
|
|
51
|
-
console.log(error.message);
|
|
52
|
-
options.template = "";
|
|
53
|
-
}
|
|
54
|
-
}
|
|
148
|
+
options.template = await promptForTemplateRepo(rl, repos, options.owner);
|
|
55
149
|
}
|
|
56
150
|
} finally {
|
|
57
151
|
rl.close();
|