@agentproto/app-kit 1.3.0 → 1.4.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/index.mjs CHANGED
@@ -1,6 +1,6 @@
1
- import { join, relative, isAbsolute, dirname, resolve } from 'path';
1
+ import { join, relative, isAbsolute, dirname, resolve, basename } from 'path';
2
2
  import { defineWorkspace } from '@agentproto/workspace';
3
- import { mkdir, writeFile, copyFile, cp, readFile, readdir } from 'fs/promises';
3
+ import { mkdir, writeFile, copyFile, cp, readFile, mkdtemp, readdir, rm, stat } from 'fs/promises';
4
4
  import matter2 from 'gray-matter';
5
5
  import { parseAgentManifest, agentFromManifest } from '@agentproto/agent/manifest';
6
6
  import { loadWorkflowHandle } from '@agentproto/workflow-loader';
@@ -10,6 +10,9 @@ import { parseDriverManifest, driverDefinitionFromManifest, driverFromManifest }
10
10
  import { normalizeToolId } from '@agentproto/driver';
11
11
  import { defineCliDriver } from '@agentproto/driver-cli';
12
12
  import { defineHttpDriver } from '@agentproto/driver-http';
13
+ import { createHash } from 'crypto';
14
+ import { tmpdir } from 'os';
15
+ import { spawn } from 'child_process';
13
16
 
