agentlas 0.4.0 → 0.5.2

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
@@ -1,7 +1,18 @@
1
1
  # agentlas
2
2
 
3
- **The Agentlas agent terminal.** Chat with your installed AI agents and teams
4
- from the terminal — Claude Code style. Standalone: no desktop app required.
3
+ ```
4
+ █████╗ ██████╗ ███████╗███╗ ██╗████████╗██╗ █████╗ ███████╗
5
+ ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██║ ██╔══██╗██╔════╝
6
+ ███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██║ ███████║███████╗
7
+ ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║╚════██║
8
+ ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ███████╗██║ ██║███████║
9
+ ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝
10
+ ```
11
+
12
+ **The operating system for agents — in your terminal.** Chat with your AI
13
+ agents and teams, build new ones, and run the full Agentlas OS surface
14
+ (`build` · `search` · `install` · `storm` · `network` · …) from one command.
15
+ Claude Code style, standalone: **no desktop app required.**
5
16
 
6
17
  ```sh
7
18
  npm install -g agentlas
@@ -40,31 +51,67 @@ Windows(미검증): `powershell -ExecutionPolicy Bypass -File install.ps1`
40
51
  ## 사용
41
52
 
42
53
  ```sh
43
- agentlas # 대화형 TUI (온보딩에이전트 피커 → 스트리밍 REPL)
44
- agentlas list # 설치된 에이전트/회사 + 활성 런타임
45
- agentlas <agent> # 해당 에이전트와 대화형 세션
46
- agentlas run <agent> "프롬프트" # 1회 실행 (prompt 없으면 stdin)
47
- agentlas firm <firm> "프롬프트" # 회사(팀) 실행
48
- agentlas cd <agent> # 에이전트 폴더 경로 → cd "$(agentlas cd seo)" && claude
54
+ agentlas # 대화형 TUI (워드마크 입력 → 스트리밍 REPL)
55
+ agentlas "할 일" # 자동 라우팅 1회 실행
56
+ ```
57
+
58
+ **대화 & 실행**
59
+ ```sh
60
+ agentlas <agent> # 해당 에이전트와 대화 (예: agentlas seo)
61
+ agentlas run [agent] "프롬프트" # 1회 실행 (agent 생략 시 자동 라우팅, prompt 없으면 stdin)
62
+ agentlas firm <firm> "프롬프트" # 회사(팀) CEO에 위임
63
+ agentlas chats [n] # 최근 대화 목록
64
+ ```
65
+
66
+ **에이전트 & 허브** (Agentlas OS 표면)
67
+ ```sh
68
+ agentlas search "할 일" # Hub에서 에이전트 발견 (hep-search)
69
+ agentlas install <slug> # Hub 에이전트 설치 (hep-cloud)
70
+ agentlas build "요청" # 에이전트/팀 빌드·수리·패키징 (hep-build)
71
+ agentlas upload <경로> # 에이전트 패키징+Hub 배포 (hep-upload)
72
+ agentlas connect # Telegram/플랫폼 연결 (hep-connect)
49
73
  agentlas import <폴더> # 로컬 에이전트/팀 임포트
50
- agentlas storm "목표" # Stormbreaker 파이프라인 (라우팅→검증→실행) [--research]
51
- agentlas swarm "목표" # emergent 에이전트 스웜 (병렬+블랙보드+종합) [--parallel N]
74
+ agentlas list # 설치된 에이전트/회사 + 활성 런타임
75
+ ```
76
+
77
+ **실행 엔진**
78
+ ```sh
79
+ agentlas storm "목표" # 견고 파이프라인 라우팅→검증→실행 (Stormbreaker) [--research]
80
+ agentlas swarm "목표" # emergent 에이전트 스웜 [--parallel N]
81
+ agentlas network "요청" # A2A 태스크포스로 분해 (hep-network)
82
+ agentlas call "a,b" "컨텍스트" # 지정 에이전트 호출 (hep-call)
83
+ agentlas browser # 실제 브라우저 하드포인트 (hep-browser)
84
+ agentlas route "요청" # 라우팅 미리보기 (실행 없음)
85
+ ```
86
+
87
+ **지식 & 리서치**
88
+ ```sh
89
+ agentlas research <sub> # Research Engine (status|gather|search|read|plan)
90
+ agentlas ontology <sub> # 프로젝트 지식 (status|list|add)
91
+ agentlas journal <sub> # Stormbreaker 저널 (status|verify|repair|gate)
92
+ ```
93
+
94
+ **계정 & 운영**
95
+ ```sh
52
96
  agentlas login | logout | whoami # Agentlas Cloud 로그인 (브라우저 플로우)
53
- agentlas automation list|add|on|off|remove|runs # 자동화 등록/관리
54
- agentlas automation run <id> # 자동화 지금 1회 실행
55
- agentlas automation daemon # 상주 실행기 — 앱 없이 예약 자동화 실행 (앱과 리스 공유)
56
- agentlas cloud search "찾는 일" # 마켓플레이스 검색 (로그인 불필요)
57
- agentlas cloud install <slug> # 마켓플레이스에서 에이전트 설치
58
- agentlas usage # 로컬 사용 현황 (실행/메시지/자동화)
59
- agentlas telegram # 텔레그램 바인딩 현황 (읽기 전용)
60
- agentlas creds / env # 자격증명 · env
97
+ agentlas automation <sub> # list|add|on|off|remove|run <id>|runs|daemon (로컬 스케줄러)
98
+ agentlas creds <sub> · env # 자격증명 볼트 · env
61
99
  agentlas multimodal # 이미지/영상/음성 provider
100
+ agentlas usage · telegram · mcp # 사용 현황 · 텔레그램 · MCP 서버
62
101
  agentlas doctor # 런타임/데이터 점검
