pleumrouter 0.5.41 → 0.5.42

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/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "pleumrouter",
3
- "version": "0.5.41",
3
+ "version": "0.5.42",
4
4
  "description": "Official PleumRouter terminal CLI — launch any coding agent against PleumRouter.",
5
5
  "type": "module",
6
6
  "bin": {
package/src/install.mjs CHANGED
@@ -2,22 +2,25 @@ import { spawn } from "node:child_process";
2
2
  import { resolveLaunchCommand } from "./launch.mjs";
3
3
  import { t } from "./i18n.mjs";
4
4
 
5
- // 설치 명령 실행기(#40·#10) — RECIPES.install 문자열을 셸 없이 안전하게 실행한다.
5
+ // 설치 명령 실행기(#40·#10) — RECIPES.install 문자열을 셸 없이 실행한다.
6
6
  //
7
- // 지원 형태(정의된 단계만 실행):
8
- // - 공백 구분 argv 명령 1개 이상("npm i -g x")
9
- // - "&&" 로 이어진 순차 단계("python -m pip install aider-install && aider-install")
10
- // 미지원(셸 문법 — shell:true 없이는 실행 불가, 주입 표면을 만들지 않는다):
11
- // - 파이프("|")·리다이렉트(<>)·치환($`;) 등 → {manual: true} 로 수동 안내.
7
+ // 지원 형태:
8
+ // - 공백 구분 argv("npm i -g x")와 "&&" 순차 단계
9
+ // - 고정된 curl|sh 한 줄. curl과 sh/bash를 각각 spawn하고 stdout만 잇는다.
10
+ // - https .sh 주소 하나(antigravity)는 curl -fsSL | bash 로 같은 경로.
11
+ // 그 외 파이프·리다이렉트·치환은 {manual: true}. 문서 HTML은 실행하지 않는다.
12
12
  // Windows .cmd/.bat spawn(EINVAL)은 resolveLaunchCommand 가 node 진입점으로
13
13
  // 우회해 예방한다(#10).
14
14
 
15
15
  const SHELL_METACHARS = /[|<>;$`' "\\]/;
16
+ const CURL_PIPE = /^curl\s+(-[fsSL]+)\s+(https:\/\/\S+)\s+\|\s+(sh|bash)$/;
16
17
 
17
18
  export function parseInstallCommand(install) {
18
19
  const raw = String(install ?? "").trim();
19
20
  if (!raw) return { manual: true, reason: "empty" };
20
- // URL 단독(antigravity/goose) — 실행할 명령이 아니라 문서 링크.
21
+ const piped = raw.match(CURL_PIPE);
22
+ if (piped) return { pipe: { curl: ["curl", piped[1], piped[2]], shell: piped[3] } };
23
+ if (/^https:\/\/\S+\.sh$/.test(raw)) return { pipe: { curl: ["curl", "-fsSL", raw], shell: "bash" } };
21
24
  if (/^https?:\/\//.test(raw) && !/\s/.test(raw)) return { manual: true, reason: "url" };
22
25
  const steps = raw.split("&&").map((part) => part.trim().split(/\s+/).filter(Boolean));
23
26
  if (steps.some((args) => args.length === 0)) return { manual: true, reason: "empty-step" };
@@ -31,12 +34,60 @@ export function parseInstallCommand(install) {
31
34
  * 설치 명령 실행. {code, manual} 반환 — manual이면 호출자가 수동 안내를 출력한다.
32
35
  * spawnImpl/resolve는 테스트 주입용.
33
36
  */
37
+ function runCurlPipe(pipe, spawnImpl, resolve) {
38
+ let curlCmd;
39
+ let shellCmd;
40
+ try {
41
+ curlCmd = resolve(pipe.curl[0], pipe.curl.slice(1));
42
+ shellCmd = resolve(pipe.shell, []);
43
+ } catch (e) {
44
+ console.error(`✗ ${e.message}`);
45
+ return Promise.resolve(1);
46
+ }
47
+ return new Promise((res) => {
48
+ const curlChild = spawnImpl(curlCmd.command, curlCmd.args, { stdio: ["ignore", "pipe", "inherit"] });
49
+ const shellChild = spawnImpl(shellCmd.command, shellCmd.args, { stdio: ["pipe", "inherit", "inherit"] });
50
+ if (!curlChild.stdout?.pipe || !shellChild.stdin) {
51
+ res(1);
52
+ return;
53
+ }
54
+ curlChild.stdout.pipe(shellChild.stdin);
55
+ let curlCode = null;
56
+ let shellCode = null;
57
+ const finish = () => {
58
+ if (curlCode == null || shellCode == null) return;
59
+ res(curlCode === 0 ? shellCode : curlCode);
60
+ };
61
+ curlChild.on("error", () => {
62
+ curlCode = 1;
63
+ try { shellChild.stdin?.end?.(); } catch { /* pipe가 이미 닫았을 수 있다 */ }
64
+ shellChild.kill?.();
65
+ finish();
66
+ });
67
+ shellChild.on("error", () => {
68
+ shellCode = 1;
69
+ finish();
70
+ });
71
+ curlChild.on("exit", (n) => {
72
+ curlCode = n ?? 1;
73
+ finish();
74
+ });
75
+ shellChild.on("exit", (n) => {
76
+ shellCode = n ?? 1;
77
+ finish();
78
+ });
79
+ });
80
+ }
81
+
34
82
  export async function runInstallCommand(install, {
35
83
  spawnImpl = spawn,
36
84
  resolve = resolveLaunchCommand,
37
85
  } = {}) {
38
86
  const parsed = parseInstallCommand(install);
39
87
  if (parsed.manual) return { manual: true, reason: parsed.reason, code: null };
88
+ if (parsed.pipe) {
89
+ return { manual: false, code: await runCurlPipe(parsed.pipe, spawnImpl, resolve) };
90
+ }
40
91
 
41
92
  for (const args of parsed.steps) {
42
93
  const [bin, ...rest] = args;
package/src/menu.mjs CHANGED
@@ -51,8 +51,8 @@ const AGENT_DESC = {
51
51
  interpreter: "Local code execution & pair programmer",
52
52
  };
53
53
 
54
- // 실행 가능한(셸 문법 없는 argv 단계) 설치만 자동 설치 — URL·파이프·리다이렉트는
55
- // 수동 안내한다(#40: naive split이 "|"·"&&" 를 인자로 넘겨 전부 실패하던 결함).
54
+ // 설치 가능한 명령은 엔터로 실행한다. npm -g, curl|sh, https .sh 가 여기 들어온다.
55
+ // 문서 주소와 그 외 셸 문법은 수동 안내로 남긴다.
56
56
  const autoInstallable = (r) => !parseInstallCommand(r.install).manual;
57
57
 
58
58
  // 최상위 메뉴 항목(순수 함수 — 테스트 대상). 로그인은 첫 에이전트 실행 시 시작한다.
@@ -223,8 +223,7 @@ async function ensureInstalled(r) {
223
223
  return false;
224
224
  }
225
225
  console.log(style.dim(`$ ${r.install}`));
226
- // 공용 실행기: "&&" 순차 단계 지원, Windows .cmd는 resolveLaunchCommand 우회,
227
- // 셸 문법(파이프 등)은 절대 임의 실행하지 않고 수동 안내로 명시한다.
226
+ // 공용 실행기: "&&" 순차 단계와 고정된 curl|sh. Windows .cmd는 resolveLaunchCommand 우회.
228
227
  const result = await runInstallCommand(r.install);
229
228
  if (result.manual) {
230
229
  printManualInstall(r.install);
package/src/recipes.mjs CHANGED
@@ -200,7 +200,7 @@ export const RECIPES = {
200
200
  label: "Goose",
201
201
  bin: "goose",
202
202
  type: "env",
203
- install: "https://block.github.io/goose/docs/getting-started/installation",
203
+ install: "curl -fsSL https://github.com/aaif-goose/goose/releases/download/stable/download_cli.sh | bash",
204
204
  note: "Goose sets its model in its own config — use the openai/<id> format in `goose configure`.",
205
205
  attribution: {
206
206
  title: "Goose",