agentlas 0.9.10 → 1.0.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/CHANGELOG.md +106 -0
- package/README.md +319 -341
- package/bin/agentlas.cjs +32 -9
- package/engine/agentlas-banner.cjs +24 -3
- package/engine/agentlas-composer.cjs +239 -31
- package/engine/agentlas-config.cjs +103 -7
- package/engine/agentlas-core-harness.cjs +48 -4
- package/engine/agentlas-evolution.cjs +3 -4
- package/engine/agentlas-i18n.cjs +88 -32
- package/engine/agentlas-input.cjs +234 -18
- package/engine/agentlas-memory-governance.cjs +3 -5
- package/engine/agentlas-memory-import.cjs +3 -3
- package/engine/agentlas-native-host.cjs +200 -9
- package/engine/agentlas-onboard.cjs +112 -20
- package/engine/agentlas-permissions.cjs +14 -7
- package/engine/agentlas-sqlite-policy.cjs +34 -0
- package/engine/agentlas-ui.cjs +93 -42
- package/engine/agentlas-workforce.cjs +472 -58
- package/engine/agentlas-workload-routing.cjs +8 -3
- package/engine/agentlas.cjs +140 -12367
- package/engine/agents/files.cjs +61 -0
- package/engine/agents/import-local.cjs +237 -0
- package/engine/agents/registry.cjs +158 -0
- package/engine/agents/router.cjs +565 -0
- package/engine/agents/routes.cjs +43 -0
- package/engine/architecture.data.json +2 -2
- package/engine/automation/daemon.cjs +335 -0
- package/engine/automation/schedule.cjs +181 -0
- package/engine/automation/store.cjs +209 -0
- package/engine/cloud/auth.cjs +279 -0
- package/engine/cloud/hub-client.cjs +239 -0
- package/engine/cloud-assets/cargo.cjs +49 -0
- package/engine/cloud-assets/cas.cjs +235 -0
- package/engine/cloud-assets/commands.cjs +273 -0
- package/engine/cloud-assets/package.cjs +936 -0
- package/engine/cloud-assets/restore.cjs +172 -0
- package/engine/cloud-assets/state.cjs +268 -0
- package/engine/commands/automation.cjs +195 -0
- package/engine/commands/billing.cjs +96 -0
- package/engine/commands/browser.cjs +20 -0
- package/engine/commands/build.cjs +33 -0
- package/engine/commands/call.cjs +24 -0
- package/engine/commands/career-graph.cjs +51 -0
- package/engine/commands/cd.cjs +22 -0
- package/engine/commands/chat.cjs +12 -0
- package/engine/commands/chats.cjs +28 -0
- package/engine/commands/cloud.cjs +17 -0
- package/engine/commands/connect.cjs +20 -0
- package/engine/commands/context.cjs +66 -0
- package/engine/commands/creds.cjs +203 -0
- package/engine/commands/doctor.cjs +68 -0
- package/engine/commands/env.cjs +33 -0
- package/engine/commands/evolve.cjs +23 -0
- package/engine/commands/experience.cjs +34 -0
- package/engine/commands/film.cjs +8 -0
- package/engine/commands/firm.cjs +115 -0
- package/engine/commands/help.cjs +65 -0
- package/engine/commands/hep.cjs +23 -0
- package/engine/commands/import.cjs +35 -0
- package/engine/commands/index.cjs +136 -0
- package/engine/commands/install.cjs +27 -0
- package/engine/commands/journal.cjs +31 -0
- package/engine/commands/legacy-network.cjs +29 -0
- package/engine/commands/list.cjs +49 -0
- package/engine/commands/login.cjs +68 -0
- package/engine/commands/logout.cjs +28 -0
- package/engine/commands/mcp.cjs +91 -0
- package/engine/commands/memory.cjs +21 -0
- package/engine/commands/multimodal.cjs +91 -0
- package/engine/commands/native.cjs +34 -0
- package/engine/commands/netadmin.cjs +31 -0
- package/engine/commands/oberon.cjs +70 -0
- package/engine/commands/ontology.cjs +24 -0
- package/engine/commands/open.cjs +48 -0
- package/engine/commands/plugin.cjs +101 -0
- package/engine/commands/project.cjs +47 -0
- package/engine/commands/research.cjs +36 -0
- package/engine/commands/route.cjs +37 -0
- package/engine/commands/run.cjs +149 -0
- package/engine/commands/search.cjs +55 -0
- package/engine/commands/setup.cjs +45 -0
- package/engine/commands/storm.cjs +75 -0
- package/engine/commands/swarm.cjs +75 -0
- package/engine/commands/telegram.cjs +32 -0
- package/engine/commands/uninstall.cjs +68 -0
- package/engine/commands/update.cjs +54 -0
- package/engine/commands/upload.cjs +18 -0
- package/engine/commands/usage.cjs +35 -0
- package/engine/commands/variant.cjs +25 -0
- package/engine/commands/version.cjs +9 -0
- package/engine/commands/whoami.cjs +38 -0
- package/engine/commands/workforce.cjs +103 -0
- package/engine/core/db.cjs +160 -0
- package/engine/core/paths.cjs +35 -0
- package/engine/experience/build.cjs +181 -0
- package/engine/experience/intents.cjs +492 -0
- package/engine/experience/runtime.cjs +242 -0
- package/engine/experience/variant.cjs +196 -0
- package/engine/firms/orchestrate.cjs +333 -0
- package/engine/hephaestus/runtime.cjs +697 -0
- package/engine/hub/install.cjs +872 -0
- package/engine/hub/plugins.cjs +213 -0
- package/engine/mcp/consent.cjs +289 -0
- package/engine/mcp/contract.cjs +202 -0
- package/engine/mcp/index.cjs +43 -0
- package/engine/mcp/inventory.cjs +322 -0
- package/engine/mcp/plan.cjs +286 -0
- package/engine/mcp/probe.cjs +151 -0
- package/engine/memory-cli/curate.cjs +163 -0
- package/engine/oberon/common.cjs +69 -0
- package/engine/oberon/manifest.cjs +164 -0
- package/engine/oberon/outputs.cjs +70 -0
- package/engine/oberon/render.cjs +164 -0
- package/engine/project/career-graph.cjs +249 -0
- package/engine/project/credentials.cjs +262 -0
- package/engine/project/env-file.cjs +46 -0
- package/engine/project/index.cjs +27 -0
- package/engine/project/memory-context.cjs +453 -0
- package/engine/project/ontology.cjs +467 -0
- package/engine/project/paths.cjs +39 -0
- package/engine/project/seed.cjs +200 -0
- package/engine/project/state.cjs +403 -0
- package/engine/project/super-ontology-seed.json +3288 -0
- package/engine/runtimes/detect.cjs +54 -0
- package/engine/runtimes/overrides.cjs +139 -0
- package/engine/runtimes/resolve.cjs +64 -0
- package/engine/sessions/apply-fences.cjs +188 -0
- package/engine/sessions/fences.cjs +362 -0
- package/engine/sessions/orchestrator.cjs +170 -0
- package/engine/sessions/prompt.cjs +212 -0
- package/engine/sessions/session.cjs +245 -0
- package/engine/sessions/sink.cjs +54 -0
- package/engine/sessions/store.cjs +79 -0
- package/engine/storm/deps.cjs +88 -0
- package/engine/storm/storm.cjs +218 -0
- package/engine/storm/swarm.cjs +422 -0
- package/engine/ui/palette.cjs +105 -0
- package/engine/ui/renderer.cjs +85 -0
- package/engine/ui/repl.cjs +444 -0
- package/engine/workforce/capture.cjs +701 -0
- package/engine/workforce/deps.cjs +472 -0
- package/package.json +2 -6
- package/engine/agentlas-experience-mcp.cjs +0 -1709
- package/engine/agentlas-parity.cjs +0 -1499
- package/engine/agentlas-repl.cjs +0 -1780
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* list — 설치 에이전트/회사 + 활성 런타임.
|
|
4
|
+
* 공유 DB(데스크탑과 동일)를 읽는다. visibility='background'/'private' 빌트인은
|
|
5
|
+
* 데스크탑과 동일하게 목록에서 숨긴다.
|
|
6
|
+
*/
|
|
7
|
+
const { activeRuntimeRow, listAvailableCliRuntimes } = require("../runtimes/detect.cjs");
|
|
8
|
+
const { listAgents } = require("../agents/registry.cjs");
|
|
9
|
+
|
|
10
|
+
function run(ctx) {
|
|
11
|
+
const db = ctx.db();
|
|
12
|
+
// 프라이버시 정책(웹 전용/백그라운드 제외)은 registry가 소유한다 — 직접 SQL 금지.
|
|
13
|
+
const agents = listAgents(db).map((a) => ({
|
|
14
|
+
slug: a.slug, name: a.name, name_en: a.nameEn, tagline: a.tagline, tagline_en: a.taglineEn, builtin: a.builtin,
|
|
15
|
+
}));
|
|
16
|
+
const firms = ctx.tableExists(db, "firms")
|
|
17
|
+
? db.prepare("SELECT id, name FROM firms ORDER BY name").all()
|
|
18
|
+
: [];
|
|
19
|
+
|
|
20
|
+
const en = ctx.lang === "en";
|
|
21
|
+
ctx.out(ctx.ui.bold(en ? "Installed agents" : "설치된 에이전트"));
|
|
22
|
+
if (!agents.length) {
|
|
23
|
+
ctx.out(" " + (en ? "(none yet — try: agentlas search \"what you need\")" : " (아직 없음 — agentlas search \"필요한 것\" 으로 찾아보세요)"));
|
|
24
|
+
}
|
|
25
|
+
for (const a of agents) {
|
|
26
|
+
const name = en && a.name_en ? a.name_en : a.name;
|
|
27
|
+
const tag = en && a.tagline_en ? a.tagline_en : a.tagline;
|
|
28
|
+
ctx.out(` ${ctx.ui.accent(a.slug.padEnd(24))} ${name}${tag ? ctx.ui.dim(" — " + tag) : ""}`);
|
|
29
|
+
}
|
|
30
|
+
if (firms.length) {
|
|
31
|
+
ctx.out("");
|
|
32
|
+
ctx.out(ctx.ui.bold(en ? "Companies" : "회사"));
|
|
33
|
+
for (const f of firms) ctx.out(` ${ctx.ui.accent(String(f.id).padEnd(24))} ${f.name}`);
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
const active = activeRuntimeRow(db);
|
|
37
|
+
const clis = listAvailableCliRuntimes();
|
|
38
|
+
ctx.out("");
|
|
39
|
+
ctx.out(ctx.ui.bold(en ? "Runtime" : "런타임"));
|
|
40
|
+
if (active) {
|
|
41
|
+
ctx.out(` active: ${active.kind}${active.model ? ` (${active.model})` : ""}${active.backend ? ` via ${active.backend}` : ""}`);
|
|
42
|
+
} else {
|
|
43
|
+
ctx.out(" active: " + (en ? "(not set)" : "(미설정)"));
|
|
44
|
+
}
|
|
45
|
+
ctx.out(` detected CLIs: ${clis.length ? clis.map((c) => c.kind).join(", ") : (en ? "none on PATH" : "PATH에 없음")}`);
|
|
46
|
+
return 0;
|
|
47
|
+
}
|
|
48
|
+
|
|
49
|
+
module.exports = { run };
|
|
@@ -0,0 +1,68 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* login — Agentlas Cloud 로그인 (데스크탑과 동일한 loopback 브라우저 플로우).
|
|
4
|
+
* 웹 /account?desktop=1&callback=<loopback+state> 이 유효 세션이면 callback의 state를
|
|
5
|
+
* 보존한 채 session을 추가해 302한다. Terminal은 state를 1회 검증한 뒤에만 저장한다.
|
|
6
|
+
* 보안 속성(1회용 state guard, GET-only, no-store)은 cloud/auth.cjs 가 소유한다.
|
|
7
|
+
*/
|
|
8
|
+
const auth = require("../cloud/auth.cjs");
|
|
9
|
+
|
|
10
|
+
async function run(ctx, args = []) {
|
|
11
|
+
const ko = ctx.lang === "ko";
|
|
12
|
+
const force = args.includes("--force");
|
|
13
|
+
if (!force) {
|
|
14
|
+
const existing = auth.cloudSessionCookie();
|
|
15
|
+
if (existing) {
|
|
16
|
+
try {
|
|
17
|
+
const j = await auth.fetchSessionMeta(existing);
|
|
18
|
+
if (j && j.authenticated) {
|
|
19
|
+
ctx.out(ko
|
|
20
|
+
? `이미 로그인되어 있습니다 (${(j.user && j.user.email) || "?"}). 다시 인증하려면: agentlas login --force`
|
|
21
|
+
: `Already signed in (${(j.user && j.user.email) || "?"}). Re-authenticate with agentlas login --force`);
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
} catch { /* 확인 실패 — 새로 로그인 진행 */ }
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
let value;
|
|
29
|
+
try {
|
|
30
|
+
value = await auth.waitForLoopbackSession({
|
|
31
|
+
onLoginUrl(url) {
|
|
32
|
+
ctx.out(ko
|
|
33
|
+
? "브라우저에서 Agentlas에 로그인하세요 (자동으로 열립니다):"
|
|
34
|
+
: "Sign in to Agentlas in the browser (opening automatically):");
|
|
35
|
+
ctx.out(" " + ctx.ui.accent(url));
|
|
36
|
+
auth.openInBrowser(url);
|
|
37
|
+
},
|
|
38
|
+
});
|
|
39
|
+
} catch (e) {
|
|
40
|
+
ctx.err(String((e && e.message) || e));
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
|
|
44
|
+
const p = auth.saveCliSession(value);
|
|
45
|
+
ctx.out((ko ? "세션 저장됨: " : "Session saved: ") + ctx.ui.dim(p));
|
|
46
|
+
|
|
47
|
+
// 저장 직후 세션 유효성 확인 (whoami 와 동일한 출력 — 명령끼리 참조 금지 규칙 때문에 인라인)
|
|
48
|
+
try {
|
|
49
|
+
const j = await auth.fetchSessionMeta(auth.cloudSessionCookie());
|
|
50
|
+
if (j && j.authenticated) {
|
|
51
|
+
const email = (j.user && j.user.email) || "?";
|
|
52
|
+
const ws = j.workspace || {};
|
|
53
|
+
ctx.out(ko
|
|
54
|
+
? `로그인됨: ${ctx.ui.bold(email)} · 작업 공간: ${ws.name || "?"} (${ws.plan || "free"})`
|
|
55
|
+
: `Signed in: ${ctx.ui.bold(email)} · workspace: ${ws.name || "?"} (${ws.plan || "free"})`);
|
|
56
|
+
return 0;
|
|
57
|
+
}
|
|
58
|
+
ctx.out(ko
|
|
59
|
+
? "세션이 만료되었거나 유효하지 않습니다. `agentlas login`으로 다시 로그인하세요."
|
|
60
|
+
: "The session is expired or invalid. Sign in again with agentlas login.");
|
|
61
|
+
return 1;
|
|
62
|
+
} catch (e) {
|
|
63
|
+
ctx.err((ko ? "세션 확인 실패: " : "Session check failed: ") + String((e && e.message) || e));
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
module.exports = { run };
|
|
@@ -0,0 +1,28 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* logout — CLI 세션 파일 삭제. 네트워크 호출 없음(서버 세션은 웹에서 관리).
|
|
4
|
+
* AGENTLAS_SESSION env 는 파일과 별개 경로라 삭제 후에도 로그인 상태로 보일 수 있다 — 경고만 한다.
|
|
5
|
+
*/
|
|
6
|
+
const auth = require("../cloud/auth.cjs");
|
|
7
|
+
|
|
8
|
+
function run(ctx) {
|
|
9
|
+
const ko = ctx.lang === "ko";
|
|
10
|
+
let result;
|
|
11
|
+
try {
|
|
12
|
+
result = auth.deleteCliSession();
|
|
13
|
+
} catch (e) {
|
|
14
|
+
ctx.err((ko ? "세션 파일을 삭제하지 못했습니다: " : "Could not delete the session file: ") + String((e && e.message) || e));
|
|
15
|
+
return 1;
|
|
16
|
+
}
|
|
17
|
+
ctx.out(result.existed
|
|
18
|
+
? (ko ? "로그아웃되었습니다 (CLI 세션 삭제)." : "Signed out (CLI session deleted).")
|
|
19
|
+
: (ko ? "저장된 CLI 세션이 없습니다." : "No saved CLI session."));
|
|
20
|
+
if (process.env.AGENTLAS_SESSION) {
|
|
21
|
+
ctx.out(ko
|
|
22
|
+
? "경고: AGENTLAS_SESSION 이 아직 설정되어 있어 CLI가 로그인 상태로 보일 수 있습니다."
|
|
23
|
+
: "Warning: AGENTLAS_SESSION is still set, so the CLI may appear signed in.");
|
|
24
|
+
}
|
|
25
|
+
return 0;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
module.exports = { run };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* mcp — 등록된 MCP 서버 목록 + stdio 연결 프리플라이트.
|
|
4
|
+
*
|
|
5
|
+
* agentlas mcp 공유 DB의 레지스트리 목록 (기존 동작 유지)
|
|
6
|
+
* agentlas mcp probe <id> 신뢰 레지스트리 행을 격리 자식 env로 띄워
|
|
7
|
+
* initialize→tools/list 핸드셰이크만 확인
|
|
8
|
+
*
|
|
9
|
+
* probe는 engine/mcp 모듈만 쓴다: 서버 정의는 항상 신뢰 레지스트리 행에서
|
|
10
|
+
* 물질화하고(materialize), 자식 env는 agentlas.mcp-child-launch.v1 경계를 따른다.
|
|
11
|
+
* "연결됨"은 툴 호출 성공을 의미하지 않는다 — 프리플라이트일 뿐이다.
|
|
12
|
+
*/
|
|
13
|
+
const { userDataDir } = require("../core/paths.cjs");
|
|
14
|
+
const { materializeTrustedSystemMcpServer } = require("../mcp/inventory.cjs");
|
|
15
|
+
const { probeSystemMcpServerConnection } = require("../mcp/probe.cjs");
|
|
16
|
+
|
|
17
|
+
function list(ctx) {
|
|
18
|
+
const en = ctx.lang === "en";
|
|
19
|
+
const db = ctx.db();
|
|
20
|
+
if (!ctx.tableExists(db, "mcp_servers")) {
|
|
21
|
+
ctx.out(en ? "No MCP servers registered." : "등록된 MCP 서버가 없습니다.");
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
const rows = db.prepare("SELECT id, name FROM mcp_servers ORDER BY name").all();
|
|
25
|
+
if (!rows.length) {
|
|
26
|
+
ctx.out(en ? "No MCP servers registered." : "등록된 MCP 서버가 없습니다.");
|
|
27
|
+
return 0;
|
|
28
|
+
}
|
|
29
|
+
ctx.out(ctx.ui.bold(en ? "MCP servers" : "MCP 서버"));
|
|
30
|
+
for (const r of rows) ctx.out(` ${ctx.ui.accent(String(r.id).padEnd(28))} ${r.name || ""}`);
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
async function probe(ctx, args) {
|
|
35
|
+
const en = ctx.lang === "en";
|
|
36
|
+
const ref = String(args[0] || "").trim();
|
|
37
|
+
if (!ref) {
|
|
38
|
+
ctx.err(en ? "Usage: agentlas mcp probe <server-id|catalog-id>" : "사용법: agentlas mcp probe <server-id|catalog-id>");
|
|
39
|
+
return 1;
|
|
40
|
+
}
|
|
41
|
+
const db = ctx.db();
|
|
42
|
+
if (!ctx.tableExists(db, "mcp_servers")) {
|
|
43
|
+
ctx.err(en ? "No MCP servers registered." : "등록된 MCP 서버가 없습니다.");
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
// catalog_id는 데스크탑 스키마 열 — 오래된 DB에는 없을 수 있어 방어적으로 조회.
|
|
47
|
+
const byCatalog = ctx.columnExists(db, "mcp_servers", "catalog_id");
|
|
48
|
+
const row = byCatalog
|
|
49
|
+
? db.prepare("SELECT id, catalog_id, name, name_en, transport, command, args_json, env_keys_json, enabled FROM mcp_servers WHERE id=? OR catalog_id=? LIMIT 1").get(ref, ref)
|
|
50
|
+
: db.prepare("SELECT id, NULL AS catalog_id, name, name_en, transport, command, args_json, env_keys_json, enabled FROM mcp_servers WHERE id=? LIMIT 1").get(ref);
|
|
51
|
+
if (!row) {
|
|
52
|
+
ctx.err(en ? `MCP server not found: ${ref}` : `MCP 서버를 찾을 수 없습니다: ${ref}`);
|
|
53
|
+
return 1;
|
|
54
|
+
}
|
|
55
|
+
const server = materializeTrustedSystemMcpServer(row, { userDataDir: userDataDir() });
|
|
56
|
+
if (!server) {
|
|
57
|
+
// 이유를 지어내지 않는다 — materialize는 비활성/비-stdio/안전하지 않은
|
|
58
|
+
// 정의를 하나의 fail-closed로 접기 때문에 관찰 가능한 사실만 알린다.
|
|
59
|
+
ctx.err(en
|
|
60
|
+
? `'${ref}' is not probe-eligible (disabled, non-stdio transport, or an unsafe runtime definition).`
|
|
61
|
+
: `'${ref}' 은(는) probe 대상이 아닙니다 (비활성, stdio가 아닌 transport, 또는 안전하지 않은 실행 정의).`);
|
|
62
|
+
return 1;
|
|
63
|
+
}
|
|
64
|
+
ctx.out(ctx.ui.dim(en
|
|
65
|
+
? `Probing ${server.catalog_id} (isolated child env, handshake only)…`
|
|
66
|
+
: `${server.catalog_id} 연결 확인 중 (격리 자식 env, 핸드셰이크만)…`));
|
|
67
|
+
const result = await probeSystemMcpServerConnection(server, { userDataDir: userDataDir(), cwd: process.cwd() });
|
|
68
|
+
if (result.connected) {
|
|
69
|
+
const toolCount = Array.isArray(result.tools) ? result.tools.length : 0;
|
|
70
|
+
ctx.out(`${ctx.ui.green("connected")} ${ctx.ui.bold(server.catalog_id)} · ${en ? `${toolCount} tool(s) listed` : `툴 ${toolCount}개 확인`}`);
|
|
71
|
+
ctx.out(ctx.ui.dim(en
|
|
72
|
+
? "Preflight only: connection readiness does not imply tool-call success."
|
|
73
|
+
: "프리플라이트일 뿐입니다: 연결 준비됨 ≠ 툴 호출 성공."));
|
|
74
|
+
return 0;
|
|
75
|
+
}
|
|
76
|
+
ctx.err(`${ctx.ui.red("failed")} ${ctx.ui.bold(server.catalog_id)} · ${result.reason}`);
|
|
77
|
+
return 1;
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
function run(ctx, args = []) {
|
|
81
|
+
const [sub, ...rest] = args;
|
|
82
|
+
if (!sub) return list(ctx);
|
|
83
|
+
if (sub === "probe") return probe(ctx, rest);
|
|
84
|
+
const en = ctx.lang === "en";
|
|
85
|
+
ctx.err(en
|
|
86
|
+
? `unknown mcp subcommand: ${sub} (available: probe)`
|
|
87
|
+
: `알 수 없는 mcp 하위 명령: ${sub} (사용 가능: probe)`);
|
|
88
|
+
return 1;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { run };
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* memory — 레거시 마크다운 메모리 → 공유 agentlas.sqlite 이관 (Phase 1b).
|
|
4
|
+
* agentlas memory import <folder-or-file> --agent <agentId> [--apply]
|
|
5
|
+
* 기본은 dry-run 프리뷰, --apply 일 때만 쓴다. 로직은 복원된 계약 모듈
|
|
6
|
+
* engine/agentlas-memory-import.cjs 가 소유한다(여기서는 주입만).
|
|
7
|
+
* v1의 fail(process.exit)은 v2에서 throw 로 바꿔 엔진이 stderr+exit 1 처리한다.
|
|
8
|
+
*/
|
|
9
|
+
const { cmdMemory } = require("../agentlas-memory-import.cjs");
|
|
10
|
+
|
|
11
|
+
function run(ctx, args) {
|
|
12
|
+
cmdMemory({
|
|
13
|
+
db: ctx.db(),
|
|
14
|
+
args,
|
|
15
|
+
out: ctx.out,
|
|
16
|
+
fail: (msg) => { throw new Error(String(msg ?? "")); },
|
|
17
|
+
});
|
|
18
|
+
return 0;
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
module.exports = { run };
|
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* multimodal — 이미지/영상/오디오 공급자 상태·선택 (공유 DB meta에 저장, 데스크탑과 공유).
|
|
4
|
+
* agentlas multimodal 현재 선택 + 카탈로그
|
|
5
|
+
* agentlas multimodal set <modality> <provider-id>
|
|
6
|
+
* 키 존재 여부는 credentials.env(standalone)만 관측한다 — 값은 출력하지 않는다.
|
|
7
|
+
*/
|
|
8
|
+
const { sharedEnvKeys } = require("./env.cjs");
|
|
9
|
+
|
|
10
|
+
const MULTIMODAL_META_KEY = "multimodal_settings_v1";
|
|
11
|
+
|
|
12
|
+
const PROVIDERS = [
|
|
13
|
+
{ id: "codex-cli-image", modality: "image", label: "Codex CLI image", labelKo: "Codex CLI 이미지", envKeys: [], billing: "subscription" },
|
|
14
|
+
{ id: "grok-cli-image", modality: "image", label: "Grok CLI image (Imagine)", labelKo: "Grok CLI 이미지 (Imagine)", envKeys: [], billing: "subscription" },
|
|
15
|
+
{ id: "grok-cli-video", modality: "video", label: "Grok CLI video (Imagine)", labelKo: "Grok CLI 영상 (Imagine)", envKeys: [], billing: "subscription" },
|
|
16
|
+
{ id: "openai-image", modality: "image", label: "OpenAI Images API", labelKo: "OpenAI 이미지 API", envKeys: ["OPENAI_API_KEY"], billing: "paid-api" },
|
|
17
|
+
{ id: "google-image", modality: "image", label: "Google Gemini Image", labelKo: "Google Gemini 이미지", envKeys: ["GOOGLE_API_KEY"], billing: "paid-api" },
|
|
18
|
+
{ id: "runway-video", modality: "video", label: "Runway API", labelKo: "Runway API", envKeys: ["RUNWAY_API_KEY"], billing: "paid-api" },
|
|
19
|
+
{ id: "google-veo", modality: "video", label: "Google Veo", labelKo: "Google Veo", envKeys: ["GOOGLE_CLOUD_PROJECT", "GOOGLE_APPLICATION_CREDENTIALS"], billing: "provider-billing" },
|
|
20
|
+
{ id: "openai-sora", modality: "video", label: "OpenAI Sora API", labelKo: "OpenAI Sora API", envKeys: ["OPENAI_API_KEY"], billing: "paid-api" },
|
|
21
|
+
{ id: "openai-audio", modality: "audio", label: "OpenAI Audio", labelKo: "OpenAI 오디오", envKeys: ["OPENAI_API_KEY"], billing: "paid-api" },
|
|
22
|
+
{ id: "elevenlabs-audio", modality: "audio", label: "ElevenLabs", labelKo: "ElevenLabs", envKeys: ["ELEVENLABS_API_KEY"], billing: "paid-api" },
|
|
23
|
+
{ id: "deepgram-audio", modality: "audio", label: "Deepgram", labelKo: "Deepgram", envKeys: ["DEEPGRAM_API_KEY"], billing: "paid-api" },
|
|
24
|
+
{ id: "replicate-video", modality: "video", label: "Replicate", labelKo: "Replicate", envKeys: ["REPLICATE_API_TOKEN"], billing: "paid-api" },
|
|
25
|
+
];
|
|
26
|
+
const DEFAULTS = { imageProvider: "codex-cli-image", videoProvider: "runway-video", audioProvider: "openai-audio" };
|
|
27
|
+
|
|
28
|
+
function normalizeSettings(input) {
|
|
29
|
+
return { ...DEFAULTS, ...(input || {}) };
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
function getSettings(db) {
|
|
33
|
+
let raw = null;
|
|
34
|
+
try {
|
|
35
|
+
const row = db.prepare("SELECT value FROM meta WHERE key=?").get(MULTIMODAL_META_KEY);
|
|
36
|
+
raw = row && row.value;
|
|
37
|
+
} catch { raw = null; }
|
|
38
|
+
try {
|
|
39
|
+
return normalizeSettings(raw ? JSON.parse(raw) : null);
|
|
40
|
+
} catch {
|
|
41
|
+
return normalizeSettings(null);
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
|
|
45
|
+
function saveSettings(db, patch) {
|
|
46
|
+
const next = normalizeSettings({ ...getSettings(db), ...patch, updatedAt: new Date().toISOString() });
|
|
47
|
+
db.prepare("INSERT INTO meta(key,value) VALUES(?,?) ON CONFLICT(key) DO UPDATE SET value=excluded.value")
|
|
48
|
+
.run(MULTIMODAL_META_KEY, JSON.stringify(next));
|
|
49
|
+
return next;
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
function run(ctx, args) {
|
|
53
|
+
const ko = ctx.lang === "ko";
|
|
54
|
+
const db = ctx.db();
|
|
55
|
+
|
|
56
|
+
if (args[0] === "set") {
|
|
57
|
+
const modality = String(args[1] || "");
|
|
58
|
+
const providerId = String(args[2] || "");
|
|
59
|
+
const provider = PROVIDERS.find((p) => p.id === providerId);
|
|
60
|
+
if (!["image", "video", "audio"].includes(modality) || !provider || provider.modality !== modality) {
|
|
61
|
+
ctx.err(ko
|
|
62
|
+
? "사용법: agentlas multimodal set <image|video|audio> <provider-id> (id는 agentlas multimodal 목록 참고)"
|
|
63
|
+
: "Usage: agentlas multimodal set <image|video|audio> <provider-id> (ids: agentlas multimodal)");
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
const key = modality === "image" ? "imageProvider" : modality === "video" ? "videoProvider" : "audioProvider";
|
|
67
|
+
saveSettings(db, { [key]: providerId });
|
|
68
|
+
ctx.out(`${ctx.ui.green("✓")} ${modality} → ${providerId}`);
|
|
69
|
+
return 0;
|
|
70
|
+
}
|
|
71
|
+
|
|
72
|
+
const settings = getSettings(db);
|
|
73
|
+
const keys = new Set(sharedEnvKeys());
|
|
74
|
+
const selected = { image: settings.imageProvider, video: settings.videoProvider, audio: settings.audioProvider };
|
|
75
|
+
for (const modality of ["image", "video", "audio"]) {
|
|
76
|
+
ctx.out(ctx.ui.bold(modality));
|
|
77
|
+
for (const p of PROVIDERS.filter((x) => x.modality === modality)) {
|
|
78
|
+
const isSel = p.id === selected[modality];
|
|
79
|
+
const needsKeys = (p.envKeys || []).filter((k) => !keys.has(k) && !process.env[k]);
|
|
80
|
+
const keyNote = p.envKeys.length
|
|
81
|
+
? (needsKeys.length ? ctx.ui.dim(` (${ko ? "키 필요" : "needs"}: ${needsKeys.join(", ")})`) : ctx.ui.dim(` (${ko ? "키 있음" : "keys ok"})`))
|
|
82
|
+
: "";
|
|
83
|
+
ctx.out(` ${isSel ? ctx.ui.accent("▸") : " "} ${p.id.padEnd(20)} ${ko ? p.labelKo : p.label}${keyNote}`);
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
ctx.out("");
|
|
87
|
+
ctx.out(ctx.ui.dim(ko ? "변경: agentlas multimodal set <image|video|audio> <provider-id>" : "Change: agentlas multimodal set <image|video|audio> <provider-id>"));
|
|
88
|
+
return 0;
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
module.exports = { run, getSettings, saveSettings, PROVIDERS, DEFAULTS, MULTIMODAL_META_KEY };
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/* native — 네이티브 CLI 문맥 파일 명시 생성: agentlas native prepare <agent> */
|
|
3
|
+
const { findAgent } = require("../agents/registry.cjs");
|
|
4
|
+
const { agentFolder, ensureNativeFiles } = require("../agents/files.cjs");
|
|
5
|
+
|
|
6
|
+
function run(ctx, args) {
|
|
7
|
+
const ko = ctx.lang === "ko";
|
|
8
|
+
const sub = String(args[0] || "help");
|
|
9
|
+
const query = args[1];
|
|
10
|
+
if (sub === "help" || sub === "--help" || sub === "-h") {
|
|
11
|
+
ctx.out(ko
|
|
12
|
+
? "사용법: agentlas native prepare <에이전트> · 네이티브 CLI 문맥 파일을 명시적으로 생성"
|
|
13
|
+
: "usage: agentlas native prepare <agent> · explicitly create native CLI context files");
|
|
14
|
+
return 0;
|
|
15
|
+
}
|
|
16
|
+
if (sub !== "prepare" || !query) {
|
|
17
|
+
ctx.err(ko ? "사용법: agentlas native prepare <에이전트>" : "usage: agentlas native prepare <agent>");
|
|
18
|
+
return 1;
|
|
19
|
+
}
|
|
20
|
+
const agent = findAgent(ctx.db(), query);
|
|
21
|
+
if (!agent) {
|
|
22
|
+
ctx.err(ko ? `에이전트를 찾지 못했습니다: ${query}` : `Agent not found: ${query}`);
|
|
23
|
+
return 1;
|
|
24
|
+
}
|
|
25
|
+
const folder = agentFolder(agent);
|
|
26
|
+
const created = ensureNativeFiles(agent, folder);
|
|
27
|
+
ctx.out(`${ko ? "네이티브 CLI 문맥" : "Native CLI context"}: ${folder}`);
|
|
28
|
+
ctx.out(created.length
|
|
29
|
+
? `${ko ? "생성됨" : "Created"}: ${created.join(", ")}`
|
|
30
|
+
: (ko ? "변경 없음: 필요한 파일이 이미 있습니다." : "No changes: the context files already exist."));
|
|
31
|
+
return 0;
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
module.exports = { run };
|
|
@@ -0,0 +1,31 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* netadmin — 로컬 에이전트 네트워크 관리 (init|status|reindex|bench|add-source).
|
|
4
|
+
*
|
|
5
|
+
* v1 디스패처 매핑 그대로:
|
|
6
|
+
* `agentlas netadmin <sub…>` → cmdHep(["network", ...rest])
|
|
7
|
+
* (Hephaestus의 `network` 서브커맨드가 로컬 네트워크 admin이다.
|
|
8
|
+
* 터미널의 `agentlas network`는 Workforce Ontology 경로로 따로 살고,
|
|
9
|
+
* netadmin이 그 이름 충돌을 피해 admin 표면만 노출한다.)
|
|
10
|
+
*
|
|
11
|
+
* v1 가드 그대로:
|
|
12
|
+
* - help 토큰 → usage, exit 0
|
|
13
|
+
* - 첫 인자가 init|status|reindex|bench|add-source 밖 → usage 실패 exit 1
|
|
14
|
+
*/
|
|
15
|
+
const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
|
|
16
|
+
|
|
17
|
+
const NETADMIN_SUBCOMMANDS = ["init", "status", "reindex", "bench", "add-source"];
|
|
18
|
+
|
|
19
|
+
async function run(ctx, args) {
|
|
20
|
+
if (args.some(isHelpToken)) {
|
|
21
|
+
ctx.out(usageFor("netadmin", ctx.lang));
|
|
22
|
+
return 0;
|
|
23
|
+
}
|
|
24
|
+
if (!NETADMIN_SUBCOMMANDS.includes(args[0])) {
|
|
25
|
+
ctx.err("✖ " + usageFor("netadmin", ctx.lang));
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
return create(ctx).cmdHep(["network", ...args]);
|
|
29
|
+
}
|
|
30
|
+
|
|
31
|
+
module.exports = { run };
|
|
@@ -0,0 +1,70 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* oberon — Oberon 필름 스튜디오 (터미널 헤드리스 렌더).
|
|
4
|
+
* v1 모놀리스 §12213-12580 (cmdOberon + oberonHelp) 포팅 — 얇은 명령 래퍼.
|
|
5
|
+
* GUI 없이 매니페스트 하나로 영상 렌더를 돌린다. 손으로 쓰던 JSON+env 노가다 대신:
|
|
6
|
+
* agentlas oberon scaffold my.json → 편집 가능한 렌더 매니페스트 생성
|
|
7
|
+
* agentlas oberon render my.json → 헤드리스 렌더 스폰 + 진행률 스트리밍
|
|
8
|
+
* agentlas oberon list → 최근 렌더 산출물
|
|
9
|
+
*
|
|
10
|
+
* v2 규칙: 기능 로직은 engine/oberon/*에만 있다. 이 파일은 서브커맨드 디스패치와
|
|
11
|
+
* OberonFail → "✖ msg" + exit 1 변환(v1 fail 계약)만 담당한다.
|
|
12
|
+
*/
|
|
13
|
+
const { scaffold } = require("../oberon/manifest.cjs");
|
|
14
|
+
const { render } = require("../oberon/render.cjs");
|
|
15
|
+
const { list, open } = require("../oberon/outputs.cjs");
|
|
16
|
+
|
|
17
|
+
function help(io) {
|
|
18
|
+
io.out(
|
|
19
|
+
[
|
|
20
|
+
"agentlas oberon — AI film rendering from the terminal",
|
|
21
|
+
"",
|
|
22
|
+
" oberon scaffold [out.json] [--title T] [--aspect 16:9] [--shots N] [--titles] [--overwrite]",
|
|
23
|
+
" create an editable manifest; existing files require explicit --overwrite",
|
|
24
|
+
" oberon render <manifest.json> [--delivery DIR] [--max-shots N] [--open] [--dry-run]",
|
|
25
|
+
" spawn full Electron render + stream progress (GEMINI_API_KEY vault required)",
|
|
26
|
+
" oberon list 최근 렌더 산출물",
|
|
27
|
+
" oberon open [path] 산출물 폴더 열기",
|
|
28
|
+
"",
|
|
29
|
+
"Fill prompts directly, or ask an agent: agentlas run oberon-film-studio \"30-second fragrance ad\"",
|
|
30
|
+
].join("\n"),
|
|
31
|
+
);
|
|
32
|
+
return 0;
|
|
33
|
+
}
|
|
34
|
+
|
|
35
|
+
// deps는 테스트/장래 배선용 seam (render의 진입점·spawn 주입) — 일반 경로에서는 미사용.
|
|
36
|
+
async function run(ctx, args, deps) {
|
|
37
|
+
const sub = args[0] || "help";
|
|
38
|
+
const rest = args.slice(1);
|
|
39
|
+
const io = { out: ctx.out, err: ctx.err };
|
|
40
|
+
try {
|
|
41
|
+
switch (sub) {
|
|
42
|
+
case "scaffold":
|
|
43
|
+
case "new":
|
|
44
|
+
return scaffold(io, rest);
|
|
45
|
+
case "render":
|
|
46
|
+
return await render(io, rest, deps || {});
|
|
47
|
+
case "list":
|
|
48
|
+
case "ls":
|
|
49
|
+
return list(io);
|
|
50
|
+
case "open":
|
|
51
|
+
return open(io, rest);
|
|
52
|
+
case "help":
|
|
53
|
+
case "--help":
|
|
54
|
+
case "-h":
|
|
55
|
+
return help(io);
|
|
56
|
+
default:
|
|
57
|
+
ctx.err(`✖ Unknown oberon subcommand: ${sub} (scaffold|render|list|open|help)`);
|
|
58
|
+
return 1;
|
|
59
|
+
}
|
|
60
|
+
} catch (e) {
|
|
61
|
+
if (e && e.oberonFail) {
|
|
62
|
+
// v1 fail() 계약: stderr에 "✖ " 접두 + exit 1. v2는 exit 대신 코드 반환.
|
|
63
|
+
ctx.err(`✖ ${e.message}`);
|
|
64
|
+
return 1;
|
|
65
|
+
}
|
|
66
|
+
throw e;
|
|
67
|
+
}
|
|
68
|
+
}
|
|
69
|
+
|
|
70
|
+
module.exports = { run };
|
|
@@ -0,0 +1,24 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* ontology — 프로젝트 온톨로지 상태/소스 등록 (v1 cmdOntology 포팅).
|
|
4
|
+
* status/list는 비변형, add/open은 초기화된 프로젝트에서만 동작한다.
|
|
5
|
+
*/
|
|
6
|
+
const { runOntologyCli } = require("../project/ontology.cjs");
|
|
7
|
+
|
|
8
|
+
function run(ctx, args) {
|
|
9
|
+
try {
|
|
10
|
+
const lines = runOntologyCli(args, {
|
|
11
|
+
cwd: process.cwd(),
|
|
12
|
+
projectPath: process.cwd(),
|
|
13
|
+
lang: ctx.lang,
|
|
14
|
+
notify: (line) => ctx.out(line),
|
|
15
|
+
});
|
|
16
|
+
for (const line of lines) ctx.out(line);
|
|
17
|
+
return 0;
|
|
18
|
+
} catch (e) {
|
|
19
|
+
ctx.err(String((e && e.message) || e));
|
|
20
|
+
return 1;
|
|
21
|
+
}
|
|
22
|
+
}
|
|
23
|
+
|
|
24
|
+
module.exports = { run };
|
|
@@ -0,0 +1,48 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* open — 기존 대화 재개: agentlas open <chat-id-prefix>
|
|
4
|
+
* 해당 챗의 에이전트로 REPL을 열고 같은 chatId에 이어 쓴다
|
|
5
|
+
* (CLI resume 세션도 chat_runtime_sessions에서 fingerprint 일치 시 복원).
|
|
6
|
+
*/
|
|
7
|
+
const { rowToAgent, isPrivateWebOnlyAgentRow } = require("../agents/registry.cjs");
|
|
8
|
+
|
|
9
|
+
function run(ctx, args) {
|
|
10
|
+
const ko = ctx.lang === "ko";
|
|
11
|
+
const prefix = String(args[0] || "").trim();
|
|
12
|
+
if (!prefix) {
|
|
13
|
+
ctx.err(ko ? "사용법: agentlas open <chat-id 앞부분> (agentlas chats 로 목록)" : "Usage: agentlas open <chat-id-prefix> (list with: agentlas chats)");
|
|
14
|
+
return 1;
|
|
15
|
+
}
|
|
16
|
+
const db = ctx.db();
|
|
17
|
+
// 사용자 표면은 kind='user' 챗만 연다 — 데스크탑 사용자 챗 목록과 동일 필터
|
|
18
|
+
// (electron/store/chats.ts:86; 레거시 NULL=user 취급은 electron/store/db.ts:717).
|
|
19
|
+
// 숨김 division 세션(자동화/본부 인프라)은 접두사를 알아도 재개 대상이 아니다.
|
|
20
|
+
const rows = db.prepare(
|
|
21
|
+
"SELECT c.id, c.title, c.agent_id FROM chats c WHERE c.id LIKE ? AND c.archived_at IS NULL AND (c.kind IS NULL OR c.kind = 'user') ORDER BY c.updated_at DESC LIMIT 2",
|
|
22
|
+
).all(prefix + "%");
|
|
23
|
+
if (!rows.length) {
|
|
24
|
+
ctx.err((ko ? "대화를 찾을 수 없음: " : "chat not found: ") + prefix);
|
|
25
|
+
return 1;
|
|
26
|
+
}
|
|
27
|
+
if (rows.length > 1) {
|
|
28
|
+
ctx.err(ko ? `모호합니다 — 더 긴 접두사를 쓰세요 (${rows.map((r) => r.id.slice(0, 8)).join(", ")})` : `Ambiguous — use a longer prefix (${rows.map((r) => r.id.slice(0, 8)).join(", ")})`);
|
|
29
|
+
return 1;
|
|
30
|
+
}
|
|
31
|
+
const chat = rows[0];
|
|
32
|
+
const agentRow = db.prepare("SELECT * FROM installed_agents WHERE id=?").get(chat.agent_id);
|
|
33
|
+
if (!agentRow) {
|
|
34
|
+
ctx.err(ko ? "이 대화의 에이전트가 더 이상 없습니다." : "The agent for this chat no longer exists.");
|
|
35
|
+
return 1;
|
|
36
|
+
}
|
|
37
|
+
// registry 정책과 동일: 웹 전용(private) 에이전트는 챗 id를 알아도 터미널에서
|
|
38
|
+
// 실행되면 안 된다 (hub/install.cjs 설치 게이트와 같은 문구).
|
|
39
|
+
if (isPrivateWebOnlyAgentRow(agentRow)) {
|
|
40
|
+
ctx.err("This web-only agent is not available in the Agentlas terminal.");
|
|
41
|
+
return 1;
|
|
42
|
+
}
|
|
43
|
+
const { startRepl } = require("../ui/repl.cjs");
|
|
44
|
+
ctx.out(ctx.ui.dim(`${ko ? "재개" : "resume"}: ${chat.title}`));
|
|
45
|
+
return startRepl(ctx, { agent: rowToAgent(agentRow).slug, chatId: chat.id });
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
module.exports = { run };
|
|
@@ -0,0 +1,101 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
/*
|
|
3
|
+
* plugin — `agentlas plugin add <slug>` / `agentlas plugin list`.
|
|
4
|
+
* 기능 로직은 hub/plugins.cjs. v1 cmdPluginAdd/cmdPluginList의 출력 계약 유지:
|
|
5
|
+
* - 설치할 MCP 서버가 없으면 조용히 성공했다고 하지 않는다 — 사용자는 이 플러그인이
|
|
6
|
+
* 붙었다고 믿고 도구를 기대하게 된다. 레포 URL을 http MCP 서버로 등록하는 일도
|
|
7
|
+
* 절대 하지 않는다(연결 불가능한 가짜 서버).
|
|
8
|
+
*/
|
|
9
|
+
const {
|
|
10
|
+
fetchPluginManifest,
|
|
11
|
+
planPluginMcpInstall,
|
|
12
|
+
installPluginMcpRows,
|
|
13
|
+
listHubPlugins,
|
|
14
|
+
} = require("../hub/plugins.cjs");
|
|
15
|
+
|
|
16
|
+
async function pluginAdd(ctx, slug) {
|
|
17
|
+
if (!slug) {
|
|
18
|
+
ctx.err("usage: agentlas plugin add <slug> (run agentlas plugin list first)");
|
|
19
|
+
return 1;
|
|
20
|
+
}
|
|
21
|
+
let manifest;
|
|
22
|
+
try {
|
|
23
|
+
manifest = await fetchPluginManifest(slug);
|
|
24
|
+
} catch (e) {
|
|
25
|
+
ctx.err(String((e && e.message) || e));
|
|
26
|
+
return 1;
|
|
27
|
+
}
|
|
28
|
+
if (!manifest) {
|
|
29
|
+
ctx.err(`Hub plugin not found: ${slug}`);
|
|
30
|
+
return 1;
|
|
31
|
+
}
|
|
32
|
+
const { rows, refused } = planPluginMcpInstall(slug, manifest);
|
|
33
|
+
const docsLink = manifest.docs || manifest.source?.repo || manifest.source?.homepage || null;
|
|
34
|
+
if (!rows.length) {
|
|
35
|
+
const reasonLines = refused.map((item) => ` ✗ ${item.name}: ${item.reason}${item.source ? ` (${item.source})` : ""}`);
|
|
36
|
+
ctx.err(
|
|
37
|
+
[
|
|
38
|
+
`${slug} ships no machine-connectable MCP endpoint yet. Nothing was registered.`,
|
|
39
|
+
...reasonLines,
|
|
40
|
+
docsLink ? ` docs: ${docsLink} (upstream project page — not an MCP endpoint)` : null,
|
|
41
|
+
" When the catalog gains verified connection info for this plugin, re-run: agentlas plugin add " + slug,
|
|
42
|
+
].filter(Boolean).join("\n"),
|
|
43
|
+
);
|
|
44
|
+
return 1;
|
|
45
|
+
}
|
|
46
|
+
let installed, reused, needsApproval;
|
|
47
|
+
try {
|
|
48
|
+
({ installed, reused, needsApproval } = installPluginMcpRows(ctx.db(), rows));
|
|
49
|
+
} catch (e) {
|
|
50
|
+
ctx.err(String((e && e.message) || e));
|
|
51
|
+
return 1;
|
|
52
|
+
}
|
|
53
|
+
ctx.out(`${ctx.ui.green("✓")} Plugin installed ${ctx.ui.accent(manifest.slug)} — ${manifest.name}`);
|
|
54
|
+
for (const item of refused) {
|
|
55
|
+
ctx.out(` ⚠ skipped ${item.name}: ${item.reason}${item.source ? ` (${item.source})` : ""}`);
|
|
56
|
+
}
|
|
57
|
+
ctx.out(` MCP servers: ${installed} added${reused ? `, ${reused} already present` : ""}`);
|
|
58
|
+
// 데스크탑 hub-plugin-bridge.ts:219-227 동형: stdio는 비활성 등록 + 승인 필요 표면화.
|
|
59
|
+
for (const name of needsApproval || []) {
|
|
60
|
+
ctx.out(` ⚠ needs-approval ${name}: local execution requires one-click approval in MCP settings`);
|
|
61
|
+
}
|
|
62
|
+
const authKind = manifest.auth?.kind;
|
|
63
|
+
if (authKind && authKind !== "none") {
|
|
64
|
+
ctx.out(` ⚠ Requires ${authKind} — set credentials before use (agentlas creds).`);
|
|
65
|
+
}
|
|
66
|
+
if (Array.isArray(manifest.skills) && manifest.skills.length) {
|
|
67
|
+
ctx.out(` skills declared: ${manifest.skills.map((skill) => skill.name).filter(Boolean).join(", ")}`);
|
|
68
|
+
}
|
|
69
|
+
ctx.out(ctx.ui.dim(" Only full-access turns wire active stdio servers into the runtime (agentlas mcp)."));
|
|
70
|
+
return 0;
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
async function pluginList(ctx) {
|
|
74
|
+
let plugins;
|
|
75
|
+
try {
|
|
76
|
+
plugins = await listHubPlugins();
|
|
77
|
+
} catch (e) {
|
|
78
|
+
ctx.err(String((e && e.message) || e));
|
|
79
|
+
return 1;
|
|
80
|
+
}
|
|
81
|
+
if (!plugins.length) {
|
|
82
|
+
ctx.out("No Hub plugins are available.");
|
|
83
|
+
return 0;
|
|
84
|
+
}
|
|
85
|
+
for (const plugin of plugins.slice(0, 60)) {
|
|
86
|
+
ctx.out(`${ctx.ui.accent(String(plugin.slug || "").padEnd(32).slice(0, 32))} ${String(plugin.name || "").slice(0, 44)}`);
|
|
87
|
+
}
|
|
88
|
+
ctx.out("");
|
|
89
|
+
ctx.out(ctx.ui.dim("Install: agentlas plugin add <slug>"));
|
|
90
|
+
return 0;
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
function run(ctx, args) {
|
|
94
|
+
const action = args[0];
|
|
95
|
+
if (action === "add") return pluginAdd(ctx, args[1]);
|
|
96
|
+
if (action === "list" || !action) return pluginList(ctx);
|
|
97
|
+
ctx.err("usage: agentlas plugin add <slug> | agentlas plugin list");
|
|
98
|
+
return 1;
|
|
99
|
+
}
|
|
100
|
+
|
|
101
|
+
module.exports = { run };
|