agentlas 1.0.41 → 1.0.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/CHANGELOG.md CHANGED
@@ -1,5 +1,23 @@
1
1
  # Changelog
2
2
 
3
+ ## 1.0.42 — 2026-08-11
4
+
5
+ Projects, automations and firms reach the interactive shell; the shell is
6
+ enabled with `AGENTLAS_TUI=1`.
7
+
8
+ - `/projects` — every connected project with chat/task counts, and a clear
9
+ marker for the one this folder belongs to (or an explicit warning when the
10
+ folder is not connected).
11
+ - `/automations` — the full list with schedule, next run, run count and failure
12
+ count; `/automations <name>` opens the detail view with recent run outcomes
13
+ and the exact commands to run or toggle it.
14
+ - `/firms` — teams with member counts; `/firms <slug>` shows the roster.
15
+ - Table layout no longer inflates narrow status columns when a row overflows
16
+ the terminal width.
17
+ - The shell is now enabled with `AGENTLAS_TUI=1`. Renderer internals are an
18
+ implementation detail and no longer appear in commands, environment
19
+ variables, or on screen.
20
+
3
21
  ## 1.0.41 — 2026-08-11
4
22
 
5
23
  Desktop surfaces, measured against the desktop inventory and rebuilt for the terminal.
@@ -16,28 +34,27 @@ Desktop surfaces, measured against the desktop inventory and rebuilt for the ter
16
34
  - `/settings` — language, permission, active runtime, installed CLIs, and the
17
35
  resolved orchestrator/worker model roles; names what remains Desktop-only
18
36
  instead of implying parity.
19
- - Those five honest stops now point at the pi shell instead of claiming the
37
+ - Those five honest stops now point at the interactive shell instead of claiming the
20
38
  surface is unreachable from the terminal.
21
39
 
22
40
  ## 1.0.40 — 2026-08-11
23
41
 
24
- Desktop surfaces reach the pi shell: dashboard and graph view.
42
+ Desktop surfaces reach the interactive shell: dashboard and graph view.
25
43
 
26
- - `/dashboard` (pi shell): agents/firms/telegram badges + automations panel
44
+ - `/dashboard` (interactive shell): agents/firms/telegram badges + automations panel
27
45
  (enabled state, last run) from the local store — first of the thirteen
28
46
  Desktop-only honest stops to be lifted.
29
- - `/graph show <name>` (pi shell): the automation graph renders as Unicode
47
+ - `/graph show <name>` (interactive shell): the automation graph renders as Unicode
30
48
  box art (grok-mermaid) — condition branches, loop-back edges, and Korean
31
49
  labels align correctly. No canvas emulation; editing stays declarative.
32
- - First-run onboarding now runs before the pi shell starts (sequential
33
- readline → pi-tui, no stdin contention), so `AGENTLAS_TUI=pi` works for
50
+ - First-run onboarding now runs before the interactive shell starts (sequential, no stdin contention), so `AGENTLAS_TUI=1` works for
34
51
  brand-new installs too.
35
52
 
36
53
  ## 1.0.39 — 2026-08-11
37
54
 
38
- pi-tui shell increment 2 + architecture mirror sync.
55
+ Interactive shell increment 2 + architecture mirror sync.
39
56
 
