@sellable/install 0.1.578 → 0.1.581

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.
@@ -3,13 +3,13 @@ FROM ${HERMES_IMAGE}
3
3
 
4
4
  USER root
5
5
 
6
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.577
7
- ARG MCP_PACKAGE=@sellable/mcp@0.1.858
6
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.581
7
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.859
8
8
  ARG INSTALLER_INTEGRITY
9
- ARG MCP_INTEGRITY=sha512-8yiUhwMbBk/KxZAXGXTsEHiN8XeB/qCqNgfApew0rhViJqiLlDN8oS67pZW5iB5gdmeuv4du0jx9Lhq/blr9hQ==
9
+ ARG MCP_INTEGRITY=sha512-U5uVeDThuUW/CdWghTXQqTAA58mPDly3XHGmfX6WVjczGQHRcDB+Q49vbrA1MOkGRCnzmjGZN1fIVfFtBlwMPw==
10
10
 
11
- RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.577" \
12
- && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.858" \
11
+ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.581" \
12
+ && test "${MCP_PACKAGE}" = "@sellable/mcp@0.1.859" \
13
13
  && test -n "${INSTALLER_INTEGRITY}" \
14
14
  && test "$(npm view "${INSTALLER_PACKAGE}" dist.integrity)" = "${INSTALLER_INTEGRITY}" \
15
15
  && test "$(npm view "${MCP_PACKAGE}" dist.integrity)" = "${MCP_INTEGRITY}" \
@@ -23,9 +23,9 @@ RUN test "${INSTALLER_PACKAGE}" = "@sellable/install@0.1.577" \
23
23
  && mkdir -p /opt/sellable-agent/install-root /opt/sellable-agent/mcp-root \
24
24
  && npm install --prefix /opt/sellable-agent/install-root --omit=dev --ignore-scripts --no-audit --no-fund "${INSTALLER_PACKAGE}" \
25
25
  && npm install --prefix /opt/sellable-agent/mcp-root --omit=dev --ignore-scripts --no-audit --no-fund "${MCP_PACKAGE}" \
26
- && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.577' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.858') throw new Error('package identity rejected');" \
26
+ && node --input-type=module -e "import { readFileSync } from 'node:fs'; const install = JSON.parse(readFileSync('/opt/sellable-agent/install-root/node_modules/@sellable/install/package.json')); const mcp = JSON.parse(readFileSync('/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp/package.json')); if (install.name !== '@sellable/install' || install.version !== '0.1.581' || mcp.name !== '@sellable/mcp' || mcp.version !== '0.1.859') throw new Error('package identity rejected');" \
27
27
  && node --input-type=module -e "import { buildExternalRuntimeClosure } from '/opt/sellable-agent/install-root/node_modules/@sellable/install/lib/sellable-agent/external-runtime-builder.mjs'; const built = buildExternalRuntimeClosure({ mcpPackageRoot: '/opt/sellable-agent/mcp-root/node_modules/@sellable/mcp', mcpNodeModulesRoot: '/opt/sellable-agent/mcp-root/node_modules', hermesSourceRoot: '/opt/hermes', ownerUid: 0, ownerGid: 0 }); if (!built.closureDigest) throw new Error('runtime closure rejected');" \
28
- && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.577', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.858', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
28
+ && node --input-type=module -e "import { chmodSync, writeFileSync } from 'node:fs'; const release = { installerPackage: '@sellable/install@0.1.581', installerIntegrity: process.argv[1], mcpPackage: '@sellable/mcp@0.1.859', mcpIntegrity: process.argv[2] }; writeFileSync('/opt/sellable-agent/release.json', JSON.stringify(release) + '\\n', { mode: 0o444 }); chmodSync('/opt/sellable-agent/release.json', 0o444);" "${INSTALLER_INTEGRITY}" "${MCP_INTEGRITY}" \
29
29
  && npm cache clean --force
30
30
 
31
31
  COPY entrypoint.sh /usr/local/bin/sellable-agent-container-entrypoint
@@ -3,13 +3,13 @@
3
3
  This image is a dedicated Agent worker/runtime boundary beside an existing Hermes dashboard. It never replaces or reconfigures the dashboard container.
