@kmugalkhod/lightcode 0.16.0 → 0.17.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/cli.js CHANGED
@@ -124,7 +124,7 @@ var init_logger = __esm(() => {
124
124
  });
125
125
 
126
126
  // packages/shared/src/version.ts
127
- var lightcodeVersion = "0.16.0";
127
+ var lightcodeVersion = "0.17.0";
128
128
 
129
129
  // packages/shared/src/index.ts
130
130
  var productName = "Lightcode";
@@ -133,6 +133,90 @@ var init_src = __esm(() => {
133
133
  init_logger();
134
134
  });
135
135
 
136
+ // packages/shared/src/network-fetch.ts
137
+ import { readFileSync, statSync } from "fs";
138
+ import { X509Certificate } from "crypto";
139
+ import * as tls from "tls";
140
+ function isCertificateError(error) {
141
+ const seen = new Set;
142
+ let current = error;
143
+ while (current && typeof current === "object" && !seen.has(current) && seen.size < 12) {
144
+ seen.add(current);
145
+ const record = current;
146
+ if (certificateErrorPattern.test(`${record.code ?? ""} ${record.message ?? ""}`))
147
+ return true;
148
+ current = record.cause;
149
+ }
150
+ return false;
151
+ }
152
+ function createNetworkFetch({
153
+ env = process.env,
154
+ fetchImpl
155
+ } = {}) {
156
+ let cachedKey = "";
157
+ let cachedCa;
158
+ let checkedAt = 0;
159
+ const resolveCa = () => {
160
+ const paths = [
161
+ ...new Set([env.LIGHTCODE_CA_CERTS, env.NODE_EXTRA_CA_CERTS, env.SSL_CERT_FILE].map((value) => value?.trim()).filter((value) => Boolean(value)))
162
+ ];
163
+ const key = JSON.stringify(paths);
164
+ if (key === cachedKey && Date.now() - checkedAt < 30000)
165
+ return cachedCa;
166
+ const extra = [];
167
+ for (const file of paths) {
168
+ try {
169
+ if (statSync(file).size > 4 * 1024 * 1024)
170
+ throw new Error("CA bundle exceeds 4 MiB");
171
+ const certificates = readFileSync(file, "utf8").match(certificatePattern);
172
+ if (!certificates?.length)
173
+ throw new Error("No PEM certificates found");
174
+ for (const cert of certificates)
175
+ new X509Certificate(cert);
176
+ extra.push(...certificates);
177
+ } catch (cause) {
178
+ throw new Error("Cannot load configured TLS CA certificate bundle. Check LIGHTCODE_CA_CERTS, NODE_EXTRA_CA_CERTS and SSL_CERT_FILE for readable PEM files.", { cause });
179
+ }
180
+ }
181
+ let defaults = [...tls.rootCertificates];
182
+ try {
183
+ defaults.push(...tls.getCACertificates("default"), ...tls.getCACertificates("system"));
184
+ } catch {}
185
+ cachedCa = extra.length ? [...new Set([...defaults, ...extra])] : undefined;
186
+ cachedKey = key;
187
+ checkedAt = Date.now();
188
+ return cachedCa;
189
+ };
190
+ return async (input, init) => {
191
+ const url = new URL(input instanceof Request ? input.url : String(input));
192
+ const ca = url.protocol === "https:" ? resolveCa() : undefined;
193
+ const local = ["localhost", "127.0.0.1", "[::1]"].includes(url.hostname);
194
+ try {
195
+ return await (fetchImpl ?? globalThis.fetch)(input, {
196
+ ...init,
197
+ ...local && init?.proxy === undefined ? { proxy: "" } : {},
198
+ ...ca ? {
199
+ tls: {
200
+ ...init?.tls,
201
+ ca: init?.tls?.ca ?? ca,
202
+ rejectUnauthorized: true
203
+ }
204
+ } : {}
205
+ });
206
+ } catch (cause) {
207
+ if (isCertificateError(cause))
208
+ throw new Error(certificateErrorHint, { cause });
209
+ throw cause;
210
+ }
211
+ };
212
+ }
213
+ var certificatePattern, certificateErrorPattern, certificateErrorHint = "TLS certificate verification failed. Configure your company CA PEM with NODE_EXTRA_CA_CERTS or LIGHTCODE_CA_CERTS, then restart Lightcode. If a CA is already configured, check the server certificate hostname, expiry, and chain.", networkFetch;
214
+ var init_network_fetch = __esm(() => {
215
+ certificatePattern = /-----BEGIN CERTIFICATE-----[\s\S]*?-----END CERTIFICATE-----/g;
216
+ certificateErrorPattern = /CERT_(?:HAS_EXPIRED|NOT_YET_VALID|REVOKED|UNTRUSTED|SIGNATURE_FAILURE)|CERTIFICATE_VERIFY_FAILED|UNABLE_TO_(?:GET_ISSUER_CERT(?:_LOCALLY)?|VERIFY_LEAF_SIGNATURE)|DEPTH_ZERO_SELF_SIGNED_CERT|SELF_SIGNED_CERT_IN_CHAIN|ERR_TLS_CERT_ALTNAME_INVALID|certificate|self.signed|unable to (?:get local issuer|verify the first)/i;
217
+ networkFetch = createNetworkFetch();
218
+ });
219
+
136
220
  // apps/cli/src/lib/api-base-url.ts