63
- agentlas setup # 온보딩(언어→런타임→권한) 다시 실행
64
- agentlas update # Desktop 최신 릴리스 확인/설치
102
+ agentlas update # npm 최신판 확인
103
+ agentlas setup # 온보딩 다시 실행
104
+ ```
105
+
106
+ **고급**
107
+ ```sh
108
+ agentlas hep <sub…> # 전체 Hephaestus 패스스루 (wizard·security·cards·ao·plugins…)
109
+ agentlas netadmin <sub> # 로컬 네트워크 관리 (init|status|reindex|bench)
110
+ agentlas cloud <sub> # 에이전트 패키징 (wizard|security|bundle|package|publish)
65
111
  ```
66
112
 
67
113
  공통 옵션: `--runtime claude-code|codex|gemini` · `--permission read|write|full`
114
+ REPL 안에서는 `/`로 명령 팔레트 (`/build` `/search` `/storm` `/network` …).
68
115
 
69
116
  ## 동작 방식
70
117
 
@@ -1,17 +1,31 @@
1
1
  "use strict";
2
2
  /*
3
- * Agentlas terminal splash — small dinosaur mascot (Chrome-dino style) + wordmark + status.
3
+ * Agentlas terminal splash — AGENTLAS wordmark banner + status card.
4
4
  */
5
5
  const path = require("node:path");
6
6
  const os = require("node:os");
7
7
 
8
- // Small T-Rex (side view, facing right) eye is ●.
9
- const DINO_ART = [
10
- " ▟████▙",
11
- " █● ▜█▙",
12
- " ▖ ████████",
13
- " ▜█▄▄▄▄▄███",
14
- " ▀▀▀█▌ █▌",
8
+ // AGENTLAS wordmark (block letters). Rendered with a brand gradient across columns.
9
+ const WORDMARK = [
10
+ " █████╗ ██████╗ ███████╗███╗ ██╗████████╗██╗ █████╗ ███████╗",
11
+ " ██╔══██╗██╔════╝ ██╔════╝████╗ ██║╚══██╔══╝██║ ██╔══██╗██╔════╝",
12
+ " ███████║██║ ███╗█████╗ ██╔██╗ ██║ ██║ ██║ ███████║███████╗",
13
+ " ██╔══██║██║ ██║██╔══╝ ██║╚██╗██║ ██║ ██║ ██╔══██║╚════██║",
14
+ " ██║ ██║╚██████╔╝███████╗██║ ╚████║ ██║ ███████╗██║ ██║███████║",
15
+ " ╚═╝ ╚═╝ ╚═════╝ ╚══════╝╚═╝ ╚═══╝ ╚═╝ ╚══════╝╚═╝ ╚═╝╚══════╝",
16
+ ];
17
+
18
+ // Compact single-line wordmark for narrow terminals.
19
+ const WORDMARK_COMPACT = "▞▖ AGENTLAS";
20
+
21
+ // Brand gradient (emerald → green → lime → blue) applied left-to-right across the wordmark.
22
+ const GRADIENT = [
23
+ [110, 231, 183],
24
+ [52, 211, 153],
25
+ [16, 185, 129],
26
+ [45, 212, 191],
27
+ [56, 189, 248],
28
+ [147, 197, 253],
15
29
  ];
16
30
 
17
31
  function readVersion() {
@@ -28,16 +42,30 @@ function shorten(p) {
28
42
  return p.startsWith(home) ? "~" + p.slice(home.length) : p;
29
43
  }
30
44
 
31
- // Just the mascot lines (used by the onboarding wizard header).
45
+ // AGENTLAS wordmark, per-column brand gradient. Falls back to plain / compact.
32
46
  function renderMascot(ui) {
33
47
  const c = ui.c;
48
+ const cols = (ui.out && ui.out.columns) || 80;
34
49
  if (!ui.enabled) {
35
- ui.line(" Agentlas");
50
+ ui.line(" AGENTLAS");
36
51
  return;
37
52
  }
38
- for (let i = 0; i < DINO_ART.length; i++) {
39
- const row = DINO_ART[i];
40
- ui.line(" " + (i === 1 ? c.text(row).split("●").join(c.emerald("●")) : c.text(row)));
53
+ // Narrow terminal compact wordmark.
54
+ if (cols < 72) {
55
+ ui.line(" " + c.bold(c.emerald(WORDMARK_COMPACT)));
56
+ return;
57
+ }
58
+ const g = (rgb, s) => `\x1b[1;38;2;${rgb[0]};${rgb[1]};${rgb[2]}m${s}\x1b[0m`;
59
+ for (const rowStr of WORDMARK) {
60
+ let outLine = "";
61
+ const n = rowStr.length || 1;
62
+ for (let i = 0; i < rowStr.length; i++) {
63
+ const ch = rowStr[i];
64
+ if (ch === " ") { outLine += " "; continue; }
65
+ const idx = Math.min(GRADIENT.length - 1, Math.floor((i / n) * GRADIENT.length));
66
+ outLine += g(GRADIENT[idx], ch);
67
+ }
68
+ ui.line(outLine);
41
69
  }
42
70
  }
43
71
 
@@ -70,10 +98,13 @@ function renderStatusCard(ctx, opts = {}) {
70
98
  const runtime = ctx.runtimeLabel || "(not configured)";
71
99
  const cwd = ctx.cwd ? shorten(ctx.cwd) : process.cwd();
72
100
 
101
+ ui.line("");
102
+ if (!opts.noWordmark) {
103
+ renderMascot(ui);
104
+ ui.line(" " + c.dim("the operating system for agents") + (version ? c.faint(" v" + version) : ""));
105
+ }
73
106
  ui.line("");
74
107
  ui.line(c.faint("╭" + "─".repeat(width - 2) + "╮"));
75
- row(ui, width, `>_ Agentlas${version ? " (v" + version + ")" : ""}`);
76
- row(ui, width, "");
77
108
  row(ui, width, `model: ${runtime}`);
78
109
  row(ui, width, `agent: ${subject}`);
79
110
  row(ui, width, `directory: ${cwd}`);
@@ -100,9 +131,9 @@ function renderBanner(ctx) {
100
131
  ctx.ui.line("");
101
132
  }
102
133
 
103
- // runtime · subject · permission · working folder
134
+ // runtime · subject · permission · working folder (no wordmark — used for /status).
104
135
  function renderStatus(ctx) {
105
- renderStatusCard(ctx, { noTip: true });
136
+ renderStatusCard(ctx, { noTip: true, noWordmark: true });
106
137
  }
107
138
 
108
- module.exports = { renderBanner, renderStatus, renderMascot, readVersion, shorten, DINO_ART, fit };
139
+ module.exports = { renderBanner, renderStatus, renderMascot, readVersion, shorten, WORDMARK, fit };
@@ -29,12 +29,23 @@ function extractHosts(error) {
29
29
  return [...hosts];
30
30
  }
31
31
 
32
- /** kind: mcp-oauth-unauthenticated | timeout | cli-exit | unknown (데스크탑 TS와 동일 규칙) */
32
+ /** 에러 텍스트에서 config.toml 스키마 위반이 지목한 mcp_servers 이름을 뽑는다. */
33
+ function extractBadMcpServer(error) {
34
+ // 예: Error loading config.toml: url is not supported for stdio in "mcp_servers.agentlas"
35
+ const m = /mcp_servers\.([a-z0-9_.-]+)/i.exec(error || "");
36
+ return m ? m[1] : null;
37
+ }
38
+
39
+ /** kind: mcp-oauth-unauthenticated | codex-config-invalid | timeout | cli-exit | unknown (데스크탑 TS와 동일 규칙) */
33
40
  function classifyFailure(error) {
34
41
  const text = error || "";
35
42
  if (/authrequired|invalid_token|oauth-protected-resource|www_authenticate/i.test(text)) {
36
43
  return { kind: "mcp-oauth-unauthenticated", hosts: extractHosts(text) };
37
44
  }
45
+ // codex config.toml 파싱 실패(예: stdio 서버에 url 키) → CLI가 아예 기동 못 함.
46
+ if (/error loading config\.toml|url is not supported for stdio|invalid config/i.test(text)) {
47
+ return { kind: "codex-config-invalid", hosts: [], badServer: extractBadMcpServer(text) };
48
+ }
38
49
  if (/no response for \d+s|auto-aborted/i.test(text)) return { kind: "timeout", hosts: [] };
39
50
  if (/CLI exit \d+|exited with code [1-9]/i.test(text)) return { kind: "cli-exit", hosts: extractHosts(text) };
40
51
  return { kind: "unknown", hosts: [] };
@@ -120,9 +131,21 @@ function disableCodexPlugin(pluginKey) {
120
131
  * 데스크탑 runtime-doctor.ts 의 runRuntimeDoctor 와 동일 계약.
121
132
  */
122
133
  function runRuntimeDoctor(errorMessage) {
123
- const { kind, hosts } = classifyFailure(errorMessage);
134
+ const { kind, hosts, badServer } = classifyFailure(errorMessage);
124
135
  const actions = [];
125
136
 
137
+ if (kind === "codex-config-invalid") {
138
+ // 진단만 한다(자동 수리 안 함): 원격 MCP 항목이라 안전한 무손실 수리가 없고,
139
+ // 사용자가 어느 항목을 어떻게 고칠지 알아야 한다. 명확한 조치를 안내한다.
140
+ const where = badServer ? `[mcp_servers.${badServer}]` : "일부 [mcp_servers.*] 항목";
141
+ return {
142
+ kind,
143
+ summary: `codex의 ~/.codex/config.toml ${where} 이(가) 잘못돼 codex가 기동하지 못합니다(예: stdio 서버에 url 키). 다른 런타임(--runtime claude-code)으로 우회하거나 그 항목을 고치세요.`,
144
+ repaired: false,
145
+ actions,
146
+ };
147
+ }
148
+
126
149
  if (kind === "mcp-oauth-unauthenticated") {
127
150
  const hitList = findOauthPluginsByHost(hosts);
128
151
  let repairedAny = false;
@@ -107,6 +107,7 @@ const STRINGS = {
107
107
  "help.setup": "run language, runtime, and permission setup again",
108
108
  "help.cwd": "show or change the working folder",
109
109
  "help.memory": "show the memory being injected",
110
+ "help.careerGraph": "show/add source refs for the Career Graph routing index",
110
111
  "help.ontology": "turn on/list/add project ontology sources with short natural commands",
111
112
  "help.side": "ask a side question without saving it to chat context",
112
113
  "help.status": "show model/runtime, agent, permission, and directory",
@@ -119,6 +120,11 @@ const STRINGS = {
119
120
  "help.build": "build/repair/package an agent or team (Hephaestus)",
120
121
  "help.route": "preview which agent/pipeline would take a request",
121
122
  "help.research": "run the Research Engine (search/gather/read)",
123
+ "help.search": "discover agents in the Hub",
124
+ "help.install": "install an agent from the Hub by slug",
125
+ "help.network": "decompose a request into an A2A task force",
126
+ "help.browser": "real browser execution hardpoint",
127
+ "help.connect": "wire Telegram / platforms to an agent team",
122
128
  "help.swarm": "fan out an emergent agent swarm on a goal",
123
129
  "help.doctor": "check runtimes and data",
124
130
  "help.compact": "drop older transcript turns and keep the latest context",
@@ -252,6 +258,7 @@ const STRINGS = {
252
258
  "help.setup": "언어·런타임·권한 설정 다시 실행",
253
259
  "help.cwd": "작업 폴더 보기/변경",
254
260
  "help.memory": "주입되는 메모리 보기",
261
+ "help.careerGraph": "Career Graph 라우팅 색인의 원본 경로 보기/등록",
255
262
  "help.ontology": "짧은 자연어로 프로젝트 온톨로지 켜기/목록/자료 등록",
256
263
  "help.side": "메인 대화 맥락에 저장하지 않는 사이드 질문",
257
264
  "help.status": "모델/런타임, 에이전트, 권한, 작업 폴더 보기",
@@ -264,6 +271,11 @@ const STRINGS = {
264
271
  "help.build": "에이전트/팀 빌드·수리·패키징 (Hephaestus)",
265
272
  "help.route": "요청이 어떤 에이전트로 라우팅되는지 미리보기",
266
273
  "help.research": "Research Engine 실행 (search/gather/read)",
274
+ "help.search": "Hub에서 에이전트 발견",
275
+ "help.install": "slug로 Hub 에이전트 설치",
276
+ "help.network": "요청을 A2A 태스크포스로 분해",
277
+ "help.browser": "실제 브라우저 실행 하드포인트",
278
+ "help.connect": "Telegram/플랫폼을 에이전트 팀에 연결",
267
279
  "help.swarm": "목표에 emergent 에이전트 스웜 전개",
268
280
  "help.doctor": "런타임/데이터 점검",
269
281
  "help.compact": "오래된 대화 맥락을 줄이고 최근 맥락 유지",
@@ -75,6 +75,7 @@ const SLASH_COMMAND_META = [
75
75
  { command: "/help", description: "Show Agentlas terminal commands", category: "Help", usage: "/help", detail: "Open the command reference, shortcuts, and common flows." },
76
76
  { command: "/status", description: "Show model/runtime, agent, permission, and directory", category: "Session", usage: "/status", detail: "Print the current runtime, active agent or company, permission level, and cwd." },
77
77
  { command: "/skills", description: "List available Agentlas terminal skills", category: "Discovery", usage: "/skills", detail: "Show the slash-command skills Agentlas can run inside this terminal." },
78
+ { command: "/career-graph", description: "Show or add Career Graph source refs", category: "Knowledge", usage: "/career-graph add ./docs", detail: "Career Graph routes agents to source Markdown, JSONL ledgers, sitemap, and code map before broad scans.", examples: ["/career-graph status", "/career-graph add ./docs", "/career-graph open"] },
78
79
  { command: "/ontology", description: "Turn on, list, or add project ontology sources", category: "Knowledge", usage: "/ontology add ./docs", detail: "Also understands natural text like /ontology use ./docs as company knowledge.", examples: ["/ontology list", "/ontology use ./docs as company knowledge", "/ontology open"] },
79
80
  { command: "/agents", description: "List installed agents", category: "Routing", usage: "/agents", detail: "Show local agents and their routed runtime." },
80
81
  { command: "/team", description: "View or pin each agent runtime", category: "Routing", usage: "/team <agent> <runtime|auto>", detail: "Pin one agent to claude-code, codex, gemini, or automatic routing." },
@@ -107,6 +108,11 @@ const SLASH_COMMAND_META = [
107
108
  { command: "/build", description: "Build/repair/package an agent or team (Hephaestus)", category: "Engine", usage: "/build <what to build>", detail: "Runs Hephaestus hep-build natively — deep interview, scaffolding, packaging." },
108
109
  { command: "/route", description: "Preview which agent/pipeline would take a request", category: "Engine", usage: "/route <request>", detail: "Runs the Hephaestus router without executing — shows the selected agent, candidates, and reasons." },
109
110
  { command: "/research", description: "Run the Hephaestus Research Engine", category: "Engine", usage: "/research search \"query\"", detail: "status|gather|search|read|plan — evidence-grade web research from the terminal." },
111
+ { command: "/search", description: "Discover agents in the Hub", category: "Hub", usage: "/search <what you need>", detail: "Search the Agentlas Hub + local for an agent that fits the task (hep-search)." },
112
+ { command: "/install", description: "Install an agent from the Hub by slug", category: "Hub", usage: "/install <slug>", detail: "Install a marketplace agent into this terminal (hep-cloud)." },
113
+ { command: "/network", description: "Decompose a request into an A2A task force", category: "Engine", usage: "/network <request>", detail: "Runs Hephaestus hep-network — splits a composite request across agents.", aliases: ["/taskforce"] },
114
+ { command: "/browser", description: "Real browser execution hardpoint", category: "Engine", usage: "/browser [sub]", detail: "Runs the Agentlas browser hardpoint (hep-browser)." },
115
+ { command: "/connect", description: "Wire Telegram / platforms to an agent team", category: "Hub", usage: "/connect", detail: "Runs Hephaestus hep-connect for platform integration." },
110
116
  { command: "/doctor", description: "Check runtimes and local data", category: "Health", usage: "/doctor", detail: "Run local checks for runtimes, data, credentials, and setup." },
111
117
  { command: "/exit", description: "Quit Agentlas", category: "Session", usage: "/exit", detail: "Close the terminal session.", aliases: ["/quit"] },
112
118
  ];
@@ -406,6 +412,7 @@ function makeCompleter(ctx) {
406
412
  return [uniqStartsWith(RUNTIME_SPECS.concat(["auto"]), last), last];
407
413
  case "/cwd":
408
414
  case "/import":
415
+ case "/career-graph":
409
416
  case "/ontology":
410
417
  return [completePath(last, getCwd(), ""), last];
411
418
  default:
@@ -23,6 +23,13 @@ const { Ui } = require("./agentlas-ui.cjs");
23
23
  const SWARM_MAX_TASKS = 24;
24
24
  const SWARM_SPAWN_PER_TURN = 12;
25
25
 
26
+ // Agentlas-OS/Hephaestus 내부 시스템 에이전트(마켓 제품 아님) — 검색/목록에서 숨긴다.
27
+ // 데스크탑 electron/agents/hired-agents.ts isInternalAgentSlug 와 규칙 동일.
28
+ function isInternalAgentSlug(slug) {
29
+ const s = String(slug || "").toLowerCase();
30
+ return /^researcher-\d+/.test(s) || s === "research-intelligence-desk" || s.startsWith("hephaestus-");
31
+ }
32
+
26
33
  function create(deps) {
27
34
  const D = deps;
28
35
 
@@ -56,6 +63,35 @@ function create(deps) {
56
63
  return null;
57
64
  }
58
65
 
66
+ function careerGraphRuntime() {
67
+ const binCandidates = [
68
+ process.env.HEPHAESTUS_CAREER_GRAPH_BIN,
69
+ process.env.HEPHAESTUS_BIN ? path.join(path.dirname(process.env.HEPHAESTUS_BIN), "career-graph") : null,
70
+ path.join(os.homedir(), ".agentlas", "runtime", "current", "bin", "career-graph"),
71
+ ];
72
+ for (const c of binCandidates) {
73
+ try {
74
+ if (c && fs.existsSync(c)) {
75
+ fs.accessSync(c, fs.constants.X_OK);
76
+ return { kind: "bin", exec: c };
77
+ }
78
+ } catch { /* 다음 후보 */ }
79
+ }
80
+ const roots = [
81
+ process.env.HEPHAESTUS_RUNTIME_ROOT,
82
+ path.join(os.homedir(), ".agentlas", "runtime", "current"),
83
+ ];
84
+ if (process.resourcesPath) roots.push(path.join(process.resourcesPath, "Hephaestus"));
85
+ if (process.platform === "darwin") roots.push("/Applications/Agentlas.app/Contents/Resources/Hephaestus");
86
+ roots.push(path.resolve(__dirname, "..", "..", "agentlas_desktop", "Hephaestus"));
87
+ for (const root of roots) {
88
+ try {
89
+ if (root && fs.existsSync(path.join(root, "career_graph", "__main__.py"))) return { kind: "python", root };
90
+ } catch { /* 다음 후보 */ }
91
+ }
92
+ return null;
93
+ }
94
+
59
95
  const PY_BOOTSTRAP =
60
96
  "import os, runpy, sys; " +
61
97
  'cwd=os.getcwd(); root=os.environ["HEPHAESTUS_RUNTIME_ROOT"]; ' +
@@ -189,17 +225,28 @@ function create(deps) {
189
225
  // ── Hephaestus 네이티브 패스스루 — 엔진 전 기능을 터미널 1급으로 노출 ──
190
226
  // stdio inherit 로 돌려 색/프롬프트/스트리밍이 네이티브 그대로 나온다.
191
227
  function runHephaestusInteractive(args, opts = {}) {
192
- const found = hephaestusBin();
228
+ const isCareerGraph = args[0] === "career-graph" || args[0] === "career_graph";
229
+ const found = isCareerGraph ? careerGraphRuntime() : hephaestusBin();
193
230
  if (!found) {
194
- process.stderr.write("Hephaestus 런타임이 없습니다 — 데스크탑 앱 설치 또는 Hephaestus 인스톨러 실행 후 다시 시도하세요.\n");
195
- process.stderr.write("설치: https://agentlas.cloud · 또는 HEPHAESTUS_BIN=<경로> 지정\n");
231
+ process.stderr.write(
232
+ isCareerGraph
233
+ ? "Career Graph 런타임이 없습니다 — 최신 Agentlas OS / Hephaestus 설치 후 다시 시도하세요.\n"
234
+ : "Hephaestus 런타임이 없습니다 — 데스크탑 앱 설치 또는 Hephaestus 인스톨러 실행 후 다시 시도하세요.\n",
235
+ );
236
+ process.stderr.write(
237
+ isCareerGraph
238
+ ? "설치 또는 지정: HEPHAESTUS_CAREER_GRAPH_BIN=<경로> 또는 HEPHAESTUS_RUNTIME_ROOT=<경로>\n"
239
+ : "설치: https://agentlas.cloud · 또는 HEPHAESTUS_BIN=<경로> 지정\n",
240
+ );
196
241
  return Promise.resolve(1);
197
242
  }
198
243
  const cwd = opts.cwd || D.runCwd();
244
+ const moduleName = found.kind === "python" && isCareerGraph ? "career_graph" : "agentlas_cloud";
245
+ const moduleArgs = moduleName === "career_graph" ? args.slice(1) : args;
199
246
  const child =
200
247
  found.kind === "bin"
201
- ? spawn(found.exec, args, { cwd, stdio: "inherit" })
202
- : spawn("python3", ["-c", PY_BOOTSTRAP, "agentlas_cloud", ...args], {
248
+ ? spawn(found.exec, isCareerGraph ? args.slice(1) : args, { cwd, stdio: "inherit" })
249
+ : spawn("python3", ["-c", PY_BOOTSTRAP, moduleName, ...moduleArgs], {
203
250
  cwd,
204
251
  stdio: "inherit",
205
252
  env: { ...process.env, HEPHAESTUS_RUNTIME_ROOT: found.root },
@@ -958,7 +1005,8 @@ function create(deps) {
958
1005
  headers,
959
1006
  body: JSON.stringify({
960
1007
  method: "marketplace.search_agents",
961
- params: { name: "marketplace.search_agents", arguments: { query, limit } },
1008
+ // Hub는 파라미터 이름이 `q` `query`만 보내면 무시하고 기본 목록을 준다(데스크탑 mcp-source.ts와 동일하게 둘 다 전송).
1009
+ params: { name: "marketplace.search_agents", arguments: { q: query, query, limit } },
962
1010
  }),
963
1011
  });
964
1012
  } catch (e) {
@@ -968,7 +1016,9 @@ function create(deps) {
968
1016
  const json = await resp.json();
969
1017
  if (json.error) return D.fail(json.error.message || "marketplace error");
970
1018
  const result = json.result || {};
971
- const items = result.results || result.agents || result.items || (Array.isArray(result) ? result : null);
1019
+ const rawItems = result.results || result.agents || result.items || (Array.isArray(result) ? result : null);
1020
+ // 엔진 내부 에이전트(researcher-<n>, research-intelligence-desk, hephaestus-*)는 제품이 아니므로 숨긴다.
1021
+ const items = Array.isArray(rawItems) ? rawItems.filter((it) => !isInternalAgentSlug(it && (it.slug || it.id))) : rawItems;
972
1022
  if (!Array.isArray(items) || !items.length) {
973
1023
  D.out(`검색 결과 없음: "${query}"`);
974
1024
  return;
@@ -981,7 +1031,7 @@ function create(deps) {
981
1031
  D.out(`${String(slug).padEnd(34).slice(0, 34)} ${String(name).slice(0, 26).padEnd(27)} ${String(kind).padEnd(14)} ${String(tagline).slice(0, 60)}`);
982
1032
  }
983
1033
  D.out("");
984
- D.out("설치: agentlas cloud install <slug>");
1034
+ D.out("설치: agentlas install <slug>");
985
1035
  }
986
1036
 
987
1037
  return {
@@ -537,7 +537,7 @@ function startRepl(opts) {
537
537
  const fmt = (e) => {
538
538
  const bits = [e.turns + (e.turns === 1 ? " turn" : " turns")];
539
539
  if (e.in || e.out) bits.push(e.in + "→" + e.out + " tok");
540
- if (e.cost) bits.push("$" + e.cost.toFixed(4));
540
+ // 달러 비용 미표시 — 토큰 수만.
541
541
  if (e.ms) bits.push((e.ms / 1000).toFixed(1) + "s");
542
542
  return bits.join(" · ");
543
543
  };
@@ -722,6 +722,37 @@ function startRepl(opts) {
722
722
  await H.hepRun(["research", ...arg.split(/\s+/)], { cwd: state.cwd });
723
723
  return true;
724
724
  }
725
+ case "search": {
726
+ if (!arg) return ui.warn("usage: /search <할 일>"), true;
727
+ if (H.cloudSearch) await H.cloudSearch(db, [arg]);
728
+ else await H.hepRun(["hep-search", arg], { cwd: state.cwd });
729
+ return true;
730
+ }
731
+ case "install": {
732
+ if (!arg) return ui.warn("usage: /install <slug> (/search 로 먼저 찾으세요)"), true;
733
+ if (H.cloudInstall) {
734
+ try { const r = await H.cloudInstall(db, arg.trim()); ui.ok((r && r.name) ? `설치됨: ${r.name}` : `설치됨: ${arg.trim()}`); }
735
+ catch (e) { ui.error((e && e.message) || String(e)); }
736
+ } else ui.warn("설치 기능을 사용할 수 없습니다.");
737
+ return true;
738
+ }
739
+ case "network":
740
+ case "taskforce": {
741
+ if (!arg) return ui.warn("usage: /network <요청> (A2A 태스크포스)"), true;
742
+ ui.line("");
743
+ await H.hepRun(["hep-network", arg, "--project", state.cwd, "--runtime", "terminal"], { cwd: state.cwd });
744
+ return true;
745
+ }
746
+ case "browser": {
747
+ ui.line("");
748
+ await H.hepRun(["hep-browser", ...(arg ? arg.split(/\s+/) : [])], { cwd: state.cwd });
749
+ return true;
750
+ }
751
+ case "connect": {
752
+ ui.line("");
753
+ await H.hepRun(["hep-connect", ...(arg ? arg.split(/\s+/) : [])], { cwd: state.cwd });
754
+ return true;
755
+ }
725
756
  case "swarm": {
726
757
  if (!arg) return ui.warn("usage: /swarm <goal> [--parallel N]"), true;
727
758
  let goal = arg;
@@ -818,6 +849,18 @@ function startRepl(opts) {
818
849
  }
819
850
  return true;
820
851
  }
852
+ case "career-graph":
853
+ case "graph": {
854
+ if (!H.careerGraphCommand) return ui.warn("career graph command unavailable"), true;
855
+ try {
856
+ const lines = H.careerGraphCommand(arg, { cwd: state.cwd, projectPath: state.projectPath });
857
+ ui.line("");
858
+ for (const item of lines || []) ui.line(" " + ui.c.text(String(item)));
859
+ } catch (e) {
860
+ ui.error((e && e.message) || String(e));
861
+ }
862
+ return true;
863
+ }
821
864
  case "side":
822
865
  case "btw":
823
866
  if (!arg) return ui.warn(ui.t("sideUsage")), true;
@@ -1212,6 +1255,7 @@ function printHelp(ui) {
1212
1255
  ["/setup", ui.t("help.setup")],
1213
1256
  ["/cwd [path]", ui.t("help.cwd")],
1214
1257
  ["/memory", ui.t("help.memory")],
1258
+ ["/career-graph [text]", ui.t("help.careerGraph")],
1215
1259
  ["/ontology [text]", ui.t("help.ontology")],
1216
1260
  ["/side <question>", ui.t("help.side")],
1217
1261
  ["/status", ui.t("help.status")],
@@ -1228,8 +1272,12 @@ function printHelp(ui) {
1228
1272
  ["/build <request>", ui.t("help.build")],
1229
1273
  ["/route <request>", ui.t("help.route")],
1230
1274
  ["/research <sub>", ui.t("help.research")],
1231
- ["/marketplace", ui.t("help.market")],
1275
+ ["/search <task>", ui.t("help.search")],
1232
1276
  ["/install <slug>", ui.t("help.install")],
1277
+ ["/network <req>", ui.t("help.network")],
1278
+ ["/browser", ui.t("help.browser")],
1279
+ ["/connect", ui.t("help.connect")],
1280
+ ["/marketplace", ui.t("help.market")],
1233
1281
  ["/clear", ui.t("help.clear")],
1234
1282
  ["/doctor", ui.t("help.doctor")],
1235
1283
  ["/keybindings", ui.t("help.keybindings")],
@@ -252,7 +252,7 @@ class Ui {
252
252
  if (usage.input_tokens != null || usage.output_tokens != null) {
253
253
  bits.push(`${usage.input_tokens ?? "?"}→${usage.output_tokens ?? "?"} tok`);
254
254
  }
255
- if (usage.cost_usd != null) bits.push(`$${Number(usage.cost_usd).toFixed(4)}`);
255
+ // 달러 비용은 표시하지 않는다 — 토큰 수만 (사용자 결정 2026-07-09).
256
256
  if (usage.duration_ms != null) bits.push(`${(usage.duration_ms / 1000).toFixed(1)}s`);
257
257
  if (bits.length) this.line(this.c.faint(" " + bits.join(" · ")));
258
258
  }
@@ -913,7 +913,7 @@ async function packageCloudAgentCli(db, root, opts) {
913
913
  const routingCard = readCloudRoutingCardCli(rootPath);
914
914
  if (routingCard.finding) scan.findings.push(routingCard.finding);
915
915
  const name = cloudReadName(rootPath);
916
- const slug = cloudSlug(opts.slug || name || path.basename(rootPath));
916
+ const slug = cloudSlug(opts.slug || cloudReadStableSlug(rootPath) || name || path.basename(rootPath));
917
917
  const packageHash = cloudHashPackage(scan.included);
918
918
  const manifest = {
919
919
  version: "0.1",
@@ -1332,11 +1332,29 @@ function printCloudPackageResult(result) {
1332
1332
  }
1333
1333
 
1334
1334
  function cloudReadName(rootPath) {
1335
+ const manifest = cloudReadPackageJson(rootPath);
1336
+ const explicit = stringFirstCli(
1337
+ manifest.agentlas?.displayName,
1338
+ manifest.agentlas?.name,
1339
+ manifest.manifest?.name,
1340
+ manifest.agentCard?.name,
1341
+ manifest.routingCard?.name,
1342
+ );
1343
+ if (explicit) return explicit.replace(/\s+/g, " ").trim().slice(0, 80);
1335
1344
  const text = cloudReadFirst(rootPath, ["agent.md", "AGENT.md", "README.md", "CLAUDE.md", "AGENTS.md"], 2000);
1336
1345
  const heading = text.match(/^#\s+(.+)$/m);
1337
1346
  return (heading ? heading[1] : path.basename(rootPath)).replace(/\s+/g, " ").trim().slice(0, 80);
1338
1347
  }
1339
1348
  function cloudReadTagline(rootPath) {
1349
+ const manifest = cloudReadPackageJson(rootPath);
1350
+ const explicit = stringFirstCli(
1351
+ manifest.agentlas?.summary,
1352
+ manifest.agentlas?.description,
1353
+ manifest.manifest?.description,
1354
+ manifest.agentCard?.summary,
1355
+ manifest.routingCard?.summary,
1356
+ );
1357
+ if (explicit) return explicit.replace(/\s+/g, " ").trim().slice(0, 160);
1340
1358
  const text = cloudReadFirst(rootPath, ["README.md", "agent.md", "AGENT.md"], 3000);
1341
1359
  for (const line of text.split(/\r?\n/)) {
1342
1360
  const t = line.trim();
@@ -1344,6 +1362,32 @@ function cloudReadTagline(rootPath) {
1344
1362
  }
1345
1363
  return "Portable Agentlas cloud agent package.";
1346
1364
  }
1365
+ function cloudReadStableSlug(rootPath) {
1366
+ const manifest = cloudReadPackageJson(rootPath);
1367
+ return stringFirstCli(
1368
+ manifest.agentlas?.slug,
1369
+ manifest.agentlas?.id,
1370
+ manifest.manifest?.package,
1371
+ manifest.manifest?.slug,
1372
+ manifest.agentCard?.slug,
1373
+ manifest.agentCard?.id,
1374
+ manifest.routingCard?.agent_card_ref?.slug,
1375
+ );
1376
+ }
1377
+ function cloudReadPackageJson(rootPath) {
1378
+ return {
1379
+ agentlas: readJsonObjectCli(path.join(rootPath, "agentlas.json"), {}),
1380
+ manifest: readJsonObjectCli(path.join(rootPath, "manifest.json"), {}),
1381
+ agentCard: readJsonObjectCli(path.join(rootPath, ".agentlas", "agent-card.json"), {}),
1382
+ routingCard: readJsonObjectCli(path.join(rootPath, ".agentlas", "routing-card.json"), {}),
1383
+ };
1384
+ }
1385
+ function stringFirstCli(...values) {
1386
+ for (const value of values) {
1387
+ if (typeof value === "string" && value.trim()) return value.trim();
1388
+ }
1389
+ return "";
1390
+ }
1347
1391
  function cloudReadFirst(rootPath, names, maxChars) {
1348
1392
  for (const name of names) {
1349
1393
  const file = path.join(rootPath, name);
@@ -1413,7 +1457,7 @@ function loadArch() {
1413
1457
  try {
1414
1458
  _arch = require("./architecture.data.json");
1415
1459
  } catch {
1416
- _arch = { version: "0", agents: [], emitterBlock: "", eventsHeading: "## Memory Events", memoryDir: ".agentlas", soulFile: "project-soul-memory.md", sitemapFile: "sitemap.json", logFile: "memory-log.jsonl", kinds: [], scopes: [] };
1460
+ _arch = { version: "0", agents: [], emitterBlock: "", eventsHeading: "## Memory Events", memoryDir: ".agentlas", soulFile: "project-soul-memory.md", sitemapFile: "sitemap.json", logFile: "memory-log.jsonl", careerGraphConfigFile: "career-graph.json", careerGraphSourceManifestFile: "career-graph-sources.json", careerGraphInboxDir: "career-graph-inbox", careerGraphDbFile: "career-graph.sqlite", kinds: [], scopes: [] };
1417
1461
  }
1418
1462
  return _arch;
1419
1463
  }
@@ -1731,6 +1775,10 @@ function ensureProjectMemoryCli(projectPath, projectName) {
1731
1775
  const ontologySourceManifestFile = arch.ontologySourceManifestFile || "ontology-sources.json";
1732
1776
  const ontologyInboxDir = arch.ontologyInboxDir || "ontology-inbox";
1733
1777
  const ontologyDbFile = arch.ontologyDbFile || "ontology-runtime.sqlite";
1778
+ const careerGraphConfigFile = arch.careerGraphConfigFile || "career-graph.json";
1779
+ const careerGraphSourceManifestFile = arch.careerGraphSourceManifestFile || "career-graph-sources.json";
1780
+ const careerGraphInboxDir = arch.careerGraphInboxDir || "career-graph-inbox";
1781
+ const careerGraphDbFile = arch.careerGraphDbFile || "career-graph.sqlite";
1734
1782
  const superOntologyContractFile = arch.superOntologyContractFile || "super-ontology-contract.json";
1735
1783
  const superOntologyOpenWorldCoverageFile =
1736
1784
  arch.superOntologyOpenWorldCoverageFile || "super-ontology-open-world-coverage.json";
@@ -1876,6 +1924,38 @@ function ensureProjectMemoryCli(projectPath, projectName) {
1876
1924
  sources: [],
1877
1925
  }, null, 2), "utf8");
1878
1926
  }
1927
+ const careerGraphInbox = path.join(dir, careerGraphInboxDir);
1928
+ if (!fs.existsSync(careerGraphInbox)) fs.mkdirSync(careerGraphInbox, { recursive: true });
1929
+ const careerGraphConfig = path.join(dir, careerGraphConfigFile);
1930
+ if (!fs.existsSync(careerGraphConfig)) {
1931
+ fs.writeFileSync(careerGraphConfig, JSON.stringify({
1932
+ schemaVersion: "1.0",
1933
+ kind: "agentlas-career-graph",
1934
+ state: "active",
1935
+ model: "ledger_first_derived_index",
1936
+ projectRoot: projectPath,
1937
+ projectName: name,
1938
+ dbPath: path.join(dir, careerGraphDbFile),
1939
+ inboxPath: careerGraphInbox,
1940
+ sourceManifest: path.join(dir, careerGraphSourceManifestFile),
1941
+ canonicalSourcePolicy: {
1942
+ sourceOfTruth: "markdown_jsonl_json",
1943
+ graphIsRebuildable: true,
1944
+ fallbackWhenStale: "read_canonical_files",
1945
+ neverScanHomeDirectory: true,
1946
+ neverScanSiblingProjects: true,
1947
+ },
1948
+ }, null, 2), "utf8");
1949
+ }
1950
+ const careerGraphSources = path.join(dir, careerGraphSourceManifestFile);
1951
+ if (!fs.existsSync(careerGraphSources)) {
1952
+ fs.writeFileSync(careerGraphSources, JSON.stringify({
1953
+ schemaVersion: "1.0",
1954
+ kind: "agentlas-career-graph-source-manifest",
1955
+ projectRoot: projectPath,
1956
+ sources: [],
1957
+ }, null, 2), "utf8");
1958
+ }
1879
1959
  for (const fileName of [skillTrialsFile, curatorDecisionsFile]) {
1880
1960
  const filePath = path.join(dir, fileName);
1881
1961
  if (!fs.existsSync(filePath)) fs.writeFileSync(filePath, "", "utf8");
@@ -5465,6 +5545,182 @@ function runOntologyNaturalCli(text, opts) {
5465
5545
  return runOntologyCli(parseOntologyNaturalArgsCli(text, cwd), { ...(opts || {}), cwd });
5466
5546
  }
5467
5547
 
5548
+ function careerGraphPathsForCli(projectPath) {
5549
+ const arch = loadArch();
5550
+ const root = path.resolve(projectPath || process.cwd());
5551
+ const memoryDir = path.join(root, arch.memoryDir || ".agentlas");
5552
+ return {
5553
+ root,
5554
+ memoryDir,
5555
+ configPath: path.join(memoryDir, arch.careerGraphConfigFile || "career-graph.json"),
5556
+ sourceManifestPath: path.join(memoryDir, arch.careerGraphSourceManifestFile || "career-graph-sources.json"),
5557
+ inboxPath: path.join(memoryDir, arch.careerGraphInboxDir || "career-graph-inbox"),
5558
+ dbPath: path.join(memoryDir, arch.careerGraphDbFile || "career-graph.sqlite"),
5559
+ };
5560
+ }
5561
+
5562
+ function careerGraphSourceManifestSkeletonCli(root) {
5563
+ return {
5564
+ schemaVersion: "1.0",
5565
+ kind: "agentlas-career-graph-source-manifest",
5566
+ projectRoot: root,
5567
+ sources: [],
5568
+ };
5569
+ }
5570
+
5571
+ function ensureCareerGraphCli(projectPath) {
5572
+ const paths = careerGraphPathsForCli(projectPath);
5573
+ ensureProjectMemoryCli(paths.root, path.basename(paths.root) || "Project");
5574
+ fs.mkdirSync(paths.inboxPath, { recursive: true });
5575
+ if (!fs.existsSync(paths.sourceManifestPath)) {
5576
+ writeJsonSafeCli(paths.sourceManifestPath, careerGraphSourceManifestSkeletonCli(paths.root));
5577
+ }
5578
+ return paths;
5579
+ }
5580
+
5581
+ function readCareerGraphSourcesCli(sourceManifestPath) {
5582
+ const manifest = readJsonSafeCli(sourceManifestPath, { sources: [] });
5583
+ return Array.isArray(manifest.sources) ? manifest.sources : [];
5584
+ }
5585
+
5586
+ function careerGraphUsageLinesCli() {
5587
+ return [
5588
+ "Career Graph commands:",
5589
+ " career-graph status show source-routing files and index state",
5590
+ " career-graph list list inbox files and registered source refs",
5591
+ " career-graph open open the project career graph inbox",
5592
+ " career-graph add ./docs register a folder as private source material",
5593
+ "",
5594
+ "Full graph index commands live in Agentlas OS / Hephaestus:",
5595
+ " hephaestus career-graph ingest --project .",
5596
+ " hephaestus career-graph query \"release failures\" --project .",
5597
+ " hephaestus career-graph verify --project .",
5598
+ "",
5599
+ "Safety: the graph is rebuildable. Markdown, JSONL ledgers, sitemap, and code map stay source of truth.",
5600
+ ];
5601
+ }
5602
+
5603
+ function existingCareerGraphCanonicalRefsCli(root) {
5604
+ return [
5605
+ ".agentlas/project-soul-memory.md",
5606
+ ".agentlas/memory-log.jsonl",
5607
+ ".agentlas/curator-decisions.jsonl",
5608
+ ".agentlas/sitemap.json",
5609
+ ".agentlas/code-map/project-map.json",
5610
+ ".agentlas/ledgers/routing-decisions.jsonl",
5611
+ ".agentlas/ledgers/executions.jsonl",
5612
+ ".agentlas/ledgers/agent-evolution-proposals.jsonl",
5613
+ ].filter((rel) => fs.existsSync(path.join(root, rel)));
5614
+ }
5615
+
5616
+ function formatCareerGraphStatusCli(paths) {
5617
+ const sources = readCareerGraphSourcesCli(paths.sourceManifestPath);
5618
+ const inbox = listOntologyInboxCli(paths.inboxPath);
5619
+ const canonical = existingCareerGraphCanonicalRefsCli(paths.root);
5620
+ const lines = [
5621
+ "Career Graph: active",
5622
+ ` project: ${paths.root}`,
5623
+ ` inbox: ${paths.inboxPath}`,
5624
+ ` db: ${paths.dbPath}`,
5625
+ ` index: ${fs.existsSync(paths.dbPath) ? "present" : "pending"}`,
5626
+ " policy: ledger_first_derived_index",
5627
+ " source of truth: Markdown / JSONL / JSON files",
5628
+ "",
5629
+ `Canonical source refs (${canonical.length}):`,
5630
+ ];
5631
+ for (const rel of canonical) lines.push(` ${rel}`);
5632
+ if (!canonical.length) lines.push(" (none yet)");
5633
+ lines.push("", `Inbox (${inbox.length}):`);
5634
+ for (const item of inbox) lines.push(` ${item.supported ? "ok" : "!"} ${item.name} ${item.supported ? "supported" : "adapter pending"}`);
5635
+ if (!inbox.length) lines.push(" (empty)");
5636
+ lines.push("", `Registered source refs (${sources.length}):`);
5637
+ for (const source of sources) {
5638
+ const sourcePath = path.resolve(String(source.path || ""));
5639
+ lines.push(` ${fs.existsSync(sourcePath) ? "ok" : "!"} ${sourcePath} ${source.kind || "project"} / ${source.scope || "private"}`);
5640
+ }
5641
+ if (!sources.length) lines.push(" (none)");
5642
+ lines.push(
5643
+ "",
5644
+ "Build the derived index with Agentlas OS:",
5645
+ ` hephaestus career-graph ingest --project ${JSON.stringify(paths.root)}`,
5646
+ );
5647
+ return lines;
5648
+ }
5649
+
5650
+ function registerCareerGraphSourceCli(paths, source, kind, scope, cwd) {
5651
+ if (!source) throw new Error("usage: career-graph add <path>");
5652
+ const sourcePath = resolveOntologyPathCli(source, cwd || paths.root);
5653
+ if (!fs.existsSync(sourcePath)) throw new Error(`source not found: ${sourcePath}`);
5654
+ const manifest = readJsonSafeCli(paths.sourceManifestPath, careerGraphSourceManifestSkeletonCli(paths.root));
5655
+ const nextSources = (Array.isArray(manifest.sources) ? manifest.sources : [])
5656
+ .filter((item) => path.resolve(String(item.path || "")) !== sourcePath);
5657
+ nextSources.push({ path: sourcePath, kind, scope, registeredAt: new Date().toISOString() });
5658
+ manifest.schemaVersion = "1.0";
5659
+ manifest.kind = "agentlas-career-graph-source-manifest";
5660
+ manifest.projectRoot = paths.root;
5661
+ manifest.sources = nextSources;
5662
+ writeJsonSafeCli(paths.sourceManifestPath, manifest);
5663
+ return [
5664
+ `Registered Career Graph source: ${sourcePath}`,
5665
+ ` kind: ${kind}`,
5666
+ ` scope: ${scope}`,
5667
+ " copy: no",
5668
+ " scan: only this registered folder, not home/sibling projects",
5669
+ ];
5670
+ }
5671
+
5672
+ function runCareerGraphCli(args, opts) {
5673
+ opts = opts || {};
5674
+ const cwd = path.resolve(opts.cwd || process.cwd());
5675
+ const projectPath = path.resolve(opts.projectPath || cwd);
5676
+ const normalizedArgs = Array.isArray(args) ? args : [];
5677
+ const sub = normalizedArgs[0] || "status";
5678
+ const paths = ensureCareerGraphCli(projectPath);
5679
+ if (sub === "status" || sub === "list") {
5680
+ return formatCareerGraphStatusCli(paths);
5681
+ }
5682
+ if (sub === "open") {
5683
+ if (!opts.noOpen) openLocalPathCli(paths.inboxPath);
5684
+ return [`Opened Career Graph inbox: ${paths.inboxPath}`];
5685
+ }
5686
+ if (sub === "help" || sub === "--help" || sub === "-h") {
5687
+ return careerGraphUsageLinesCli();
5688
+ }
5689
+ if (sub === "add") {
5690
+ const flags = parseCloudFlags(normalizedArgs.slice(1));
5691
+ const source = flags._[0];
5692
+ const kind = inferOntologyKindCli(flags.kind || flags._[1], normalizedArgs.join(" "));
5693
+ const scope = inferOntologyScopeCli(flags.scope || flags._[2], normalizedArgs.join(" "), kind);
5694
+ return registerCareerGraphSourceCli(paths, source, kind, scope, cwd);
5695
+ }
5696
+ if (["ingest", "query", "verify", "trace"].includes(String(sub))) {
5697
+ return [
5698
+ "Career Graph index execution is provided by Agentlas OS / Hephaestus.",
5699
+ `Run: hephaestus career-graph ${normalizedArgs.join(" ")} --project ${JSON.stringify(paths.root)}`,
5700
+ ];
5701
+ }
5702
+ return runCareerGraphCli(parseOntologyNaturalArgsCli(normalizedArgs.join(" "), cwd), opts);
5703
+ }
5704
+
5705
+ function runCareerGraphNaturalCli(text, opts) {
5706
+ const cwd = path.resolve((opts && opts.cwd) || process.cwd());
5707
+ return runCareerGraphCli(parseOntologyNaturalArgsCli(text, cwd), { ...(opts || {}), cwd });
5708
+ }
5709
+
5710
+ async function cmdCareerGraph(args) {
5711
+ const sub = String((args && args[0]) || "status");
5712
+ if (["ingest", "query", "verify", "trace", "public-card"].includes(sub)) {
5713
+ const code = await parity().runHephaestusInteractive(["career-graph", ...args], { cwd: process.cwd() });
5714
+ if (code !== 0) process.exitCode = code;
5715
+ return;
5716
+ }
5717
+ try {
5718
+ for (const line of runCareerGraphCli(args, { cwd: process.cwd(), projectPath: process.cwd() })) out(line);
5719
+ } catch (e) {
5720
+ fail((e && e.message) || String(e));
5721
+ }
5722
+ }
5723
+
5468
5724
  function cmdOntology(args) {
5469
5725
  try {
5470
5726
  for (const line of runOntologyCli(args, { cwd: process.cwd(), projectPath: process.cwd() })) out(line);
@@ -6130,6 +6386,11 @@ function buildHelpers(db) {
6130
6386
  stormRun: (db_, goal, ctx) => parity().stormRun(db_, goal, ctx),
6131
6387
  swarmRun: (db_, goal, ctx) => parity().swarmRun(db_, goal, ctx),
6132
6388
  hepRun: (args, opts) => parity().runHephaestusInteractive(args, opts),
6389
+ cloudSearch: (db_, args) => parity().cloudSearch(db_, args),
6390
+ careerGraphCommand: (text, ctx) => runCareerGraphNaturalCli(text, {
6391
+ cwd: (ctx && ctx.cwd) || projectCwd(),
6392
+ projectPath: (ctx && ctx.cwd) || projectCwd(),
6393
+ }),
6133
6394
  ontologyCommand: (text, ctx) => runOntologyNaturalCli(text, {
6134
6395
  cwd: (ctx && ctx.cwd) || projectCwd(),
6135
6396
  projectPath: (ctx && ctx.cwd) || projectCwd(),
@@ -7300,56 +7561,65 @@ async function cmdOberon(args) {
7300
7561
  }
7301
7562
 
7302
7563
  function cmdHelp() {
7564
+ const H = (s) => `\n\x1b[1m${s}\x1b[0m`;
7565
+ const useColor = process.stdout.isTTY && process.env.NO_COLOR == null;
7566
+ const hdr = (s) => (useColor ? H(s) : "\n" + s);
7303
7567
  out(
7304
7568
  [
7305
- "agentlas — local agent terminal",
7569
+ "agentlas — the operating system for agents, in your terminal",
7306
7570
  "",
7307
- " agentlas open the terminal (status card, then pick an agent)",
7308
- " agentlas \"prompt\" auto-route to the best agent, then run once",
7309
- " agentlas <agent> jump straight into a chat with one agent",
7310
- " open <agent> same as above (explicit)",
7311
- " firm <firm> [cmd] delegate to a company's CEO (interactive if no cmd)",
7312
- " run [agent] [prompt] one-shot — omit agent to auto-route (reads stdin if no prompt)",
7313
- " import <path> import a local folder (agent or team)",
7314
- " cd <agent> print the agent folder — cd \"$(agentlas cd seo)\" && claude",
7315
- " list agents/companies + active runtime",
7316
- " env shared env key names",
7317
- " multimodal image/video/audio fallback providers",
7318
- " oberon <sub> AI film render from the terminal (scaffold|render|list) — see: oberon help",
7319
- " ontology project-local ontology status/list/add; inside REPL use /ontology",
7320
- " storm <goal> Stormbreaker force-robust pipeline (route → verify → execute) [--research]",
7321
- " swarm <goal> emergent agent swarm — parallel workers + blackboard + synthesizer [--parallel N]",
7322
- " build \"<request>\" build/repair/package an agent or team (Hephaestus hep-build)",
7323
- " route \"<request>\" routing preview — which agent/pipeline would take this",
7324
- " research <sub> Research Engine: status|gather|search|read|plan …",
7325
- " network <sub> local agent network: init|status|reindex|add-source …",
7326
- " journal <sub> Stormbreaker run journal: status|verify|repair|gate",
7327
- " call \"a,b\" \"<ctx>\" prepare named Hub/Cloud agents (hep-call)",
7328
- " hep <sub…> full Hephaestus passthrough (wizard·security·cards·ao·plugins…)",
7329
- " mcp installed MCP servers (shared with the app)",
7330
- " chats [n] recent app/terminal chats",
7331
- " automation <sub> list|add|on|off|remove|run <id>|runs|daemon — local runner included",
7332
- " login | logout | whoami",
7333
- " Agentlas Cloud sign-in (browser flow) — marketplace install/publish",
7334
- " usage local usage summary (runs, messages, automations)",
7335
- " telegram telegram binding status (pairing lives in the app)",
7336
- " cloud wizard <path> create/repair agentlas.json for Cloud MCP calls",
7337
- " cloud security scan <path>",
7338
- " risk-screen an agent folder before run/publish",
7339
- " cloud runtime bundle <path>",
7340
- " compile manifest-based runtime bundle",
7341
- " cloud field-test run local Cloud contract fixture test",
7342
- " cloud package <path> package + static security review for Agentlas Cloud",
7343
- " cloud publish <path> register after local review (submitter runtime only)",
7344
- " cloud install <slug> download/install a cloud marketplace agent",
7345
- " creds save ... save an issued key (project vault + project .env + project-scoped global env)",
7346
- " creds file ... copy a credential file into signing/credentials and set an env path",
7347
- " update check and install the latest Agentlas Desktop release",
7348
- " doctor check runtimes and data",
7349
- " setup re-run first-launch setup (language · runtime · permission)",
7350
- " version print the Agentlas CLI version",
7571
+ " agentlas open the terminal (wordmark, then type a task)",
7572
+ " agentlas \"<task>\" auto-route to the best agent and run once",
7351
7573
  "",
7352
- "Options: --runtime claude-code|codex|gemini · --permission read|write|full (default write) · --version",
7574
+ hdr("TALK & RUN"),
7575
+ " <agent> jump into a chat with one agent (e.g. agentlas seo)",
7576
+ " run [agent] [prompt] one-shot — omit agent to auto-route (reads stdin if no prompt)",
7577
+ " firm <firm> [cmd] delegate to a company's CEO (interactive if no cmd)",
7578
+ " chats [n] recent conversations · chat resume in REPL: /resume",
7579
+ "",
7580
+ hdr("AGENTS & HUB (Agentlas OS surface)"),
7581
+ " search \"<what you need>\" discover agents in the Hub + local (hep-search)",
7582
+ " install <slug> install an agent from the Hub (hep-cloud)",
7583
+ " build \"<request>\" build/repair/package an agent or team (hep-build)",
7584
+ " upload <path> package + publish an agent to the Hub (hep-upload)",
7585
+ " connect [<sub>] wire Telegram / platforms to an agent team (hep-connect)",
7586
+ " import <path> import a local agent/team folder",
7587
+ " list installed agents/companies + active runtime",
7588
+ "",
7589
+ hdr("EXECUTE"),
7590
+ " storm <goal> force-robust pipeline: route → verify → execute (Stormbreaker) [--research]",
7591
+ " swarm <goal> emergent agent swarm — parallel workers + synthesizer [--parallel N]",
7592
+ " network <request> decompose a request into an A2A task force (hep-network)",
7593
+ " call \"a,b\" \"<ctx>\" invoke named Hub/Cloud agents (hep-call)",
7594
+ " browser [<sub>] real browser execution hardpoint (hep-browser)",
7595
+ " route \"<request>\" routing preview — which agent/pipeline would take this",
7596
+ "",
7597
+ hdr("KNOWLEDGE & RESEARCH"),
7598
+ " research <sub> Research Engine: status|gather|search|read|plan",
7599
+ " career-graph <sub> source routing index: status|list|add",
7600
+ " ontology <sub> project knowledge: status|list|add (REPL: /ontology)",
7601
+ " journal <sub> Stormbreaker run journal: status|verify|repair|gate",
7602
+ "",
7603
+ hdr("ACCOUNT & OPS"),
7604
+ " login | logout | whoami Agentlas Cloud sign-in (browser flow)",
7605
+ " automation <sub> list|add|on|off|remove|run <id>|runs|daemon (local scheduler)",
7606
+ " creds <sub> · env credentials vault and shared env keys",
7607
+ " multimodal image/video/audio provider settings",
7608
+ " usage · telegram · mcp local usage · telegram bindings · MCP servers",
7609
+ " doctor check runtimes, data, credentials",
7610
+ " update check for a newer agentlas on npm",
7611
+ " setup re-run first-launch setup (language · runtime · permission)",
7612
+ " version print the Agentlas CLI version",
7613
+ "",
7614
+ hdr("ADVANCED"),
7615
+ " hep <sub…> full Hephaestus passthrough (wizard·security·cards·ao·plugins·meta-agent…)",
7616
+ " netadmin <sub> local network admin: init|status|reindex|bench|add-source",
7617
+ " cloud <sub> agent packaging: wizard|security|bundle|package|publish|field-test",
7618
+ " cd <agent> print the agent folder — cd \"$(agentlas cd seo)\" && claude",
7619
+ " oberon <sub> AI film render (scaffold|render|list)",
7620
+ "",
7621
+ "Options: --runtime claude-code|codex|gemini · --permission read|write|full (default write)",
7622
+ "In the REPL, type / for the command palette (/build /route /research /storm /swarm …).",
7353
7623
  ].join("\n"),
7354
7624
  );
7355
7625
  }
@@ -7429,6 +7699,10 @@ async function main() {
7429
7699
  return cmdOberon(rest.slice(1));
7430
7700
  case "ontology":
7431
7701
  return cmdOntology(rest.slice(1));
7702
+ case "career-graph":
7703
+ case "career_graph":
7704
+ case "graph":
7705
+ return cmdCareerGraph(rest.slice(1));
7432
7706
  case "cloud":
7433
7707
  return cmdCloud(db, rest.slice(1), runtimeOverride);
7434
7708
  case "creds":
@@ -7443,24 +7717,41 @@ async function main() {
7443
7717
  case "hep":
7444
7718
  case "hephaestus":
7445
7719
  return parity().cmdHep(db, rest.slice(1));
7720
+ // ── Agentlas OS 정식 표면 (hep-*) 1급 노출 ──
7446
7721
  case "build":
7447
- // hep-build "<요청>" — 요청 전체를 한 인자로 (엔진이 딥인터뷰/빌더로 라우팅)
7722
+ // hep-build "<요청>" — 에이전트/팀 빌드 (Meta-Agent Factory)
7448
7723
  return parity().cmdHep(db, rest.length > 1 ? ["hep-build", rest.slice(1).join(" ")] : ["hep-build"]);
7449
- case "route":
7724
+ case "search": // hep-search — 에이전트 디렉터리 발견 (Hub + 로컬)
7725
+ if (!rest[1]) return fail('usage: agentlas search "<찾는 일>" [--limit 10]');
7726
+ return parity().cloudSearch(db, rest.slice(1));
7727
+ case "install": // hep-cloud import — slug로 에이전트 설치
7728
+ if (!rest[1]) return fail('usage: agentlas install <slug> (먼저 agentlas search "할 일" 로 찾으세요)');
7729
+ return cmdCloudInstall(db, rest[1]);
7730
+ case "upload": // hep-upload — 컴파일된 에이전트를 Hub로 배포 (경로 필요)
7731
+ if (!rest[1]) return fail("usage: agentlas upload <에이전트 폴더 경로> (패키징 후 Hub 배포)");
7732
+ return cmdCloud(db, ["publish", ...rest.slice(1)], runtimeOverride);
7733
+ case "connect": // hep-connect — Telegram 등 플랫폼 연결
7734
+ return parity().cmdHep(db, ["hep-connect", ...rest.slice(1)]);
7735
+ case "browser": // hep-browser — 실제 브라우저 실행 하드포인트
7736
+ return parity().cmdHep(db, ["hep-browser", ...rest.slice(1)]);
7737
+ case "call": // hep-call — 지정 에이전트 호출/준비
7738
+ return parity().cmdHep(db, ["hep-call", ...rest.slice(1)]);
7739
+ case "network": // hep-network — A2A 태스크포스 분해/스케줄
7740
+ case "taskforce":
7741
+ return parity().cmdHep(db, ["hep-network", ...rest.slice(1)]);
7742
+ case "route": // 라우팅 미리보기 (실행 없음)
7450
7743
  return parity().cmdHep(
7451
7744
  db,
7452
7745
  rest.length > 1
7453
7746
  ? ["route", rest.slice(1).join(" "), "--project", runCwd(), "--runtime", "terminal"]
7454
7747
  : ["route"],
7455
7748
  );
7456
- case "research":
7749
+ case "research": // Research Engine
7457
7750
  return parity().cmdHep(db, ["research", ...rest.slice(1)]);
7458
- case "network":
7751
+ case "netadmin": // 로컬 에이전트 네트워크 관리 (init|status|reindex|bench|add-source)
7459
7752
  return parity().cmdHep(db, ["network", ...rest.slice(1)]);
7460
- case "journal":
7753
+ case "journal": // Stormbreaker 런 저널
7461
7754
  return parity().cmdHep(db, ["stormbreaker", "journal", ...rest.slice(1)]);
7462
- case "call":
7463
- return parity().cmdHep(db, ["hep-call", ...rest.slice(1)]);
7464
7755
  case "mcp":
7465
7756
  return parity().cmdMcp(db);
7466
7757
  case "chats":
@@ -1,5 +1,5 @@
1
1
  {
2
- "version": "1.5.34",
2
+ "version": "1.5.35",
3
3
  "emitterBlock": "## Memory (Agentlas curated memory)\n\nIf — and only if — this turn produced something durable (a decision, a stable fact,\na user preference, a risk, a reusable procedure), end your reply with a Memory Events\nblock. Emit nothing when nothing durable was learned.\n\nRules:\n- Never include secrets, credentials, API keys, raw logs, or full transcripts.\n- Real credential values may live only in local project .env/.env.local,\n ignored signing/ or credentials/ files, or a local keychain/vault. Memory\n Events may mention env names and local relative paths only.\n- For deploy, release, store, billing, auth, API, or cloud work, first read the\n project's .agentlas/local-credentials.map.json and the top\n \"Local Credential Index\" section of .agentlas/project-soul-memory.md\n before saying a credential is missing.\n- One event per durable item. Keep \"content\" to one or two sentences.\n- \"memory_kind\": fact | decision | preference | risk | procedure | hypothesis | evidence | deprecation | conflict\n- \"suggested_scope\": user_identity | team_memory | project (this folder) | agent_repo | session (temporary) | discard\n- \"agent_team\" is accepted only as a legacy alias for team_memory.\n- Add \"request_context\" when it improves future recall: user_intent, trigger_terms,\n cwd_at_request, target_project, target_path, cross_context, outcome.\n- Never put the raw user prompt or transcript in request_context.\n- Suggest a scope; the Memory Curator decides the final destination.\n\nFormat (omit entirely if empty):\n\n## Memory Events\n```json\n[\n {\n \"memory_kind\": \"decision\",\n \"content\": \"...\",\n \"suggested_scope\": \"project\",\n \"confidence\": \"high\",\n \"evidence_refs\": [],\n \"request_context\": {\n \"user_intent\": \"...\",\n \"trigger_terms\": [\"...\"],\n \"cwd_at_request\": null,\n \"target_project\": null,\n \"target_path\": null,\n \"cross_context\": false,\n \"outcome\": \"...\"\n }\n }\n]\n```",
4
4
  "eventsHeading": "## Memory Events",
5
5
  "memoryDir": ".agentlas",
@@ -14,6 +14,10 @@
14
14
  "skillRegistryFile": "skill-registry.json",
15
15
  "skillTrialsFile": "skill-trials.jsonl",
16
16
  "curatorDecisionsFile": "curator-decisions.jsonl",
17
+ "careerGraphConfigFile": "career-graph.json",
18
+ "careerGraphSourceManifestFile": "career-graph-sources.json",
19
+ "careerGraphInboxDir": "career-graph-inbox",
20
+ "careerGraphDbFile": "career-graph.sqlite",
17
21
  "superOntologyContractFile": "super-ontology-contract.json",
18
22
  "superOntologyOpenWorldCoverageFile": "super-ontology-open-world-coverage.json",
19
23
  "superOntologyConsensusCoordinationFile": "super-ontology-consensus-coordination.json",
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "agentlas",
3
- "version": "0.4.0",
3
+ "version": "0.5.2",
4
4
  "description": "Agentlas agent terminal — chat with your installed AI agents and teams from the terminal, Claude Code style. Standalone: no desktop app required.",
5
5
  "bin": {
6
6
  "agentlas": "bin/agentlas.cjs"
package/test/smoke.sh CHANGED
@@ -25,6 +25,20 @@ check "where" node "$BIN" --where
25
25
  check "version" node "$BIN" version
26
26
  check "list" node "$BIN" list
27
27
  check "doctor" node "$BIN" doctor
28
+ check "help" node "$BIN" help
29
+ check "usage" node "$BIN" usage
30
+ check "mcp" node "$BIN" mcp
31
+ check "chats" node "$BIN" chats
32
+
33
+ # Agentlas OS 표면: 무인자 호출은 usage를 내고 exit 1 (프롬프트 오라우팅 방지 확인)
34
+ guard() {
35
+ name="$1"; shift
36
+ if out="$("$@" 2>&1)"; then echo "FAIL $name (should exit non-zero)"; fail=$((fail + 1));
37
+ else echo "PASS $name"; pass=$((pass + 1)); fi
38
+ }
39
+ guard "guard-search" node "$BIN" search
40
+ guard "guard-install" node "$BIN" install
41
+ guard "guard-upload" node "$BIN" upload
28
42
 
29
43
  # 신선 환경 첫 실행 (표준: mktemp 사용, 검증 후 Trash로 이동)
30
44
  FRESH="$(mktemp -d "${TMPDIR:-/tmp}/agentlas-smoke-XXXXXX")"