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
@@ -24,6 +24,9 @@ const {
24
24
  upsertLocalCredentialMapCli,
25
25
  projectScopedGlobalEnvKeyCli,
26
26
  safeCredentialDestRelCli,
27
+ credentialProjectRootCli,
28
+ resolveCredentialDestinationCli,
29
+ copyCredentialFileAtomicCli,
27
30
  } = require("../project/credentials.cjs");
28
31
 
29
32
  // 데스크탑과 공유하는 키체인 서비스/키 접두어 (v1 상수 그대로).
@@ -41,19 +44,27 @@ function readKeytar() {
41
44
  }
42
45
  }
43
46
 
44
- function parseCredFlags(args) {
47
+ function parseCredFlags(args, schema) {
45
48
  const f = {};
49
+ const values = new Set(schema.values || []);
50
+ const booleans = new Set(schema.booleans || []);
46
51
  for (let i = 0; i < args.length; i++) {
47
- const a = args[i];
48
- if (a && a.startsWith("--")) {
49
- const next = args[i + 1];
50
- if (next !== undefined && !String(next).startsWith("--")) {
51
- f[a.slice(2)] = next;
52
- i++;
53
- } else {
54
- f[a.slice(2)] = true;
55
- }
52
+ const token = String(args[i]);
53
+ if (!token.startsWith("--") || token === "--") throw new Error(`unexpected argument: ${token}`);
54
+ const at = token.indexOf("=");
55
+ const key = token.slice(2, at >= 0 ? at : undefined);
56
+ if (!values.has(key) && !booleans.has(key)) throw new Error(`unknown option: --${key}`);
57
+ if (Object.prototype.hasOwnProperty.call(f, key)) throw new Error(`duplicate option: --${key}`);
58
+ if (booleans.has(key)) {
59
+ if (at >= 0) throw new Error(`--${key} does not take a value`);
60
+ f[key] = true;
61
+ continue;
56
62
  }
63
+ const next = at >= 0 ? token.slice(at + 1) : args[++i];
64
+ if (next === undefined || next === "" || (at < 0 && String(next).startsWith("--"))) {
65
+ throw new Error(`--${key} requires a value`);
66
+ }
67
+ f[key] = String(next);
57
68
  }
58
69
  return f;
59
70
  }
@@ -66,20 +77,35 @@ function resolveCredentialSourcePath(source, cwd) {
66
77
 
67
78
  async function cmdCredsFile(ctx, args) {
68
79
  const fail = (msg) => { ctx.err(msg); return 1; };
69
- const f = parseCredFlags(args);
80
+ let f;
81
+ try {
82
+ f = parseCredFlags(args, {
83
+ values: ["source", "path", "provider", "env", "dest", "project", "id", "requiredFor", "staleCheck"],
84
+ booleans: ["force"],
85
+ });
86
+ } catch (error) { return fail(String((error && error.message) || error)); }
87
+ if (f.source !== undefined && f.path !== undefined) return fail("use only one of --source or --path");
70
88
  const source = typeof f.source === "string" ? f.source : typeof f.path === "string" ? f.path : "";
71
89
  if (!source) return fail("usage: agentlas creds file --source <path> [--provider <name>] [--env <ENV_NAME>] [--dest <relative-path>] [--project <path>] [--force]");
72
- const project = typeof f.project === "string" && f.project ? f.project : activeProjectPath(ctx.db());
73
- if (!project) return fail("creds file requires a project path");
90
+ const requestedProject = typeof f.project === "string" && f.project ? f.project : activeProjectPath(ctx.db());
91
+ if (!requestedProject) return fail("creds file requires a project path");
92
+ let project;
93
+ try { project = credentialProjectRootCli(requestedProject); }
94
+ catch (error) { return fail(`credential project is unsafe: ${String((error && error.message) || error)}`); }
74
95
  const provider = typeof f.provider === "string" && f.provider ? f.provider : "credential_file";
96
+ if (provider.length > 128 || /[\u0000-\u001f\u007f]/.test(provider)) return fail("credential provider name contains unsafe text");
75
97
  const envKey = typeof f.env === "string" && f.env ? f.env.trim() : "";
76
98
  if (envKey && !/^[A-Z][A-Z0-9_]*$/.test(envKey)) return fail("credential env name must look like ENV_NAME");
77
99
 
78
100
  const arch = loadArch();
79
101
  const cfg = localCredentialConfigCli(arch);
80
102
  const projectName = path.basename(project) || "Project";
81
- ensureLocalCredentialStoreCli(project, projectName, arch);
82
- ensureSoulCredentialIndexCli(project, projectName, arch);
103
+ try {
104
+ ensureLocalCredentialStoreCli(project, projectName, arch);
105
+ ensureSoulCredentialIndexCli(project, projectName, arch);
106
+ } catch (error) {
107
+ return fail(`credential store is unsafe: ${String((error && error.message) || error)}`);
108
+ }
83
109
 
84
110
  const sourceAbs = resolveCredentialSourcePath(source);
85
111
  let stat;
@@ -99,15 +125,13 @@ async function cmdCredsFile(ctx, args) {
99
125
  } catch (e) {
100
126
  return fail(String((e && e.message) || e));
101
127
  }
102
- const destAbs = path.join(project, destRel);
103
- if (!path.resolve(destAbs).startsWith(path.resolve(project) + path.sep)) {
104
- return fail("credential destination must stay inside the project");
128
+ let destAbs;
129
+ try {
130
+ destAbs = resolveCredentialDestinationCli(project, destRel);
131
+ copyCredentialFileAtomicCli(sourceAbs, destAbs, { force: Boolean(f.force) });
132
+ } catch (error) {
133
+ return fail(`credential copy refused: ${String((error && error.message) || error)}`);
105
134
  }
106
- if (fs.existsSync(destAbs) && !f.force) return fail(`credential destination already exists: ${destRel} (use --force to replace)`);
107
-
108
- fs.mkdirSync(path.dirname(destAbs), { recursive: true });
109
- fs.copyFileSync(sourceAbs, destAbs);
110
- try { fs.chmodSync(destAbs, 0o600); } catch { /* best-effort */ }
111
135
 
112
136
  const targets = [destRel];
113
137
  if (envKey) {
@@ -185,11 +209,20 @@ async function run(ctx, args) {
185
209
  const fail = (msg) => { ctx.err(msg); return 1; };
186
210
  const sub = args[0];
187
211
  if (sub === "file") return cmdCredsFile(ctx, args.slice(1));
188
- if (sub === "list" || sub === "ls") return cmdCredsList(ctx);
212
+ if (sub === "list" || sub === "ls") {
213
+ if (args.length !== 1) return fail(`unexpected argument: ${String(args[1])}`);
214
+ return cmdCredsList(ctx);
215
+ }
189
216
  if (sub !== "save") {
190
217
  return fail('usage: agentlas creds save --provider <name> --key <ENV_NAME> --value <value> [--project <path>] OR agentlas creds file --source <path> [--env <ENV_NAME>] OR agentlas creds list');
191
218
  }
192
- const f = parseCredFlags(args.slice(1));
219
+ let f;
220
+ try {
221
+ f = parseCredFlags(args.slice(1), {
222
+ values: ["provider", "key", "value", "project"],
223
+ booleans: [],
224
+ });
225
+ } catch (error) { return fail(String((error && error.message) || error)); }
193
226
  const key = typeof f.key === "string" ? f.key.trim() : "";
194
227
  let value = f.value === undefined || f.value === true ? "" : String(f.value);
195
228
  /*
@@ -207,14 +240,26 @@ async function run(ctx, args) {
207
240
  : "Note: a secret passed via --value lands in shell history and `ps`. Prefer stdin next time: printf '%s' \"$SECRET\" | agentlas creds save --key X --value -");
208
241
  }
209
242
  if (!key || !value) return fail("creds save requires --key and --value (use `--value -` to read the secret from stdin)");
243
+ if (!/^[A-Z][A-Z0-9_]*$/.test(key)) return fail("credential env name must look like ENV_NAME");
244
+ if (/[\u0000\r\n]/.test(value)) return fail("credential env value must be a single line; use `agentlas creds file` for multiline credentials");
210
245
  const provider = typeof f.provider === "string" && f.provider ? f.provider : key;
211
- const project = typeof f.project === "string" && f.project ? f.project : activeProjectPath(ctx.db());
246
+ if (provider.length > 128 || /[\u0000-\u001f\u007f]/.test(provider)) return fail("credential provider name contains unsafe text");
247
+ const requestedProject = typeof f.project === "string" && f.project ? f.project : activeProjectPath(ctx.db());
248
+ let project = null;
249
+ if (requestedProject) {
250
+ try { project = credentialProjectRootCli(requestedProject); }
251
+ catch (error) { return fail(`credential project is unsafe: ${String((error && error.message) || error)}`); }
252
+ }
212
253
  const targets = [];
213
254
  const arch = loadArch();
214
255
  const projectName = project ? path.basename(project) || "Project" : "Project";
215
256
  if (project) {
216
- ensureLocalCredentialStoreCli(project, projectName, arch);
217
- ensureSoulCredentialIndexCli(project, projectName, arch);
257
+ try {
258
+ ensureLocalCredentialStoreCli(project, projectName, arch);
259
+ ensureSoulCredentialIndexCli(project, projectName, arch);
260
+ } catch (error) {
261
+ return fail(`credential store is unsafe: ${String((error && error.message) || error)}`);
262
+ }
218
263
  }
219
264
 
220
265
  // 1) keychain vault — project-scoped when a project is active.
@@ -262,4 +307,4 @@ async function run(ctx, args) {
262
307
  return 0;
263
308
  }
264
309
 
265
- module.exports = { run, resolveCredentialSourcePath };
310
+ module.exports = { run, resolveCredentialSourcePath, parseCredFlags };
@@ -14,6 +14,14 @@ const { runtimeAuthEvidence } = require("../runtimes/auth-evidence.cjs");
14
14
  const { sharedRuntimeKind } = require("../runtimes/resolve.cjs");
15
15
  const { resolvedModelRole } = require("../runtimes/roles.cjs");
16
16
 
17
+ function hasCloudSessionCredential() {
18
+ try {
19
+ return Boolean(require("../cloud/auth.cjs").cloudSessionCookie());
20
+ } catch {
21
+ return false;
22
+ }
23
+ }
24
+
17
25
  function roleDetail(selection, role, en) {
18
26
  if (!selection) return en ? "not set" : "미설정";
19
27
  const provider = selection.kind === "byok" ? selection.backend || "byok" : selection.kind;
@@ -28,6 +36,12 @@ function roleDetail(selection, role, en) {
28
36
 
29
37
  async function run(ctx, args = []) {
30
38
  const en = ctx.lang === "en";
39
+ const unknown = args.filter((arg) => arg !== "--json");
40
+ if (unknown.length || args.filter((arg) => arg === "--json").length > 1) {
41
+ const error = new Error("usage: agentlas doctor [--json]");
42
+ error.code = "INVALID_ARGUMENT";
43
+ throw error;
44
+ }
31
45
  // clig.dev: 스크립트 소비자를 위한 기계 계약. 사람용 줄과 같은 사실만 담는다.
32
46
  if (ctx.output?.format === "json" || args.includes("--json")) {
33
47
  const db = ctx.db();
@@ -43,7 +57,9 @@ async function run(ctx, args = []) {
43
57
  orchestrator: resolvedModelRole(db, "orchestrator"),
44
58
  worker: resolvedModelRole(db, "worker"),
45
59
  },
46
- cloudSession: Boolean(process.env.AGENTLAS_SESSION) || fs.existsSync(path.join(userDataDir(), "auth", "cli-session.v1.json")),
60
+ // JSON is intentionally offline, but it must use the same validated
61
+ // credential interpretation as the human path rather than file existence.
62
+ cloudSession: hasCloudSessionCredential(),
47
63
  }, null, 2));
48
64
  return 0;
49
65
  })();
@@ -26,13 +26,42 @@ function toUrl(src) {
26
26
  return "file://" + path.resolve(src); // 로컬 파일
27
27
  }
28
28
 
29
+ function documentArgumentError(message) {
30
+ const error = new Error(message);
31
+ error.code = "INVALID_ARGUMENT";
32
+ return error;
33
+ }
34
+
35
+ function parsePdfArgs(args, ko) {
36
+ let landscape = false;
37
+ let out = null;
38
+ let src = null;
39
+ for (let index = 0; index < args.length; index += 1) {
40
+ const token = String(args[index]);
41
+ if (token === "--landscape") {
42
+ if (landscape) throw documentArgumentError(usage(ko));
43
+ landscape = true;
44
+ continue;
45
+ }
46
+ if (token === "-o") {
47
+ if (out !== null) throw documentArgumentError(usage(ko));
48
+ const value = args[index + 1];
49
+ if (!value || String(value).startsWith("-")) throw documentArgumentError(usage(ko));
50
+ out = String(value);
51
+ index += 1;
52
+ continue;
53
+ }
54
+ if (token.startsWith("-")) throw documentArgumentError(usage(ko));
55
+ if (src !== null) throw documentArgumentError(usage(ko));
56
+ src = token;
57
+ }
58
+ if (!src) throw documentArgumentError(usage(ko));
59
+ return { landscape, out, src };
60
+ }
61
+
29
62
  async function toPdf(ctx, args) {
30
63
  const ko = ctx.lang === "ko";
31
- const flags = { landscape: args.includes("--landscape") };
32
- const oi = args.indexOf("-o");
33
- const out = oi >= 0 && args[oi + 1] ? args[oi + 1] : null;
34
- const src = args.find((a, i) => a && !String(a).startsWith("-") && (oi < 0 || i !== oi + 1));
35
- if (!src) { ctx.err(usage(ko)); return 1; }
64
+ const { landscape, out, src } = parsePdfArgs(args, ko);
36
65
 
37
66
  // 로컬 파일이면 존재 확인(없는 걸 조용히 빈 PDF 로 굽지 않는다).
38
67
  if (!/^[a-z][a-z0-9+.-]*:\/\//i.test(src) && !fs.existsSync(path.resolve(src))) {
@@ -54,7 +83,7 @@ async function toPdf(ctx, args) {
54
83
  page = await cdp.attachPage({ selection: "new" });
55
84
  await page.navigate(url, { waitMs: 1800 });
56
85
  await page.waitFor("document.readyState === 'complete'", { timeoutMs: 8000 });
57
- const r = await page.printPdf(outPath, { landscape: flags.landscape });
86
+ const r = await page.printPdf(outPath, { landscape });
58
87
  if (typeof ctx.ui.stopSpinner === "function") ctx.ui.stopSpinner();
59
88
  ctx.out(`${ctx.ui.green("✓")} ${ko ? "PDF 를 만들었습니다" : "wrote PDF"}: ${r.path} ${ctx.ui.dim(`(${Math.round(r.bytes / 1024)} KB)`)}`);
60
89
  return 0;
@@ -70,10 +99,9 @@ async function toPdf(ctx, args) {
70
99
  async function run(ctx, args) {
71
100
  const ko = ctx.lang === "ko";
72
101
  const sub = String(args[0] || "").toLowerCase();
73
- if (!sub || sub === "help" || sub === "-h" || sub === "--help") { ctx.out(usage(ko)); return 0; }
102
+ if (!sub || (["help", "-h", "--help"].includes(sub) && args.length === 1)) { ctx.out(usage(ko)); return 0; }
74
103
  if (sub === "pdf") return toPdf(ctx, args.slice(1));
75
- ctx.err(usage(ko));
76
- return 1;
104
+ throw documentArgumentError(usage(ko));
77
105
  }
78
106
 
79
- module.exports = { run };
107
+ module.exports = { run, parsePdfArgs };
@@ -21,7 +21,12 @@ function sharedEnvKeys() {
21
21
  return Object.keys(fromFiles).sort();
22
22
  }
23
23
 
24
- function run(ctx) {
24
+ function run(ctx, args = []) {
25
+ if (args.length) {
26
+ const error = new Error("usage: agentlas env");
27
+ error.code = "INVALID_ARGUMENT";
28
+ throw error;
29
+ }
25
30
  const keys = sharedEnvKeys();
26
31
  ctx.out(`Shared env keys: ${keys.length} (values hidden; from credentials.env):`);
27
32
  for (const k of keys) ctx.out(` ${k}`);
@@ -9,6 +9,42 @@
9
9
  */
10
10
  const { rowToAgent } = require("../agents/registry.cjs");
11
11
 
12
+ function parseFirmArgs(args) {
13
+ const flags = { runtime: null, model: null, effort: null, tier: null, permission: null, task: [] };
14
+ const fields = new Map([
15
+ ["--runtime", "runtime"],
16
+ ["--model", "model"],
17
+ ["--effort", "effort"],
18
+ ["--tier", "tier"],
19
+ ["--permission", "permission"],
20
+ ]);
21
+ const seen = new Set();
22
+ let passthrough = false;
23
+ for (let i = 0; i < args.length; i += 1) {
24
+ const token = String(args[i]);
25
+ if (passthrough) { flags.task.push(token); continue; }
26
+ if (token === "--") { passthrough = true; continue; }
27
+ const at = token.indexOf("=");
28
+ const option = at >= 0 ? token.slice(0, at) : token;
29
+ const field = fields.get(option);
30
+ if (field) {
31
+ if (seen.has(field)) throw new Error(`duplicate option: ${option}`);
32
+ seen.add(field);
33
+ const value = at >= 0 ? token.slice(at + 1) : args[++i];
34
+ if (value === undefined || value === "" || (at < 0 && String(value).startsWith("--"))) {
35
+ throw new Error(`${option} requires a value`);
36
+ }
37
+ flags[field] = String(value);
38
+ continue;
39
+ }
40
+ if (token.startsWith("-")) {
41
+ throw new Error(`unknown option: ${token} (use -- before a task that starts with '-')`);
42
+ }
43
+ flags.task.push(token);
44
+ }
45
+ return flags;
46
+ }
47
+
12
48
  function findFirm(db, token) {
13
49
  const q = String(token || "").trim().toLowerCase();
14
50
  if (!q) return null;
@@ -50,17 +86,11 @@ async function run(ctx, args) {
50
86
  }
51
87
  const ceo = rowToAgent(ceoRow);
52
88
 
53
- // 간단 플래그 파싱 — 명령끼리 참조 금지 규칙상 run.cjs 미차용.
54
- const rest = args.slice(1);
55
- const flags = { runtime: null, model: null, effort: null, tier: null, permission: null, task: [] };
56
- for (let i = 0; i < rest.length; i++) {
57
- if (rest[i] === "--runtime") flags.runtime = rest[++i];
58
- else if (rest[i] === "--model") flags.model = rest[++i];
59
- else if (rest[i] === "--effort") flags.effort = rest[++i];
60
- else if (rest[i] === "--tier") flags.tier = rest[++i];
61
- else if (rest[i] === "--permission") flags.permission = rest[++i];
62
- else flags.task.push(rest[i]);
63
- }
89
+ // 명령끼리 참조 금지 규칙상 run.cjs 미차용. 오타 옵션은 CEO 계획 턴으로
90
+ // 흘리지 않고, 대시로 시작하는 실제 작업은 `--` 뒤에서만 받는다.
91
+ let flags;
92
+ try { flags = parseFirmArgs(args.slice(1)); }
93
+ catch (error) { ctx.err(String((error && error.message) || error)); return 1; }
64
94
  const task = flags.task.join(" ").trim();
65
95
  if (task) {
66
96
  // 3-tier 위임 실행 — PLAN → DELEGATE → SYNTHESIZE (firms/orchestrate.cjs).
@@ -176,4 +206,4 @@ async function run(ctx, args) {
176
206
  return startRepl(ctx, { agent: ceo.slug });
177
207
  }
178
208
 
179
- module.exports = { run, findFirm };
209
+ module.exports = { run, findFirm, parseFirmArgs };
@@ -15,7 +15,11 @@ const fs = require("node:fs");
15
15
  const path = require("node:path");
16
16
  const pkgLib = require("../graph/package.cjs");
17
17
  const desktopCore = require("../core/desktop-core.cjs");
18
+ const { terminalTextOf } = require("../cli-output.cjs");
18
19
 
20
+ function inlineText(value, maxLength = 500) {
21
+ return terminalTextOf(value, maxLength).replace(/\s+/g, " ").trim();
22
+ }
19
23
 
20
24
 
21
25
  function graphRows(ctx, db) {
@@ -116,7 +120,7 @@ function describe(ctx, row, graph, en) {
116
120
  const kindLabel = kind === "cron"
117
121
  ? (en ? "schedule" : "예약")
118
122
  : (en ? "input" : "입력");
119
- return `${ctx.ui.bold(row.name)} ${ctx.ui.dim(`${kindLabel} · ${nodeCount} ${en ? "steps" : "단계"} · ${state} · ${when}`)}`;
123
+ return `${ctx.ui.bold(inlineText(row.name))} ${ctx.ui.dim(`${kindLabel} · ${nodeCount} ${en ? "steps" : "단계"} · ${state} · ${inlineText(when)}`)}`;
120
124
  }
121
125
 
122
126
  function findGraph(rows, needle) {
@@ -135,9 +139,9 @@ function findGraph(rows, needle) {
135
139
  /** 이름이 여러 개 걸렸을 때, 고르지 말고 후보를 보여준다. */
136
140
  function reportAmbiguous(ctx, needle, matches, en) {
137
141
  ctx.err(en
138
- ? `"${needle}" matches ${matches.length} automations. Say which one:`
139
- : `"${needle}"에 자동화 ${matches.length}개가 걸립니다. 어느 것인지 정확히 적어 주세요:`);
140
- for (const row of matches) ctx.err(` · ${row.name}`);
142
+ ? `"${inlineText(needle)}" matches ${matches.length} automations. Say which one:`
143
+ : `"${inlineText(needle)}"에 자동화 ${matches.length}개가 걸립니다. 어느 것인지 정확히 적어 주세요:`);
144
+ for (const row of matches) ctx.err(` · ${inlineText(row.name)}`);
141
145
  }
142
146
 
143
147
  function listGraphs(ctx) {
@@ -240,8 +244,8 @@ async function reconcileGraph(ctx, needle, flags, en) {
240
244
  const view = reconciliation.get(row.id);
241
245
  if (!view) {
242
246
  ctx.out(en
243
- ? `"${row.name}" has nothing waiting to be reconciled.`
244
- : `"${row.name}"에는 재조정을 기다리는 것이 없습니다.`);
247
+ ? `"${inlineText(row.name)}" has nothing waiting to be reconciled.`
248
+ : `"${inlineText(row.name)}"에는 재조정을 기다리는 것이 없습니다.`);
245
249
  return 0;
246
250
  }
247
251
 
@@ -255,34 +259,34 @@ async function reconcileGraph(ctx, needle, flags, en) {
255
259
 
256
260
  if (done.size === 0 && notDone.size === 0) {
257
261
  ctx.out(en
258
- ? `"${row.name}" stopped with steps whose effect cannot be told from the record.`
259
- : `"${row.name}"은(는) 기록만으로는 일어났는지 알 수 없는 단계가 있어 멈춰 있습니다.`);
262
+ ? `"${inlineText(row.name)}" stopped with steps whose effect cannot be told from the record.`
263
+ : `"${inlineText(row.name)}"은(는) 기록만으로는 일어났는지 알 수 없는 단계가 있어 멈춰 있습니다.`);
260
264
  ctx.out("");
261
265
  for (const node of view.nodes) {
262
266
  const needs = node.outputRequired
263
267
  ? (en ? ` (needs --output ${node.nodeId}=<value> when done)` : ` (일어났다면 --output ${node.nodeId}=<값> 필요)`)
264
268
  : "";
265
- ctx.out(` ${node.nodeId} ${node.label}${needs}`);
269
+ ctx.out(` ${inlineText(node.nodeId)} ${inlineText(node.label)}${inlineText(needs)}`);
266
270
  }
267
271
  ctx.out("");
268
272
  ctx.out(ctx.ui.dim(en
269
- ? `Decide each one: agentlas graph reconcile "${row.name}" --done <node> --not-done <node>`
270
- : `각각 정하세요: agentlas graph reconcile "${row.name}" --done <노드> --not-done <노드>`));
273
+ ? `Decide each one: agentlas graph reconcile "${inlineText(row.name)}" --done <node> --not-done <node>`
274
+ : `각각 정하세요: agentlas graph reconcile "${inlineText(row.name)}" --done <노드> --not-done <노드>`));
271
275
  return 0;
272
276
  }
273
277
 
274
278
  const undecided = view.nodes.filter((n) => !done.has(n.nodeId) && !notDone.has(n.nodeId));
275
279
  if (undecided.length > 0) {
276
280
  ctx.err(en
277
- ? `Still undecided: ${undecided.map((n) => n.nodeId).join(", ")}. Every step needs one answer.`
278
- : `아직 안 정한 단계가 있습니다: ${undecided.map((n) => n.nodeId).join(", ")}. 모든 단계에 답이 필요합니다.`);
281
+ ? `Still undecided: ${undecided.map((n) => inlineText(n.nodeId)).join(", ")}. Every step needs one answer.`
282
+ : `아직 안 정한 단계가 있습니다: ${undecided.map((n) => inlineText(n.nodeId)).join(", ")}. 모든 단계에 답이 필요합니다.`);
279
283
  return 1;
280
284
  }
281
285
  const missingOutput = view.nodes.filter((n) => done.has(n.nodeId) && n.outputRequired && !outputs.has(n.nodeId));
282
286
  if (missingOutput.length > 0) {
283
287
  ctx.err(en
284
- ? `These steps populate a value, so completing them needs it: ${missingOutput.map((n) => `--output ${n.nodeId}=<value>`).join(" ")}`
285
- : `값을 만드는 단계라 "일어났다"로 두려면 값이 필요합니다: ${missingOutput.map((n) => `--output ${n.nodeId}=<값>`).join(" ")}`);
288
+ ? `These steps populate a value, so completing them needs it: ${missingOutput.map((n) => `--output ${inlineText(n.nodeId)}=<value>`).join(" ")}`
289
+ : `값을 만드는 단계라 "일어났다"로 두려면 값이 필요합니다: ${missingOutput.map((n) => `--output ${inlineText(n.nodeId)}=<값>`).join(" ")}`);
286
290
  return 1;
287
291
  }
288
292
 
@@ -304,8 +308,8 @@ async function reconcileGraph(ctx, needle, flags, en) {
304
308
  ? `Reconciled. ${result.completedNodeIds.length} step(s) marked as done, ${result.retryNodeIds.length} to redo.`
305
309
  : `재조정했습니다. ${result.completedNodeIds.length}개는 일어난 것으로, ${result.retryNodeIds.length}개는 다시 하는 것으로 두었습니다.`);
306
310
  ctx.out(ctx.ui.dim(en
307
- ? `Now run it: agentlas graph run "${row.name}"`
308
- : `이제 실행하세요: agentlas graph run "${row.name}"`));
311
+ ? `Now run it: agentlas graph run "${inlineText(row.name)}"`
312
+ : `이제 실행하세요: agentlas graph run "${inlineText(row.name)}"`));
309
313
  return 0;
310
314
  } catch (error) {
311
315
  ctx.err(en
@@ -342,8 +346,8 @@ function showGraph(ctx, needle) {
342
346
  const requirement = graphInputRequirement(graph, en);
343
347
  if (requirement) {
344
348
  ctx.out(" " + ctx.ui.dim(en
345
- ? `Starts from a value you provide — ${requirement.label}`
346
- : `시작할 때 값을 받습니다 — ${requirement.label}`));
349
+ ? `Starts from a value you provide — ${inlineText(requirement.label)}`
350
+ : `시작할 때 값을 받습니다 — ${inlineText(requirement.label)}`));
347
351
  }
348
352
  ctx.out("");
349
353
  renderGraphTree(ctx, graph, en);
@@ -354,8 +358,8 @@ function showGraph(ctx, needle) {
354
358
  ? `This graph will stop when it runs (${problems.length}):`
355
359
  : `이대로 실행하면 도중에 멈춥니다 (${problems.length}건):`));
356
360
  for (const p of problems) {
357
- ctx.out(` ⚠ ${p.what}`);
358
- ctx.out(` ${ctx.ui.dim(p.fix)}`);
361
+ ctx.out(` ⚠ ${inlineText(p.what, 1000)}`);
362
+ ctx.out(` ${ctx.ui.dim(inlineText(p.fix, 1000))}`);
359
363
  }
360
364
  }
361
365
  // 그대로 복사해 쓸 수 있는 명령. 예전에는 값이 필요한 그래프인지 화면에서만 알려주고
@@ -363,8 +367,8 @@ function showGraph(ctx, needle) {
363
367
  ctx.out("");
364
368
  ctx.out(ctx.ui.dim(en ? "Run it with:" : "실행하려면:"));
365
369
  ctx.out(requirement
366
- ? ` agentlas graph run "${row.name}" --input "<${requirement.label}>"`
367
- : ` agentlas graph run "${row.name}"`);
370
+ ? ` agentlas graph run "${inlineText(row.name)}" --input "<${inlineText(requirement.label)}>"`
371
+ : ` agentlas graph run "${inlineText(row.name)}"`);
368
372
  return 0;
369
373
  }
370
374
 
@@ -469,17 +473,17 @@ function nodeLine(ctx, node, en) {
469
473
  const rule = conditionRule(node, en);
470
474
  if (rule) marks.unshift(rule);
471
475
  }
472
- return `${ctx.ui.accent(kindWord(node.type, en))} ${node.label || node.id}`
473
- + (marks.length ? ctx.ui.dim(` — ${marks.join(", ")}`) : "");
476
+ return `${ctx.ui.accent(inlineText(kindWord(node.type, en)))} ${inlineText(node.label || node.id)}`
477
+ + (marks.length ? ctx.ui.dim(` — ${marks.map((mark) => inlineText(mark)).join(", ")}`) : "");
474
478
  }
475
479
 
476
480
  /** 갈림길이 실제로 무엇을 보는지 한 줄로. 모르는 연산은 지어내지 않고 그대로 보여준다. */
477
481
  function conditionRule(node, en) {
478
482
  const cfg = node.config || {};
479
- const v = cfg.var;
483
+ const v = inlineText(cfg.var);
480
484
  if (typeof v !== "string" || !v.trim()) return null;
481
485
  const value = cfg.value;
482
- const shown = typeof value === "string" ? `"${value}"` : String(value ?? "");
486
+ const shown = typeof value === "string" ? `"${inlineText(value)}"` : inlineText(value);
483
487
  switch (cfg.op) {
484
488
  case "contains": return en ? `yes when {{${v}}} contains ${shown}` : `{{${v}}}에 ${shown}이(가) 들어 있으면 예`;
485
489
  case "truthy": return en ? `yes when {{${v}}} has a value` : `{{${v}}}에 값이 있으면 예`;
@@ -488,7 +492,7 @@ function conditionRule(node, en) {
488
492
  case "neq": return en ? `yes when {{${v}}} differs from ${shown}` : `{{${v}}}이(가) ${shown}과 다르면 예`;
489
493
  case "gt": return en ? `yes when {{${v}}} > ${shown}` : `{{${v}}}이(가) ${shown}보다 크면 예`;
490
494
  case "lt": return en ? `yes when {{${v}}} < ${shown}` : `{{${v}}}이(가) ${shown}보다 작으면 예`;
491
- default: return en ? `checks {{${v}}} with "${cfg.op ?? "?"}"` : `{{${v}}}을(를) "${cfg.op ?? "?"}"(으)로 검사`;
495
+ default: return en ? `checks {{${v}}} with "${inlineText(cfg.op ?? "?")}"` : `{{${v}}}을(를) "${inlineText(cfg.op ?? "?")}"(으)로 검사`;
492
496
  }
493
497
  }
494
498
 
@@ -525,7 +529,7 @@ function kindWord(type, en) {
525
529
  const read = vocabulary.readEnum(type, vocabulary.GRAPH_NODE_KINDS);
526
530
  if ("unknown" in read) {
527
531
  // 이 버전이 모르는 종류 — 지어내지 않고 그렇다고 말한다(원문 보존).
528
- return vocabulary.degradedLabel(read, en ? "en" : "ko");
532
+ return vocabulary.degradedLabel({ unknown: inlineText(read.unknown) }, en ? "en" : "ko");
529
533
  }
530
534
  return (en ? enWords[read.known] : ko[read.known]) || read.known;
531
535
  }
@@ -557,8 +561,8 @@ function renderGraphTree(ctx, graph, en) {
557
561
  ? (en ? " — no repeat limit set, so it will refuse to run" : " — 반복 횟수가 정해져 있지 않아 실행이 거절됩니다")
558
562
  : (en ? ` — up to ${cap} more time(s)` : ` — 최대 ${cap}바퀴까지`);
559
563
  ctx.out(`${indent}${prefix}↩ ${ctx.ui.dim(en
560
- ? `back to "${node.label || node.id}"${capText}`
561
- : `"${node.label || node.id}"(으)로 되돌아감${capText}`)}`);
564
+ ? `back to "${inlineText(node.label || node.id)}"${capText}`
565
+ : `"${inlineText(node.label || node.id)}"(으)로 되돌아감${capText}`)}`);
562
566
  return;
563
567
  }
564
568
  seen.add(nodeId);
@@ -568,7 +572,7 @@ function renderGraphTree(ctx, graph, en) {
568
572
  for (const item of items) {
569
573
  if (!item || typeof item.text !== "string" || !item.text.trim()) continue;
570
574
  const mark = item.kind === "mustNot" ? (en ? "must not" : "하면 안 됨") : (en ? "must" : "있어야 함");
571
- ctx.out(`${indent} ${ctx.ui.dim(`· [${mark}] ${item.text.trim()}`)}`);
575
+ ctx.out(`${indent} ${ctx.ui.dim(`· [${mark}] ${inlineText(item.text, 1000)}`)}`);
572
576
  }
573
577
  }
574
578
  const edges = outgoing.get(nodeId) ?? [];
@@ -10,9 +10,19 @@
10
10
  const catalog = require("../ui/commands-catalog.cjs");
11
11
 
12
12
  function run(ctx, args = []) {
13
+ if (!Array.isArray(args) || args.length > 1) {
14
+ const error = new Error("usage: agentlas help [all|<command>]");
15
+ error.code = "INVALID_ARGUMENT";
16
+ throw error;
17
+ }
13
18
  const first = String((args && args[0]) || "").trim();
19
+ if (first.startsWith("-")) {
20
+ const error = new Error("usage: agentlas help [all|<command>]");
21
+ error.code = "INVALID_ARGUMENT";
22
+ throw error;
23
+ }
14
24
  const all = first.toLowerCase() === "all";
15
- const name = !all && first && !first.startsWith("-") ? first : null;
25
+ const name = !all && first ? first : null;
16
26
  if (name) return runForCommand(ctx, name);
17
27
  ctx.out(catalog.renderHelp({ lang: ctx.lang, surface: "cli", all }));
18
28
  return 0;
@@ -13,7 +13,7 @@
13
13
  const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
14
14
 
15
15
  async function run(ctx, args) {
16
- if (args.length && args.some(isHelpToken)) {
16
+ if (args.length === 1 && isHelpToken(args[0])) {
17
17
  ctx.out(usageFor("hep", ctx.lang));
18
18
  return 0;
19
19
  }
@@ -4,11 +4,12 @@ const { importLocalFolder } = require("../agents/import-local.cjs");
4
4
 
5
5
  function run(ctx, args) {
6
6
  const ko = ctx.lang === "ko";
7
- const absPath = args[0];
8
- if (!absPath) {
9
- ctx.err(ko ? "사용법: agentlas import <폴더-경로>" : "Usage: agentlas import <folder-path>");
10
- return 1;
7
+ if (args.length !== 1) {
8
+ const error = new Error(ko ? "사용법: agentlas import <폴더-경로>" : "Usage: agentlas import <folder-path>");
9
+ error.code = "INVALID_ARGUMENT";
10
+ throw error;
11
11
  }
12
+ const absPath = args[0];
12
13
  let r;
13
14
  try {
14
15
  r = importLocalFolder(ctx.db(), absPath);
@@ -181,7 +181,7 @@ function resolveCommandName(cmd) {
181
181
  * 인자로 읽는다(`agentlas run --help` 가 "help"라는 이름을 찾는 식). 추측하지 않는다.
182
182
  * `test/command-help-contract.cjs` 가 이 목록과 실제 분기를 대조한다.
183
183
  */
184
- const SELF_HELP_COMMANDS = new Set(["graph", "plugin", "billing", "roles", "native"]);
184
+ const SELF_HELP_COMMANDS = new Set(["graph", "plugin", "billing", "roles", "native", "research"]);
185
185
 
186
186
  function dispatch(ctx, argv) {
187
187
  const [rawCmd, ...rest] = argv;
@@ -7,11 +7,13 @@
7
7
  const { installHubAgent } = require("../hub/install.cjs");
8
8
 
9
9
  async function run(ctx, args) {
10
- const slug = (args[0] || "").trim();
11
- if (!slug) {
12
- ctx.err("usage: agentlas install <slug>");
13
- return 1;
10
+ if (args.length !== 1 || String(args[0] || "").startsWith("-")) {
11
+ const error = new Error("usage: agentlas install <slug>");
12
+ error.code = "INVALID_ARGUMENT";
13
+ throw error;
14
14
  }
15
+ const slug = String(args[0]).trim();
16
+ if (!slug) throw Object.assign(new Error("usage: agentlas install <slug>"), { code: "INVALID_ARGUMENT" });
15
17
  let agent;
16
18
  try {
17
19
  agent = await installHubAgent(ctx.db(), slug);