137
221
  var apiBaseUrl;
138
222
  var init_api_base_url = __esm(() => {
@@ -191,7 +275,7 @@ function existingLightcodeInterfaceError(requestedInterface, port) {
191
275
  }
192
276
  async function checkServer(url) {
193
277
  try {
194
- const response = await fetch(new URL("/config/status", url), {
278
+ const response = await networkFetch(new URL("/config/status", url), {
195
279
  signal: AbortSignal.timeout(healthCheckTimeoutMs)
196
280
  });
197
281
  if (!response.ok) {
@@ -209,7 +293,7 @@ async function checkServer(url) {
209
293
  }
210
294
  async function checkWebPort(url) {
211
295
  try {
212
- const response = await fetch(new URL("/healthz", url), {
296
+ const response = await networkFetch(new URL("/healthz", url), {
213
297
  signal: AbortSignal.timeout(healthCheckTimeoutMs)
214
298
  });
215
299
  if (!response.ok) {
@@ -227,7 +311,7 @@ async function checkWebPort(url) {
227
311
  }
228
312
  async function checkAuthenticatedWebServer(url, token) {
229
313
  try {
230
- const response = await fetch(new URL("/config/status", url), {
314
+ const response = await networkFetch(new URL("/config/status", url), {
231
315
  headers: { Authorization: `Bearer ${token}` },
232
316
  signal: AbortSignal.timeout(healthCheckTimeoutMs)
233
317
  });
@@ -416,6 +500,7 @@ async function ensureServerRunning() {
416
500
  }
417
501
  var currentOwnedProcess = null, healthCheckTimeoutMs = 1500, startupPollIntervalMs = 250, startupPollAttempts = 40, webPortProbeCount = 12;
418
502
  var init_server_launcher = __esm(() => {
503
+ init_network_fetch();
419
504
  init_api_base_url();
420
505
  });
421
506
 
@@ -2370,10 +2455,11 @@ var init_schema10 = __esm(() => {
2370
2455
 
2371
2456
  // packages/ai/src/skills/schema.ts
2372
2457
  import { z as z20 } from "zod";
2373
- var skillDescription = "Load a local Lightcode skill by name and return its instructions and metadata.", skillInputSchema, skillProviderInputSchema, skillSummarySchema, skillOutputSchema, skillListOutputSchema;
2458
+ var skillDescription = "Load an installed skill by name before following its instructions. Use resource to read a supporting file relative to that skill's directory.", skillInputSchema, skillProviderInputSchema, skillSummarySchema, skillOutputSchema, skillListOutputSchema;
2374
2459
  var init_schema11 = __esm(() => {
2375
2460
  skillInputSchema = z20.object({
2376
- name: z20.string().min(1).max(160)
2461
+ name: z20.string().min(1).max(160),
2462
+ resource: z20.string().min(1).max(1024).optional()
2377
2463
  });
2378
2464
  skillProviderInputSchema = skillInputSchema;
2379
2465
  skillSummarySchema = z20.object({
@@ -2723,7 +2809,7 @@ var init_tool_registry = __esm(() => {
2723
2809
  permissionMode: "read-only",
2724
2810
  execution: "server",
2725
2811
  availability: "skills",
2726
- activation: "specialized",
2812
+ activation: "core",
2727
2813
  outputPolicy: "artifact-if-large"
2728
2814
  },
2729
2815
  list_mcp_resources: {
@@ -3433,7 +3519,7 @@ ${environmentContext.trim()}` : "");
3433
3519
  var cwdPromptPlaceholder = "{cwd}", defaultCodingAgentSystemPrompt, toolCallingDisciplineAddendum;
3434
3520
  var init_coding_agent_prompt = __esm(() => {
3435
3521
  init_coding_agent_modes();
3436
- defaultCodingAgentSystemPrompt = "You are a coding agent working inside the user's project at this working directory: {cwd}. " + 'You can already see the project \u2014 proactively read it. When the user asks you to review, explain, or work on "my code"/"this", inspect the working directory yourself with list_files, glob_search, grep, and read_file; NEVER ask the user to paste or share code that lives in this directory \u2014 open it. ' + "Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under the working directory. " + "Prefer dedicated tools \u2014 glob_search for paths, grep for text search, structured git tools for repository inspection \u2014 and bash only when those are not enough. " + `Inspect context before risky operations. Progress notes: before a major tool action you may emit ONE short note (one sentence, ~15 words) saying what you are about to do. After a tool returns, mention a finding only if it changes your plan, and state it as fact grounded in what the tool actually returned \u2014 cite the file/line/symbol. NEVER claim you found, changed, or fixed something before a tool result confirms it. Skip notes for routine reads, and do not restate the todo list in prose.
3522
+ defaultCodingAgentSystemPrompt = "You are a coding agent working inside the user's project at this working directory: {cwd}. " + 'You can already see the project \u2014 proactively read it. When the user asks you to review, explain, or work on "my code"/"this", inspect the working directory yourself with list_files, glob_search, grep, and read_file; NEVER ask the user to paste or share code that lives in this directory \u2014 open it. ' + "Use tools for filesystem and codebase tasks instead of guessing, and only interact with files under the working directory. " + "Prefer dedicated tools \u2014 glob_search for paths, grep for text search, structured git tools for repository inspection \u2014 and bash only when those are not enough. " + "Check the available skill descriptions for every task. When a skill is explicitly named or clearly matches the task, load it with the skill tool before acting and follow its instructions within the user's scope and tool permissions. Skill files may be installed outside the workspace; use the skill tool to read their relative supporting resources. Never claim to have used a skill you have not loaded. " + `Inspect context before risky operations. Progress notes: before a major tool action you may emit ONE short note (one sentence, ~15 words) saying what you are about to do. After a tool returns, mention a finding only if it changes your plan, and state it as fact grounded in what the tool actually returned \u2014 cite the file/line/symbol. NEVER claim you found, changed, or fixed something before a tool result confirms it. Skip notes for routine reads, and do not restate the todo list in prose.
3437
3523
 
3438
3524
  ` + `Task list discipline \u2014 use the todo_write tool to keep a visible, evolving checklist:
3439
3525
  ` + `- Create the list FIRST. For any task with roughly three or more distinct steps, or any multi-file or multi-stage work, call todo_write as your very first action \u2014 before reading or editing \u2014 with every step listed and all of them set to pending.
@@ -4105,7 +4191,7 @@ import {
4105
4191
  existsSync as existsSync3,
4106
4192
  lstatSync,
4107
4193
  realpathSync,
4108
- statSync
4194
+ statSync as statSync2
4109
4195
  } from "fs";
4110
4196
  function stripTrailingSeparator(value) {
4111
4197
  const parsed = path5.parse(value);
@@ -4155,7 +4241,7 @@ function findNearestExistingParent(absolutePath, context, input) {
4155
4241
  }
4156
4242
  function createWorkspaceContext(cwd = process.cwd()) {
4157
4243
  const root = nativeRealpathSync(cwd);
4158
- const rootStats = statSync(root);
4244
+ const rootStats = statSync2(root);
4159
4245
  if (!rootStats.isDirectory()) {
4160
4246
  throw new Error(`Workspace root is not a directory: ${cwd}`);
4161
4247
  }
@@ -5715,51 +5801,74 @@ var init_runtime10 = __esm(() => {
5715
5801
  });
5716
5802
 
5717
5803
  // packages/ai/src/skills/runtime.ts
5718
- import { existsSync as existsSync4, readFileSync, readdirSync as readdirSync2, statSync as statSync2 } from "fs";
5804
+ import { readFileSync as readFileSync2, readdirSync as readdirSync2, realpathSync as realpathSync2, statSync as statSync3 } from "fs";
5719
5805
  import os from "os";
5720
5806
  import path11 from "path";
5721
- function defaultSkillRoots(cwd) {
5807
+ function defaultSkillRoots(cwd, home, env) {
5722
5808
  return [
5723
5809
  { source: "project", path: path11.join(cwd, ".lightcode", "skills") },
5724
5810
  { source: "project", path: path11.join(cwd, ".agents", "skills") },
5725
- { source: "user", path: path11.join(os.homedir(), ".lightcode", "skills") }
5811
+ ...[".claude", ".codex", ".opencode"].map((dir) => ({
5812
+ source: "project",
5813
+ path: path11.join(cwd, dir, "skills")
5814
+ })),
5815
+ ...[".lightcode", ".agents", ".claude"].map((dir) => ({
5816
+ source: "user",
5817
+ path: path11.join(home, dir, "skills")
5818
+ })),
5819
+ {
5820
+ source: "user",
5821
+ path: path11.join(env.CODEX_HOME || path11.join(home, ".codex"), "skills")
5822
+ },
5823
+ {
5824
+ source: "user",
5825
+ path: path11.join(env.XDG_CONFIG_HOME || path11.join(home, ".config"), "opencode", "skills")
5826
+ },
5827
+ ...(env.LIGHTCODE_SKILL_PATHS ?? "").split(path11.delimiter).filter(Boolean).map((root) => ({
5828
+ source: "user",
5829
+ path: path11.resolve(cwd, root)
5830
+ }))
5726
5831
  ];
5727
5832
  }
5728
5833
  function normalizeSkillName(value) {
5729
5834
  return value.trim().toLowerCase().replace(/[^a-z0-9._-]+/g, "-");
5730
5835
  }
5731
5836
  function parseBasicFrontmatter(content) {
5732
- if (!content.startsWith("---")) {
5837
+ const match = /^\uFEFF?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
5838
+ if (!match)
5733
5839
  return {};
5734
- }
5735
- const endIndex = content.indexOf(`
5736
- ---`, 3);
5737
- if (endIndex === -1) {
5840
+ const parsed = Bun.YAML.parse(match[1]);
5841
+ if (!parsed || typeof parsed !== "object" || Array.isArray(parsed))
5738
5842
  return {};
5739
- }
5740
- const frontmatter = content.slice(3, endIndex).trim();
5741
- return Object.fromEntries(frontmatter.split(/\r?\n/).map((line) => line.match(/^([A-Za-z0-9_-]+):\s*(.*)$/)).filter((match) => match !== null).map((match) => [
5742
- match[1],
5743
- match[2].replace(/^["']|["']$/g, "").trim()
5744
- ]));
5843
+ return Object.fromEntries(Object.entries(parsed).filter((entry) => typeof entry[1] === "string"));
5745
5844
  }
5746
5845
  function findSkillFiles(root) {
5747
- if (!existsSync4(root.path)) {
5748
- return [];
5749
- }
5750
5846
  const files = [];
5847
+ const visited = new Set;
5751
5848
  const visit = (directory, depth) => {
5752
- if (depth > 4) {
5849
+ if (depth > 8 || visited.size >= 2000) {
5753
5850
  return;
5754
5851
  }
5755
- for (const entry of readdirSync2(directory)) {
5852
+ let entries;
5853
+ try {
5854
+ const real = realpathSync2(directory);
5855
+ if (visited.has(real))
5856
+ return;
5857
+ visited.add(real);
5858
+ entries = readdirSync2(directory).sort();
5859
+ } catch {
5860
+ return;
5861
+ }
5862
+ for (const entry of entries) {
5756
5863
  const entryPath = path11.join(directory, entry);
5757
- const stat3 = statSync2(entryPath);
5758
- if (stat3.isDirectory()) {
5759
- visit(entryPath, depth + 1);
5760
- } else if (entry.toLowerCase() === "skill.md") {
5761
- files.push(entryPath);
5762
- }
5864
+ try {
5865
+ const stat3 = statSync3(entryPath);
5866
+ if (stat3.isDirectory()) {
5867
+ visit(entryPath, depth + 1);
5868
+ } else if (stat3.isFile() && stat3.size <= 1048576 && entry.toLowerCase() === "skill.md") {
5869
+ files.push(entryPath);
5870
+ }
5871
+ } catch {}
5763
5872
  }
5764
5873
  };
5765
5874
  visit(root.path, 0);
@@ -5769,52 +5878,71 @@ function clearSkillsCache() {
5769
5878
  skillsCache.clear();
5770
5879
  }
5771
5880
  function listSkills({
5772
- cwd = process.cwd()
5881
+ cwd = process.cwd(),
5882
+ home = os.homedir(),
5883
+ env = process.env
5773
5884
  } = {}) {
5774
- const cached = skillsCache.get(cwd);
5885
+ const roots = defaultSkillRoots(path11.resolve(cwd), home, env);
5886
+ const cacheKey = JSON.stringify(roots);
5887
+ const cached = skillsCache.get(cacheKey);
5775
5888
  if (cached && cached.expiresAt > Date.now()) {
5776
5889
  return cached.skills;
5777
5890
  }
5778
- const skills = listSkillsUncached(cwd);
5779
- skillsCache.set(cwd, {
5891
+ const skills = listSkillsUncached(roots);
5892
+ if (skillsCache.size >= 100)
5893
+ skillsCache.delete(skillsCache.keys().next().value);
5894
+ skillsCache.set(cacheKey, {
5780
5895
  expiresAt: Date.now() + skillsCacheTtlMs,
5781
5896
  skills
5782
5897
  });
5783
5898
  return skills;
5784
5899
  }
5785
- function listSkillsUncached(cwd) {
5900
+ function listSkillsUncached(roots) {
5786
5901
  const seen = new Set;
5787
5902
  const summaries = [];
5788
- for (const root of defaultSkillRoots(cwd)) {
5903
+ for (const root of roots) {
5789
5904
  for (const skillPath of findSkillFiles(root)) {
5790
- const content = readFileSync(skillPath, "utf8");
5791
- const frontmatter = parseBasicFrontmatter(content);
5792
- const fallbackName = path11.basename(path11.dirname(skillPath));
5793
- const name = normalizeSkillName(frontmatter.name ?? fallbackName);
5794
- if (!name || seen.has(name)) {
5795
- continue;
5796
- }
5797
- seen.add(name);
5798
- summaries.push({
5799
- name,
5800
- description: frontmatter.description || null,
5801
- path: skillPath,
5802
- source: root.source
5803
- });
5905
+ try {
5906
+ const content = readFileSync2(skillPath, "utf8");
5907
+ const frontmatter = parseBasicFrontmatter(content);
5908
+ const fallbackName = path11.basename(path11.dirname(skillPath));
5909
+ const name = normalizeSkillName(frontmatter.name ?? fallbackName);
5910
+ if (!name || name.length > 160 || seen.has(name)) {
5911
+ continue;
5912
+ }
5913
+ seen.add(name);
5914
+ summaries.push({
5915
+ name,
5916
+ description: frontmatter.description || null,
5917
+ path: skillPath,
5918
+ source: root.source
5919
+ });
5920
+ } catch {}
5804
5921
  }
5805
5922
  }
5806
5923
  return summaries.sort((left, right) => left.name.localeCompare(right.name));
5807
5924
  }
5808
- function loadSkill(input, { cwd = process.cwd() } = {}) {
5925
+ function loadSkill(input, options = {}) {
5809
5926
  const parsedInput = skillInputSchema.parse(input);
5810
5927
  const normalizedName = normalizeSkillName(parsedInput.name);
5811
- const skill = listSkills({ cwd }).find((candidate) => normalizeSkillName(candidate.name) === normalizedName);
5928
+ const skill = listSkills(options).find((candidate) => normalizeSkillName(candidate.name) === normalizedName);
5812
5929
  if (!skill) {
5813
5930
  throw new Error(`Skill not found: ${parsedInput.name}`);
5814
5931
  }
5932
+ const root = realpathSync2(path11.dirname(skill.path));
5933
+ const target = parsedInput.resource ? realpathSync2(path11.resolve(root, parsedInput.resource)) : skill.path;
5934
+ if (parsedInput.resource) {
5935
+ const relative = path11.relative(root, target);
5936
+ if (relative === ".." || relative.startsWith(`..${path11.sep}`) || path11.isAbsolute(relative)) {
5937
+ throw new Error("Skill resources must stay inside the installed skill directory.");
5938
+ }
5939
+ }
5940
+ const stat3 = statSync3(target);
5941
+ if (!stat3.isFile() || stat3.size > 1048576)
5942
+ throw new Error("Skill resource must be a file smaller than 1 MiB.");
5815
5943
  return skillOutputSchema.parse({
5816
5944
  skill,
5817
- content: readFileSync(skill.path, "utf8")
5945
+ content: readFileSync2(target, "utf8")
5818
5946
  });
5819
5947
  }
5820
5948
  var skillsCacheTtlMs = 30000, skillsCache;
@@ -7572,7 +7700,7 @@ var init_context = __esm(() => {
7572
7700
  });
7573
7701
 
7574
7702
  // packages/ai/src/config/lightcode-config.ts
7575
- import { existsSync as existsSync5, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync3 } from "fs";
7703
+ import { existsSync as existsSync4, mkdirSync as mkdirSync3, readFileSync as readFileSync3, writeFileSync as writeFileSync3 } from "fs";
7576
7704
  import os2 from "os";
7577
7705
  import path13 from "path";
7578
7706
  import { z as z33 } from "zod";
@@ -7583,7 +7711,7 @@ function getDefaultUserConfigPath(env) {
7583
7711
  return path13.join(os2.homedir(), ".lightcode", "settings.json");
7584
7712
  }
7585
7713
  function readConfigFile(scope, configPath) {
7586
- if (!existsSync5(configPath)) {
7714
+ if (!existsSync4(configPath)) {
7587
7715
  return {
7588
7716
  config: {},
7589
7717
  file: {
@@ -7596,7 +7724,7 @@ function readConfigFile(scope, configPath) {
7596
7724
  }
7597
7725
  let rawConfig;
7598
7726
  try {
7599
- rawConfig = JSON.parse(readFileSync2(configPath, "utf8"));
7727
+ rawConfig = JSON.parse(readFileSync3(configPath, "utf8"));
7600
7728
  } catch (error) {
7601
7729
  throw new LightcodeConfigError(`Unable to parse Lightcode config at ${configPath}: ${error instanceof Error ? error.message : "invalid JSON"}`);
7602
7730
  }
@@ -8051,11 +8179,11 @@ function decodeHtmlEntities(value) {
8051
8179
  const normalized = entity.toLowerCase();
8052
8180
  if (normalized.startsWith("#x")) {
8053
8181
  const codePoint = Number.parseInt(normalized.slice(2), 16);
8054
- return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
8182
+ return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : match;
8055
8183
  }
8056
8184
  if (normalized.startsWith("#")) {
8057
8185
  const codePoint = Number.parseInt(normalized.slice(1), 10);
8058
- return Number.isFinite(codePoint) ? String.fromCodePoint(codePoint) : match;
8186
+ return Number.isInteger(codePoint) && codePoint >= 0 && codePoint <= 1114111 ? String.fromCodePoint(codePoint) : match;
8059
8187
  }
8060
8188
  return htmlEntityMap[normalized] ?? match;
8061
8189
  });
@@ -8082,6 +8210,34 @@ function normalizeContentToText(content, contentType) {
8082
8210
  return content.replace(/\r\n/g, `
8083
8211
  `).trim();
8084
8212
  }
8213
+ async function readBoundedBody(response) {
8214
+ if (!response.body)
8215
+ return { text: "", truncated: false };
8216
+ const reader = response.body.getReader();
8217
+ const decoder = new TextDecoder;
8218
+ let bytes = 0;
8219
+ let text = "";
8220
+ let truncated = false;
8221
+ try {
8222
+ for (;; ) {
8223
+ const { done, value } = await reader.read();
8224
+ if (done)
8225
+ break;
8226
+ const available = maxDownloadBytes - bytes;
8227
+ text += decoder.decode(value.subarray(0, available), { stream: true });
8228
+ bytes += value.byteLength;
8229
+ if (bytes >= maxDownloadBytes) {
8230
+ truncated = true;
8231
+ await reader.cancel();
8232
+ break;
8233
+ }
8234
+ }
8235
+ text += decoder.decode();
8236
+ return { text, truncated };
8237
+ } finally {
8238
+ reader.releaseLock();
8239
+ }
8240
+ }
8085
8241
  function errorOutput({
8086
8242
  parsedInput,
8087
8243
  code,
@@ -8109,7 +8265,7 @@ async function executeWebFetch(input, options = {}) {
8109
8265
  const timeoutSignal = AbortSignal.timeout(parsedInput.timeoutMs);
8110
8266
  const signal = options.signal ? AbortSignal.any([options.signal, timeoutSignal]) : timeoutSignal;
8111
8267
  try {
8112
- const response = await (options.fetch ?? globalThis.fetch)(parsedInput.url, {
8268
+ const response = await (options.fetch ?? networkFetch)(parsedInput.url, {
8113
8269
  redirect: "follow",
8114
8270
  signal,
8115
8271
  headers: {
@@ -8118,7 +8274,8 @@ async function executeWebFetch(input, options = {}) {
8118
8274
  }
8119
8275
  });
8120
8276
  const contentType = response.headers.get("content-type");
8121
- const rawText = await response.text();
8277
+ const downloaded = await readBoundedBody(response);
8278
+ const rawText = downloaded.text;
8122
8279
  const normalizedText = normalizeContentToText(rawText, contentType);
8123
8280
  const truncated = truncateText(normalizedText, parsedInput.maxChars);
8124
8281
  return webFetchOutputSchema.parse({
@@ -8130,7 +8287,7 @@ async function executeWebFetch(input, options = {}) {
8130
8287
  contentType,
8131
8288
  title: contentType?.toLowerCase().includes("html") ? extractTitle(rawText) : null,
8132
8289
  text: truncated.text,
8133
- truncated: truncated.truncated,
8290
+ truncated: truncated.truncated || downloaded.truncated,
8134
8291
  fetchedAt: new Date().toISOString(),
8135
8292
  error: response.ok ? null : {
8136
8293
  code: "http_error",
@@ -8145,8 +8302,9 @@ async function executeWebFetch(input, options = {}) {
8145
8302
  });
8146
8303
  }
8147
8304
  }
8148
- var htmlEntityMap;
8305
+ var htmlEntityMap, maxDownloadBytes;
8149
8306
  var init_runtime13 = __esm(() => {
8307
+ init_network_fetch();
8150
8308
  init_output_utils();
8151
8309
  init_schema14();
8152
8310
  htmlEntityMap = {
@@ -8157,10 +8315,11 @@ var init_runtime13 = __esm(() => {
8157
8315
  apos: "'",
8158
8316
  nbsp: " "
8159
8317
  };
8318
+ maxDownloadBytes = 2 * 1024 * 1024;
8160
8319
  });
8161
8320
 
8162
8321
  // packages/ai/src/config/credentials.ts
8163
- import { chmodSync as chmodSync2, existsSync as existsSync6, mkdirSync as mkdirSync4, readFileSync as readFileSync3, writeFileSync as writeFileSync4 } from "fs";
8322
+ import { chmodSync as chmodSync2, existsSync as existsSync5, mkdirSync as mkdirSync4, readFileSync as readFileSync4, writeFileSync as writeFileSync4 } from "fs";
8164
8323
  import os3 from "os";
8165
8324
  import path14 from "path";
8166
8325
  import { z as z34 } from "zod";
@@ -8173,11 +8332,11 @@ function getCredentialsPath(env = process.env) {
8173
8332
  }
8174
8333
  function readStoredCredentials(env = process.env) {
8175
8334
  const credentialsPath = getCredentialsPath(env);
8176
- if (!existsSync6(credentialsPath)) {
8335
+ if (!existsSync5(credentialsPath)) {
8177
8336
  return {};
8178
8337
  }
8179
8338
  try {
8180
- const parsed = storedCredentialsSchema.safeParse(JSON.parse(readFileSync3(credentialsPath, "utf8")));
8339
+ const parsed = storedCredentialsSchema.safeParse(JSON.parse(readFileSync4(credentialsPath, "utf8")));
8181
8340
  return parsed.success ? parsed.data : {};
8182
8341
  } catch {
8183
8342
  return {};
@@ -8406,7 +8565,7 @@ async function executeWebSearch(input, options = {}) {
8406
8565
  }
8407
8566
  return await searchBrave(parsedInput, {
8408
8567
  apiKey: keys.brave,
8409
- fetch: options.fetch ?? globalThis.fetch,
8568
+ fetch: options.fetch ?? networkFetch,
8410
8569
  signal,
8411
8570
  maxCharactersPerResult: config2.maxCharactersPerResult
8412
8571
  });
@@ -8422,7 +8581,7 @@ async function executeWebSearch(input, options = {}) {
8422
8581
  }
8423
8582
  return await searchTavily(parsedInput, {
8424
8583
  apiKey: keys.tavily,
8425
- fetch: options.fetch ?? globalThis.fetch,
8584
+ fetch: options.fetch ?? networkFetch,
8426
8585
  signal,
8427
8586
  maxCharactersPerResult: config2.maxCharactersPerResult
8428
8587
  });
@@ -8463,6 +8622,7 @@ async function executeWebSearch(input, options = {}) {
8463
8622
  }
8464
8623
  var localUsesByTurn;
8465
8624
  var init_runtime14 = __esm(() => {
8625
+ init_network_fetch();
8466
8626
  init_lightcode_config();
8467
8627
  init_credentials();
8468
8628
  init_config2();
@@ -9989,7 +10149,7 @@ var init_schema18 = __esm(() => {
9989
10149
  });
9990
10150
 
9991
10151
  // packages/ai/src/plugins/runtime.ts
9992
- import { existsSync as existsSync7, readFileSync as readFileSync4, readdirSync as readdirSync3 } from "fs";
10152
+ import { existsSync as existsSync6, readFileSync as readFileSync5, readdirSync as readdirSync3 } from "fs";
9993
10153
  import os4 from "os";
9994
10154
  import path16 from "path";
9995
10155
  function pluginRoots(cwd) {
@@ -10001,7 +10161,7 @@ function pluginRoots(cwd) {
10001
10161
  function listPlugins({ cwd = process.cwd() } = {}) {
10002
10162
  const plugins = [];
10003
10163
  for (const root of pluginRoots(cwd)) {
10004
- if (!existsSync7(root)) {
10164
+ if (!existsSync6(root)) {
10005
10165
  continue;
10006
10166
  }
10007
10167
  for (const entry of readdirSync3(root, { withFileTypes: true })) {
@@ -10009,10 +10169,10 @@ function listPlugins({ cwd = process.cwd() } = {}) {
10009
10169
  continue;
10010
10170
  }
10011
10171
  const manifestPath = path16.join(root, entry.name, "plugin.json");
10012
- if (!existsSync7(manifestPath)) {
10172
+ if (!existsSync6(manifestPath)) {
10013
10173
  continue;
10014
10174
  }
10015
- const manifest = pluginManifestSchema.parse(JSON.parse(readFileSync4(manifestPath, "utf8")));
10175
+ const manifest = pluginManifestSchema.parse(JSON.parse(readFileSync5(manifestPath, "utf8")));
10016
10176
  plugins.push({
10017
10177
  id: manifest.id,
10018
10178
  name: manifest.name,
@@ -10044,7 +10204,7 @@ async function runPluginHooks({
10044
10204
  if (!plugin.enabled) {
10045
10205
  continue;
10046
10206
  }
10047
- const manifest = pluginManifestSchema.parse(JSON.parse(readFileSync4(plugin.path, "utf8")));
10207
+ const manifest = pluginManifestSchema.parse(JSON.parse(readFileSync5(plugin.path, "utf8")));
10048
10208
  for (const hook of manifest.hooks.filter((candidate) => candidate.event === event)) {
10049
10209
  hooksRun += 1;
10050
10210
  const child = Bun.spawn([hook.command, ...hook.args], {
@@ -10262,8 +10422,9 @@ var init_src2 = __esm(() => {
10262
10422
  import { hc } from "hono/client";
10263
10423
  var client;
10264
10424
  var init_client = __esm(() => {
10425
+ init_network_fetch();
10265
10426
  init_api_base_url();
10266
- client = hc(apiBaseUrl);
10427
+ client = hc(apiBaseUrl, { fetch: networkFetch });
10267
10428
  });
10268
10429
 
10269
10430
  // apps/cli/src/utils/markdown-code.ts
@@ -18100,6 +18261,7 @@ function ChatScreen() {
18100
18261
  status,
18101
18262
  todos
18102
18263
  } = useCodingSessionChat({
18264
+ fetch: networkFetch,
18103
18265
  chatApi,
18104
18266
  initialPrompt,
18105
18267
  isSessionIdValid,
@@ -19121,6 +19283,7 @@ var autoImplementationInstruction = "Please implement the approved plan now. Exe
19121
19283
  var init_chat_screen = __esm(() => {
19122
19284
  init_src2();
19123
19285
  init_react();
19286
+ init_network_fetch();
19124
19287
  init_runtime17();
19125
19288
  init_chat_slash_actions();
19126
19289
  init_clipboard();
@@ -21995,7 +22158,7 @@ __export(exports_register_grammars, {
21995
22158
  registerExtraGrammars: () => registerExtraGrammars,
21996
22159
  GRAMMARS: () => GRAMMARS
21997
22160
  });
21998
- import { existsSync as existsSync8 } from "fs";
22161
+ import { existsSync as existsSync7 } from "fs";
21999
22162
  import { join } from "path";
22000
22163
  import { getTreeSitterClient as getTreeSitterClient2 } from "@opentui/core";
22001
22164
  function resolveGrammarsDir() {
@@ -22005,7 +22168,7 @@ function resolveGrammarsDir() {
22005
22168
  join(import.meta.dir, "..", "..", "assets", "grammars")
22006
22169
  ];
22007
22170
  for (const dir of candidates) {
22008
- if (existsSync8(dir)) {
22171
+ if (existsSync7(dir)) {
22009
22172
  return dir;
22010
22173
  }
22011
22174
  }
@@ -22026,7 +22189,7 @@ function registerExtraGrammars() {
22026
22189
  for (const grammar of GRAMMARS) {
22027
22190
  const wasmPath = join(dir, grammar.dir, grammar.wasm);
22028
22191
  const highlightsPath = join(dir, grammar.dir, "highlights.scm");
22029
- if (!existsSync8(wasmPath) || !existsSync8(highlightsPath)) {
22192
+ if (!existsSync7(wasmPath) || !existsSync7(highlightsPath)) {
22030
22193
  continue;
22031
22194
  }
22032
22195
  try {