40
- - Experimental pi shell (`AGENTLAS_TUI=pi`): persisted history (cli-history.json
57
+ - Experimental interactive shell (`AGENTLAS_TUI=1`): persisted history (cli-history.json
41
58
  v2 contract), Shift-Tab permission cycling (same two-step FULL arming state
42
59
  machine as the classic REPL), `!` shell passthrough (full-permission gate,
43
60
  secret masking), and `/s` `/switch` `/kill` `/rm` `/sessions` `/tree` session
@@ -93,13 +93,13 @@ const DESKTOP_ONLY_SURFACES = {
93
93
  trex: "T-rex slide studio is Desktop-only.",
94
94
  slides: "T-rex slide studio is Desktop-only.",
95
95
  prompts: "Prompt Store is Desktop-only.",
96
- dashboard: "Dashboard: run `AGENTLAS_TUI=pi agentlas` then /dashboard — or: agentlas doctor · usage · list",
97
- marketplace: "Hub view: run `AGENTLAS_TUI=pi agentlas` then /marketplace — or: agentlas search \"<what you need>\"",
98
- library: "Library: run `AGENTLAS_TUI=pi agentlas` then /library — or: agentlas list · env · mcp",
99
- settings: "Settings: run `AGENTLAS_TUI=pi agentlas` then /settings — change with: agentlas setup · env · creds · multimodal",
96
+ dashboard: "Dashboard: run `AGENTLAS_TUI=1 agentlas` then /dashboard — or: agentlas doctor · usage · list",
97
+ marketplace: "Hub view: run `AGENTLAS_TUI=1 agentlas` then /marketplace — or: agentlas search \"<what you need>\"",
98
+ library: "Library: run `AGENTLAS_TUI=1 agentlas` then /library — or: agentlas list · env · mcp",
99
+ settings: "Settings: run `AGENTLAS_TUI=1 agentlas` then /settings — change with: agentlas setup · env · creds · multimodal",
100
100
  apps: "Apps surface is Desktop-only.",
101
101
  quests: "Quests are Desktop-only.",
102
- bookmarks: "Hub bookmarks: run `AGENTLAS_TUI=pi agentlas` then /marketplace.",
102
+ bookmarks: "Hub bookmarks: run `AGENTLAS_TUI=1 agentlas` then /marketplace.",
103
103
  one: "Agentlas One is a separate Desktop/Mobile product surface.",
104
104
  };
105
105
 
@@ -125,12 +125,12 @@ async function startRepl(ctx, opts = {}) {
125
125
  }
126
126
 
127
127
  /*
128
- * 실험 셸 opt-in (D3 Phase 2): AGENTLAS_TUI=pi + TTY.
128
+ * 대화형 셸 opt-in (D3 Phase 2): AGENTLAS_TUI=1 + TTY.
129
129
  * 온보딩 마법사(위 readline 블록)가 먼저 끝난 뒤 진입한다 — 순차 실행이라
130
- * stdin 경합이 없고, 첫 실행 사용자도 pi 셸을 쓸 수 있다(증분 2c).
130
+ * stdin 경합이 없고, 첫 실행 사용자도 셸을 쓸 수 있다(증분 2c).
131
131
  */
132
- if (process.env.AGENTLAS_TUI === "pi" && process.stdin.isTTY) {
133
- return require("./pitui-shell.cjs").startPiShell(ctx, opts);
132
+ if (/^(1|true|on)$/i.test(String(process.env.AGENTLAS_TUI || "")) && process.stdin.isTTY) {
133
+ return require("./shell.cjs").startShell(ctx, opts);
134
134
  }
135
135
  const orch = new Orchestrator({ db, lang: ctx.lang });
136
136
  const renderer = new Renderer(ui);
@@ -1,6 +1,6 @@
1
1
  "use strict";
2
2
  /*
3
- * ui/pitui-screens — 데스크탑 화면의 터미널 대응물 (D3 Phase 3).
3
+ * ui/screens — 데스크탑 화면의 터미널 대응물 (D3 Phase 3).
4
4
  *
5
5
  * 대조 원본: docs/2026-08-11-terminal-tui-overhaul/D1-데스크탑-기능-인벤토리.md
6
6
  * 각 화면은 데스크탑이 IPC로 읽는 것과 같은 로컬 저장소를 직접 읽는다.
@@ -17,10 +17,24 @@ function table(ui, rows, opts = {}) {
17
17
  if (!rows.length) return;
18
18
  const cols = rows[0].length;
19
19
  const max = Number(opts.maxWidth) || 78;
20
- const widths = Array.from({ length: cols }, (_, i) =>
21
- Math.min(opts.cap?.[i] || 40, Math.max(...rows.map((r) => visWidth(String(r[i] ?? ""))))));
22
- const total = widths.reduce((a, b) => a + b + 2, 0);
23
- if (total > max && widths.length) widths[0] = Math.max(8, widths[0] - (total - max));
20
+ const widths = Array.from({ length: cols }, (_, i) => {
21
+ const cap = opts.cap?.[i];
22
+ return Math.min(Number.isFinite(cap) ? cap : 40, Math.max(...rows.map((r) => visWidth(String(r[i] ?? "")))));
23
+ });
24
+ /*
25
+ * 넘치면 "가장 넓은 열"을 깎는다. 예전엔 0번 열을 깎으면서 Math.max(8, …) 바닥을
26
+ * 뒀는데, 1칸짜리 상태 마커 열이 8칸으로 부풀어 표가 깨졌다(실측). 바닥은 4칸이고
27
+ * 이미 그보다 좁은 열은 건드리지 않는다.
28
+ */
29
+ let over = widths.reduce((a, b) => a + b + 2, 0) - max;
30
+ while (over > 0) {
31
+ let widest = -1;
32
+ widths.forEach((w, i) => { if (w > 4 && (widest < 0 || w > widths[widest])) widest = i; });
33
+ if (widest < 0) break;
34
+ const cut = Math.min(over, widths[widest] - 4);
35
+ widths[widest] -= cut;
36
+ over -= cut;
37
+ }
24
38
  rows.forEach((row, index) => {
25
39
  const line = row.map((cell, i) => {
26
40
  const text = truncateWidth(String(cell ?? ""), widths[i]);
@@ -214,4 +228,127 @@ function settings(ui, db, en, ctx) {
214
228
  : "여기서 불가(데스크탑 전용): 테마, 모바일 페어링 QR, 자동 업데이트, 멀티모달 프로바이더"));
215
229
  }
216
230
 
217
- module.exports = { dashboard, library, marketplace, settings, table };
231
+ /* ── /projects 데스크탑 workspace + project/detail ── */
232
+ function projects(ui, db, en) {
233
+ const list = rows(db,
234
+ `SELECT p.id, p.name, p.folder_path, p.source_type,
235
+ (SELECT COUNT(*) FROM chats c WHERE c.project_id = p.id AND c.archived_at IS NULL) chats,
236
+ (SELECT COUNT(*) FROM tasks t WHERE t.project_id = p.id AND t.archived_at IS NULL) tasks,
237
+ p.updated_at
238
+ FROM projects p ORDER BY COALESCE(p.updated_at,'') DESC`);
239
+ ui.ensureNl();
240
+ ui.line(ui.c.bold(en ? `Projects (${list.length})` : `프로젝트 (${list.length})`));
241
+ const cwd = process.cwd();
242
+ if (list.length) {
243
+ table(ui, [[en ? "project" : "프로젝트", en ? "source" : "소스", en ? "chats" : "채팅", en ? "tasks" : "작업", en ? "updated" : "수정"],
244
+ ...list.map((p) => [
245
+ (p.folder_path && cwd.startsWith(p.folder_path) ? "▸ " : " ") + (p.name || p.id),
246
+ p.source_type || "local", String(p.chats), String(p.tasks), shortTs(p.updated_at)])],
247
+ { cap: [30, 10, 6, 6, 16] });
248
+ const here = list.find((p) => p.folder_path && cwd.startsWith(p.folder_path));
249
+ ui.line("");
250
+ ui.line(here
251
+ ? ui.c.dim(en ? `▸ this folder is connected to "${here.name}"` : `▸ 이 폴더는 "${here.name}"에 연결돼 있습니다`)
252
+ : ui.c.amber(en ? "this folder is not connected — /project use <agent>" : "이 폴더는 연결 안 됨 — /project use <에이전트>"));
253
+ } else {
254
+ ui.line(ui.c.dim(en ? " none — /project use <agent>" : " 없음 — /project use <에이전트>"));
255
+ }
256
+ ui.line(ui.c.dim(en
257
+ ? "detail: /project status · team: /project team <agent>… · timeline lives in the project store"
258
+ : "상세: /project status · 팀: /project team <에이전트>… · 타임라인은 프로젝트 저장소에"));
259
+ }
260
+
261
+ /* ── /automations — 데스크탑 automation 목록+상세 ── */
262
+ function automations(ui, db, en, ctx, arg) {
263
+ const name = String(arg || "").trim();
264
+ if (name) {
265
+ const a = db.prepare("SELECT * FROM automations WHERE name = ? OR id = ?").get(name, name);
266
+ if (!a) { ui.line(ui.c.dim(en ? `no automation named "${name}"` : `"${name}" 자동화가 없습니다`)); return; }
267
+ ui.ensureNl();
268
+ ui.line(ui.c.bold(a.name) + " " + (a.enabled ? ui.c.green("● on") : ui.c.faint("○ off")));
269
+ table(ui, [
270
+ [en ? "field" : "항목", en ? "value" : "값"],
271
+ [en ? "schedule" : "일정", a.schedule || (a.trigger_type || "—")],
272
+ [en ? "next run" : "다음 실행", shortTs(a.next_run_at) || "—"],
273
+ [en ? "last run" : "마지막 실행", shortTs(a.last_run_at) || "—"],
274
+ [en ? "runs" : "실행 횟수", String(a.run_count || 0)],
275
+ [en ? "target" : "대상", `${a.target_type || "—"}${a.target_id ? `:${String(a.target_id).slice(0, 20)}` : ""}`],
276
+ [en ? "permission" : "권한", a.execution_permission || "—"],
277
+ [en ? "graph" : "그래프", a.graph_json ? (en ? `yes — /graph show ${a.name}` : `있음 — /graph show ${a.name}`) : (en ? "no" : "없음")],
278
+ ], { cap: [16, 54] });
279
+ const runs = rows(db,
280
+ "SELECT status, started_at FROM automation_runs WHERE automation_id = ? ORDER BY COALESCE(started_at,'') DESC LIMIT 6", [a.id]);
281
+ if (runs.length) {
282
+ ui.line("");
283
+ ui.line(ui.c.bold(en ? "Recent runs" : "최근 실행"));
284
+ for (const r of runs) {
285
+ const mark = r.status === "ok" ? ui.c.green("✓") : r.status === "error" ? ui.c.amber("✗") : ui.c.dim("·");
286
+ ui.line(` ${mark} ${r.status} ${ui.c.dim(shortTs(r.started_at))}`);
287
+ }
288
+ }
289
+ ui.line("");
290
+ ui.line(ui.c.dim(en
291
+ ? `run now: /automation run ${a.name} · toggle: /automation ${a.enabled ? "off" : "on"} ${a.name}`
292
+ : `지금 실행: /automation run ${a.name} · ${a.enabled ? "끄기: /automation off" : "켜기: /automation on"} ${a.name}`));
293
+ return;
294
+ }
295
+ const list = rows(db,
296
+ `SELECT a.name, a.enabled, a.schedule, a.next_run_at, a.run_count,
297
+ (SELECT COUNT(*) FROM automation_runs r WHERE r.automation_id = a.id AND r.status='error') fails
298
+ FROM automations a ORDER BY a.enabled DESC, COALESCE(a.next_run_at,'')`);
299
+ ui.ensureNl();
300
+ ui.line(ui.c.bold(en ? `Automations (${list.length})` : `자동화 (${list.length})`));
301
+ if (!list.length) { ui.line(ui.c.dim(en ? " none — /automation add" : " 없음 — /automation add")); return; }
302
+ table(ui, [[" ", en ? "name" : "이름", en ? "schedule" : "일정", en ? "next" : "다음", en ? "runs" : "실행", en ? "fails" : "실패"],
303
+ ...list.map((a) => [a.enabled ? "●" : "○", a.name, a.schedule || "—",
304
+ shortTs(a.next_run_at) || "—", String(a.run_count || 0), a.fails ? String(a.fails) : ""])],
305
+ { cap: [1, 30, 14, 16, 5, 5] });
306
+ ui.line("");
307
+ ui.line(ui.c.dim(en
308
+ ? "detail: /automations <name> · graph: /graph show <name> · add: /automation add"
309
+ : "상세: /automations <이름> · 그래프: /graph show <이름> · 추가: /automation add"));
310
+ void ctx;
311
+ }
312
+
313
+ /* ── /firms — 데스크탑 firm/detail (조직도) ── */
314
+ function firms(ui, db, en, ctx, arg) {
315
+ const name = String(arg || "").trim();
316
+ if (name) {
317
+ const f = db.prepare("SELECT * FROM firms WHERE slug = ? OR name = ?").get(name, name);
318
+ if (!f) { ui.line(ui.c.dim(en ? `no firm named "${name}"` : `"${name}" 회사가 없습니다`)); return; }
319
+ ui.ensureNl();
320
+ ui.line(ui.c.bold(f.name || f.slug) + (f.tagline ? ui.c.dim(` · ${f.tagline}`) : ""));
321
+ let org = null;
322
+ try { org = JSON.parse(f.org_chart_json || "null"); } catch { org = null; }
323
+ const members = rows(db,
324
+ "SELECT slug, COALESCE(local_display_name, name) nm, role FROM installed_agents WHERE parent_team_id = ? ORDER BY role, slug", [f.id]);
325
+ if (members.length) {
326
+ ui.line("");
327
+ ui.line(ui.c.bold(en ? `Roster (${members.length})` : `구성원 (${members.length})`));
328
+ table(ui, [[en ? "slug" : "슬러그", en ? "name" : "이름", en ? "role" : "역할"],
329
+ ...members.map((m) => [m.slug, m.nm || "", m.role || ""])], { cap: [30, 26, 14] });
330
+ } else if (org) {
331
+ ui.line(ui.c.dim(en ? " roster is declared in the org chart only" : " 로스터가 조직도 선언에만 있습니다"));
332
+ }
333
+ const chats = count(db, "SELECT COUNT(*) n FROM chats WHERE firm_id = ? AND archived_at IS NULL", );
334
+ void chats;
335
+ ui.line("");
336
+ ui.line(ui.c.dim(en ? `run: /firm ${f.slug} "<task>"` : `실행: /firm ${f.slug} "<할 일>"`));
337
+ return;
338
+ }
339
+ const list = rows(db,
340
+ `SELECT f.slug, f.name, f.tagline,
341
+ (SELECT COUNT(*) FROM installed_agents a WHERE a.parent_team_id = f.id) members
342
+ FROM firms f ORDER BY f.slug`);
343
+ ui.ensureNl();
344
+ ui.line(ui.c.bold(en ? `Firms (${list.length})` : `회사 (${list.length})`));
345
+ if (!list.length) { ui.line(ui.c.dim(en ? " none" : " 없음")); return; }
346
+ table(ui, [[en ? "slug" : "슬러그", en ? "name" : "이름", en ? "members" : "구성원"],
347
+ ...list.map((f) => [f.slug, f.name || "", String(f.members)])], { cap: [30, 30, 8] });
348
+ ui.line("");
349
+ ui.line(ui.c.dim(en ? "detail: /firms <slug>" : "상세: /firms <슬러그>"));
350
+ void ctx;
351
+ }
352
+
353
+ module.exports = { dashboard, library, marketplace, settings, projects, automations, firms, table };
354
+
@@ -1,16 +1,18 @@
1
1
  "use strict";
2
2
  /*
3
- * ui/pitui-shell — pi-tui 기반 실험 셸 (D3 Phase 2 증분 1, 2026-08-11).
3
+ * ui/shell — Agentlas 대화형 셸 (D3 Phase 2~3, 2026-08-11).
4
4
  *
5
- * 켜는 법: AGENTLAS_TUI=pi agentlas (TTY 필수 · 기본 REPL은 그대로 정본)
5
+ * 켜는 법: AGENTLAS_TUI=1 agentlas (TTY 필수 · 기본 REPL은 그대로 정본)
6
+ *
7
+ * 렌더러 의존은 구현 세부다 — 사용자 문구·환경변수·명령 어디에도 상류 이름을 쓰지 않는다.
6
8
  *
7
9
  * 설계 (D2 위험 5의 해법이 이 파일의 구조다):
8
- * - PiUi 는 기존 Ui 를 상속하되 write() 초크포인트만 pi-tui 트랜스크립트로 돌린다.
10
+ * - PiUi 는 기존 Ui 를 상속하되 write() 초크포인트만 렌더러 트랜스크립트로 돌린다.
9
11
  * line/_message/tool/rule 등 기존 메서드는 전부 write 로 수렴하므로 그대로 산다.
10
12
  * - 스트리밍 3종은 Markdown 누적으로 교체 — 표·코드블록이 실시간 재렌더된다.
11
- * - 스피너는 pi-tui Loader 로 교체 (기존 \r 기반 페인트는 dummy 스트림으로 무해화).
13
+ * - 스피너는 렌더러 Loader 로 교체 (기존 \r 기반 페인트는 dummy 스트림으로 무해화).
12
14
  * - ctx.out/err 55파일의 직출력은 shellCtx 재지정으로 전부 프레임 안에 들어온다.
13
- * - 자동완성은 ui/palette 정본(SLASH_COMMANDS)을 pi-tui SlashCommand 로 변환 — 목록 드리프트 금지.
15
+ * - 자동완성은 ui/palette 정본(SLASH_COMMANDS)을 렌더러 SlashCommand 로 변환 — 목록 드리프트 금지.
14
16
  *
15
17
  * 증분 1 범위 밖(기본 REPL로): Shift-Tab 권한 순환 · ! 셸 · 세션 전환(/s) ·
16
18
  * 히스토리 디스크 영속 · 스티어링 큐 표시. 이 항목들은 D3 Phase 2-2에서 이전한다.
@@ -25,20 +27,20 @@ const palette = require("./palette.cjs");
25
27
  const { readVersion } = require("../agentlas-banner.cjs");
26
28
  const { resolveProjectController, withProjectControllerContext } = require("../project/controller.cjs");
27
29
 
28
- function loadPiTui() {
30
+ function loadRenderer() {
29
31
  try {
30
32
  // ESM 패키지 — Node >=20.19 의 require(esm). engines 가 이 최소선을 선언한다.
31
33
  return require("@earendil-works/pi-tui");
32
34
  } catch (cause) {
33
35
  throw Object.assign(
34
- new Error("pi-tui shell needs Node >=20.19 (require(esm)). Run without AGENTLAS_TUI=pi, or upgrade Node."),
35
- { code: "pitui_unavailable", cause },
36
+ new Error("The Agentlas shell needs Node >=20.19. Run without AGENTLAS_TUI=1, or upgrade Node."),
37
+ { code: "shell_unavailable", cause },
36
38
  );
37
39
  }
38
40
  }
39
41
 
40
- /* Ui 를 상속해 write 초크포인트만 pi-tui 돌린다. */
41
- class PiUi extends Ui {
42
+ /* Ui 를 상속해 write 초크포인트만 렌더러로 돌린다. */
43
+ class ShellUi extends Ui {
42
44
  constructor(opts, pi, tui, transcript) {
43
45
  // 실터미널 대신 dummy 스트림 — 놓친 직접 out.write 가 프레임을 찢는 대신 소멸한다.
44
46
  super({ ...opts, stream: new PassThrough(), color: true });
@@ -128,39 +130,42 @@ class PiUi extends Ui {
128
130
  }
129
131
 
130
132
  /*
131
- * pi 셸 전용 화면 (D3 Phase 3). 공용 팔레트(ui/palette)에는 넣지 않는다 —
133
+ * 셸 전용 화면 (D3 Phase 3). 공용 팔레트(ui/palette)에는 넣지 않는다 —
132
134
  * 그 정본은 기본 REPL 이 실제로 처리하는 명령만 광고해야 하고,
133
135
  * palette-command-coverage-contract 가 그 계약을 잠근다.
134
136
  */
135
- const PI_SCREENS = [
137
+ const SHELL_SCREENS = [
136
138
  { name: "dashboard", ko: "관제 대시보드 — 확인 필요·실행 활동·자동화", en: "Dashboard — attention, run activity, automations" },
137
139
  { name: "library", ko: "라이브러리 — 에이전트·MCP", en: "Library — agents, MCP" },
138
140
  { name: "marketplace", ko: "Hub — 북마크·대여 현황", en: "Hub — bookmarks and borrows" },
139
141
  { name: "settings", ko: "설정 현황", en: "Settings overview" },
142
+ { name: "projects", ko: "프로젝트 목록 — 채팅·작업 수", en: "Projects — chats and tasks" },
143
+ { name: "automations", ko: "자동화 목록·상세 [이름]", en: "Automations list/detail [name]" },
144
+ { name: "firms", ko: "회사(팀) 목록·조직도 [슬러그]", en: "Firms and rosters [slug]" },
140
145
  ];
141
146
 
142
147
  function toSlashCommands(lang) {
143
- // 팔레트 정본 → pi-tui SlashCommand. "/" 접두는 pi-tui 관리하므로 벗긴다.
148
+ // 팔레트 정본 → 렌더러 SlashCommand. "/" 접두는 렌더러가 관리하므로 벗긴다.
144
149
  const base = palette.SLASH_COMMANDS.map((cmd) => ({
145
150
  name: cmd.command.slice(1),
146
151
  description: lang === "ko" ? cmd.ko : cmd.en,
147
152
  argumentHint: cmd.args || undefined,
148
153
  }));
149
154
  const seen = new Set(base.map((c) => c.name));
150
- for (const s of PI_SCREENS) {
155
+ for (const s of SHELL_SCREENS) {
151
156
  if (!seen.has(s.name)) base.push({ name: s.name, description: lang === "ko" ? s.ko : s.en });
152
157
  }
153
158
  return base;
154
159
  }
155
160
 
156
- async function startPiShell(ctx, opts = {}) {
157
- const pi = loadPiTui();
161
+ async function startShell(ctx, opts = {}) {
162
+ const pi = loadRenderer();
158
163
  const en = ctx.lang === "en";
159
164
  const db = ctx.db();
160
165
 
161
166
  const terminal = new pi.ProcessTerminal();
162
167
  const tui = new pi.TuiMainScreen(terminal);
163
- const ui = new PiUi({ lang: ctx.lang }, pi, tui, tui);
168
+ const ui = new ShellUi({ lang: ctx.lang }, pi, tui, tui);
164
169
 
165
170
  // ctx 초크포인트 재지정 — 55파일의 ctx.out 직출력이 전부 프레임 안으로 들어온다.
166
171
  const shellCtx = {
@@ -202,7 +207,7 @@ async function startPiShell(ctx, opts = {}) {
202
207
  };
203
208
 
204
209
  // ── 헤더 ──
205
- ui.line(`${ui.c.paw("▞▖")} ${ui.c.bold("AGENTLAS")} ${ui.c.dim(`${readVersion()} · pi-tui shell (experimental) · parallel ≤${maxParallel()}`)}`);
210
+ ui.line(`${ui.c.paw("▞▖")} ${ui.c.bold("AGENTLAS")} ${ui.c.dim(`${readVersion()} · parallel ≤${maxParallel()}`)}`);
206
211
  ui.line(ui.c.dim(en
207
212
  ? "plain words run a task · / commands · Esc interrupts · Ctrl+C quits"
208
213
  : "문장을 치면 실행 · / 명령 · Esc 중단 · Ctrl+C 종료"));
@@ -279,15 +284,19 @@ async function startPiShell(ctx, opts = {}) {
279
284
  }
280
285
  // 데스크탑 대응 화면 (Phase 3) — 정직 정지였던 표면들을 실물로 대체
281
286
  {
282
- const screens = require("./pitui-screens.cjs");
287
+ const screens = require("./screens.cjs");
283
288
  const SCREEN = {
284
289
  dashboard: screens.dashboard,
285
290
  library: screens.library,
286
291
  marketplace: screens.marketplace,
287
292
  bookmarks: screens.marketplace,
288
293
  settings: screens.settings,
294
+ projects: screens.projects,
295
+ automations: screens.automations,
296
+ firms: screens.firms,
289
297
  };
290
- if (SCREEN[cmd]) { SCREEN[cmd](ui, db, en, shellCtx); return; }
298
+ // 인자 있는 화면(automations/firms 상세)은 restStr 그대로 넘긴다.
299
+ if (SCREEN[cmd]) { SCREEN[cmd](ui, db, en, shellCtx, cmdline.slice(raw.length).trim()); return; }
291
300
  }
292
301
  // 세션 관찰/전환 (증분 2b) — 기본 REPL과 같은 orch/renderer 배선
293
302
  if (cmd === "sessions" || cmd === "tree") {
@@ -318,7 +327,7 @@ async function startPiShell(ctx, opts = {}) {
318
327
  ui.line(ui.c.dim(commands.DESKTOP_ONLY_SURFACES[cmd]));
319
328
  return;
320
329
  }
321
- ui.line(ui.c.dim(en ? `not in the pi shell yet: /${cmd} — use the classic REPL` : `pi 셸에는 아직 없음: /${cmd} — 기본 REPL을 쓰세요`));
330
+ ui.line(ui.c.dim(en ? `not available here yet: /${cmd}` : `여기서는 아직 됩니다: /${cmd}`));
322
331
  };
323
332
 
324
333
  let busy = false;
@@ -368,7 +377,7 @@ async function startPiShell(ctx, opts = {}) {
368
377
  };
369
378
 
370
379
  tui.addInputListener((data) => {
371
- // Shift-Tab 권한 순환 — pi-tui raw mode 를 단독 소유하므로 readline 의
380
+ // Shift-Tab 권한 순환 — 렌더러가 raw mode 를 단독 소유하므로 readline 의
372
381
  // swallowCompletion 우회 없이 여기서 직접 소비한다 (D2 위험 2의 해소 형태).
373
382
  if (pi.matchesKey(data, "shift+tab")) {
374
383
  permShortcut.handleKey("", { name: "tab", shift: true });
@@ -398,8 +407,8 @@ async function startPiShell(ctx, opts = {}) {
398
407
  });
399
408
 
400
409
  tui.start();
401
- // pi-tui 프로세스를 잡고 있는 동안 살아 있는 프라미스
410
+ // 렌더러가 프로세스를 잡고 있는 동안 살아 있는 프라미스
402
411
  return new Promise(() => {});
403
412
  }
404
413
 
405
- module.exports = { startPiShell };
414
+ module.exports = { startShell };
@@ -3,7 +3,7 @@
3
3
  * ui/width: 터미널 셀 폭·그래핌 유틸 정본.
4
4
  * agentlas-composer.cjs(데드코드가 된 바텀 입력 박스)에서 2026-08-11 추출 — 함수는
5
5
  * 바이트 동일. 소비자: agentlas-ui / agentlas-banner / agentlas-onboard /
6
- * ui/repl / hephaestus/runtime. pi-tui 이행(D3 Phase 1-3) 정지작업.
6
+ * ui/repl / hephaestus/runtime. 이행(D3 Phase 1-3) 정지작업.
7
7
  */
8
8
  const GRAPHEME_SEGMENTER = new Intl.Segmenter(undefined, { granularity: "grapheme" });
9
9
  const MARK_RE = /\p{Mark}/u;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "1.0.41",
3
+ "version": "1.0.42",
4
4
  "description": "Agentlas project terminal — run project controllers and task-scoped agent teams from the terminal. Standalone: no desktop app process required.",
5
5
  "bin": {
6
6
  "agentlas": "bin/agentlas.cjs"