agentlas 1.0.65 → 1.0.67

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.
Files changed (163) hide show
  1. package/CHANGELOG.md +94 -0
  2. package/README.md +12 -4
  3. package/bin/agentlas.cjs +354 -16
  4. package/engine/acp/server.cjs +34 -3
  5. package/engine/agentlas-api-agent.cjs +151 -120
  6. package/engine/agentlas-banner.cjs +6 -2
  7. package/engine/agentlas-capabilities.cjs +17 -10
  8. package/engine/agentlas-cloud-runtime.cjs +4 -5
  9. package/engine/agentlas-config.cjs +19 -54
  10. package/engine/agentlas-core-harness.cjs +59 -5
  11. package/engine/agentlas-desktop-loadout.cjs +17 -3
  12. package/engine/agentlas-evolution.cjs +75 -18
  13. package/engine/agentlas-experience-exchange.cjs +145 -14
  14. package/engine/agentlas-experience-intake.cjs +2 -2
  15. package/engine/agentlas-i18n.cjs +0 -2
  16. package/engine/agentlas-input.cjs +273 -20
  17. package/engine/agentlas-judgment.cjs +0 -0
  18. package/engine/agentlas-mcp-env.cjs +84 -23
  19. package/engine/agentlas-mcp-wrapper.cjs +36 -4
  20. package/engine/agentlas-memory-governance.cjs +2 -1
  21. package/engine/agentlas-memory-import.cjs +49 -12
  22. package/engine/agentlas-native-host.cjs +279 -44
  23. package/engine/agentlas-onboard.cjs +23 -1
  24. package/engine/agentlas-permissions.cjs +5 -1
  25. package/engine/agentlas-sqlite-policy.cjs +40 -2
  26. package/engine/agentlas-tasks.cjs +7 -0
  27. package/engine/agentlas-tools.cjs +37 -10
  28. package/engine/agentlas-ui.cjs +26 -4
  29. package/engine/agentlas-workforce.cjs +30 -5
  30. package/engine/agentlas-workload-routing.cjs +83 -10
  31. package/engine/agentlas.cjs +21 -4
  32. package/engine/agents/builder.cjs +35 -10
  33. package/engine/agents/files.cjs +81 -10
  34. package/engine/agents/import-local.cjs +10 -2
  35. package/engine/agents/registry.cjs +10 -3
  36. package/engine/agents/router.cjs +7 -7
  37. package/engine/agents/routes.cjs +31 -9
  38. package/engine/automation/daemon.cjs +154 -40
  39. package/engine/automation/launchd.cjs +395 -16
  40. package/engine/automation/schedule.cjs +90 -3
  41. package/engine/automation/store.cjs +163 -59
  42. package/engine/browser/cdp.cjs +190 -17
  43. package/engine/browser/vault.cjs +1 -0
  44. package/engine/cli-output.cjs +96 -30
  45. package/engine/cloud/auth.cjs +131 -13
  46. package/engine/cloud/hub-client.cjs +41 -16
  47. package/engine/cloud-assets/cas.cjs +18 -9
  48. package/engine/cloud-assets/commands.cjs +108 -30
  49. package/engine/cloud-assets/package.cjs +66 -3
  50. package/engine/cloud-assets/restore.cjs +12 -9
  51. package/engine/cloud-assets/state.cjs +609 -116
  52. package/engine/commands/acp.cjs +13 -2
  53. package/engine/commands/automation.cjs +75 -24
  54. package/engine/commands/billing.cjs +6 -1
  55. package/engine/commands/browser.cjs +61 -29
  56. package/engine/commands/build.cjs +72 -14
  57. package/engine/commands/call.cjs +1 -1
  58. package/engine/commands/cd.cjs +1 -1
  59. package/engine/commands/cloud.cjs +2 -1
  60. package/engine/commands/connect.cjs +38 -13
  61. package/engine/commands/creds.cjs +74 -29
  62. package/engine/commands/doctor.cjs +17 -1
  63. package/engine/commands/document.cjs +38 -10
  64. package/engine/commands/env.cjs +6 -1
  65. package/engine/commands/firm.cjs +42 -12
  66. package/engine/commands/graph.cjs +36 -32
  67. package/engine/commands/help.cjs +11 -1
  68. package/engine/commands/hep.cjs +1 -1
  69. package/engine/commands/import.cjs +5 -4
  70. package/engine/commands/index.cjs +1 -1
  71. package/engine/commands/install.cjs +6 -4
  72. package/engine/commands/list.cjs +56 -16
  73. package/engine/commands/login.cjs +5 -0
  74. package/engine/commands/logout.cjs +6 -1
  75. package/engine/commands/mcp.cjs +150 -37
  76. package/engine/commands/memory.cjs +3 -2
  77. package/engine/commands/multimodal.cjs +12 -0
  78. package/engine/commands/native.cjs +5 -1
  79. package/engine/commands/one.cjs +78 -33
  80. package/engine/commands/plugin.cjs +13 -2
  81. package/engine/commands/project.cjs +15 -12
  82. package/engine/commands/research.cjs +18 -7
  83. package/engine/commands/roles.cjs +104 -28
  84. package/engine/commands/route.cjs +2 -2
  85. package/engine/commands/run.cjs +42 -9
  86. package/engine/commands/search.cjs +23 -2
  87. package/engine/commands/setup.cjs +6 -1
  88. package/engine/commands/storm.cjs +44 -6
  89. package/engine/commands/swarm.cjs +47 -9
  90. package/engine/commands/telegram.cjs +6 -1
  91. package/engine/commands/uninstall.cjs +12 -6
  92. package/engine/commands/update.cjs +5 -0
  93. package/engine/commands/upload.cjs +2 -1
  94. package/engine/commands/usage.cjs +6 -1
  95. package/engine/commands/version.cjs +6 -1
  96. package/engine/commands/whoami.cjs +6 -1
  97. package/engine/commands/workforce.cjs +60 -16
  98. package/engine/core/capability-grants.cjs +7 -2
  99. package/engine/core/daemon-client.cjs +36 -7
  100. package/engine/core/db.cjs +10 -2
  101. package/engine/core/desktop-core-fetch.cjs +477 -77
  102. package/engine/core/schema-ensure.cjs +23 -11
  103. package/engine/experience/build.cjs +5 -1
  104. package/engine/experience/intents.cjs +35 -82
  105. package/engine/experience/runtime.cjs +7 -1
  106. package/engine/experience/variant.cjs +58 -4
  107. package/engine/firms/orchestrate.cjs +9 -6
  108. package/engine/graph/ask-model.cjs +22 -11
  109. package/engine/graph/interview.cjs +74 -23
  110. package/engine/graph/package.cjs +63 -8
  111. package/engine/hephaestus/local-core.cjs +18 -5
  112. package/engine/hephaestus/runtime.cjs +48 -43
  113. package/engine/hub/install.cjs +484 -63
  114. package/engine/hub/plugins.cjs +391 -29
  115. package/engine/mcp/consent.cjs +25 -17
  116. package/engine/mcp/contract.cjs +229 -24
  117. package/engine/mcp/inventory.cjs +46 -10
  118. package/engine/mcp/plan.cjs +10 -1
  119. package/engine/mcp/probe.cjs +24 -9
  120. package/engine/memory-cli/curate.cjs +74 -16
  121. package/engine/oberon/common.cjs +37 -12
  122. package/engine/oberon/manifest.cjs +25 -2
  123. package/engine/oberon/outputs.cjs +49 -19
  124. package/engine/oberon/render.cjs +665 -40
  125. package/engine/project/career-graph.cjs +15 -2
  126. package/engine/project/controller.cjs +133 -15
  127. package/engine/project/credentials.cjs +382 -34
  128. package/engine/project/env-file.cjs +93 -8
  129. package/engine/project/memory-context.cjs +11 -57
  130. package/engine/project/ontology.cjs +66 -25
  131. package/engine/project/paths.cjs +42 -8
  132. package/engine/project/seed.cjs +88 -51
  133. package/engine/project/state.cjs +41 -16
  134. package/engine/project/team.cjs +26 -1
  135. package/engine/runtime-refusal.cjs +4 -1
  136. package/engine/runtimes/acp-driver.cjs +56 -9
  137. package/engine/runtimes/auth-evidence.cjs +21 -2
  138. package/engine/runtimes/detect.cjs +28 -8
  139. package/engine/runtimes/kinds.cjs +12 -4
  140. package/engine/runtimes/overrides.cjs +4 -3
  141. package/engine/runtimes/resolve.cjs +8 -7
  142. package/engine/runtimes/roles.cjs +7 -5
  143. package/engine/sessions/apply-fences.cjs +396 -56
  144. package/engine/sessions/fences.cjs +111 -27
  145. package/engine/sessions/memory-turn.cjs +62 -3
  146. package/engine/sessions/orchestrator.cjs +11 -1
  147. package/engine/sessions/prompt.cjs +66 -9
  148. package/engine/sessions/session.cjs +70 -10
  149. package/engine/sessions/sink.cjs +78 -12
  150. package/engine/sessions/store.cjs +29 -7
  151. package/engine/storm/storm.cjs +26 -3
  152. package/engine/storm/swarm.cjs +31 -3
  153. package/engine/telegram/connect.cjs +185 -22
  154. package/engine/ui/commands-catalog.cjs +1 -1
  155. package/engine/ui/palette.cjs +12 -2
  156. package/engine/ui/repl.cjs +57 -9
  157. package/engine/ui/screens.cjs +2 -4
  158. package/engine/vendor/desktop-core.manifest.json +5 -5
  159. package/engine/workforce/capture.cjs +9 -27
  160. package/engine/workforce/deps.cjs +18 -6
  161. package/engine/workforce/local-core-transport.cjs +68 -5
  162. package/package.json +1 -1
  163. package/engine/commands/career-graph.cjs +0 -51