14
17
  /**
15
18
  * @agentproto/app-kit v0.1.0
@@ -58,7 +61,8 @@ async function emitApp(app, dir) {
58
61
  ...app.ui.description !== void 0 ? { description: app.ui.description } : {},
59
62
  ...app.ui.tools !== void 0 ? { tools: app.ui.tools } : {},
60
63
  ...app.ui.port !== void 0 ? { port: app.ui.port } : {},
61
- ...app.ui.csp !== void 0 ? { csp: app.ui.csp } : {}
64
+ ...app.ui.csp !== void 0 ? { csp: app.ui.csp } : {},
65
+ ...app.ui.build !== void 0 ? { build: app.ui.build } : {}
62
66
  };
63
67
  }
64
68
  let artifactPath;
@@ -111,7 +115,10 @@ async function emitApp(app, dir) {
111
115
  agents: agentRefs,
112
116
  workflows: workflowRefs2,
113
117
  ...app.workspace ? { workspace: app.workspace.id } : {},
114
- ...app.requires !== void 0 ? { requires: app.requires } : {},
118
+ ...requiresFrontmatter(app),
119
+ ...app.placement !== void 0 && app.placement !== "any" ? { placement: app.placement } : {},
120
+ ...app.exposes && (app.exposes.agents.length > 0 || app.exposes.workflows.length > 0) ? { exposes: { agents: app.exposes.agents, workflows: app.exposes.workflows } } : {},
121
+ ...app.accepts?.tasks ? { accepts: { tasks: true } } : {},
115
122
  ...uiFrontmatter !== void 0 ? { ui: uiFrontmatter } : {},
116
123
  ...artifactFrontmatter !== void 0 ? { artifact: artifactFrontmatter } : {},
117
124
  ...skillFrontmatter !== void 0 ? { skill: skillFrontmatter } : {},
@@ -119,6 +126,7 @@ async function emitApp(app, dir) {
119
126
  ...app.dev !== void 0 ? { dev: app.dev } : {},
120
127
  ...app.data !== void 0 ? { data: app.data } : {},
121
128
  ...app.externalReadRoots !== void 0 ? { externalReadRoots: app.externalReadRoots } : {},
129
+ ...app.boundaries !== void 0 ? { boundaries: app.boundaries } : {},
122
130
  ...app.category !== void 0 ? { category: app.category } : {}
123
131
  };
124
132
  await writeFile(appPath, toManifest(appFrontmatter, app.description ?? ""), "utf8");
@@ -132,6 +140,15 @@ async function emitApp(app, dir) {
132
140
  ...skillPath ? { skillPath } : {}
133
141
  };
134
142
  }
143
+ function requiresFrontmatter(app) {
144
+ const r = app.requirements;
145
+ if (r && (r.browser || r.fs || r.gpu || r.secrets.length > 0)) {
146
+ return {
147
+ requires: { browser: r.browser, fs: r.fs, gpu: r.gpu, secrets: r.secrets, apps: r.apps }
148
+ };
149
+ }
150
+ return app.requires !== void 0 ? { requires: app.requires } : {};
151
+ }
135
152
  function toManifest(handle, body) {
136
153
  const data = JSON.parse(JSON.stringify(handle));
137
154
  return matter2.stringify(`
@@ -139,6 +156,86 @@ ${body}
139
156
  `, data);
140
157
  }
141
158
 
159
+ // src/manifest-fields.ts
160
+ var APP_PLACEMENTS = ["local", "box", "any", "split"];
161
+ function isRecord(v) {
162
+ return typeof v === "object" && v !== null && !Array.isArray(v);
163
+ }
164
+ function stringList(v, key, fail) {
165
+ if (v === void 0) return [];
166
+ if (!Array.isArray(v) || !v.every((e) => typeof e === "string" && e.trim() !== "")) {
167
+ throw fail(`'${key}' must be an array of non-empty strings.`);
168
+ }
169
+ return [...v];
170
+ }
171
+ function bool(v, key, fail) {
172
+ if (v === void 0) return false;
173
+ if (typeof v !== "boolean") throw fail(`'${key}' must be a boolean.`);
174
+ return v;
175
+ }
176
+ function normalizeManifestFields(input, declared, fail) {
177
+ let placement = "any";
178
+ if (input.placement !== void 0) {
179
+ if (typeof input.placement !== "string" || !APP_PLACEMENTS.includes(input.placement)) {
180
+ throw fail(
181
+ `'placement' must be one of ${APP_PLACEMENTS.map((p) => `"${p}"`).join(", ")}, got ${JSON.stringify(input.placement)}.`
182
+ );
183
+ }
184
+ placement = input.placement;
185
+ }
186
+ let requires;
187
+ let requirements = { browser: false, fs: false, gpu: false, secrets: [], apps: [] };
188
+ const r = input.requires;
189
+ if (r !== void 0) {
190
+ if (Array.isArray(r)) {
191
+ if (!r.every((e) => typeof e === "string")) {
192
+ throw fail("'requires' must be an array of strings or an object { browser, fs, gpu, secrets, apps }.");
193
+ }
194
+ requires = [...r];
195
+ requirements = { ...requirements, apps: requires };
196
+ } else if (isRecord(r)) {
197
+ const apps = stringList(r.apps, "requires.apps", fail);
198
+ requirements = {
199
+ browser: bool(r.browser, "requires.browser", fail),
200
+ fs: bool(r.fs, "requires.fs", fail),
201
+ gpu: bool(r.gpu, "requires.gpu", fail),
202
+ secrets: stringList(r.secrets, "requires.secrets", fail),
203
+ apps
204
+ };
205
+ if (apps.length > 0) requires = apps;
206
+ } else {
207
+ throw fail("'requires' must be an array of strings or an object { browser, fs, gpu, secrets, apps }.");
208
+ }
209
+ }
210
+ let exposes = { agents: [], workflows: [] };
211
+ if (input.exposes !== void 0) {
212
+ if (!isRecord(input.exposes)) throw fail("'exposes' must be an object { agents, workflows }.");
213
+ const agents = stringList(input.exposes.agents, "exposes.agents", fail);
214
+ const workflows = stringList(input.exposes.workflows, "exposes.workflows", fail);
215
+ for (const id of agents) {
216
+ if (!declared.agents.includes(id)) {
217
+ throw fail(
218
+ `'exposes.agents' names '${id}' but the app declares no such agent. Declared: [${declared.agents.join(", ") || "none"}].`
219
+ );
220
+ }
221
+ }
222
+ for (const id of workflows) {
223
+ if (!declared.workflows.includes(id)) {
224
+ throw fail(
225
+ `'exposes.workflows' names '${id}' but the app declares no such workflow. Declared: [${declared.workflows.join(", ") || "none"}].`
226
+ );
227
+ }
228
+ }
229
+ exposes = { agents, workflows };
230
+ }
231
+ let accepts = { tasks: false };
232
+ if (input.accepts !== void 0) {
233
+ if (!isRecord(input.accepts)) throw fail("'accepts' must be an object { tasks }.");
234
+ accepts = { tasks: bool(input.accepts.tasks, "accepts.tasks", fail) };
235
+ }
236
+ return { placement, requires, requirements, exposes, accepts };
237
+ }
238
+
142
239
  // src/define-app.ts
143
240
  var AppDefinitionError = class extends Error {
144
241
  constructor(message) {
@@ -193,7 +290,23 @@ function defineApp(def) {
193
290
  const name = def.name;
194
291
  const version = def.version ?? (id ? "0.1.0" : void 0);
195
292
  const description = def.description;
196
- const requires = def.requires ? Object.freeze([...def.requires]) : void 0;
293
+ const fields = normalizeManifestFields(
294
+ { placement: def.placement, requires: def.requires, exposes: def.exposes, accepts: def.accepts },
295
+ { agents: agents.map((e) => e.agent.id), workflows: workflows.map((w) => w.id) },
296
+ (msg) => new AppDefinitionError(msg)
297
+ );
298
+ const requires = fields.requires ? Object.freeze([...fields.requires]) : void 0;
299
+ const requirements = Object.freeze({
300
+ ...fields.requirements,
301
+ secrets: Object.freeze([...fields.requirements.secrets]),
302
+ apps: Object.freeze([...fields.requirements.apps])
303
+ });
304
+ const placement = fields.placement;
305
+ const exposes = Object.freeze({
306
+ agents: Object.freeze([...fields.exposes.agents]),
307
+ workflows: Object.freeze([...fields.exposes.workflows])
308
+ });
309
+ const accepts = Object.freeze({ ...fields.accepts });
197
310
  const ui = def.ui ? Object.freeze({ ...def.ui }) : void 0;
198
311
  const artifact = def.artifact ? Object.freeze({ ...def.artifact }) : void 0;
199
312
  const skill = def.skill ? Object.freeze({ ...def.skill }) : void 0;
@@ -201,6 +314,7 @@ function defineApp(def) {
201
314
  const dev = def.dev ? Object.freeze({ launch: Object.freeze(def.dev.launch.map((l) => Object.freeze({ ...l }))) }) : void 0;
202
315
  const externalReadRoots = def.externalReadRoots ? Object.freeze([...def.externalReadRoots]) : void 0;
203
316
  const data = def.data ? Object.freeze({ ...def.data }) : void 0;
317
+ const boundaries = def.boundaries ? Object.freeze({ ...def.boundaries }) : void 0;
204
318
  const category = def.category;
205
319
  validateAttachment(agents, workflows);
206
320
  validateUniqueIds(tools, "tool");
@@ -222,6 +336,10 @@ function defineApp(def) {
222
336
  ...version !== void 0 ? { version } : {},
223
337
  ...description !== void 0 ? { description } : {},
224
338
  ...requires !== void 0 ? { requires } : {},
339
+ requirements,
340
+ placement,
341
+ exposes,
342
+ accepts,
225
343
  ...ui !== void 0 ? { ui } : {},
226
344
  ...artifact !== void 0 ? { artifact } : {},
227
345
  ...skill !== void 0 ? { skill } : {},
@@ -229,6 +347,7 @@ function defineApp(def) {
229
347
  ...dev !== void 0 ? { dev } : {},
230
348
  ...data !== void 0 ? { data } : {},
231
349
  ...externalReadRoots !== void 0 ? { externalReadRoots } : {},
350
+ ...boundaries !== void 0 ? { boundaries } : {},
232
351
  ...category !== void 0 ? { category } : {},
233
352
  async toMastraAgents(opts, only) {
234
353
  const targets = only ? selectAgents(frozenAgents, only) : frozenAgents;
@@ -260,6 +379,10 @@ function defineApp(def) {
260
379
  ...version !== void 0 ? { version } : {},
261
380
  ...description !== void 0 ? { description } : {},
262
381
  ...requires !== void 0 ? { requires } : {},
382
+ requirements,
383
+ placement,
384
+ exposes,
385
+ accepts,
263
386
  ...ui !== void 0 ? { ui } : {},
264
387
  ...artifact !== void 0 ? { artifact } : {},
265
388
  ...skill !== void 0 ? { skill } : {},
@@ -267,6 +390,7 @@ function defineApp(def) {
267
390
  ...dev !== void 0 ? { dev } : {},
268
391
  ...data !== void 0 ? { data } : {},
269
392
  ...externalReadRoots !== void 0 ? { externalReadRoots } : {},
393
+ ...boundaries !== void 0 ? { boundaries } : {},
270
394
  ...category !== void 0 ? { category } : {}
271
395
  },
272
396
  dir
@@ -527,6 +651,54 @@ async function resolveAppUIRoot(appDir) {
527
651
  }
528
652
  return dirname(resolveRef(appDir, path));
529
653
  }
654
+ function parseUiBuildFrontmatter(raw, appMdPath) {
655
+ if (raw === void 0) return void 0;
656
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw)) {
657
+ throw new AppLoadError(`'${appMdPath}': frontmatter 'ui.build' must be an object.`);
658
+ }
659
+ const command = raw.command;
660
+ if (typeof command !== "string" || command.trim() === "") {
661
+ throw new AppLoadError(`'${appMdPath}': frontmatter 'ui.build.command' must be a non-empty string.`);
662
+ }
663
+ const cwd = raw.cwd;
664
+ if (cwd !== void 0 && (typeof cwd !== "string" || cwd.trim() === "")) {
665
+ throw new AppLoadError(`'${appMdPath}': frontmatter 'ui.build.cwd' must be a non-empty string.`);
666
+ }
667
+ const sources = raw.sources;
668
+ if (sources !== void 0 && (!Array.isArray(sources) || !sources.every((s) => typeof s === "string" && s.trim() !== ""))) {
669
+ throw new AppLoadError(
670
+ `'${appMdPath}': frontmatter 'ui.build.sources' must be an array of non-empty strings.`
671
+ );
672
+ }
673
+ return {
674
+ command,
675
+ ...cwd !== void 0 ? { cwd } : {},
676
+ ...sources !== void 0 ? { sources } : {}
677
+ };
678
+ }
679
+ async function peekAppUi(appDir) {
680
+ const appMdPath = join(appDir, ".agentproto", "APP.md");
681
+ let source;
682
+ try {
683
+ source = await readFile(appMdPath, "utf8");
684
+ } catch {
685
+ return void 0;
686
+ }
687
+ const data = matter2(source).data;
688
+ const ui = data.ui;
689
+ if (ui === void 0) return void 0;
690
+ if (typeof ui !== "object" || ui === null || Array.isArray(ui)) {
691
+ throw new AppLoadError(`'${appMdPath}': frontmatter 'ui' must be an object.`);
692
+ }
693
+ const path = ui.path;
694
+ if (typeof path !== "string" || path.trim() === "") {
695
+ throw new AppLoadError(
696
+ `'${appMdPath}': frontmatter 'ui.path' must be a non-empty string.`
697
+ );
698
+ }
699
+ const build = parseUiBuildFrontmatter(ui.build, appMdPath);
700
+ return { path: resolveRef(appDir, path), ...build ? { build } : {} };
701
+ }
530
702
  function isRefArray(v) {
531
703
  return Array.isArray(v) && v.every(
532
704
  (e) => typeof e === "object" && e !== null && typeof e.id === "string" && typeof e.path === "string"
@@ -546,11 +718,11 @@ function parseAppFrontmatter(data, appPath) {
546
718
  `'${appPath}': frontmatter 'workflows' must be an array of { id, path }.`
547
719
  );
548
720
  }
549
- if (data.requires !== void 0) {
550
- if (!Array.isArray(data.requires) || !data.requires.every((e) => typeof e === "string")) {
551
- throw new AppLoadError(`'${appPath}': frontmatter 'requires' must be an array of strings.`);
552
- }
553
- }
721
+ normalizeManifestFields(
722
+ data,
723
+ { agents: data.agents.map((a) => a.id), workflows: data.workflows.map((w) => w.id) },
724
+ (msg) => new AppLoadError(`'${appPath}': frontmatter ${msg}`)
725
+ );
554
726
  if (data.data !== void 0) {
555
727
  const d = data.data;
556
728
  if (typeof d !== "object" || d === null || d.dir !== void 0 && (typeof d.dir !== "string" || d.dir.trim() === "")) {
@@ -566,6 +738,14 @@ function parseAppFrontmatter(data, appPath) {
566
738
  );
567
739
  }
568
740
  }
741
+ if (data.boundaries !== void 0) {
742
+ const b = data.boundaries;
743
+ if (typeof b !== "object" || b === null || Array.isArray(b) || b.enforce !== void 0 && b.enforce !== "required" && b.enforce !== "best-effort") {
744
+ throw new AppLoadError(
745
+ `'${appPath}': frontmatter 'boundaries' must be an object whose optional 'enforce' is "required" or "best-effort".`
746
+ );
747
+ }
748
+ }
569
749
  if (data.category !== void 0 && (typeof data.category !== "string" || data.category.trim() === "")) {
570
750
  throw new AppLoadError(`'${appPath}': frontmatter 'category' must be a non-empty string.`);
571
751
  }
@@ -654,13 +834,15 @@ async function loadAppHandle(dir) {
654
834
  `ui: cannot read '${uiPath}': ${err instanceof Error ? err.message : String(err)}`
655
835
  );
656
836
  }
837
+ const build = parseUiBuildFrontmatter(fm.ui.build, appPath);
657
838
  ui = {
658
839
  html,
659
840
  ...fm.ui.title !== void 0 ? { title: fm.ui.title } : {},
660
841
  ...fm.ui.description !== void 0 ? { description: fm.ui.description } : {},
661
842
  ...fm.ui.tools !== void 0 ? { tools: fm.ui.tools } : {},
662
843
  ...fm.ui.port !== void 0 ? { port: fm.ui.port } : {},
663
- ...fm.ui.csp !== void 0 ? { csp: fm.ui.csp } : {}
844
+ ...fm.ui.csp !== void 0 ? { csp: fm.ui.csp } : {},
845
+ ...build !== void 0 ? { build } : {}
664
846
  };
665
847
  }
666
848
  return defineApp({
@@ -674,6 +856,9 @@ async function loadAppHandle(dir) {
674
856
  ...fm.version !== void 0 ? { version: fm.version } : {},
675
857
  ...fm.description !== void 0 ? { description: fm.description } : {},
676
858
  ...fm.requires !== void 0 ? { requires: fm.requires } : {},
859
+ ...fm.placement !== void 0 ? { placement: fm.placement } : {},
860
+ ...fm.exposes !== void 0 ? { exposes: fm.exposes } : {},
861
+ ...fm.accepts !== void 0 ? { accepts: fm.accepts } : {},
677
862
  ...ui !== void 0 ? { ui } : {},
678
863
  ...fm.artifact !== void 0 ? { artifact: { path: resolveRef(dir, fm.artifact.path), ...fm.artifact.title !== void 0 ? { title: fm.artifact.title } : {}, ...fm.artifact.description !== void 0 ? { description: fm.artifact.description } : {} } } : {},
679
864
  ...fm.skill !== void 0 ? { skill: { path: resolveRef(dir, fm.skill.path), ...fm.skill.title !== void 0 ? { title: fm.skill.title } : {}, ...fm.skill.description !== void 0 ? { description: fm.skill.description } : {} } } : {},
@@ -681,10 +866,230 @@ async function loadAppHandle(dir) {
681
866
  ...fm.dev !== void 0 ? { dev: fm.dev } : {},
682
867
  ...fm.data !== void 0 ? { data: fm.data } : {},
683
868
  ...fm.externalReadRoots !== void 0 ? { externalReadRoots: fm.externalReadRoots } : {},
869
+ ...fm.boundaries !== void 0 ? { boundaries: fm.boundaries } : {},
684
870
  ...fm.category !== void 0 ? { category: fm.category } : {}
685
871
  });
686
872
  }
873
+ var AGENTAPP_FORMAT = "agentapp/v1";
874
+ var DEFAULT_VERSION = "0.1.0";
875
+ var SKIP_DIR_NAMES = /* @__PURE__ */ new Set(["node_modules", ".git"]);
876
+ var AgentAppPackError = class extends Error {
877
+ code;
878
+ constructor(code, message) {
879
+ super(message);
880
+ this.name = "AgentAppPackError";
881
+ this.code = code;
882
+ }
883
+ };
884
+ async function pathExists(p) {
885
+ try {
886
+ await stat(p);
887
+ return true;
888
+ } catch {
889
+ return false;
890
+ }
891
+ }
892
+ function safeId(id) {
893
+ return String(id || "app").replace(/^@/, "").replace(/[^A-Za-z0-9._-]+/g, "-");
894
+ }
895
+ function isManifest(value) {
896
+ if (typeof value !== "object" || value === null) return false;
897
+ const m = value;
898
+ return typeof m.format === "string" && typeof m.id === "string" && typeof m.version === "string" && Array.isArray(m.agents) && Array.isArray(m.workflows) && Array.isArray(m.files) && typeof m.fileCount === "number" && typeof m.totalSize === "number" && typeof m.sha256 === "string";
899
+ }
900
+ async function collectFiles(root) {
901
+ const files = [];
902
+ async function walk(relDir) {
903
+ const absDir = join(root, relDir);
904
+ for (const entry of await readdir(absDir, { withFileTypes: true })) {
905
+ if (entry.isDirectory() && SKIP_DIR_NAMES.has(entry.name)) continue;
906
+ const rel = relDir ? join(relDir, entry.name) : entry.name;
907
+ const abs = join(absDir, entry.name);
908
+ if (entry.isDirectory()) {
909
+ await walk(rel);
910
+ } else if (entry.isFile()) {
911
+ const st = await stat(abs);
912
+ files.push({ path: rel, size: st.size });
913
+ }
914
+ }
915
+ }
916
+ await walk("");
917
+ return files;
918
+ }
919
+ async function aggregateSha256(root, files) {
920
+ const hash = createHash("sha256");
921
+ for (const file of files) {
922
+ hash.update(await readFile(join(root, file)));
923
+ }
924
+ return hash.digest("hex");
925
+ }
926
+ function runTar(args, opts) {
927
+ return new Promise((resolvePromise, reject) => {
928
+ const child = spawn("tar", args, { stdio: "ignore", cwd: opts?.cwd });
929
+ child.once("error", reject);
930
+ child.once("exit", (code) => resolvePromise(code ?? 0));
931
+ });
932
+ }
933
+ function extractIds(value) {
934
+ if (!Array.isArray(value)) return [];
935
+ const out = [];
936
+ for (const entry of value) {
937
+ if (typeof entry === "string") {
938
+ if (entry) out.push(entry);
939
+ } else if (entry && typeof entry === "object") {
940
+ const id = entry.id;
941
+ if (typeof id === "string" && id) out.push(id);
942
+ }
943
+ }
944
+ return out;
945
+ }
946
+ function extractPaths(front, key) {
947
+ const value = front[key];
948
+ if (value === void 0 || value === null) return void 0;
949
+ const items = Array.isArray(value) ? value : [value];
950
+ const out = [];
951
+ for (const item of items) {
952
+ if (typeof item === "string") {
953
+ if (item) out.push(basename(item));
954
+ } else if (item && typeof item === "object") {
955
+ const p = item.path;
956
+ if (typeof p === "string" && p) out.push(basename(p));
957
+ }
958
+ }
959
+ return out.length > 0 ? out : void 0;
960
+ }
961
+ function extractMeta(front) {
962
+ const id = typeof front.id === "string" && front.id.length > 0 ? front.id : typeof front.slug === "string" && front.slug.length > 0 ? front.slug : "app";
963
+ const version = typeof front.version === "string" && front.version.length > 0 ? front.version : DEFAULT_VERSION;
964
+ const name = typeof front.name === "string" ? front.name : void 0;
965
+ const description = typeof front.description === "string" ? front.description : void 0;
966
+ const ui = extractPaths(front, "ui");
967
+ return {
968
+ id,
969
+ ...name !== void 0 ? { name } : {},
970
+ version,
971
+ ...description !== void 0 ? { description } : {},
972
+ agents: extractIds(front.agents),
973
+ workflows: extractIds(front.workflows),
974
+ ...ui !== void 0 ? { ui } : {}
975
+ };
976
+ }
977
+ async function packApp(input) {
978
+ const appDirAbs = resolve(input.appDir);
979
+ const appMdPath = join(appDirAbs, ".agentproto", "APP.md");
980
+ if (!await pathExists(appMdPath)) {
981
+ throw new AgentAppPackError(
982
+ "not-an-app",
983
+ `${appDirAbs} is not an agentproto app (missing ${appMdPath}).`
984
+ );
985
+ }
986
+ const raw = await readFile(appMdPath, "utf8");
987
+ const front = matter2(raw).data;
988
+ const meta = extractMeta(front);
989
+ const outAbs = input.out !== void 0 ? resolve(input.out) : resolve(process.cwd(), `${safeId(meta.id)}-${meta.version}.agentapp`);
990
+ await mkdir(dirname(outAbs), { recursive: true });
991
+ const files = (await collectFiles(appDirAbs)).filter((f) => f.path !== "manifest.json").filter((f) => join(appDirAbs, f.path) !== outAbs).sort((a, b) => a.path < b.path ? -1 : a.path > b.path ? 1 : 0);
992
+ const fileCount = files.length;
993
+ const totalSize = files.reduce((sum, f) => sum + f.size, 0);
994
+ const sha256 = await aggregateSha256(
995
+ appDirAbs,
996
+ files.map((f) => f.path)
997
+ );
998
+ const manifest = {
999
+ format: AGENTAPP_FORMAT,
1000
+ id: meta.id,
1001
+ ...meta.name !== void 0 ? { name: meta.name } : {},
1002
+ version: meta.version,
1003
+ ...meta.description !== void 0 ? { description: meta.description } : {},
1004
+ agents: meta.agents,
1005
+ workflows: meta.workflows,
1006
+ ...meta.ui !== void 0 ? { ui: meta.ui } : {},
1007
+ files: files.map((f) => f.path),
1008
+ fileCount,
1009
+ totalSize,
1010
+ sha256,
1011
+ createdAt: (/* @__PURE__ */ new Date()).toISOString(),
1012
+ agentprotoVersion: ">=0.1.0"
1013
+ };
1014
+ const staging = await mkdtemp(join(tmpdir(), "agentapp-"));
1015
+ try {
1016
+ await cp(appDirAbs, staging, {
1017
+ recursive: true,
1018
+ filter: (source) => !SKIP_DIR_NAMES.has(basename(source))
1019
+ });
1020
+ await writeFile(join(staging, "manifest.json"), JSON.stringify(manifest, null, 2) + "\n", "utf8");
1021
+ const entries = (await readdir(staging)).sort();
1022
+ const packCode = await runTar(["-czf", outAbs, ...entries], { cwd: staging });
1023
+ if (packCode !== 0) {
1024
+ throw new AgentAppPackError("tar-failed", `tar failed with exit code ${packCode}`);
1025
+ }
1026
+ } finally {
1027
+ await rm(staging, { recursive: true, force: true });
1028
+ }
1029
+ return { file: outAbs, manifest };
1030
+ }
1031
+ function isUnsafeBundlePath(p) {
1032
+ if (p === "" || isAbsolute(p)) return true;
1033
+ return p.split(/[\\/]/).includes("..");
1034
+ }
1035
+ async function unpackApp(input) {
1036
+ const bundleAbs = resolve(input.file);
1037
+ if (!await pathExists(bundleAbs)) {
1038
+ throw new AgentAppPackError("bundle-not-found", `bundle not found: ${bundleAbs}`);
1039
+ }
1040
+ const temp = await mkdtemp(join(tmpdir(), "agentapp-"));
1041
+ try {
1042
+ const unpackCode = await runTar(["-xzf", bundleAbs, "-C", temp]);
1043
+ if (unpackCode !== 0) {
1044
+ throw new AgentAppPackError("tar-failed", `tar failed with exit code ${unpackCode}`);
1045
+ }
1046
+ const manifestPath = join(temp, "manifest.json");
1047
+ if (!await pathExists(manifestPath)) {
1048
+ throw new AgentAppPackError(
1049
+ "missing-manifest",
1050
+ `${bundleAbs} is not a valid .agentapp (missing manifest.json).`
1051
+ );
1052
+ }
1053
+ let parsed;
1054
+ try {
1055
+ parsed = JSON.parse(await readFile(manifestPath, "utf8"));
1056
+ } catch {
1057
+ throw new AgentAppPackError("malformed-manifest", `${bundleAbs} has a malformed manifest.json.`);
1058
+ }
1059
+ if (!isManifest(parsed)) {
1060
+ throw new AgentAppPackError("malformed-manifest", `${bundleAbs} has a malformed manifest.json.`);
1061
+ }
1062
+ const manifest = parsed;
1063
+ if (manifest.format !== AGENTAPP_FORMAT) {
1064
+ throw new AgentAppPackError(
1065
+ "unsupported-format",
1066
+ `unsupported bundle format '${manifest.format}' (expected ${AGENTAPP_FORMAT}).`
1067
+ );
1068
+ }
1069
+ const unsafe = manifest.files.find((f) => typeof f !== "string" || isUnsafeBundlePath(f));
1070
+ if (unsafe !== void 0) {
1071
+ throw new AgentAppPackError(
1072
+ "unsafe-path",
1073
+ `manifest.json lists a path outside the bundle root: ${JSON.stringify(unsafe)}.`
1074
+ );
1075
+ }
1076
+ const actual = await aggregateSha256(temp, manifest.files);
1077
+ if (actual !== manifest.sha256) {
1078
+ throw new AgentAppPackError(
1079
+ "digest-mismatch",
1080
+ `SHA-256 mismatch (expected ${manifest.sha256}, got ${actual}). Bundle is corrupted.`
1081
+ );
1082
+ }
1083
+ const outDir = input.dest !== void 0 ? resolve(input.dest) : resolve(process.cwd(), `${safeId(manifest.id)}-${manifest.version}`);
1084
+ await rm(manifestPath, { force: true });
1085
+ await mkdir(outDir, { recursive: true });
1086
+ await cp(temp, outDir, { recursive: true });
1087
+ return { dir: outDir, manifest };
1088
+ } finally {
1089
+ await rm(temp, { recursive: true, force: true });
1090
+ }
1091
+ }
687
1092
 
688
- export { AppDefinitionError, AppLoadError, defineApp, emitApp, loadAppBundledTools, loadAppHandle, refKey, resolveAppUIRoot, stripOwner };
1093
+ export { AGENTAPP_FORMAT, AgentAppPackError, AppDefinitionError, AppLoadError, aggregateSha256, collectFiles, defineApp, emitApp, isManifest, loadAppBundledTools, loadAppHandle, packApp, peekAppUi, refKey, resolveAppUIRoot, safeId, stripOwner, unpackApp };
689
1094
  //# sourceMappingURL=index.mjs.map
690
1095
  //# sourceMappingURL=index.mjs.map