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 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`을 자동 실행한다.
@@ -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
- console.log("");
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();
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentic-dev",
3
- "version": "0.2.8",
3
+ "version": "0.2.9",
4
4
  "description": "Scaffold a public say828/template-* repo and run install/bootstrap automatically.",
5
5
  "type": "module",
6
6
  "packageManager": "pnpm@10.32.0",