@@ -7,6 +7,10 @@
7
7
  const { activeRuntimeRow, listAvailableCliRuntimes } = require("../runtimes/detect.cjs");
8
8
  const { resolvedModelRole } = require("../runtimes/roles.cjs");
9
9
  const { listAgents } = require("../agents/registry.cjs");
10
+ const { render, single } = require("../cli-output.cjs");
11
+
12
+ const LIST_OUTPUT_SCHEMA = Object.freeze({ idField: "slug", columns: [] });
13
+ const LIST_OUTPUT_FLAGS = new Set(["--json", "--yaml", "--quiet", "-q", "--no-headers", "--no-color"]);
10
14
 
11
15
  function roleRuntimeLabel(selection, role, en) {
12
16
  if (!selection) return en ? "(not set)" : "(미설정)";
@@ -25,6 +29,12 @@ function roleRuntimeLabel(selection, role, en) {
25
29
  }
26
30
 
27
31
  function run(ctx, args = []) {
32
+ const invalidArgs = args.filter((arg) => !LIST_OUTPUT_FLAGS.has(arg));
33
+ if (invalidArgs.length) {
34
+ const error = new Error(`Usage: agentlas list [--json|--yaml|--quiet|--no-headers|--no-color]. Unknown argument: ${invalidArgs[0]}`);
35
+ error.code = "INVALID_ARGUMENT";
36
+ throw error;
37
+ }
28
38
  const db = ctx.db();
29
39
  // 프라이버시 정책(웹 전용/백그라운드 제외)은 registry가 소유한다 — 직접 SQL 금지.
30
40
  const agents = listAgents(db).map((a) => ({
@@ -33,18 +43,40 @@ function run(ctx, args = []) {
33
43
  const firms = ctx.tableExists(db, "firms")
34
44
  ? db.prepare("SELECT id, slug, name FROM firms ORDER BY name").all()
35
45
  : [];
46
+ const output = ctx.output || {};
47
+ let format = output.format || "table";
48
+ for (const arg of args) {
49
+ if (arg === "--json") format = "json";
50
+ if (arg === "--yaml") format = "yaml";
51
+ }
52
+ const quiet = Boolean(output.quiet) || args.includes("--quiet") || args.includes("-q");
53
+ const noHeaders = Boolean(output.noHeaders) || args.includes("--no-headers");
54
+ const noColor = Boolean(output.noColor) || args.includes("--no-color");
55
+ const payload = () => ({
56
+ agents,
57
+ firms,
58
+ modelRoles: {
59
+ orchestrator: resolvedModelRole(db, "orchestrator"),
60
+ worker: resolvedModelRole(db, "worker"),
61
+ multimodal: resolvedModelRole(db, "multimodal"),
62
+ },
63
+ });
36
64
 
37
- // clig.dev: 스크립트 소비자는 사람용 표를 파싱하게 두지 말 것 — --json
38
- // 사람용 출력과 같은 사실을 기계 계약으로 준다.
39
- if (ctx.output?.format === "json" || args.includes("--json")) {
40
- const orchestrator = resolvedModelRole(db, "orchestrator");
41
- const worker = resolvedModelRole(db, "worker");
42
- ctx.out(JSON.stringify({ agents, firms, modelRoles: { orchestrator, worker } }, null, 2));
65
+ // clig.dev: 스크립트 소비자는 사람용 표를 파싱하게 두지 말 것 — 공통 출력
66
+ // renderer가 quiet 우선과 JSON/YAML 형식을 곳에서 해석한다.
67
+ if (quiet) {
68
+ const ids = agents.map((agent) => agent.slug).concat(firms.map((firm) => firm.slug || firm.id));
69
+ if (ids.length) ctx.out(ids.join("\n"));
70
+ return 0;
71
+ }
72
+ if (format === "json" || format === "yaml") {
73
+ ctx.out(render(single(payload(), LIST_OUTPUT_SCHEMA), { ...output, format, quiet: false }));
43
74
  return 0;
44
75
  }
45
76
 
46
77
  const en = ctx.lang === "en";
47
- ctx.out(ctx.ui.bold(en ? "Installed agents" : "설치된 에이전트"));
78
+ const paint = (name, value) => noColor ? String(value) : ctx.ui[name](value);
79
+ if (!noHeaders) ctx.out(paint("bold", en ? "Installed agents" : "설치된 에이전트"));
48
80
  if (!agents.length) {
49
81
  ctx.out(" " + (en ? "(none yet — try: agentlas search \"what you need\")" : " (아직 없음 — agentlas search \"필요한 것\" 으로 찾아보세요)"));
50
82
  }
@@ -104,31 +136,39 @@ function run(ctx, args = []) {
104
136
  // 2 indent + slug + space + name + " — " + tagline must fit one line.
105
137
  const budget = Math.max(20, cols - (2 + slugWidth + 1 + displayWidth(name) + 3));
106
138
  const shown = tag ? clampTag(tag, budget) : "";
107
- ctx.out(` ${ctx.ui.accent(clampSlug(a.slug))} ${name}${shown ? ctx.ui.dim(" — " + shown) : ""}`);
139
+ ctx.out(` ${paint("accent", clampSlug(a.slug))} ${name}${shown ? paint("dim", " — " + shown) : ""}`);
108
140
  }
109
141
  if (firms.length) {
110
- ctx.out("");
111
- ctx.out(ctx.ui.bold(en ? "Companies" : "회사"));
142
+ if (!noHeaders) {
143
+ ctx.out("");
144
+ ctx.out(paint("bold", en ? "Companies" : "회사"));
145
+ }
112
146
  for (const f of firms) {
113
147
  const callable = String(f.slug || f.id);
114
- ctx.out(` ${ctx.ui.accent(callable.padEnd(28))} ${f.name}`);
148
+ ctx.out(` ${paint("accent", callable.padEnd(28))} ${f.name}`);
115
149
  }
116
- ctx.out(ctx.ui.dim(en
150
+ ctx.out(paint("dim", en
117
151
  ? " Run one with: agentlas firm <company-key> \"<task>\""
118
152
  : " 실행: agentlas firm <회사 키> \"<작업>\""));
119
153
  }
120
154
 
121
155
  const active = activeRuntimeRow(db);
122
156
  const clis = listAvailableCliRuntimes();
123
- ctx.out("");
124
- ctx.out(ctx.ui.bold(en ? "Model roles" : "모델 역할"));
157
+ if (!noHeaders) {
158
+ ctx.out("");
159
+ ctx.out(paint("bold", en ? "Model roles" : "모델 역할"));
160
+ }
125
161
  const orchestrator = resolvedModelRole(db, "orchestrator");
126
162
  const worker = resolvedModelRole(db, "worker");
163
+ const multimodal = resolvedModelRole(db, "multimodal");
127
164
  ctx.out(` orchestrator: ${roleRuntimeLabel(orchestrator, "orchestrator", en)}`);
128
165
  ctx.out(` worker: ${roleRuntimeLabel(worker, "worker", en)}`);
166
+ ctx.out(` multimodal: ${roleRuntimeLabel(multimodal, "multimodal", en)}`);
129
167
 
130
- ctx.out("");
131
- ctx.out(ctx.ui.bold(en ? "Legacy runtime compatibility" : "레거시 런타임 호환"));
168
+ if (!noHeaders) {
169
+ ctx.out("");
170
+ ctx.out(paint("bold", en ? "Legacy runtime compatibility" : "레거시 런타임 호환"));
171
+ }
132
172
  if (active) {
133
173
  ctx.out(` active: ${active.kind}${active.model ? ` (${active.model})` : ""}${active.backend ? ` via ${active.backend}` : ""}`);
134
174
  } else {
@@ -9,6 +9,11 @@ const auth = require("../cloud/auth.cjs");
9
9
 
10
10
  async function run(ctx, args = []) {
11
11
  const ko = ctx.lang === "ko";
12
+ if (args.some((arg) => arg !== "--force") || args.filter((arg) => arg === "--force").length > 1) {
13
+ const error = new Error("usage: agentlas login [--force]");
14
+ error.code = "INVALID_ARGUMENT";
15
+ throw error;
16
+ }
12
17
  const force = args.includes("--force");
13
18
  if (!force) {
14
19
  const existing = auth.cloudSessionCookie();
@@ -5,8 +5,13 @@
5
5
  */
6
6
  const auth = require("../cloud/auth.cjs");
7
7
 
8
- function run(ctx) {
8
+ function run(ctx, args = []) {
9
9
  const ko = ctx.lang === "ko";
10
+ if (args.length) {
11
+ const error = new Error("usage: agentlas logout");
12
+ error.code = "INVALID_ARGUMENT";
13
+ throw error;
14
+ }
10
15
  let result;
11
16
  try {
12
17
  result = auth.deleteCliSession();
@@ -13,35 +13,124 @@
13
13
  const { userDataDir } = require("../core/paths.cjs");
14
14
  const { materializeTrustedSystemMcpServer } = require("../mcp/inventory.cjs");
15
15
  const { probeSystemMcpServerConnection } = require("../mcp/probe.cjs");
16
+ const {
17
+ DEFAULT_OPTIONS,
18
+ list: outputList,
19
+ render,
20
+ single,
21
+ parseOutputFlags,
22
+ displayWidth,
23
+ terminalTextOf,
24
+ } = require("../cli-output.cjs");
25
+
26
+ const OUTPUT_FLAGS = new Set(["--json", "--yaml", "--quiet", "-q", "--no-headers", "--no-color"]);
27
+
28
+ function commandError(message, code = "INVALID_ARGUMENT", details) {
29
+ const error = new Error(message);
30
+ error.code = code;
31
+ if (details !== undefined) error.details = details;
32
+ return error;
33
+ }
34
+
35
+ function withOutputFlags(ctx, args) {
36
+ if (!args.some((arg) => OUTPUT_FLAGS.has(arg))) return { ctx, args };
37
+ const parsed = parseOutputFlags(args);
38
+ return {
39
+ ctx: { ...ctx, output: { ...(ctx.output || DEFAULT_OPTIONS), ...parsed.options } },
40
+ args: parsed.rest,
41
+ };
42
+ }
43
+
44
+ function emit(ctx, result) {
45
+ if (typeof ctx.emit === "function") {
46
+ ctx.emit(result);
47
+ return;
48
+ }
49
+ ctx.out(render(result, ctx.output || DEFAULT_OPTIONS));
50
+ }
51
+
52
+ function ansi(options, code, value) {
53
+ const text = String(value);
54
+ return options?.noColor ? text : `\u001b[${code}m${text}\u001b[0m`;
55
+ }
56
+
57
+ function isMachineOutput(ctx) {
58
+ const output = ctx.output || DEFAULT_OPTIONS;
59
+ return output.quiet || output.format === "json" || output.format === "yaml";
60
+ }
61
+
62
+ function mcpListSchema(en) {
63
+ return Object.freeze({
64
+ idField: "id",
65
+ columns: [
66
+ { header: "id", field: "id" },
67
+ { header: en ? "name" : "이름", field: "name" },
68
+ ],
69
+ renderHuman(result, options = {}) {
70
+ const rows = Array.isArray(result.data) ? result.data : [];
71
+ if (!rows.length) {
72
+ return options.noHeaders ? "" : (en ? "No MCP servers registered." : "등록된 MCP 서버가 없습니다.");
73
+ }
74
+ const lines = [];
75
+ if (!options.noHeaders) lines.push(ansi(options, 1, en ? "MCP servers" : "MCP 서버"));
76
+ for (const row of rows) {
77
+ const id = terminalTextOf(row.id, 256);
78
+ const name = terminalTextOf(row.name, 4096);
79
+ const padding = " ".repeat(Math.max(1, 28 - displayWidth(id)));
80
+ lines.push(` ${ansi(options, 36, id)}${padding}${name}`);
81
+ }
82
+ return lines.join("\n");
83
+ },
84
+ });
85
+ }
86
+
87
+ function mcpProbeSchema(en) {
88
+ return Object.freeze({
89
+ idField: "serverId",
90
+ columns: [
91
+ { header: en ? "server" : "서버", field: "serverId" },
92
+ { header: en ? "status" : "상태", field: "status" },
93
+ { header: en ? "tools" : "툴", field: "toolCount" },
94
+ ],
95
+ renderHuman(result, options = {}) {
96
+ const row = result.data || {};
97
+ const serverId = terminalTextOf(row.serverId, 256);
98
+ const toolCount = Number.isSafeInteger(row.toolCount) ? row.toolCount : 0;
99
+ const status = row.connected ? ansi(options, 32, en ? "connected" : "연결됨") : ansi(options, 31, en ? "failed" : "실패");
100
+ const toolLabel = en ? `${toolCount} tool(s) listed` : `툴 ${toolCount}개 확인`;
101
+ const note = en
102
+ ? "Preflight only: connection readiness does not imply tool-call success."
103
+ : "프리플라이트일 뿐입니다: 연결 준비됨 ≠ 툴 호출 성공.";
104
+ return [
105
+ `${status} ${ansi(options, 1, serverId)} · ${toolLabel}`,
106
+ ansi(options, 2, note),
107
+ ].join("\n");
108
+ },
109
+ });
110
+ }
16
111
 
17
112
  function list(ctx) {
18
113
  const en = ctx.lang === "en";
19
114
  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 || ""}`);
115
+ const rows = ctx.tableExists(db, "mcp_servers")
116
+ ? db.prepare("SELECT id, name FROM mcp_servers ORDER BY name").all().map((row) => ({
117
+ id: String(row.id || ""),
118
+ name: String(row.name || ""),
119
+ }))
120
+ : [];
121
+ emit(ctx, outputList(rows, mcpListSchema(en)));
31
122
  return 0;
32
123
  }
33
124
 
34
125
  async function probe(ctx, args) {
35
126
  const en = ctx.lang === "en";
36
127
  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;
128
+ if (!ref || args.length !== 1 || ref.startsWith("-")) {
129
+ throw commandError(en ? "Usage: agentlas mcp probe <server-id|catalog-id>" : "사용법: agentlas mcp probe <server-id|catalog-id>");
40
130
  }
41
131
  const db = ctx.db();
42
132
  if (!ctx.tableExists(db, "mcp_servers")) {
43
- ctx.err(en ? "No MCP servers registered." : "등록된 MCP 서버가 없습니다.");
44
- return 1;
133
+ throw commandError(en ? "No MCP servers registered." : "등록된 MCP 서버가 없습니다.", "MCP_SERVER_NOT_FOUND");
45
134
  }
46
135
  // catalog_id는 데스크탑 스키마 열 — 오래된 DB에는 없을 수 있어 방어적으로 조회.
47
136
  const byCatalog = ctx.columnExists(db, "mcp_servers", "catalog_id");
@@ -49,47 +138,71 @@ async function probe(ctx, args) {
49
138
  ? 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
139
  : 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
140
  if (!row) {
52
- ctx.err(en ? `MCP server not found: ${ref}` : `MCP 서버를 찾을 수 없습니다: ${ref}`);
53
- return 1;
141
+ throw commandError(
142
+ en ? `MCP server not found: ${ref}` : `MCP 서버를 찾을 수 없습니다: ${ref}`,
143
+ "MCP_SERVER_NOT_FOUND",
144
+ { serverId: ref },
145
+ );
54
146
  }
55
147
  const server = materializeTrustedSystemMcpServer(row, { userDataDir: userDataDir() });
56
148
  if (!server) {
57
149
  // 이유를 지어내지 않는다 — materialize는 비활성/비-stdio/안전하지 않은
58
150
  // 정의를 하나의 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;
151
+ throw commandError(
152
+ en
153
+ ? `'${ref}' is not probe-eligible (disabled, non-stdio transport, or an unsafe runtime definition).`
154
+ : `'${ref}' 은(는) probe 대상이 아닙니다 (비활성, stdio가 아닌 transport, 또는 안전하지 않은 실행 정의).`,
155
+ "MCP_PROBE_INELIGIBLE",
156
+ { serverId: ref },
157
+ );
158
+ }
159
+ if (!isMachineOutput(ctx)) {
160
+ const options = ctx.output || DEFAULT_OPTIONS;
161
+ ctx.out(ansi(options, 2, en
162
+ ? `Probing ${server.catalog_id} (isolated child env, handshake only)…`
163
+ : `${server.catalog_id} 연결 확인 중 (격리 자식 env, 핸드셰이크만)…`));
63
164
  }
64
- ctx.out(ctx.ui.dim(en
65
- ? `Probing ${server.catalog_id} (isolated child env, handshake only)…`
66
- : `${server.catalog_id} 연결 확인 중 (격리 자식 env, 핸드셰이크만)…`));
67
165
  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;
166
+ if (!result.connected) {
167
+ throw commandError(
168
+ en
169
+ ? `MCP probe failed: ${server.catalog_id} · ${result.reason || "connection_failed"}`
170
+ : `MCP 연결 확인 실패: ${server.catalog_id} · ${result.reason || "connection_failed"}`,
171
+ "MCP_PROBE_FAILED",
172
+ { serverId: server.catalog_id, reason: result.reason || "connection_failed" },
173
+ );
75
174
  }
76
- ctx.err(`${ctx.ui.red("failed")} ${ctx.ui.bold(server.catalog_id)} · ${result.reason}`);
77
- return 1;
175
+ const toolCount = Array.isArray(result.tools) ? result.tools.length : 0;
176
+ emit(ctx, single({
177
+ serverId: server.catalog_id,
178
+ status: "connected",
179
+ connected: true,
180
+ reason: result.reason || "connected",
181
+ toolCount,
182
+ }, mcpProbeSchema(en)));
183
+ return 0;
78
184
  }
79
185
 
80
186
  function run(ctx, args = []) {
187
+ const normalized = withOutputFlags(ctx, args);
188
+ ctx = normalized.ctx;
189
+ args = normalized.args;
81
190
  const [sub, ...rest] = args;
82
191
  if (!sub) return list(ctx);
83
192
  // 무인자 기본 동작이 목록인데 이름으로 부르면 거부되는 비대칭이 있었다
84
193
  // (2026-08-05 감사 결함 G): 다른 명령들(cloud list, plugin list)이 만든
85
194
  // "list를 붙이는" 습관이 여기서만 usage 오류가 됐다.
86
- if (sub === "list" || sub === "ls") return list(ctx);
195
+ if (sub === "list" || sub === "ls") {
196
+ if (rest.length) {
197
+ throw commandError(ctx.lang === "en" ? "Usage: agentlas mcp list" : "사용법: agentlas mcp list");
198
+ }
199
+ return list(ctx);
200
+ }
87
201
  if (sub === "probe") return probe(ctx, rest);
88
202
  const en = ctx.lang === "en";
89
- ctx.err(en
203
+ throw commandError(en
90
204
  ? `unknown mcp subcommand: ${sub} (available: list · probe)`
91
205
  : `알 수 없는 mcp 하위 명령: ${sub} (사용 가능: list · probe)`);
92
- return 1;
93
206
  }
94
207
 
95
208
  module.exports = { run };
@@ -6,11 +6,12 @@
6
6
  * engine/agentlas-memory-import.cjs 가 소유한다(여기서는 주입만).
7
7
  * v1의 fail(process.exit)은 v2에서 throw 로 바꿔 엔진이 stderr+exit 1 처리한다.
8
8
  */
9
- const { cmdMemory } = require("../agentlas-memory-import.cjs");
9
+ const { cmdMemory, parseMemoryArgs } = require("../agentlas-memory-import.cjs");
10
10
 
11
11
  function run(ctx, args) {
12
+ const parsed = parseMemoryArgs(args);
12
13
  cmdMemory({
13
- db: ctx.db(),
14
+ db: parsed.help ? null : ctx.db(),
14
15
  args,
15
16
  out: ctx.out,
16
17
  fail: (msg) => { throw new Error(String(msg ?? "")); },
@@ -51,6 +51,18 @@ function saveSettings(db, patch) {
51
51
 
52
52
  function run(ctx, args) {
53
53
  const ko = ctx.lang === "ko";
54
+ const usage = ko
55
+ ? "사용법: agentlas multimodal [set <image|video|audio> <provider-id>]"
56
+ : "Usage: agentlas multimodal [set <image|video|audio> <provider-id>]";
57
+ if (args.length === 1 && ["help", "--help", "-h"].includes(args[0])) {
58
+ ctx.out(usage);
59
+ return 0;
60
+ }
61
+ if (args.length !== 0 && !(args.length === 3 && args[0] === "set")) {
62
+ const error = new Error(usage);
63
+ error.code = "INVALID_ARGUMENT";
64
+ throw error;
65
+ }
54
66
  const db = ctx.db();
55
67
 
56
68
  if (args[0] === "set") {
@@ -8,6 +8,10 @@ function run(ctx, args) {
8
8
  const sub = String(args[0] || "help");
9
9
  const query = args[1];
10
10
  if (sub === "help" || sub === "--help" || sub === "-h") {
11
+ if (args.length > 1) {
12
+ ctx.err(ko ? "사용법: agentlas native help" : "usage: agentlas native help");
13
+ return 1;
14
+ }
11
15
  // SELF_HELP_COMMANDS 계약: --help 는 스텁이 아니라 실제 안내여야 한다 —
12
16
  // 무엇이 만들어지고 언제 필요한지까지 말한다.
13
17
  ctx.out(ko
@@ -29,7 +33,7 @@ function run(ctx, args) {
29
33
  ].join("\n"));
30
34
  return 0;
31
35
  }
32
- if (sub !== "prepare" || !query) {
36
+ if (sub !== "prepare" || !query || args.length !== 2) {
33
37
  ctx.err(ko ? "사용법: agentlas native prepare <에이전트>" : "usage: agentlas native prepare <agent>");
34
38
  return 1;
35
39
  }
@@ -20,6 +20,7 @@
20
20
  * agentlas one --list One 대화 목록
21
21
  * agentlas one --new "<프롬프트>" 새 One 대화로 시작
22
22
  * agentlas one --chat <id> "<p>" 특정 One 대화에 이어 붙임
23
+ * agentlas one -- "list 같은 옵션 모양의 프롬프트"
23
24
  * 공통: -p/--print · --runtime · --model · --effort · --permission
24
25
  */
25
26
  const readline = require("node:readline");
@@ -30,7 +31,7 @@ const { Renderer } = require("../ui/renderer.cjs");
30
31
  const permissions = require("../agentlas-permissions.cjs");
31
32
  const { EFFORTS } = require("../agentlas-workload-routing.cjs");
32
33
  const { projectCwd } = require("../project/paths.cjs");
33
- const { columnExists, runWriteTransaction } = require("../core/db.cjs");
34
+ const { runWriteTransaction } = require("../core/db.cjs");
34
35
  const store = require("../sessions/store.cjs");
35
36
 
36
37
  /** 데스크탑 정본 신원(electron/architecture/manifest.ts + builtinAgentId). */
@@ -45,13 +46,8 @@ const ONE_ORIGIN_SURFACE = "one";
45
46
  * 걸러 낸다(설계). One 은 라우팅 후보가 아니라 **신원 행**이므로 직접 읽는다.
46
47
  */
47
48
  function resolveOneAgent(db) {
48
- let row = null;
49
- try {
50
- row = db.prepare("SELECT * FROM installed_agents WHERE id=?").get(ONE_AGENT_ID)
51
- || db.prepare("SELECT * FROM installed_agents WHERE slug=?").get(ONE_AGENT_SLUG);
52
- } catch {
53
- row = null;
54
- }
49
+ const row = db.prepare("SELECT * FROM installed_agents WHERE id=?").get(ONE_AGENT_ID)
50
+ || db.prepare("SELECT * FROM installed_agents WHERE slug=?").get(ONE_AGENT_SLUG);
55
51
  return rowToAgent(row);
56
52
  }
57
53
 
@@ -78,26 +74,22 @@ function desktopOnePersona() {
78
74
  }
79
75
 
80
76
  function chatsHaveOriginSurface(db) {
81
- return columnExists(db, "chats", "origin_surface");
77
+ return db.prepare("PRAGMA table_info(chats)").all().some((column) => column.name === "origin_surface");
82
78
  }
83
79
 
84
80
  /** One 소유 대화 목록(최근 순). origin_surface 열이 없는 구형 DB 는 에이전트 소유로 폴백. */
85
81
  function listOneChats(db, limit = 20) {
86
82
  const bounded = Math.max(1, Math.min(Number(limit) || 20, 200));
87
- try {
88
- if (chatsHaveOriginSurface(db)) {
89
- return db.prepare(
90
- "SELECT id, title, updated_at, origin_surface FROM chats " +
91
- "WHERE origin_surface = ? ORDER BY updated_at DESC, rowid DESC LIMIT ?",
92
- ).all(ONE_ORIGIN_SURFACE, bounded);
93
- }
83
+ if (chatsHaveOriginSurface(db)) {
94
84
  return db.prepare(
95
- "SELECT id, title, updated_at FROM chats WHERE agent_id IN (?, ?) AND (kind IS NULL OR kind <> 'division') " +
96
- "ORDER BY updated_at DESC, rowid DESC LIMIT ?",
97
- ).all(ONE_AGENT_ID, ONE_AGENT_SLUG, bounded);
98
- } catch {
99
- return [];
85
+ "SELECT id, title, updated_at, origin_surface FROM chats " +
86
+ "WHERE origin_surface = ? ORDER BY updated_at DESC, rowid DESC LIMIT ?",
87
+ ).all(ONE_ORIGIN_SURFACE, bounded);
100
88
  }
89
+ return db.prepare(
90
+ "SELECT id, title, updated_at FROM chats WHERE agent_id IN (?, ?) AND (kind IS NULL OR kind <> 'division') " +
91
+ "ORDER BY updated_at DESC, rowid DESC LIMIT ?",
92
+ ).all(ONE_AGENT_ID, ONE_AGENT_SLUG, bounded);
101
93
  }
102
94
 
103
95
  /**
@@ -121,19 +113,48 @@ function createOneChat(db, { agentId, title, workingFolder }) {
121
113
  }
122
114
 
123
115
  function parseArgs(args) {
124
- const out = { print: false, list: false, fresh: false, chatId: null, runtime: null, model: null, effort: null, permission: null, rest: [] };
116
+ const out = { print: false, list: false, fresh: false, chatId: null, runtime: null, model: null, effort: null, permission: null, rest: [], error: null };
117
+ const readValue = (index, flag) => {
118
+ const value = args[index + 1];
119
+ if (typeof value !== "string" || !value || value === "--" || value.startsWith("--")) {
120
+ out.error = `${flag} requires a value`;
121
+ return null;
122
+ }
123
+ return value;
124
+ };
125
125
  for (let i = 0; i < args.length; i++) {
126
126
  const a = args[i];
127
+ if (a === "--") {
128
+ out.rest.push(...args.slice(i + 1));
129
+ break;
130
+ }
127
131
  if (a === "-p" || a === "--print") out.print = true;
128
- else if (a === "--list" || a === "list") out.list = true;
129
- else if (a === "--new" || a === "new") out.fresh = true;
130
- else if (a === "--chat") out.chatId = args[++i];
131
- else if (a === "--runtime") out.runtime = args[++i];
132
- else if (a === "--model") out.model = args[++i];
133
- else if (a === "--effort") out.effort = args[++i];
134
- else if (a === "--permission") out.permission = args[++i];
132
+ else if (a === "--list") out.list = true;
133
+ else if (a === "--new") out.fresh = true;
134
+ else if (["--chat", "--runtime", "--model", "--effort", "--permission"].includes(a)) {
135
+ const value = readValue(i, a);
136
+ if (value === null) break;
137
+ if (a === "--chat") out.chatId = value;
138
+ else if (a === "--runtime") out.runtime = value;
139
+ else if (a === "--model") out.model = value;
140
+ else if (a === "--effort") out.effort = value;
141
+ else out.permission = value;
142
+ i += 1;
143
+ }
144
+ else if (a.startsWith("-")) {
145
+ out.error = `unknown option ${a}`;
146
+ break;
147
+ }
135
148
  else out.rest.push(a);
136
149
  }
150
+ if (!out.error && out.list && (
151
+ out.fresh || out.chatId || out.runtime || out.model || out.effort || out.permission || out.rest.length > 0
152
+ )) {
153
+ out.error = "--list cannot be combined with a prompt or execution options";
154
+ }
155
+ if (!out.error && out.fresh && out.chatId) {
156
+ out.error = "--new cannot be combined with --chat";
157
+ }
137
158
  return out;
138
159
  }
139
160
 
@@ -153,6 +174,10 @@ function renderList(ctx, rows) {
153
174
 
154
175
  async function runOne(ctx, args) {
155
176
  const parsed = parseArgs(args);
177
+ if (parsed.error) {
178
+ ctx.err(`invalid one arguments: ${parsed.error}`);
179
+ return 1;
180
+ }
156
181
  if (parsed.permission && !permissions.LEVELS.includes(String(parsed.permission))) {
157
182
  ctx.err(`unknown --permission ${parsed.permission} (use: ${permissions.LEVELS.join(" | ")})`);
158
183
  return 1;
@@ -163,12 +188,29 @@ async function runOne(ctx, args) {
163
188
  }
164
189
 
165
190
  const db = ctx.db();
191
+ const readChats = (limit) => {
192
+ try {
193
+ return listOneChats(db, limit);
194
+ } catch (error) {
195
+ ctx.err(`Could not read One conversations: ${String((error && error.message) || error)}`);
196
+ return null;
197
+ }
198
+ };
166
199
 
167
200
  // 목록은 신원 행이 없어도 답할 수 있어야 한다 — 대화는 chats 에 있고, One 행은
168
201
  // 실행에만 필요하다. 조회를 실행 전제조건 뒤에 두면 "볼 수도 없는" 화면이 된다.
169
- if (parsed.list) return renderList(ctx, listOneChats(db, 20));
202
+ if (parsed.list) {
203
+ const rows = readChats(20);
204
+ return rows ? renderList(ctx, rows) : 1;
205
+ }
170
206
 
171
- const agent = resolveOneAgent(db);
207
+ let agent;
208
+ try {
209
+ agent = resolveOneAgent(db);
210
+ } catch (error) {
211
+ ctx.err(`Could not read the Agentlas One identity: ${String((error && error.message) || error)}`);
212
+ return 1;
213
+ }
172
214
  if (!agent) {
173
215
  ctx.err(
174
216
  "Agentlas One is not present in the shared database yet.\n" +
@@ -212,14 +254,17 @@ async function runOne(ctx, args) {
212
254
  let chatId = null;
213
255
  let created = false;
214
256
  if (parsed.chatId) {
215
- const row = listOneChats(db, 200).find((item) => item.id === parsed.chatId);
257
+ const rows = readChats(200);
258
+ if (!rows) return 1;
259
+ const row = rows.find((item) => item.id === parsed.chatId);
216
260
  if (!row) {
217
261
  ctx.err(`No One conversation with id ${parsed.chatId} (see: agentlas one --list)`);
218
262
  return 1;
219
263
  }
220
264
  chatId = row.id;
221
265
  } else if (!parsed.fresh) {
222
- const recent = listOneChats(db, 1);
266
+ const recent = readChats(1);
267
+ if (!recent) return 1;
223
268
  if (recent.length) chatId = recent[0].id;
224
269
  }
225
270
  if (!chatId) {