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
@@ -28,14 +28,25 @@ function descriptor() {
28
28
  }
29
29
 
30
30
  async function run(ctx, args) {
31
- if (args.includes("--info") || args.includes("--json")) {
31
+ const descriptorMode = ctx.output?.format === "json" || (args.length === 1 && ["--info", "--json"].includes(args[0]));
32
+ if (descriptorMode) {
33
+ if (args.some((arg) => !["--info", "--json"].includes(arg)) || new Set(args).size !== args.length) {
34
+ const error = new Error("Usage: agentlas acp [--info]");
35
+ error.code = "INVALID_ARGUMENT";
36
+ throw error;
37
+ }
32
38
  ctx.out(JSON.stringify(descriptor(), null, 2));
33
39
  return 0;
34
40
  }
35
- if (args.includes("--help") || args.includes("help")) {
41
+ if (args.length === 1 && ["--help", "-h", "help"].includes(args[0])) {
36
42
  ctx.out("Usage: agentlas acp [--info]\n Speak the Agent Client Protocol (v1) on stdio so an editor can run Agentlas as its agent.");
37
43
  return 0;
38
44
  }
45
+ if (args.length) {
46
+ const error = new Error("Usage: agentlas acp [--info]");
47
+ error.code = "INVALID_ARGUMENT";
48
+ throw error;
49
+ }
39
50
  // stdout is the protocol wire from here on: route everything human to stderr.
40
51
  const server = new AcpAgentServer({ ctx, input: process.stdin, output: process.stdout });
41
52
  await server.start();
@@ -15,6 +15,39 @@ const schedule = require("../automation/schedule.cjs");
15
15
  const store = require("../automation/store.cjs");
16
16
  const daemon = require("../automation/daemon.cjs");
17
17
 
18
+ function parseOptions(args, schema) {
19
+ const values = new Set(schema.values || []);
20
+ const booleans = new Set(schema.booleans || []);
21
+ const out = {};
22
+ for (let i = 0; i < args.length; i += 1) {
23
+ const token = String(args[i]);
24
+ if (!token.startsWith("--") || token === "--") throw new Error(`unexpected argument: ${token}`);
25
+ const at = token.indexOf("=");
26
+ const key = token.slice(2, at >= 0 ? at : undefined);
27
+ if (!values.has(key) && !booleans.has(key)) throw new Error(`unknown option: --${key}`);
28
+ if (Object.prototype.hasOwnProperty.call(out, key)) throw new Error(`duplicate option: --${key}`);
29
+ if (booleans.has(key)) {
30
+ if (at >= 0) throw new Error(`--${key} does not take a value`);
31
+ out[key] = true;
32
+ continue;
33
+ }
34
+ const value = at >= 0 ? token.slice(at + 1) : args[++i];
35
+ if (value === undefined || value === "" || (at < 0 && String(value).startsWith("--"))) {
36
+ throw new Error(`--${key} requires a value`);
37
+ }
38
+ out[key] = String(value);
39
+ }
40
+ return out;
41
+ }
42
+
43
+ function intervalOption(raw, minimum, label = "--interval") {
44
+ const parsed = Number(raw);
45
+ if (!Number.isInteger(parsed) || parsed < minimum) {
46
+ throw new Error(`${label} requires an integer of at least ${minimum} seconds`);
47
+ }
48
+ return parsed;
49
+ }
50
+
18
51
  function resolveFirm(db, token) {
19
52
  const q = String(token || "").trim();
20
53
  if (!q) return null;
@@ -34,6 +67,7 @@ async function run(ctx, args) {
34
67
  const fail = (msg) => { ctx.err(msg); return 1; };
35
68
 
36
69
  if (sub === "list") {
70
+ if (args.length !== 1 && args.length !== 0) return fail(`unexpected argument: ${String(args[1])}`);
37
71
  const rows = store.listAutomations(db);
38
72
  if (!rows.length) {
39
73
  ctx.out(ko
@@ -62,17 +96,14 @@ async function run(ctx, args) {
62
96
 
63
97
  if (sub === "add") {
64
98
  // agentlas automation add --name "..." --agent <slug>|--firm <slug> --cron "0 9 * * *" --prompt "..."
65
- const flags = {};
66
- for (let i = 1; i < args.length; i++) {
67
- const a = args[i];
68
- if (a === "--name") flags.name = args[++i];
69
- else if (a === "--agent") flags.agent = args[++i];
70
- else if (a === "--firm") flags.firm = args[++i];
71
- else if (a === "--cron") flags.cron = args[++i];
72
- else if (a === "--prompt") flags.prompt = args[++i];
73
- else if (a === "--tz") flags.tz = args[++i];
74
- else if (a === "--disabled") flags.disabled = true;
75
- }
99
+ let flags;
100
+ try {
101
+ flags = parseOptions(args.slice(1), {
102
+ values: ["name", "agent", "firm", "cron", "prompt", "tz"],
103
+ booleans: ["disabled"],
104
+ });
105
+ } catch (error) { return fail(String((error && error.message) || error)); }
106
+ if (flags.agent && flags.firm) return fail("use only one of --agent or --firm");
76
107
  if (!flags.cron || !flags.prompt || (!flags.agent && !flags.firm)) {
77
108
  ctx.err(ko
78
109
  ? '사용법: agentlas automation add --name "이름" --agent <슬러그>|--firm <슬러그> --cron "0 9 * * *" --prompt "지시" [--tz Asia/Seoul] [--disabled]'
@@ -128,9 +159,15 @@ async function run(ctx, args) {
128
159
  if (sub === "on" || sub === "off") {
129
160
  const idPrefix = args[1];
130
161
  if (!idPrefix) return fail(usage(`${sub} <id>`));
162
+ if (args.length !== 2) return fail(`unexpected argument: ${String(args[2])}`);
131
163
  const row = store.getAutomationByPrefix(db, idPrefix);
132
164
  if (!row) return fail(missingAutomation(idPrefix));
133
- store.setEnabled(db, row, sub === "on");
165
+ const outcome = store.setEnabledChecked(db, row, sub === "on");
166
+ if (!outcome.changed) {
167
+ return fail(ko
168
+ ? `자동화가 변경되는 동안 사라졌습니다: ${row.id}`
169
+ : `Automation changed before it could be updated: ${row.id}`);
170
+ }
134
171
  ctx.out(`${sub === "on" ? (ko ? "활성화됨" : "Enabled") : (ko ? "비활성화됨" : "Disabled")}: ${row.id.slice(0, 8)} ${row.name}`);
135
172
  return 0;
136
173
  }
@@ -138,14 +175,20 @@ async function run(ctx, args) {
138
175
  if (sub === "remove" || sub === "rm") {
139
176
  const idPrefix = args[1];
140
177
  if (!idPrefix) return fail(usage("remove <id>"));
178
+ if (args.length !== 2) return fail(`unexpected argument: ${String(args[2])}`);
141
179
  const row = store.getAutomationByPrefix(db, idPrefix);
142
180
  if (!row) return fail(missingAutomation(idPrefix));
143
- store.removeAutomation(db, row.id);
181
+ if (!store.removeAutomation(db, row.id)) {
182
+ return fail(ko
183
+ ? `자동화가 삭제되는 동안 사라졌습니다: ${row.id}`
184
+ : `Automation changed before it could be deleted: ${row.id}`);
185
+ }
144
186
  ctx.out(`${ko ? "삭제됨" : "Deleted"}: ${row.id.slice(0, 8)} ${row.name}`);
145
187
  return 0;
146
188
  }
147
189
 
148
190
  if (sub === "runs") {
191
+ if (args.length !== 1) return fail(`unexpected argument: ${String(args[1])}`);
149
192
  const rows = store.listRuns(db, 15);
150
193
  if (!rows.length) {
151
194
  ctx.out(ko ? "실행 기록이 없습니다." : "No run history.");
@@ -165,11 +208,11 @@ async function run(ctx, args) {
165
208
 
166
209
  if (sub === "run") {
167
210
  const idPrefix = args[1];
168
- let runtimeOverride = null;
169
- for (let i = 2; i < args.length; i++) {
170
- if (args[i] === "--runtime") runtimeOverride = args[++i];
171
- }
172
211
  if (!idPrefix) return fail(usage("run <id>"));
212
+ let runFlags;
213
+ try { runFlags = parseOptions(args.slice(2), { values: ["runtime"], booleans: [] }); }
214
+ catch (error) { return fail(String((error && error.message) || error)); }
215
+ const runtimeOverride = runFlags.runtime || null;
173
216
  const row = store.getAutomationByPrefix(db, idPrefix);
174
217
  if (!row) return fail(missingAutomation(idPrefix));
175
218
  // run-now는 스케줄을 건드리지 않는다 (앱의 advanceSchedule=false와 동일).
@@ -190,17 +233,19 @@ async function run(ctx, args) {
190
233
  }
191
234
 
192
235
  if (sub === "daemon") {
236
+ let daemonFlags;
237
+ try { daemonFlags = parseOptions(args.slice(1), { values: ["interval", "runtime"], booleans: [] }); }
238
+ catch (error) { return fail(String((error && error.message) || error)); }
193
239
  let interval = 30;
194
- let runtimeOverride = null;
195
- for (let i = 1; i < args.length; i++) {
196
- if (args[i] === "--interval") interval = Math.max(10, Number(args[++i]) || 30);
197
- else if (args[i] === "--runtime") runtimeOverride = args[++i];
198
- }
240
+ try { if (daemonFlags.interval !== undefined) interval = intervalOption(daemonFlags.interval, 10); }
241
+ catch (error) { return fail(String((error && error.message) || error)); }
242
+ const runtimeOverride = daemonFlags.runtime || null;
199
243
  return daemon.automationDaemon(ctx, db, { intervalSec: interval, runtimeOverride });
200
244
  }
201
245
 
202
246
  // 1회 due 스윕 후 종료 — launchd/cron 이 poke 하는 진입점(상주 루프 아님).
203
247
  if (sub === "tick") {
248
+ if (args.length !== 1) return fail(`unexpected argument: ${String(args[1])}`);
204
249
  await daemon.daemonTick(ctx, db, {});
205
250
  return 0;
206
251
  }
@@ -209,6 +254,7 @@ async function run(ctx, args) {
209
254
  if (sub === "install" || sub === "uninstall" || sub === "status") {
210
255
  const launchd = require("../automation/launchd.cjs");
211
256
  if (sub === "status") {
257
+ if (args.length !== 1) return fail(`unexpected argument: ${String(args[1])}`);
212
258
  const st = launchd.launchdStatus();
213
259
  if (!st.supported) { ctx.out(ko ? "launchd 상주는 macOS 전용입니다. 다른 OS 는 `agentlas automation daemon` 을 켜 두세요." : "launchd persistence is macOS-only. On other systems keep `agentlas automation daemon` running."); return 0; }
214
260
  ctx.out(`${st.loaded ? ctx.ui.green("✓") : ctx.ui.dim("○")} ${ko ? "상주(launchd)" : "persistence (launchd)"}: ${st.loaded ? (ko ? "실행 중" : "loaded") : st.installed ? (ko ? "설치됨(미로드)" : "installed (not loaded)") : (ko ? "미설치" : "not installed")}`);
@@ -217,8 +263,12 @@ async function run(ctx, args) {
217
263
  return 0;
218
264
  }
219
265
  if (sub === "install") {
266
+ let installFlags;
267
+ try { installFlags = parseOptions(args.slice(1), { values: ["interval"], booleans: [] }); }
268
+ catch (error) { return fail(String((error && error.message) || error)); }
220
269
  let interval = 300;
221
- for (let i = 1; i < args.length; i++) if (args[i] === "--interval") interval = Math.max(30, Number(args[++i]) || 300);
270
+ try { if (installFlags.interval !== undefined) interval = intervalOption(installFlags.interval, 30); }
271
+ catch (error) { return fail(String((error && error.message) || error)); }
222
272
  const st = launchd.enableLaunchd({ intervalSec: interval });
223
273
  if (st.error) { ctx.err(`${ctx.ui.red("✖")} ${st.error}`); return 1; }
224
274
  ctx.out(`${ctx.ui.green("✓")} ${ko ? "상주를 켰습니다 — 앱/창이 꺼져 있어도 자동화가 발동합니다" : "persistence on — automations fire even with the app/window closed"} (${interval}s)`);
@@ -226,6 +276,7 @@ async function run(ctx, args) {
226
276
  return 0;
227
277
  }
228
278
  // uninstall
279
+ if (args.length !== 1) return fail(`unexpected argument: ${String(args[1])}`);
229
280
  const st = launchd.disableLaunchd();
230
281
  if (st.error) { ctx.err(`${ctx.ui.red("✖")} ${st.error}`); return 1; }
231
282
  ctx.out(`${ctx.ui.green("✓")} ${ko ? "상주를 껐습니다. 자동화는 포그라운드 daemon 을 켜 둘 때만 발동합니다." : "persistence off. Automations fire only while a foreground daemon runs."}`);
@@ -236,4 +287,4 @@ async function run(ctx, args) {
236
287
  return 1;
237
288
  }
238
289
 
239
- module.exports = { run };
290
+ module.exports = { run, parseOptions, intervalOption };
@@ -36,10 +36,15 @@ function usage(ko) {
36
36
 
37
37
  async function run(ctx, args) {
38
38
  const ko = ctx.lang === "ko";
39
- if (args.some((a) => a === "--help" || a === "-h" || a === "help")) {
39
+ if (args.length === 1 && ["--help", "-h", "help"].includes(args[0])) {
40
40
  ctx.out(usage(ko));
41
41
  return 0;
42
42
  }
43
+ if (args.length) {
44
+ const error = new Error(usage(ko).split("\n")[0]);
45
+ error.code = "INVALID_ARGUMENT";
46
+ throw error;
47
+ }
43
48
 
44
49
  const cookie = await cloudSessionCookie();
45
50
  if (!cookie) {
@@ -26,11 +26,40 @@ const cdp = require("../browser/cdp.cjs");
26
26
 
27
27
  const SUBS = new Set(["status", "sites", "add", "login", "mark", "go", "rm", "remove"]);
28
28
 
29
- function flag(rest, name) {
30
- const i = rest.findIndex((a) => a === `--${name}`);
31
- return i >= 0 && rest[i + 1] ? rest[i + 1] : null;
29
+ function browserArgumentError(message) {
30
+ const error = new Error(message);
31
+ error.code = "INVALID_ARGUMENT";
32
+ return error;
33
+ }
34
+
35
+ function parseBrowserRest(rest, { values = [], positionals, usage }) {
36
+ const allowedValues = new Set(values);
37
+ const seen = new Set();
38
+ const parsed = { _: [] };
39
+ for (let index = 0; index < rest.length; index += 1) {
40
+ const token = String(rest[index]);
41
+ if (!token.startsWith("-")) {
42
+ parsed._.push(token);
43
+ continue;
44
+ }
45
+ if (!token.startsWith("--")) throw browserArgumentError(usage);
46
+ const equal = token.indexOf("=");
47
+ const key = token.slice(2, equal === -1 ? undefined : equal);
48
+ if (!allowedValues.has(key) || seen.has(key)) throw browserArgumentError(usage);
49
+ let value;
50
+ if (equal !== -1) value = token.slice(equal + 1);
51
+ else {
52
+ value = rest[index + 1];
53
+ if (value === undefined || String(value).startsWith("-")) throw browserArgumentError(usage);
54
+ index += 1;
55
+ }
56
+ if (!String(value)) throw browserArgumentError(usage);
57
+ seen.add(key);
58
+ parsed[key] = String(value);
59
+ }
60
+ if (parsed._.length !== positionals) throw browserArgumentError(usage);
61
+ return parsed;
32
62
  }
33
- function positional(rest) { return rest.find((a) => a && !String(a).startsWith("-")) || null; }
34
63
 
35
64
  function siteUrl(site) { return /^[a-z][a-z0-9+.-]*:\/\//i.test(site) ? site : `https://${site}`; }
36
65
 
@@ -70,12 +99,11 @@ function browserSites(ctx) {
70
99
  return 0;
71
100
  }
72
101
 
73
- function browserAdd(ctx, rest) {
102
+ function browserAdd(ctx, parsed) {
74
103
  const ko = ctx.lang === "ko";
75
- const site = positional(rest);
76
- if (!site) { ctx.err(ko ? "사용법: agentlas browser add <site> [--label 이름] [--user 아이디]" : "Usage: agentlas browser add <site> [--label name] [--user handle]"); return 1; }
104
+ const site = parsed._[0];
77
105
  try {
78
- const row = vault.upsertBrowserSite(ctx.db(), { site, label: flag(rest, "label"), username: flag(rest, "user") });
106
+ const row = vault.upsertBrowserSite(ctx.db(), { site, label: parsed.label || null, username: parsed.user || null });
79
107
  vault.logBrowserAction(ctx.db(), { site: row.site, action: "vault.save", result: "ok" });
80
108
  ctx.out(`${ctx.ui.green("✓")} ${ko ? "사이트를 저장했습니다" : "site saved"}: ${row.site}`);
81
109
  ctx.out(ctx.ui.dim(ko ? `로그인: agentlas browser login ${row.site}` : `Log in: agentlas browser login ${row.site}`));
@@ -83,10 +111,8 @@ function browserAdd(ctx, rest) {
83
111
  } catch (e) { ctx.err(`${ctx.ui.red("✖")} ${String((e && e.message) || e)}`); return 1; }
84
112
  }
85
113
 
86
- async function browserLogin(ctx, rest) {
114
+ async function browserLogin(ctx, site) {
87
115
  const ko = ctx.lang === "ko";
88
- const site = positional(rest);
89
- if (!site) { ctx.err(ko ? "사용법: agentlas browser login <site>" : "Usage: agentlas browser login <site>"); return 1; }
90
116
  const db = ctx.db();
91
117
  let row;
92
118
  try { row = vault.upsertBrowserSite(db, { site }); } catch (e) { ctx.err(`${ctx.ui.red("✖")} ${String((e && e.message) || e)}`); return 1; }
@@ -117,11 +143,8 @@ async function browserLogin(ctx, rest) {
117
143
  return 0;
118
144
  }
119
145
 
120
- function browserMark(ctx, rest) {
146
+ function browserMark(ctx, site, status) {
121
147
  const ko = ctx.lang === "ko";
122
- const site = positional(rest);
123
- const status = rest.filter((a) => a && !String(a).startsWith("-") && a !== site)[0];
124
- if (!site || !status) { ctx.err(ko ? "사용법: agentlas browser mark <site> <valid|expired|none>" : "Usage: agentlas browser mark <site> <valid|expired|none>"); return 1; }
125
148
  try {
126
149
  const row = vault.setBrowserSession(ctx.db(), site, status);
127
150
  vault.logBrowserAction(ctx.db(), { site: row.site, action: "session.mark", result: status });
@@ -130,10 +153,8 @@ function browserMark(ctx, rest) {
130
153
  } catch (e) { ctx.err(`${ctx.ui.red("✖")} ${String((e && e.message) || e)}`); return 1; }
131
154
  }
132
155
 
133
- async function browserGo(ctx, rest) {
156
+ async function browserGo(ctx, url) {
134
157
  const ko = ctx.lang === "ko";
135
- const url = positional(rest);
136
- if (!url) { ctx.err(ko ? "사용법: agentlas browser go <url>" : "Usage: agentlas browser go <url>"); return 1; }
137
158
  if (!(await cdp.cdpReady())) {
138
159
  ctx.err(ko
139
160
  ? `Agentlas 브라우저가 실행 중이 아닙니다. 먼저 열어 주세요: agentlas browser ${siteUrl(url)}`
@@ -149,10 +170,8 @@ async function browserGo(ctx, rest) {
149
170
  } catch (e) { ctx.err(`${ctx.ui.red("✖")} ${String((e && e.message) || e)}`); return 1; }
150
171
  }
151
172
 
152
- function browserRemove(ctx, rest) {
173
+ function browserRemove(ctx, site) {
153
174
  const ko = ctx.lang === "ko";
154
- const site = positional(rest);
155
- if (!site) { ctx.err(ko ? "사용법: agentlas browser rm <site>" : "Usage: agentlas browser rm <site>"); return 1; }
156
175
  vault.deleteBrowserSite(ctx.db(), site);
157
176
  vault.logBrowserAction(ctx.db(), { site: vault.normalizeSite(site), action: "vault.delete", result: "ok" });
158
177
  ctx.out(`${ctx.ui.green("✓")} ${ko ? "사이트를 삭제했습니다" : "site removed"}: ${vault.normalizeSite(site) || site}`);
@@ -160,17 +179,30 @@ function browserRemove(ctx, rest) {
160
179
  }
161
180
 
162
181
  async function run(ctx, args) {
163
- if (args.some(isHelpToken)) { ctx.out(usageFor("browser", ctx.lang)); return 0; }
182
+ if (args.length === 1 && isHelpToken(args[0])) { ctx.out(usageFor("browser", ctx.lang)); return 0; }
164
183
  const sub = String(args[0] || "").toLowerCase();
165
184
  const rest = args.slice(1);
166
185
  if (SUBS.has(sub)) {
167
- if (sub === "status") return browserStatus(ctx);
168
- if (sub === "sites") return browserSites(ctx);
169
- if (sub === "add") return browserAdd(ctx, rest);
170
- if (sub === "login") return browserLogin(ctx, rest);
171
- if (sub === "mark") return browserMark(ctx, rest);
172
- if (sub === "go") return browserGo(ctx, rest);
173
- if (sub === "rm" || sub === "remove") return browserRemove(ctx, rest);
186
+ if (sub === "status" || sub === "sites") {
187
+ parseBrowserRest(rest, { positionals: 0, usage: `Usage: agentlas browser ${sub}` });
188
+ return sub === "status" ? browserStatus(ctx) : browserSites(ctx);
189
+ }
190
+ if (sub === "add") {
191
+ const parsed = parseBrowserRest(rest, {
192
+ values: ["label", "user"], positionals: 1,
193
+ usage: "Usage: agentlas browser add <site> [--label name] [--user handle]",
194
+ });
195
+ return browserAdd(ctx, parsed);
196
+ }
197
+ if (sub === "mark") {
198
+ const parsed = parseBrowserRest(rest, { positionals: 2, usage: "Usage: agentlas browser mark <site> <valid|expired|none>" });
199
+ return browserMark(ctx, parsed._[0], parsed._[1]);
200
+ }
201
+ const usage = `Usage: agentlas browser ${sub} <${sub === "go" ? "url" : "site"}>`;
202
+ const parsed = parseBrowserRest(rest, { positionals: 1, usage });
203
+ if (sub === "login") return browserLogin(ctx, parsed._[0]);
204
+ if (sub === "go") return browserGo(ctx, parsed._[0]);
205
+ return browserRemove(ctx, parsed._[0]);
174
206
  }
175
207
  // 그 외(무인자·URL·검색어)는 v1 그대로 hep-browser 하드포인트로 — Chrome 을 실제로 띄운다.
176
208
  return create(ctx).cmdHep(["hep-browser", ...args]);
@@ -23,24 +23,72 @@ const fs = require("node:fs");
23
23
 
24
24
  function usage(ko) {
25
25
  return ko
26
- ? "사용법: agentlas build \"<만들고 싶은 에이전트>\" [--runtime <kind>] [--print]"
27
- : "Usage: agentlas build \"<the agent you want>\" [--runtime <kind>] [--print]";
26
+ ? "사용법: agentlas build \"<만들고 싶은 에이전트>\" [--runtime <kind>] [--print] [-- <옵션처럼 시작하는 요청>]"
27
+ : "Usage: agentlas build \"<the agent you want>\" [--runtime <kind>] [--print] [-- <request starting like an option>]";
28
28
  }
29
29
 
30
- async function run(ctx, args) {
31
- const ko = ctx.lang === "ko";
32
- if (args.some((a) => a === "--help" || a === "-h" || a === "help")) { ctx.out(usage(ko)); return 0; }
30
+ function buildError(message, code = "INVALID_ARGUMENT") {
31
+ const error = new Error(message);
32
+ error.code = code;
33
+ return error;
34
+ }
33
35
 
34
- // --runtime/--print 만 벗겨내고 나머지는 요청 문장.
35
- const flags = {};
36
+ function parseArgs(args) {
37
+ const flags = { runtime: null, print: false };
36
38
  const rest = [];
39
+ const seen = new Set();
40
+ let passthrough = false;
37
41
  for (let i = 0; i < args.length; i += 1) {
38
- if (args[i] === "--runtime" && args[i + 1]) { flags.runtime = args[++i]; continue; }
39
- if (args[i] === "--print" || args[i] === "-p") { flags.print = true; continue; }
40
- rest.push(args[i]);
42
+ const token = String(args[i]);
43
+ if (passthrough) { rest.push(token); continue; }
44
+ if (token === "--") { passthrough = true; continue; }
45
+ if (token === "--print" || token === "-p") {
46
+ if (seen.has("print")) throw new Error("duplicate option: --print");
47
+ seen.add("print");
48
+ flags.print = true;
49
+ continue;
50
+ }
51
+ if (token === "--runtime" || token.startsWith("--runtime=")) {
52
+ if (seen.has("runtime")) throw new Error("duplicate option: --runtime");
53
+ seen.add("runtime");
54
+ const inline = token.startsWith("--runtime=");
55
+ const value = inline ? token.slice(10) : args[++i];
56
+ if (value === undefined || value === "" || (!inline && String(value).startsWith("--"))) {
57
+ throw new Error("--runtime requires a value");
58
+ }
59
+ flags.runtime = String(value);
60
+ continue;
61
+ }
62
+ if (token.startsWith("-")) {
63
+ throw new Error(`unknown option: ${token} (use -- before a request that starts with '-')`);
64
+ }
65
+ rest.push(token);
66
+ }
67
+ return { flags, rest };
68
+ }
69
+
70
+ async function run(ctx, args) {
71
+ const ko = ctx.lang === "ko";
72
+ if (args.length === 1 && ["--help", "-h", "help"].includes(args[0])) { ctx.out(usage(ko)); return 0; }
73
+
74
+ let parsed;
75
+ try { parsed = parseArgs(args); }
76
+ catch (error) {
77
+ const typed = error && typeof error === "object" && typeof error.code === "string"
78
+ ? error
79
+ : buildError(String((error && error.message) || error));
80
+ if (typeof ctx.fail === "function") ctx.fail(typed);
81
+ else ctx.err(String(typed.message || typed));
82
+ return 1;
41
83
  }
84
+ const { flags, rest } = parsed;
42
85
  const request = rest.join(" ").trim();
43
- if (!request) { ctx.err("✖ " + usage(ko)); return 1; }
86
+ if (!request) {
87
+ const error = buildError("✖ " + usage(ko));
88
+ if (typeof ctx.fail === "function") ctx.fail(error);
89
+ else ctx.err(error.message);
90
+ return 1;
91
+ }
44
92
 
45
93
  const db = ctx.db();
46
94
  const cwd = projectCwd();
@@ -53,7 +101,12 @@ async function run(ctx, args) {
53
101
  });
54
102
  } catch (e) {
55
103
  // no_runtime 등 정직 정지 그대로.
56
- ctx.err(String((e && e.message) || e));
104
+ const error = e && typeof e === "object" && typeof e.message === "string"
105
+ ? e
106
+ : buildError(String(e), "RUNTIME_RESOLUTION_FAILED");
107
+ if (typeof error.code !== "string") error.code = "RUNTIME_RESOLUTION_FAILED";
108
+ if (typeof ctx.fail === "function") ctx.fail(error);
109
+ else ctx.err(error.message);
57
110
  return 1;
58
111
  }
59
112
 
@@ -77,7 +130,12 @@ async function run(ctx, args) {
77
130
  if (flags.print && finalText) process.stdout.write(finalText.trimEnd() + "\n");
78
131
 
79
132
  if (session.status === "failed") {
80
- if (session.lastError) ctx.err(session.lastError);
133
+ const error = session.lastError && typeof session.lastError === "object" && typeof session.lastError.message === "string"
134
+ ? session.lastError
135
+ : buildError(String(session.lastError || (ko ? "빌드 실행에 실패했습니다." : "The build session failed.")), "BUILD_FAILED");
136
+ if (typeof error.code !== "string") error.code = "BUILD_FAILED";
137
+ if (typeof ctx.fail === "function") ctx.fail(error);
138
+ else ctx.err(error.message);
81
139
  return 1;
82
140
  }
83
141
 
@@ -111,4 +169,4 @@ async function run(ctx, args) {
111
169
  }
112
170
  }
113
171
 
114
- module.exports = { run };
172
+ module.exports = { run, parseArgs };
@@ -10,7 +10,7 @@
10
10
  const { create, usageFor, isHelpToken } = require("../hephaestus/runtime.cjs");
11
11
 
12
12
  async function run(ctx, args) {
13
- if (args.some(isHelpToken)) {
13
+ if (args.length === 1 && isHelpToken(args[0])) {
14
14
  ctx.out(usageFor("call", ctx.lang));
15
15
  return 0;
16
16
  }
@@ -5,7 +5,7 @@ const { agentFolder } = require("../agents/files.cjs");
5
5
 
6
6
  function run(ctx, args) {
7
7
  const ko = ctx.lang === "ko";
8
- if (!args[0]) {
8
+ if (!args[0] || args.length !== 1) {
9
9
  ctx.err(ko ? "사용법: agentlas cd <agent>" : "Usage: agentlas cd <agent>");
10
10
  return 1;
11
11
  }
@@ -12,7 +12,8 @@ async function run(ctx, args) {
12
12
  : args;
13
13
  return await runCloud(ctx, commandArgs);
14
14
  } catch (e) {
15
- ctx.err(String((e && e.message) || e));
15
+ if (typeof ctx.fail === "function") ctx.fail(e);
16
+ else ctx.err(String((e && e.message) || e));
16
17
  return 1;
17
18
  }
18
19
  }
@@ -43,10 +43,21 @@ function resolveTarget(db, token) {
43
43
  /** 봇 토큰을 stdin 에서 읽는다(비밀은 argv 금지). 파이프면 첫 줄, TTY면 숨김 안내. */
44
44
  async function readTokenFromStdin(ctx, ko) {
45
45
  if (!process.stdin.isTTY) {
46
- return new Promise((resolve) => {
46
+ return new Promise((resolve, reject) => {
47
47
  let buf = "";
48
- process.stdin.on("data", (d) => { buf += d; });
49
- process.stdin.on("end", () => resolve(buf.split(/\r?\n/)[0].trim()));
48
+ let tooLarge = false;
49
+ process.stdin.on("data", (d) => {
50
+ if (tooLarge) return;
51
+ buf += d;
52
+ if (Buffer.byteLength(buf, "utf8") > 2048) tooLarge = true;
53
+ });
54
+ process.stdin.on("end", () => {
55
+ if (tooLarge) return reject(new Error("Telegram bot token input is too large"));
56
+ const value = buf.replace(/\r?\n$/, "");
57
+ if (/[\r\n]/.test(value)) return reject(new Error("Telegram bot token input must contain exactly one line"));
58
+ resolve(value.trim());
59
+ });
60
+ process.stdin.on("error", reject);
50
61
  });
51
62
  }
52
63
  const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
@@ -95,7 +106,11 @@ async function connectTelegram(ctx, targetToken, { auto = false } = {}) {
95
106
  }
96
107
  ctx.out(ctx.ui.dim(ko ? "토큰을 읽었습니다." : "Captured the token."));
97
108
  } else {
98
- token = await readTokenFromStdin(ctx, ko);
109
+ try { token = await readTokenFromStdin(ctx, ko); }
110
+ catch (error) {
111
+ ctx.err(`${ctx.ui.red("✖")} ${String((error && error.message) || error)}`);
112
+ return 1;
113
+ }
99
114
  if (!token) { ctx.err(ko ? "토큰이 필요합니다." : "a bot token is required."); return 1; }
100
115
  }
101
116
 
@@ -145,16 +160,21 @@ async function run(ctx, args) {
145
160
  const sub = String(args[0] || "").toLowerCase();
146
161
 
147
162
  if (!sub || sub === "help" || sub === "--help" || sub === "-h") { ctx.out(usage(ko)); return 0; }
148
- if (sub === "status") return renderTelegram(ctx);
163
+ if (sub === "status") {
164
+ if (args.length !== 1) { ctx.err(usage(ko)); return 1; }
165
+ return renderTelegram(ctx);
166
+ }
149
167
  if (sub === "telegram") {
150
168
  const rest = args.slice(1);
169
+ const unknownOptions = rest.filter((item) => String(item).startsWith("-") && item !== "--auto");
170
+ const positionals = rest.filter((item) => item && !String(item).startsWith("-"));
171
+ if (unknownOptions.length || positionals.length !== 1) { ctx.err(usage(ko)); return 1; }
151
172
  const auto = rest.includes("--auto");
152
- const targetToken = rest.find((a) => a && !String(a).startsWith("-"));
153
- if (!targetToken) { ctx.err(usage(ko)); return 1; }
173
+ const targetToken = positionals[0];
154
174
  return connectTelegram(ctx, targetToken, { auto });
155
175
  }
156
176
  if (sub === "pair") {
157
- if (!args[1]) { ctx.err(ko ? "사용법: agentlas connect pair <id>" : "Usage: agentlas connect pair <id>"); return 1; }
177
+ if (!args[1] || args.length !== 2) { ctx.err(ko ? "사용법: agentlas connect pair <id>" : "Usage: agentlas connect pair <id>"); return 1; }
158
178
  const row = tg.getBinding(ctx.db(), args[1]);
159
179
  if (!row) { ctx.err(ko ? "연결을 찾을 수 없습니다." : "connection not found."); return 1; }
160
180
  if (row.telegram_chat_id) {
@@ -181,7 +201,7 @@ async function run(ctx, args) {
181
201
  }
182
202
  }
183
203
  if (sub === "test") {
184
- if (!args[1]) { ctx.err(ko ? "사용법: agentlas connect test <id>" : "Usage: agentlas connect test <id>"); return 1; }
204
+ if (!args[1] || args.length !== 2) { ctx.err(ko ? "사용법: agentlas connect test <id>" : "Usage: agentlas connect test <id>"); return 1; }
185
205
  try {
186
206
  await tg.sendTest(ctx.db(), args[1], ko ? "Agentlas 테스트 메시지" : "Agentlas test message");
187
207
  ctx.out(`${ctx.ui.green("✓")} ${ko ? "메시지를 보냈습니다." : "message sent."}`);
@@ -189,10 +209,15 @@ async function run(ctx, args) {
189
209
  } catch (e) { ctx.err(`${ctx.ui.red("✖")} ${String((e && e.message) || e)}`); return 1; }
190
210
  }
191
211
  if (sub === "remove") {
192
- if (!args[1]) { ctx.err(ko ? "사용법: agentlas connect remove <id>" : "Usage: agentlas connect remove <id>"); return 1; }
193
- tg.removeBinding(ctx.db(), args[1]);
194
- ctx.out(`${ctx.ui.green("✓")} ${ko ? "연결을 제거했습니다." : "connection removed."}`);
195
- return 0;
212
+ if (!args[1] || args.length !== 2) { ctx.err(ko ? "사용법: agentlas connect remove <id>" : "Usage: agentlas connect remove <id>"); return 1; }
213
+ try {
214
+ tg.removeBinding(ctx.db(), args[1]);
215
+ ctx.out(`${ctx.ui.green("✓")} ${ko ? "연결을 제거했습니다." : "connection removed."}`);
216
+ return 0;
217
+ } catch (error) {
218
+ ctx.err(`${ctx.ui.red("✖")} ${String((error && error.message) || error)}`);
219
+ return 1;
220
+ }
196
221
  }
197
222
  ctx.err(usage(ko));
198
223
  return 1;