agentlas 0.9.10 → 1.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
Files changed (145) hide show
  1. package/CHANGELOG.md +106 -0
  2. package/README.md +319 -341
  3. package/bin/agentlas.cjs +32 -9
  4. package/engine/agentlas-banner.cjs +24 -3
  5. package/engine/agentlas-composer.cjs +239 -31
  6. package/engine/agentlas-config.cjs +103 -7
  7. package/engine/agentlas-core-harness.cjs +48 -4
  8. package/engine/agentlas-evolution.cjs +3 -4
  9. package/engine/agentlas-i18n.cjs +88 -32
  10. package/engine/agentlas-input.cjs +234 -18
  11. package/engine/agentlas-memory-governance.cjs +3 -5
  12. package/engine/agentlas-memory-import.cjs +3 -3
  13. package/engine/agentlas-native-host.cjs +200 -9
  14. package/engine/agentlas-onboard.cjs +112 -20
  15. package/engine/agentlas-permissions.cjs +14 -7
  16. package/engine/agentlas-sqlite-policy.cjs +34 -0
  17. package/engine/agentlas-ui.cjs +93 -42
  18. package/engine/agentlas-workforce.cjs +472 -58
  19. package/engine/agentlas-workload-routing.cjs +8 -3
  20. package/engine/agentlas.cjs +140 -12367
  21. package/engine/agents/files.cjs +61 -0
  22. package/engine/agents/import-local.cjs +237 -0
  23. package/engine/agents/registry.cjs +158 -0
  24. package/engine/agents/router.cjs +565 -0
  25. package/engine/agents/routes.cjs +43 -0
  26. package/engine/architecture.data.json +2 -2
  27. package/engine/automation/daemon.cjs +335 -0
  28. package/engine/automation/schedule.cjs +181 -0
  29. package/engine/automation/store.cjs +209 -0
  30. package/engine/cloud/auth.cjs +279 -0
  31. package/engine/cloud/hub-client.cjs +239 -0
  32. package/engine/cloud-assets/cargo.cjs +49 -0
  33. package/engine/cloud-assets/cas.cjs +235 -0
  34. package/engine/cloud-assets/commands.cjs +273 -0
  35. package/engine/cloud-assets/package.cjs +936 -0
  36. package/engine/cloud-assets/restore.cjs +172 -0
  37. package/engine/cloud-assets/state.cjs +268 -0
  38. package/engine/commands/automation.cjs +195 -0
  39. package/engine/commands/billing.cjs +96 -0
  40. package/engine/commands/browser.cjs +20 -0
  41. package/engine/commands/build.cjs +33 -0
  42. package/engine/commands/call.cjs +24 -0
  43. package/engine/commands/career-graph.cjs +51 -0
  44. package/engine/commands/cd.cjs +22 -0
  45. package/engine/commands/chat.cjs +12 -0
  46. package/engine/commands/chats.cjs +28 -0
  47. package/engine/commands/cloud.cjs +17 -0
  48. package/engine/commands/connect.cjs +20 -0
  49. package/engine/commands/context.cjs +66 -0
  50. package/engine/commands/creds.cjs +203 -0
  51. package/engine/commands/doctor.cjs +68 -0
  52. package/engine/commands/env.cjs +33 -0
  53. package/engine/commands/evolve.cjs +23 -0
  54. package/engine/commands/experience.cjs +34 -0
  55. package/engine/commands/film.cjs +8 -0
  56. package/engine/commands/firm.cjs +115 -0
  57. package/engine/commands/help.cjs +65 -0
  58. package/engine/commands/hep.cjs +23 -0
  59. package/engine/commands/import.cjs +35 -0
  60. package/engine/commands/index.cjs +136 -0
  61. package/engine/commands/install.cjs +27 -0
  62. package/engine/commands/journal.cjs +31 -0
  63. package/engine/commands/legacy-network.cjs +29 -0
  64. package/engine/commands/list.cjs +49 -0
  65. package/engine/commands/login.cjs +68 -0
  66. package/engine/commands/logout.cjs +28 -0
  67. package/engine/commands/mcp.cjs +91 -0
  68. package/engine/commands/memory.cjs +21 -0
  69. package/engine/commands/multimodal.cjs +91 -0
  70. package/engine/commands/native.cjs +34 -0
  71. package/engine/commands/netadmin.cjs +31 -0
  72. package/engine/commands/oberon.cjs +70 -0
  73. package/engine/commands/ontology.cjs +24 -0
  74. package/engine/commands/open.cjs +48 -0
  75. package/engine/commands/plugin.cjs +101 -0
  76. package/engine/commands/project.cjs +47 -0
  77. package/engine/commands/research.cjs +36 -0
  78. package/engine/commands/route.cjs +37 -0
  79. package/engine/commands/run.cjs +149 -0
  80. package/engine/commands/search.cjs +55 -0
  81. package/engine/commands/setup.cjs +45 -0
  82. package/engine/commands/storm.cjs +75 -0
  83. package/engine/commands/swarm.cjs +75 -0
  84. package/engine/commands/telegram.cjs +32 -0
  85. package/engine/commands/uninstall.cjs +68 -0
  86. package/engine/commands/update.cjs +54 -0
  87. package/engine/commands/upload.cjs +18 -0
  88. package/engine/commands/usage.cjs +35 -0
  89. package/engine/commands/variant.cjs +25 -0
  90. package/engine/commands/version.cjs +9 -0
  91. package/engine/commands/whoami.cjs +38 -0
  92. package/engine/commands/workforce.cjs +103 -0
  93. package/engine/core/db.cjs +160 -0
  94. package/engine/core/paths.cjs +35 -0
  95. package/engine/experience/build.cjs +181 -0
  96. package/engine/experience/intents.cjs +492 -0
  97. package/engine/experience/runtime.cjs +242 -0
  98. package/engine/experience/variant.cjs +196 -0
  99. package/engine/firms/orchestrate.cjs +333 -0
  100. package/engine/hephaestus/runtime.cjs +697 -0
  101. package/engine/hub/install.cjs +872 -0
  102. package/engine/hub/plugins.cjs +213 -0
  103. package/engine/mcp/consent.cjs +289 -0
  104. package/engine/mcp/contract.cjs +202 -0
  105. package/engine/mcp/index.cjs +43 -0
  106. package/engine/mcp/inventory.cjs +322 -0
  107. package/engine/mcp/plan.cjs +286 -0
  108. package/engine/mcp/probe.cjs +151 -0
  109. package/engine/memory-cli/curate.cjs +163 -0
  110. package/engine/oberon/common.cjs +69 -0
  111. package/engine/oberon/manifest.cjs +164 -0
  112. package/engine/oberon/outputs.cjs +70 -0
  113. package/engine/oberon/render.cjs +164 -0
  114. package/engine/project/career-graph.cjs +249 -0
  115. package/engine/project/credentials.cjs +262 -0
  116. package/engine/project/env-file.cjs +46 -0
  117. package/engine/project/index.cjs +27 -0
  118. package/engine/project/memory-context.cjs +453 -0
  119. package/engine/project/ontology.cjs +467 -0
  120. package/engine/project/paths.cjs +39 -0
  121. package/engine/project/seed.cjs +200 -0
  122. package/engine/project/state.cjs +403 -0
  123. package/engine/project/super-ontology-seed.json +3288 -0
  124. package/engine/runtimes/detect.cjs +54 -0
  125. package/engine/runtimes/overrides.cjs +139 -0
  126. package/engine/runtimes/resolve.cjs +64 -0
  127. package/engine/sessions/apply-fences.cjs +188 -0
  128. package/engine/sessions/fences.cjs +362 -0
  129. package/engine/sessions/orchestrator.cjs +170 -0
  130. package/engine/sessions/prompt.cjs +212 -0
  131. package/engine/sessions/session.cjs +245 -0
  132. package/engine/sessions/sink.cjs +54 -0
  133. package/engine/sessions/store.cjs +79 -0
  134. package/engine/storm/deps.cjs +88 -0
  135. package/engine/storm/storm.cjs +218 -0
  136. package/engine/storm/swarm.cjs +422 -0
  137. package/engine/ui/palette.cjs +105 -0
  138. package/engine/ui/renderer.cjs +85 -0
  139. package/engine/ui/repl.cjs +444 -0
  140. package/engine/workforce/capture.cjs +701 -0
  141. package/engine/workforce/deps.cjs +472 -0
  142. package/package.json +2 -6
  143. package/engine/agentlas-experience-mcp.cjs +0 -1709
  144. package/engine/agentlas-parity.cjs +0 -1499
  145. package/engine/agentlas-repl.cjs +0 -1780
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ /*
3
+ * oberon/manifest — 렌더 매니페스트 scaffold + 원자적 쓰기.
4
+ * v1 §12243-12358 (oberonSampleTitles / writeOberonManifestCli / oberonScaffold) 충실 포팅.
5
+ *
6
+ * 사용 흐름 (v1 헤더 주석 그대로):
7
+ * agentlas oberon scaffold my.json → 편집 가능한 렌더 매니페스트 생성
8
+ * agentlas oberon render my.json → 헤드리스 렌더 스폰 + 진행률 스트리밍
9
+ * 프롬프트는 직접 채우거나 `agentlas run oberon-film-studio "<브리프>"`로 에이전트가
10
+ * 채운다 (OpenMontage "어시스턴트=오케스트레이터" 스킴).
11
+ */
12
+ const fs = require("node:fs");
13
+ const path = require("node:path");
14
+ const crypto = require("node:crypto");
15
+ const { fail, parseFlags } = require("./common.cjs");
16
+
17
+ // scaffold 기본 타이포 프리셋 (v1 oberonSampleTitles).
18
+ // 한국어 본문/자막은 CJK 폰트를 강제한다 — 비-CJK 폰트로 한글을 태우면 두부(tofu)가 뜬다.
19
+ function oberonSampleTitles(title) {
20
+ const koStyle = (over) => ({
21
+ fontName: "Pretendard",
22
+ fontStack: '"Pretendard", system-ui, sans-serif',
23
+ fontCategory: "humanist_sans",
24
+ cjk: true,
25
+ sizePct: 5,
26
+ weight: 700,
27
+ tracking: 0,
28
+ case: "none",
29
+ position: "center",
30
+ fill: "#FFFFFF",
31
+ safeAreaPct: 10,
32
+ ...over,
33
+ });
34
+ return {
35
+ aspectRatio: "16:9",
36
+ titleCard: { kind: "title", lines: [title], style: koStyle({ sizePct: 9, outline: { color: "rgba(0,0,0,0.45)", widthPx: 2 } }), bg: "#000000", durationSec: 2 },
37
+ endCard: { kind: "end_card", lines: ["AGENTLAS"], style: koStyle({ sizePct: 7, cjk: false }), bg: "#0A0A0A", durationSec: 1.5 },
38
+ lowerThirds: [],
39
+ subtitles: [],
40
+ subtitleStyle: koStyle({ sizePct: 4.6, weight: 600, position: "lower_center", boxBg: "rgba(0,0,0,0.34)", outline: { color: "rgba(0,0,0,0.9)", widthPx: 3 }, safeAreaPct: 8 }),
41
+ rationale: "scaffold 기본 타이포 — 한국어 본문/자막은 CJK 폰트 강제",
42
+ };
43
+ }
44
+
45
+ /*
46
+ * 원자적 no-clobber 매니페스트 쓰기 (v1 writeOberonManifestCli).
47
+ * 비자명 제약 — 전부 v1에서 실사고/보안 검토로 굳은 규칙:
48
+ * - 심볼릭 링크를 통해 쓰지 않는다 (링크 타깃 오염 방지).
49
+ * - 일반 파일이 아니면 거부.
50
+ * - overwrite 아님 + 존재 → EXISTS 에러 (사용자 편집본을 조용히 덮지 않는다).
51
+ * - tmp에 wx(0o600)로 쓰고 fsync 후:
52
+ * overwrite → rename(2)
53
+ * 신규 → link(2). link(2)는 원자적 no-clobber publish다. 우리 lstat 이후
54
+ * 다른 writer가 타깃을 만들었으면 EEXIST가 그 파일을 보존한다.
55
+ */
56
+ function writeOberonManifest(outPath, manifest, options = {}) {
57
+ const overwrite = options.overwrite === true;
58
+ let existing = null;
59
+ try { existing = fs.lstatSync(outPath); } catch (error) {
60
+ if (!error || error.code !== "ENOENT") throw error;
61
+ }
62
+ if (existing && existing.isSymbolicLink()) {
63
+ const error = new Error(`Refusing to write through a symbolic link: ${outPath}`);
64
+ error.code = "AGENTLAS_OBERON_SYMLINK";
65
+ throw error;
66
+ }
67
+ if (existing && !existing.isFile()) {
68
+ const error = new Error(`Manifest target is not a regular file: ${outPath}`);
69
+ error.code = "AGENTLAS_OBERON_NOT_FILE";
70
+ throw error;
71
+ }
72
+ if (existing && !overwrite) {
73
+ const error = new Error(`Manifest already exists: ${outPath}`);
74
+ error.code = "AGENTLAS_OBERON_EXISTS";
75
+ throw error;
76
+ }
77
+
78
+ const dir = path.dirname(outPath);
79
+ const tmp = path.join(dir, `.${path.basename(outPath)}.${process.pid}.${crypto.randomBytes(6).toString("hex")}.tmp`);
80
+ let fd;
81
+ try {
82
+ fd = fs.openSync(tmp, "wx", 0o600);
83
+ fs.writeFileSync(fd, JSON.stringify(manifest, null, 2) + "\n", "utf8");
84
+ fs.fsyncSync(fd);
85
+ fs.closeSync(fd);
86
+ fd = null;
87
+ try { fs.chmodSync(tmp, 0o644); } catch { /* win32 */ }
88
+ if (overwrite) {
89
+ fs.renameSync(tmp, outPath);
90
+ } else {
91
+ // link(2) is an atomic no-clobber publish. If another writer created the
92
+ // target after our lstat, EEXIST preserves that file.
93
+ fs.linkSync(tmp, outPath);
94
+ fs.unlinkSync(tmp);
95
+ }
96
+ } finally {
97
+ if (fd != null) {
98
+ try { fs.closeSync(fd); } catch { /* ignore */ }
99
+ }
100
+ try { fs.unlinkSync(tmp); } catch { /* already published or never created */ }
101
+ }
102
+ }
103
+
104
+ // scaffold 매니페스트 본문 구성 (v1 oberonScaffold의 순수 부분 분리 — 테스트 용이성).
105
+ function buildScaffoldManifest({ title, aspect, shotCount, provider, model, resolution, withTitles }) {
106
+ const shots = Array.from({ length: shotCount }, (_, i) => ({
107
+ shotId: `SH_${String(i + 1).padStart(3, "0")}`,
108
+ index: i,
109
+ durationSec: 4,
110
+ aspectRatio: aspect,
111
+ providerId: "google-veo",
112
+ providerMode: "text_to_video",
113
+ prompt: `((샷 ${i + 1} 프롬프트를 여기에 — 카메라/피사체/조명/무드. 'agentlas run oberon-film-studio' 로 에이전트가 채우게 할 수 있다.))`,
114
+ negativePrompt: "low quality, blurry, distorted text, watermark",
115
+ }));
116
+ const manifest = {
117
+ productionId: `oberon-${Date.now().toString(36)}`,
118
+ title,
119
+ aspectRatio: aspect,
120
+ maxShots: shotCount,
121
+ takesPerShot: 1,
122
+ provider,
123
+ model,
124
+ resolution,
125
+ shots,
126
+ };
127
+ if (withTitles) manifest.titles = oberonSampleTitles(title);
128
+ return manifest;
129
+ }
130
+
131
+ // `oberon scaffold [out.json] [--title T] [--aspect 16:9] [--shots N] [--titles] [--overwrite]`
132
+ function scaffold(io, args) {
133
+ const { flags, rest } = parseFlags(args);
134
+ const outPath = path.resolve(rest[0] || "oberon-manifest.json");
135
+ const title = flags.title || "My Oberon Film";
136
+ const aspect = flags.aspect || "16:9";
137
+ // v1 계약: 샷 수는 1..12로 클램프, 기본 2 (Veo 폴링 비용 상한).
138
+ const shotCount = Math.max(1, Math.min(Number(flags.shots) || 2, 12));
139
+ const manifest = buildScaffoldManifest({
140
+ title,
141
+ aspect,
142
+ shotCount,
143
+ provider: flags.provider || "google-gemini-veo",
144
+ model: flags.model || "veo-3.1-lite-generate-001",
145
+ resolution: flags.resolution || "720p",
146
+ withTitles: !!flags.titles,
147
+ });
148
+ try {
149
+ writeOberonManifest(outPath, manifest, { overwrite: !!flags.overwrite });
150
+ } catch (error) {
151
+ if (error && error.code === "AGENTLAS_OBERON_EXISTS") {
152
+ fail(`Manifest already exists; it was not changed. Re-run with --overwrite to replace it: ${outPath}`, error.code);
153
+ }
154
+ fail((error && error.message) || String(error), error && error.code);
155
+ }
156
+ io.out(`✓ Manifest created: ${outPath}`);
157
+ io.out(` · ${shotCount} shots · ${aspect} · ${manifest.provider}`);
158
+ io.out(` · fill prompts, then run: agentlas oberon render ${path.basename(outPath)}`);
159
+ io.out(` · or use an agent: agentlas run oberon-film-studio "30-second fragrance ad trailer"`);
160
+ if (!flags.titles) io.out(` · use --titles to include title/subtitle burn-in samples`);
161
+ return 0;
162
+ }
163
+
164
+ module.exports = { oberonSampleTitles, writeOberonManifest, buildScaffoldManifest, scaffold };
@@ -0,0 +1,70 @@
1
+ "use strict";
2
+ /*
3
+ * oberon/outputs — 렌더 산출물 조회/열기.
4
+ * v1 §12494-12540 (oberonList / oberonOpen) 충실 포팅.
5
+ * 산출물 홈은 <userData>/oberon/ — Desktop Oberon 렌더 잡과 같은 위치를 공유한다.
6
+ */
7
+ const fs = require("node:fs");
8
+ const path = require("node:path");
9
+ const { spawn } = require("node:child_process");
10
+ const { userDataDir } = require("../core/paths.cjs");
11
+ const { fail } = require("./common.cjs");
12
+
13
+ function oberonHome() {
14
+ return path.join(userDataDir(), "oberon");
15
+ }
16
+
17
+ // `oberon list` — 최근 렌더 산출물 15개 (mtime 내림차순).
18
+ function list(io) {
19
+ const dir = oberonHome();
20
+ if (!fs.existsSync(dir)) {
21
+ io.out("No render outputs yet. Start with agentlas oberon scaffold my.json.");
22
+ return 0;
23
+ }
24
+ const entries = fs
25
+ .readdirSync(dir, { withFileTypes: true })
26
+ .filter((d) => d.isDirectory())
27
+ .map((d) => {
28
+ const full = path.join(dir, d.name);
29
+ let mtime = 0;
30
+ try {
31
+ mtime = fs.statSync(full).mtimeMs;
32
+ } catch {}
33
+ const files = (() => {
34
+ try {
35
+ return fs.readdirSync(full);
36
+ } catch {
37
+ return [];
38
+ }
39
+ })();
40
+ return { name: d.name, full, mtime, files };
41
+ })
42
+ .sort((a, b) => b.mtime - a.mtime)
43
+ .slice(0, 15);
44
+ if (!entries.length) {
45
+ io.out("No render outputs yet.");
46
+ return 0;
47
+ }
48
+ io.out(`Recent Oberon renders (${dir}):\n`);
49
+ for (const e of entries) {
50
+ // master/titled 최종본만 요약에 노출 (중간 클립·로그 숨김) — v1 필터 그대로.
51
+ const masters = e.files.filter((f) => /master|titled/.test(f) && /\.(mp4|mov)$/.test(f));
52
+ const when = e.mtime ? new Date(e.mtime).toISOString().slice(0, 16).replace("T", " ") : "";
53
+ io.out(` ${when} ${e.name}`);
54
+ if (masters.length) io.out(` ${masters.join(", ")}`);
55
+ }
56
+ io.out(`\nOpen the folder with: agentlas oberon open`);
57
+ return 0;
58
+ }
59
+
60
+ // `oberon open [path]` — 산출물 폴더를 OS 파일 매니저로 연다.
61
+ function open(io, args) {
62
+ const target = args[0] ? path.resolve(args[0]) : oberonHome();
63
+ if (!fs.existsSync(target)) fail(`Path not found: ${target}`);
64
+ const opener = process.platform === "darwin" ? "open" : process.platform === "win32" ? "explorer" : "xdg-open";
65
+ spawn(opener, [target], { detached: true, stdio: "ignore" }).unref();
66
+ io.out(`Opening folder: ${target}`);
67
+ return 0;
68
+ }
69
+
70
+ module.exports = { list, open, oberonHome };
@@ -0,0 +1,164 @@
1
+ "use strict";
2
+ /*
3
+ * oberon/render — 헤드리스 렌더 스폰 + 진행률 스트리밍.
4
+ * v1 §12360-12471 (oberonRender / oberonRenderLine) 충실 포팅.
5
+ *
6
+ * 렌더 경로가 실제로 필요로 하는 것 (v1 계약):
7
+ * - <packageRoot>/scripts/render-oberon-live-request.cjs (헤드리스 렌더 진입 스크립트)
8
+ * - <packageRoot>/dist/electron/oberon/render.js (Electron 렌더 빌드 산출물)
9
+ * 둘 다 Desktop 쪽 Electron 빌드 산출물이며 터미널 npm 패키지에는 실려 있지 않다.
10
+ * v1도 fs.existsSync 검사 후 정직하게 실패했다 — 렌더를 가짜로 성공시키지 않는다.
11
+ *
12
+ * v2 seam: 렌더 진입점 해석과 spawn은 deps 파라미터로 주입 가능하다.
13
+ * deps.resolveRenderEntry() → { script, builtRender }
14
+ * deps.spawn / deps.execPath / deps.stdout / deps.stderr
15
+ * 테스트는 가짜 렌더 스크립트를 주입해 프로토콜(POLL/FILE/DELIVERY 라인)을 검증하고,
16
+ * 실제 배포에서는 진입점이 없으면 v1과 동일한 메시지로 정직 정지한다.
17
+ */
18
+ const fs = require("node:fs");
19
+ const os = require("node:os");
20
+ const path = require("node:path");
21
+ const { spawn } = require("node:child_process");
22
+ const { packageRoot } = require("../core/paths.cjs");
23
+ const { fail, parseFlags, oberonBar, oberonBytes, slugifyOberon } = require("./common.cjs");
24
+
25
+ // v1 oberonRepoRoot() = engine/의 부모 = 패키지 루트. v2에서는 core/paths가 정본.
26
+ function defaultRenderEntry() {
27
+ const root = packageRoot();
28
+ return {
29
+ script: path.join(root, "scripts", "render-oberon-live-request.cjs"),
30
+ builtRender: path.join(root, "dist", "electron", "oberon", "render.js"),
31
+ };
32
+ }
33
+
34
+ /*
35
+ * 렌더 자식의 stdout 프로토콜 한 줄 처리 (v1 oberonRenderLine).
36
+ * POLL status= phase= clips= percent= → \r 진행률 바 갱신
37
+ * FILE kind= name= bytes= → 산출물 수집 (titled 요약용)
38
+ * DELIVERY kind= name= path= bytes= → 딜리버리 복사 알림
39
+ * WARNINGS= → 경고 표시
40
+ * JOB= / OUT_DIR= / *=present|missing → 내부 추적/키 존재 점검 라인 — 숨김
41
+ */
42
+ function oberonRenderLine(line, files, io) {
43
+ let m;
44
+ if ((m = line.match(/^POLL status=(\S+) phase=(\S+) clips=(\S+) percent=(\d+)/))) {
45
+ const [, status, phase, clips, pct] = m;
46
+ const bar = oberonBar(Number(pct));
47
+ io.write(`\r⏳ ${bar} ${String(pct).padStart(3)}% ${phase} clips ${clips} `);
48
+ if (status === "succeeded") io.write("\n");
49
+ return;
50
+ }
51
+ if ((m = line.match(/^FILE kind=(\S+) name=(\S+) bytes=(\d+)/))) {
52
+ files.push({ kind: m[1], name: m[2], bytes: Number(m[3]) });
53
+ return;
54
+ }
55
+ if ((m = line.match(/^DELIVERY kind=(\S+) name=(\S+) path=(\S+) bytes=(\d+)/))) {
56
+ io.out(` 📦 ${m[1].padEnd(11)} ${m[2]} (${oberonBytes(Number(m[4]))})`);
57
+ return;
58
+ }
59
+ if (line.startsWith("WARNINGS=")) {
60
+ io.out(` ⚠ ${line.slice("WARNINGS=".length)}`);
61
+ return;
62
+ }
63
+ if (line.startsWith("JOB=") || line.startsWith("OUT_DIR=")) return; // 내부 추적
64
+ if (/=(present|missing)$/.test(line)) return; // 키 존재 점검 라인
65
+ if (line.trim()) io.out(` ${line}`);
66
+ }
67
+
68
+ // `oberon render <manifest.json> [--delivery DIR] [--max-shots N] [--takes N]
69
+ // [--resolution R] [--max-polls N] [--poll-ms N] [--open] [--dry-run]`
70
+ async function render(io, args, deps = {}) {
71
+ const { flags, rest } = parseFlags(args);
72
+ if (!rest[0]) fail("A manifest path is required: agentlas oberon render <manifest.json>");
73
+ const manifestPath = path.resolve(rest[0]);
74
+ if (!fs.existsSync(manifestPath)) fail(`Manifest not found: ${manifestPath}`);
75
+
76
+ let manifest;
77
+ try {
78
+ manifest = JSON.parse(fs.readFileSync(manifestPath, "utf8"));
79
+ } catch (e) {
80
+ fail(`Failed to parse manifest JSON: ${e.message}`);
81
+ }
82
+ if (!Array.isArray(manifest.shots) || !manifest.shots.length) fail("The manifest has no shots[].");
83
+
84
+ // seam: 다른 산출물 배치(예: Desktop 동봉 빌드)가 생기면 여기만 주입 교체.
85
+ const { script, builtRender } = (deps.resolveRenderEntry || defaultRenderEntry)();
86
+ // 정직 정지 — v1과 동일한 문구. 렌더 진입점이 없으면 절대 가짜 렌더를 하지 않는다.
87
+ if (!fs.existsSync(script)) fail(`Headless render script not found (not included in the packaged app): ${script}`);
88
+ if (!fs.existsSync(builtRender)) fail(`An Electron build is required. Run npm run build:electron first (missing: ${builtRender})`);
89
+
90
+ // --max-shots 등 오버라이드가 있으면 사용자 매니페스트는 그대로 두고 임시 패치본을 만든다.
91
+ let reqPath = manifestPath;
92
+ const overrides = {};
93
+ if (flags["max-shots"]) overrides.maxShots = Number(flags["max-shots"]);
94
+ if (flags["takes"]) overrides.takesPerShot = Number(flags["takes"]);
95
+ if (flags["resolution"]) overrides.resolution = flags["resolution"];
96
+ if (Object.keys(overrides).length) {
97
+ const patched = { ...manifest, ...overrides };
98
+ reqPath = path.join(os.tmpdir(), `oberon-req-${Date.now().toString(36)}.json`);
99
+ fs.writeFileSync(reqPath, JSON.stringify(patched, null, 2), "utf8");
100
+ }
101
+
102
+ const deliveryDir = path.resolve(flags.delivery || path.join(path.dirname(manifestPath), `${slugifyOberon(manifest.title)}-delivery`));
103
+
104
+ const childEnv = { ...process.env };
105
+ delete childEnv.ELECTRON_RUN_AS_NODE; // full Electron으로 부팅 (Desktop execPath일 때)
106
+ childEnv.OBERON_LIVE_VEO = "1";
107
+ childEnv.OBERON_LIVE_REQUEST_FILE = reqPath;
108
+ childEnv.OBERON_LIVE_DELIVERY_DIR = deliveryDir;
109
+ if (flags["max-polls"]) childEnv.OBERON_LIVE_MAX_POLLS = String(flags["max-polls"]);
110
+ if (flags["poll-ms"]) childEnv.OBERON_LIVE_POLL_MS = String(flags["poll-ms"]);
111
+ if (flags.open) childEnv.OBERON_LIVE_OPEN_DELIVERY = "1";
112
+
113
+ io.out(`▶ Oberon render: "${manifest.title}" (${manifest.shots.length} shots, max ${overrides.maxShots ?? manifest.maxShots ?? 3})`);
114
+ io.out(` Manifest: ${manifestPath}`);
115
+ io.out(` Delivery folder: ${deliveryDir}`);
116
+ if (manifest.titles) io.out(` title/subtitle burn-in: enabled → generating additional *_titled.mp4`);
117
+
118
+ if (flags["dry-run"]) {
119
+ const execPath = deps.execPath || process.execPath;
120
+ io.out("\n[dry-run] Command to run:");
121
+ io.out(` ${execPath} ${script}`);
122
+ io.out(" env: OBERON_LIVE_VEO=1");
123
+ io.out(` OBERON_LIVE_REQUEST_FILE=${reqPath}`);
124
+ io.out(` OBERON_LIVE_DELIVERY_DIR=${deliveryDir}`);
125
+ io.out(" (full Electron · GEMINI_API_KEY/GOOGLE_CLOUD_PROJECT vault required)");
126
+ return 0;
127
+ }
128
+
129
+ const doSpawn = deps.spawn || spawn;
130
+ const execPath = deps.execPath || process.execPath;
131
+ const stdoutStream = deps.stdout || process.stdout;
132
+ const stderrStream = deps.stderr || process.stderr;
133
+ const lineIo = { out: io.out, write: (s) => stdoutStream.write(s) };
134
+
135
+ return new Promise((resolve) => {
136
+ const child = doSpawn(execPath, [script], { env: childEnv, stdio: ["ignore", "pipe", "pipe"] });
137
+ const files = [];
138
+ let buf = "";
139
+ const handle = (chunk) => {
140
+ buf += chunk.toString();
141
+ let nl;
142
+ while ((nl = buf.indexOf("\n")) >= 0) {
143
+ const line = buf.slice(0, nl);
144
+ buf = buf.slice(nl + 1);
145
+ oberonRenderLine(line, files, lineIo);
146
+ }
147
+ };
148
+ child.stdout.on("data", handle);
149
+ child.stderr.on("data", (c) => stderrStream.write(c));
150
+ child.on("close", (code) => {
151
+ if (code === 0) {
152
+ io.out(`\n✓ Render complete — delivery folder: ${deliveryDir}`);
153
+ const titled = files.filter((f) => f.kind && f.kind.startsWith("titled"));
154
+ if (titled.length) io.out(` title/subtitle burn-in files: ${titled.map((f) => f.name).join(", ")}`);
155
+ resolve(0);
156
+ } else {
157
+ stderrStream.write(`\n✖ Render failed (exit ${code})\n`);
158
+ resolve(code || 1); // v1은 process.exitCode 설정 — v2는 코드 반환
159
+ }
160
+ });
161
+ });
162
+ }
163
+
164
+ module.exports = { render, oberonRenderLine, defaultRenderEntry };
@@ -0,0 +1,249 @@
1
+ "use strict";
2
+ /*
3
+ * project/career-graph — 커리어 그래프 소스 라우팅 (v1 monolith 8042–8252 포팅).
4
+ *
5
+ * 원칙: 그래프는 재생성 가능한 파생 인덱스다 — Markdown/JSONL/JSON 원장이 원본.
6
+ * 전체 인덱스 실행(ingest/query/verify/trace)은 Agentlas OS / Hephaestus 런타임
7
+ * (career_graph 파이썬 모듈)이 소유하고, 터미널은 소스 등록/상태만 담당한다.
8
+ * status/list는 비변형(초기화 안 된 프로젝트에 파일 생성 금지 — 0.9.10 경계).
9
+ */
10
+ const fs = require("node:fs");
11
+ const path = require("node:path");
12
+ const { loadArch } = require("../core/db.cjs");
13
+ const { initializedAgentlasProjectPathCli } = require("./state.cjs");
14
+ const {
15
+ parseFlagsCli,
16
+ readJsonSafeCli,
17
+ writeJsonSafeCli,
18
+ listOntologyInboxCli,
19
+ resolveOntologyPathCli,
20
+ inferOntologyKindCli,
21
+ inferOntologyScopeCli,
22
+ parseOntologyNaturalArgsCli,
23
+ openLocalPathCli,
24
+ } = require("./ontology.cjs");
25
+
26
+ function careerGraphPathsForCli(projectPath) {
27
+ const arch = loadArch();
28
+ const root = path.resolve(projectPath || process.cwd());
29
+ const memoryDir = path.join(root, arch.memoryDir || ".agentlas");
30
+ return {
31
+ root,
32
+ memoryDir,
33
+ configPath: path.join(memoryDir, arch.careerGraphConfigFile || "career-graph.json"),
34
+ sourceManifestPath: path.join(memoryDir, arch.careerGraphSourceManifestFile || "career-graph-sources.json"),
35
+ inboxPath: path.join(memoryDir, arch.careerGraphInboxDir || "career-graph-inbox"),
36
+ dbPath: path.join(memoryDir, arch.careerGraphDbFile || "career-graph.sqlite"),
37
+ };
38
+ }
39
+
40
+ function careerGraphSourceManifestSkeletonCli(root) {
41
+ return {
42
+ schemaVersion: "1.0",
43
+ kind: "agentlas-career-graph-source-manifest",
44
+ projectRoot: root,
45
+ sources: [],
46
+ };
47
+ }
48
+
49
+ function ensureCareerGraphCli(projectPath, lang) {
50
+ const paths = careerGraphPathsForCli(projectPath);
51
+ if (!initializedAgentlasProjectPathCli(paths.root)) {
52
+ throw new Error(lang === "ko"
53
+ ? "Agentlas 프로젝트 상태가 초기화되지 않았습니다. 먼저 `agentlas project init`을 명시적으로 실행하세요."
54
+ : "Agentlas project state is not initialized. Run `agentlas project init` explicitly first.");
55
+ }
56
+ fs.mkdirSync(paths.inboxPath, { recursive: true });
57
+ if (!fs.existsSync(paths.sourceManifestPath)) {
58
+ writeJsonSafeCli(paths.sourceManifestPath, careerGraphSourceManifestSkeletonCli(paths.root));
59
+ }
60
+ return paths;
61
+ }
62
+
63
+ function readCareerGraphSourcesCli(sourceManifestPath) {
64
+ const manifest = readJsonSafeCli(sourceManifestPath, { sources: [] });
65
+ return Array.isArray(manifest.sources) ? manifest.sources : [];
66
+ }
67
+
68
+ function careerGraphUsageLinesCli(lang) {
69
+ if (lang === "ko") {
70
+ return [
71
+ "커리어 그래프 명령:",
72
+ " career-graph status 소스 라우팅 파일과 인덱스 상태 표시",
73
+ " career-graph list 수신함 파일과 등록 소스 참조 목록",
74
+ " career-graph open 프로젝트 커리어 그래프 수신함 열기",
75
+ " career-graph add ./docs 폴더를 비공개 소스 자료로 등록",
76
+ "",
77
+ "전체 그래프 인덱스 실행은 Agentlas OS / Hephaestus에서 제공합니다.",
78
+ "안전: 그래프는 재생성 가능하며 Markdown·JSONL·sitemap·code map이 원본입니다.",
79
+ ];
80
+ }
81
+ return [
82
+ "Career Graph commands:",
83
+ " career-graph status show source-routing files and index state",
84
+ " career-graph list list inbox files and registered source refs",
85
+ " career-graph open open the project career graph inbox",
86
+ " career-graph add ./docs register a folder as private source material",
87
+ "",
88
+ "Full graph index commands live in Agentlas OS / Hephaestus:",
89
+ " hephaestus career-graph ingest --project .",
90
+ " hephaestus career-graph query \"release failures\" --project .",
91
+ " hephaestus career-graph verify --project .",
92
+ "",
93
+ "Safety: the graph is rebuildable. Markdown, JSONL ledgers, sitemap, and code map stay source of truth.",
94
+ ];
95
+ }
96
+
97
+ function existingCareerGraphCanonicalRefsCli(root) {
98
+ return [
99
+ ".agentlas/project-soul-memory.md",
100
+ ".agentlas/memory-log.jsonl",
101
+ ".agentlas/curator-decisions.jsonl",
102
+ ".agentlas/sitemap.json",
103
+ ".agentlas/code-map/project-map.json",
104
+ ".agentlas/ledgers/routing-decisions.jsonl",
105
+ ".agentlas/ledgers/executions.jsonl",
106
+ ".agentlas/ledgers/agent-evolution-proposals.jsonl",
107
+ ].filter((rel) => fs.existsSync(path.join(root, rel)));
108
+ }
109
+
110
+ function formatCareerGraphStatusCli(paths, lang) {
111
+ const ko = lang === "ko";
112
+ const sources = readCareerGraphSourcesCli(paths.sourceManifestPath);
113
+ const inbox = listOntologyInboxCli(paths.inboxPath);
114
+ const canonical = existingCareerGraphCanonicalRefsCli(paths.root);
115
+ const lines = [
116
+ ko ? "커리어 그래프: 활성" : "Career Graph: active",
117
+ ` ${ko ? "프로젝트" : "project"}: ${paths.root}`,
118
+ ` ${ko ? "수신함" : "inbox"}: ${paths.inboxPath}`,
119
+ ` DB: ${paths.dbPath}`,
120
+ ` ${ko ? "인덱스" : "index"}: ${fs.existsSync(paths.dbPath) ? (ko ? "있음" : "present") : (ko ? "대기" : "pending")}`,
121
+ ` ${ko ? "정책" : "policy"}: ledger_first_derived_index`,
122
+ ko ? " 원본 기준: Markdown / JSONL / JSON 파일" : " source of truth: Markdown / JSONL / JSON files",
123
+ "",
124
+ `${ko ? "기본 소스 참조" : "Canonical source refs"} (${canonical.length}):`,
125
+ ];
126
+ for (const rel of canonical) lines.push(` ${rel}`);
127
+ if (!canonical.length) lines.push(ko ? " (아직 없음)" : " (none yet)");
128
+ lines.push("", `${ko ? "수신함" : "Inbox"} (${inbox.length}):`);
129
+ for (const item of inbox) lines.push(` ${item.supported ? "ok" : "!"} ${item.name} ${item.supported ? (ko ? "지원됨" : "supported") : (ko ? "어댑터 대기" : "adapter pending")}`);
130
+ if (!inbox.length) lines.push(ko ? " (비어 있음)" : " (empty)");
131
+ lines.push("", `${ko ? "등록된 소스 참조" : "Registered source refs"} (${sources.length}):`);
132
+ for (const source of sources) {
133
+ const sourcePath = path.resolve(String(source.path || ""));
134
+ lines.push(` ${fs.existsSync(sourcePath) ? "ok" : "!"} ${sourcePath} ${source.kind || "project"} / ${source.scope || "private"}`);
135
+ }
136
+ if (!sources.length) lines.push(ko ? " (없음)" : " (none)");
137
+ lines.push(
138
+ "",
139
+ ko ? "Agentlas OS로 파생 인덱스 만들기:" : "Build the derived index with Agentlas OS:",
140
+ ` hephaestus career-graph ingest --project ${JSON.stringify(paths.root)}`,
141
+ );
142
+ return lines;
143
+ }
144
+
145
+ function registerCareerGraphSourceCli(paths, source, kind, scope, cwd, lang) {
146
+ const ko = lang === "ko";
147
+ if (!source) throw new Error(ko ? "사용법: career-graph add <경로>" : "usage: career-graph add <path>");
148
+ const sourcePath = resolveOntologyPathCli(source, cwd || paths.root);
149
+ if (!fs.existsSync(sourcePath)) throw new Error(ko ? `소스를 찾지 못했습니다: ${sourcePath}` : `source not found: ${sourcePath}`);
150
+ const manifest = readJsonSafeCli(paths.sourceManifestPath, careerGraphSourceManifestSkeletonCli(paths.root));
151
+ const nextSources = (Array.isArray(manifest.sources) ? manifest.sources : [])
152
+ .filter((item) => path.resolve(String(item.path || "")) !== sourcePath);
153
+ nextSources.push({ path: sourcePath, kind, scope, registeredAt: new Date().toISOString() });
154
+ manifest.schemaVersion = "1.0";
155
+ manifest.kind = "agentlas-career-graph-source-manifest";
156
+ manifest.projectRoot = paths.root;
157
+ manifest.sources = nextSources;
158
+ writeJsonSafeCli(paths.sourceManifestPath, manifest);
159
+ return ko
160
+ ? [
161
+ `커리어 그래프 소스 등록됨: ${sourcePath}`,
162
+ ` 종류: ${kind}`,
163
+ ` 범위: ${scope}`,
164
+ " 복사: 안 함",
165
+ " 스캔: 이 등록 폴더만 사용하며 홈/이웃 프로젝트는 스캔하지 않음",
166
+ ]
167
+ : [
168
+ `Registered Career Graph source: ${sourcePath}`,
169
+ ` kind: ${kind}`,
170
+ ` scope: ${scope}`,
171
+ " copy: no",
172
+ " scan: only this registered folder, not home/sibling projects",
173
+ ];
174
+ }
175
+
176
+ function runCareerGraphCli(args, opts) {
177
+ opts = opts || {};
178
+ const ko = opts.lang === "ko";
179
+ const cwd = path.resolve(opts.cwd || process.cwd());
180
+ const projectPath = path.resolve(opts.projectPath || cwd);
181
+ const normalizedArgs = Array.isArray(args) ? args : [];
182
+ const sub = normalizedArgs[0] || "status";
183
+ const passivePaths = careerGraphPathsForCli(projectPath);
184
+ if (sub === "status" || sub === "list") {
185
+ if (!initializedAgentlasProjectPathCli(projectPath)) {
186
+ return [
187
+ ko ? "커리어 그래프: 초기화되지 않음" : "Career Graph: not initialized",
188
+ ` ${ko ? "프로젝트" : "project"}: ${projectPath}`,
189
+ ko ? " 생성된 파일 없음" : " no files were created",
190
+ ko ? " 명시적 초기화: agentlas project init" : " initialize explicitly: agentlas project init",
191
+ ];
192
+ }
193
+ return formatCareerGraphStatusCli(passivePaths, opts.lang);
194
+ }
195
+ if (sub === "help" || sub === "--help" || sub === "-h") return careerGraphUsageLinesCli(opts.lang);
196
+ if (["ingest", "query", "verify", "trace"].includes(String(sub))) {
197
+ return [
198
+ ko
199
+ ? "Career Graph 인덱스 실행은 Agentlas OS / Hephaestus에서 제공합니다."
200
+ : "Career Graph index execution is provided by Agentlas OS / Hephaestus.",
201
+ `${ko ? "실행" : "Run"}: hephaestus career-graph ${normalizedArgs.join(" ")} --project ${JSON.stringify(passivePaths.root)}`,
202
+ ];
203
+ }
204
+ const directCareerCommand = ["open", "add"].includes(String(sub));
205
+ if (!directCareerCommand) {
206
+ const parsed = parseOntologyNaturalArgsCli(normalizedArgs.join(" "), cwd);
207
+ if (!parsed) throw new Error(ko
208
+ ? "사용법: /career-graph status|list|open|add <경로>"
209
+ : "usage: /career-graph status|list|open|add <path>");
210
+ return runCareerGraphCli(parsed, opts);
211
+ }
212
+ const paths = ensureCareerGraphCli(projectPath, opts.lang);
213
+ if (sub === "open") {
214
+ if (!opts.noOpen) openLocalPathCli(paths.inboxPath, opts.notify);
215
+ return [`${ko ? "커리어 그래프 수신함을 열었습니다" : "Opened Career Graph inbox"}: ${paths.inboxPath}`];
216
+ }
217
+ if (sub === "add") {
218
+ const flags = parseFlagsCli(normalizedArgs.slice(1));
219
+ const source = flags._[0];
220
+ const kind = inferOntologyKindCli(flags.kind || flags._[1], normalizedArgs.join(" "));
221
+ const scope = inferOntologyScopeCli(flags.scope || flags._[2], normalizedArgs.join(" "), kind);
222
+ return registerCareerGraphSourceCli(paths, source, kind, scope, cwd, opts.lang);
223
+ }
224
+ throw new Error(ko
225
+ ? "사용법: /career-graph status|list|open|add <경로>"
226
+ : "usage: /career-graph status|list|open|add <path>");
227
+ }
228
+
229
+ function runCareerGraphNaturalCli(text, opts) {
230
+ const cwd = path.resolve((opts && opts.cwd) || process.cwd());
231
+ const parsed = parseOntologyNaturalArgsCli(text, cwd);
232
+ if (!parsed) throw new Error((opts && opts.lang) === "ko"
233
+ ? "사용법: /career-graph status|list|open|add <경로>"
234
+ : "usage: /career-graph status|list|open|add <path>");
235
+ return runCareerGraphCli(parsed, { ...(opts || {}), cwd });
236
+ }
237
+
238
+ module.exports = {
239
+ careerGraphPathsForCli,
240
+ careerGraphSourceManifestSkeletonCli,
241
+ ensureCareerGraphCli,
242
+ readCareerGraphSourcesCli,
243
+ careerGraphUsageLinesCli,
244
+ existingCareerGraphCanonicalRefsCli,
245
+ formatCareerGraphStatusCli,
246
+ registerCareerGraphSourceCli,
247
+ runCareerGraphCli,
248
+ runCareerGraphNaturalCli,
249
+ };