4
4
 
5
5
  Build from this directory after
6
- `@sellable/install@0.1.577` is
6
+ `@sellable/install@0.1.581` is
7
7
  published:
8
8
 
9
9
  ```sh
10
10
  docker build --platform linux/amd64 \
11
11
  --build-arg INSTALLER_INTEGRITY='sha512-…' \
12
- --tag sellable-agent-host:0.1.577 .
12
+ --tag sellable-agent-host:0.1.581 .
13
13
  ```
14
14
 
15
15
  Create a private `bootstrap/` directory owned by root with mode `0700`. It must contain `bootstrap.json`, `provider-auth`, and `slack-app`, each owned by root with mode `0600`. `bootstrap.json` contains only non-secret identities, hashes, package integrities, and the control-plane URL. The other two files contain the provider JSON and global Slack app token whose hashes are pinned by `bootstrap.json`.
@@ -28,7 +28,7 @@ import {
28
28
 
29
29
  const MANIFEST_VERSION = "sellable-agent-external-runtime-closure/v1";
30
30
  const MCP_PACKAGE = "@sellable/mcp";
31
- const MCP_VERSION = "0.1.858";
31
+ const MCP_VERSION = "0.1.859";
32
32
  const SAFE_COMPONENT = /^[A-Za-z0-9@._+-]+$/;
33
33
  const HERMES_EXCLUDES = new Set([".git", ".playwright", "node_modules"]);
34
34
 
@@ -86,6 +86,8 @@ COPY hermes-bridge.mjs /usr/local/lib/sellable-agent/hermes-bridge.mjs
86
86
  COPY hermes-memory-snapshot.mjs /usr/local/lib/sellable-agent/hermes-memory-snapshot.mjs
87
87
  COPY fly-soul-bridge.mjs /usr/local/lib/sellable-agent/fly-soul-bridge.mjs
88
88
  COPY fly-soul-bridge-exec.mjs /usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs
89
+ COPY fly-skills-bridge.mjs /usr/local/lib/sellable-agent/fly-skills-bridge.mjs
90
+ COPY fly-skills-bridge-exec.mjs /usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs
89
91
  COPY soul-artifact-validator.mjs /usr/local/lib/sellable-agent/soul-artifact-validator.mjs
90
92
  COPY hermes-memory-dirty.mjs /opt/sellable/bin/hermes-memory-dirty
91
93
  COPY hermes-memory-reconcile.sh /opt/sellable/share/hermes-memory-reconcile.sh
@@ -107,9 +109,18 @@ RUN node --input-type=module -e \
107
109
  'import("file:///usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs").then(({ runFlySoulBridgeExecCli }) => runFlySoulBridgeExecCli());' \
108
110
  > /usr/local/bin/sellable-soul-bridge-exec \
109
111
  && chmod 0555 /usr/local/bin/sellable-soul-bridge-exec \
112
+ && printf '%s\n' \
113
+ '#!/usr/bin/env node' \
114
+ 'import("file:///usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs").then(({ runFlySkillsBridgeExecCli }) => runFlySkillsBridgeExecCli());' \
115
+ > /usr/local/bin/sellable-skills-bridge-exec \
116
+ && chmod 0555 /usr/local/bin/sellable-skills-bridge-exec \
110
117
  && sha256sum /usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
111
118
  > /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
119
+ && sha256sum /usr/local/lib/sellable-agent/fly-skills-bridge.mjs \
120
+ /usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs \
121
+ > /usr/local/share/sellable-agent/skills-bridge-source.sha256 \
112
122
  && chmod 0444 /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
123
+ /usr/local/share/sellable-agent/skills-bridge-source.sha256 \
113
124
  && chmod 0755 /etc \
114
125
  && chmod 0555 \
115
126
  /usr/local/lib/sellable-agent/admin-runtime/admin-runtime.mjs \
@@ -122,6 +133,8 @@ RUN node --input-type=module -e \
122
133
  /usr/local/lib/sellable-agent/hermes-memory-snapshot.mjs \
123
134
  /usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
124
135
  /usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs \
136
+ /usr/local/lib/sellable-agent/fly-skills-bridge.mjs \
137
+ /usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs \
125
138
  /usr/local/lib/sellable-agent/soul-artifact-validator.mjs \
126
139
  /opt/sellable/bin/hermes-memory-dirty \
127
140
  /opt/sellable/bin/hermes-endpoint-cron \
@@ -606,6 +606,21 @@ function installAdminModelConfig(profileRoot, modelConfig) {
606
606
  modelConfig.credential_pool_strategies
607
607
  );
608
608
  document.set("fallback_providers", modelConfig.fallback_providers);
609
+ if (document.get("provider_routing") === undefined) {
610
+ // OpenRouter provider selection defaults to highest throughput; an
611
+ // explicit admin config value wins.
612
+ document.set("provider_routing", { sort: "throughput" });
613
+ }
614
+ const currentAuxiliary = document.toJS().auxiliary ?? {};
615
+ if (!currentAuxiliary.vision) {
616
+ // Non-vision main models need a working vision auxiliary; the Nous
617
+ // portal has no credentials on these machines, so image input would
618
+ // silently fail without this default.
619
+ document.set("auxiliary", {
620
+ ...currentAuxiliary,
621
+ vision: { provider: "openrouter", model: "google/gemini-3.5-flash" },
622
+ });
623
+ }
609
624
  atomicPrivateFile(path, document.toString());
610
625
  const readback = parseDocument(readFileSync(path, "utf8")).toJS();
611
626
  if (
@@ -622,6 +637,78 @@ function installAdminModelConfig(profileRoot, modelConfig) {
622
637
  }
623
638
  }
624
639
 
640
+ const ADMIN_LIVE_SKILLS_CONFIG_KEYS = Object.freeze([
641
+ "disabled",
642
+ "platform_disabled",
643
+ ]);
644
+
645
+ function adminPreservedSkillsIdentity(profileRoot) {
646
+ if (!existsSync(profileRoot)) return null;
647
+ const entries = [];
648
+ const skillsRoot = join(profileRoot, "skills");
649
+ const walk = (directory, relativePath = "") => {
650
+ for (const name of readdirSync(directory).sort()) {
651
+ const nextRelative = relativePath ? `${relativePath}/${name}` : name;
652
+ if (nextRelative === "sellable") continue;
653
+ const path = join(directory, name);
654
+ const stat = lstatSync(path);
655
+ if (stat.isSymbolicLink()) {
656
+ throw new Error("admin_runtime_live_skills_symlink_rejected");
657
+ }
658
+ if (stat.isDirectory()) walk(path, nextRelative);
659
+ else if (stat.isFile()) {
660
+ entries.push({
661
+ path: nextRelative,
662
+ sha256: sha256(readFileSync(path)),
663
+ });
664
+ } else throw new Error("admin_runtime_live_skills_entry_rejected");
665
+ }
666
+ };
667
+ if (existsSync(skillsRoot)) walk(skillsRoot);
668
+ const configPath = join(profileRoot, "config.yaml");
669
+ let disabled = {};
670
+ if (existsSync(configPath)) {
671
+ const document = parseDocument(readFileSync(configPath, "utf8"));
672
+ if (document.errors.length > 0) {
673
+ throw new Error("admin_runtime_live_skills_config_rejected");
674
+ }
675
+ const skills = document.toJS()?.skills;
676
+ if (skills && typeof skills === "object" && !Array.isArray(skills)) {
677
+ disabled = Object.fromEntries(
678
+ ADMIN_LIVE_SKILLS_CONFIG_KEYS.filter((key) =>
679
+ Object.prototype.hasOwnProperty.call(skills, key)
680
+ ).map((key) => [key, skills[key]])
681
+ );
682
+ }
683
+ }
684
+ return sha256(canonicalJson({ disabled, entries }));
685
+ }
686
+
687
+ function mergeAdminLiveSkillsConfig(sourceRoot, targetRoot) {
688
+ const sourcePath = join(sourceRoot, "config.yaml");
689
+ const targetPath = join(targetRoot, "config.yaml");
690
+ if (!existsSync(sourcePath) || !existsSync(targetPath)) return;
691
+ const sourceDocument = parseDocument(readFileSync(sourcePath, "utf8"));
692
+ const targetDocument = parseDocument(readFileSync(targetPath, "utf8"));
693
+ if (sourceDocument.errors.length > 0 || targetDocument.errors.length > 0) {
694
+ throw new Error("admin_runtime_live_skills_config_rejected");
695
+ }
696
+ const sourceSkills = sourceDocument.toJS()?.skills;
697
+ if (
698
+ !sourceSkills ||
699
+ typeof sourceSkills !== "object" ||
700
+ Array.isArray(sourceSkills)
701
+ ) {
702
+ return;
703
+ }
704
+ for (const key of ADMIN_LIVE_SKILLS_CONFIG_KEYS) {
705
+ if (Object.prototype.hasOwnProperty.call(sourceSkills, key)) {
706
+ targetDocument.setIn(["skills", key], structuredClone(sourceSkills[key]));
707
+ }
708
+ }
709
+ atomicPrivateFile(targetPath, targetDocument.toString());
710
+ }
711
+
625
712
  async function materializeAdminProfile({
626
713
  profileContract,
627
714
  settingsRoot,
@@ -727,7 +814,6 @@ async function materializeAdminProfile({
727
814
  },
728
815
  });
729
816
  installAdminModelConfig(stageRoot, modelConfig);
730
- mergeHermesState(profileHome, stageRoot);
731
817
  installAdminHermesMemoryHook(stageRoot);
732
818
  stageAdminRuntimeTree(stageRoot);
733
819
  return stage;
@@ -751,6 +837,16 @@ async function materializeAdminProfile({
751
837
  rmSync(stageRoot, { recursive: true, force: true });
752
838
  return { status: "RESTAGE", stagedAuthority: finalAuthority };
753
839
  }
840
+ const liveSkillsIdentity = adminPreservedSkillsIdentity(profileHome);
841
+ mergeHermesState(profileHome, stageRoot);
842
+ mergeAdminLiveSkillsConfig(profileHome, stageRoot);
843
+ if (
844
+ liveSkillsIdentity !== null &&
845
+ adminPreservedSkillsIdentity(stageRoot) !== liveSkillsIdentity
846
+ ) {
847
+ rmSync(stageRoot, { recursive: true, force: true });
848
+ return { status: "RESTAGE", stagedAuthority: finalAuthority };
849
+ }
754
850
  const stagedSoulPath = join(stageRoot, "SOUL.md");
755
851
  const stagedSoul = lstatSync(stagedSoulPath);
756
852
  const stagedSoulBytes = readFileSync(stagedSoulPath);
@@ -4,9 +4,9 @@ FROM ${HERMES_IMAGE}
4
4
 
5
5
  USER root
6
6
 
7
- ARG MCP_PACKAGE=@sellable/mcp@0.1.858
8
- ARG MCP_INTEGRITY=sha512-8yiUhwMbBk/KxZAXGXTsEHiN8XeB/qCqNgfApew0rhViJqiLlDN8oS67pZW5iB5gdmeuv4du0jx9Lhq/blr9hQ==
9
- ARG INSTALLER_PACKAGE=@sellable/install@0.1.577
7
+ ARG MCP_PACKAGE=@sellable/mcp@0.1.859
8
+ ARG MCP_INTEGRITY=sha512-U5uVeDThuUW/CdWghTXQqTAA58mPDly3XHGmfX6WVjczGQHRcDB+Q49vbrA1MOkGRCnzmjGZN1fIVfFtBlwMPw==
9
+ ARG INSTALLER_PACKAGE=@sellable/install@0.1.581
10
10
  ARG TUS_JS_CLIENT_VERSION=4.3.1
11
11
  ARG HERMES_VERSION=0.20.0
12
12
  ARG TIRITH_VERSION=0.3.3
@@ -59,7 +59,7 @@ RUN --mount=type=secret,id=mcp_trust_root,required=true \
59
59
  /etc/cont-init.d/02-reconcile-profiles \
60
60
  && npm cache clean --force
61
61
 
62
- COPY mcp-context-proxy.mjs mcp-runtime-binding.mjs fly-customer-model.mjs fly-customer-worker.mjs profile-materializer.mjs hermes-bridge.mjs hermes-memory-snapshot.mjs fly-runtime-identity.mjs fly-soul-bridge.mjs fly-soul-bridge-exec.mjs soul-artifact-validator.mjs model-auth-reconciler.mjs model-auth-canary-reseed.mjs model-health-reporter.mjs \
62
+ COPY mcp-context-proxy.mjs mcp-runtime-binding.mjs fly-customer-model.mjs fly-customer-worker.mjs profile-materializer.mjs hermes-bridge.mjs hermes-memory-snapshot.mjs fly-runtime-identity.mjs fly-soul-bridge.mjs fly-soul-bridge-exec.mjs fly-skills-bridge.mjs fly-skills-bridge-exec.mjs soul-artifact-validator.mjs model-auth-reconciler.mjs model-auth-canary-reseed.mjs model-health-reporter.mjs \
63
63
  /usr/local/lib/sellable-agent/
64
64
  COPY hermes-memory-dirty.mjs /opt/sellable/bin/hermes-memory-dirty
65
65
  COPY hermes-memory-reconcile.sh /opt/sellable/share/hermes-memory-reconcile.sh
@@ -86,9 +86,18 @@ RUN node --input-type=module -e \
86
86
  'import("file:///usr/local/lib/sellable-agent/fly-soul-bridge-exec.mjs").then(({ runFlySoulBridgeExecCli }) => runFlySoulBridgeExecCli());' \
87
87
  > /usr/local/bin/sellable-soul-bridge-exec \
88
88
  && chmod 0555 /usr/local/bin/sellable-soul-bridge-exec \
89
+ && printf '%s\n' \
90
+ '#!/usr/bin/env node' \
91
+ 'import("file:///usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs").then(({ runFlySkillsBridgeExecCli }) => runFlySkillsBridgeExecCli());' \
92
+ > /usr/local/bin/sellable-skills-bridge-exec \
93
+ && chmod 0555 /usr/local/bin/sellable-skills-bridge-exec \
89
94
  && sha256sum /usr/local/lib/sellable-agent/fly-soul-bridge.mjs \
90
95
  > /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
96
+ && sha256sum /usr/local/lib/sellable-agent/fly-skills-bridge.mjs \
97
+ /usr/local/lib/sellable-agent/fly-skills-bridge-exec.mjs \
98
+ > /usr/local/share/sellable-agent/skills-bridge-source.sha256 \
91
99
  && chmod 0444 /usr/local/share/sellable-agent/soul-bridge-source.sha256 \
100
+ /usr/local/share/sellable-agent/skills-bridge-source.sha256 \
92
101
  && chmod 0755 /etc \
93
102
  && chmod 0555 /usr/local/lib/sellable-agent/*.mjs \
94
103
  && chmod 0555 /opt/sellable/bin/hermes-memory-dirty /opt/sellable/bin/hermes-endpoint-cron \
@@ -93,11 +93,9 @@ const RUNTIME_GENERATION = Number(
93
93
  const BOOT_SESSION_ID = randomUUID();
94
94
  const HERMES_VERSION = process.env.SELLABLE_AGENT_HERMES_VERSION ?? "0.20.0";
95
95
  const INSTALLER_PACKAGE =
96
- process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ??
97
- "@sellable/install@0.1.577";
96
+ process.env.SELLABLE_AGENT_INSTALLER_PACKAGE ?? "@sellable/install@0.1.581";
98
97
  const MCP_PACKAGE =
99
- process.env.SELLABLE_AGENT_MCP_PACKAGE ??
100
- "@sellable/mcp@0.1.858";
98
+ process.env.SELLABLE_AGENT_MCP_PACKAGE ?? "@sellable/mcp@0.1.859";
101
99
 
102
100
  const sha256 = (value) => createHash("sha256").update(value).digest("hex");
103
101
  const sleep = (ms) => new Promise((resolve) => setTimeout(resolve, ms));
@@ -1639,6 +1637,16 @@ function writeRuntimeHermesConfig({
1639
1637
  );
1640
1638
  const sellable = baseConfig.mcp_servers?.sellable;
1641
1639
  if (!sellable?.env) throw new Error("customer_mcp_config_rejected");
1640
+ const adminServer = baseConfig.mcp_servers?.["sellable-admin"];
1641
+ if (adminServer?.command && existsSync(adminServer.command)) {
1642
+ // The admin MCP installer has shipped this wrapper without an execute
1643
+ // bit; repair it here so a config refresh never revives a dead admin
1644
+ // server entry.
1645
+ const wrapperMode = lstatSync(adminServer.command).mode;
1646
+ if ((wrapperMode & 0o100) === 0) {
1647
+ chmodSync(adminServer.command, wrapperMode | 0o100);
1648
+ }
1649
+ }
1642
1650
  // The named-profile materializer owns the one canonical SOUL file and seeds
1643
1651
  // it only when absent. Runtime config refreshes must never rewrite live SOUL
1644
1652
  // or create a root-level second copy.
@@ -1699,6 +1707,23 @@ function writeRuntimeHermesConfig({
1699
1707
  : {
1700
1708
  ...modelConfig,
1701
1709
  }),
1710
+ provider_routing: {
1711
+ // OpenRouter provider selection defaults to highest throughput; an
1712
+ // explicit profile value always wins.
1713
+ sort: "throughput",
1714
+ ...(baseConfig.provider_routing ?? {}),
1715
+ },
1716
+ auxiliary: {
1717
+ ...(baseConfig.auxiliary ?? {}),
1718
+ vision: {
1719
+ // Non-vision main models (e.g. deepseek) need a working vision
1720
+ // auxiliary; the Nous portal has no credentials on customer
1721
+ // machines, so image input silently fails without this default.
1722
+ provider: "openrouter",
1723
+ model: "google/gemini-3.5-flash",
1724
+ ...(baseConfig.auxiliary?.vision ?? {}),
1725
+ },
1726
+ },
1702
1727
  mcp_servers: {
1703
1728
  ...baseConfig.mcp_servers,
1704
1729
  sellable: {
@@ -0,0 +1,45 @@
1
+ #!/usr/bin/env node
2
+
3
+ import { resolve } from "node:path";
4
+ import { fileURLToPath } from "node:url";
5
+
6
+ import {
7
+ FlySkillsBridgeError,
8
+ SKILLS_REQUEST_MAX_BYTES,
9
+ executeExternalSkillsBridgeOperation,
10
+ } from "./fly-skills-bridge.mjs";
11
+
12
+ export async function runFlySkillsBridgeExecCli() {
13
+ try {
14
+ if (process.argv.length !== 2)
15
+ throw new FlySkillsBridgeError("skills_request_rejected");
16
+ const chunks = [];
17
+ let total = 0;
18
+ for await (const chunk of process.stdin) {
19
+ total += chunk.length;
20
+ if (total > SKILLS_REQUEST_MAX_BYTES)
21
+ throw new FlySkillsBridgeError("skills_request_too_large");
22
+ chunks.push(chunk);
23
+ }
24
+ let request;
25
+ try {
26
+ request = JSON.parse(Buffer.concat(chunks).toString("utf8"));
27
+ } catch {
28
+ throw new FlySkillsBridgeError("skills_request_rejected");
29
+ }
30
+ const result = executeExternalSkillsBridgeOperation(request);
31
+ process.stdout.write(`${JSON.stringify(result)}\n`);
32
+ process.exitCode = result.ok ? 0 : 2;
33
+ } catch (error) {
34
+ process.stdout.write(
35
+ `${JSON.stringify({ ok: false, code: error instanceof FlySkillsBridgeError ? error.code : "skills_bridge_failed" })}\n`
36
+ );
37
+ process.exitCode = 2;
38
+ }
39
+ }
40
+
41
+ if (
42
+ resolve(process.argv[1] ?? "") === resolve(fileURLToPath(import.meta.url))
43
+ ) {
44
+ await runFlySkillsBridgeExecCli();
45
+ }