@kmugalkhod/lightcode 0.16.0 → 0.17.1
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/README.md +50 -5
- package/assets/web/geist-LICENSE.txt +93 -0
- package/assets/web/geist-latin.woff2 +0 -0
- package/assets/web/index-1rsanca1.js +171 -0
- package/assets/web/index-68c606db.css +1 -0
- package/assets/web/index.html +1 -1
- package/cli.js +244 -81
- package/lightcode.cjs +21 -16
- package/package.json +5 -5
- package/server.js +304 -110
- package/assets/web/index-wma2v20c.css +0 -1
- package/assets/web/index-wzb9vzdt.js +0 -149
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.
|
|
127
|
+
var lightcodeVersion = "0.17.1";
|
|
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
|
|
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
|
|
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
|
|
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
|
|
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: "
|
|
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 =
|
|
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 {
|
|
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
|
-
|
|
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
|
-
|
|
5837
|
+
const match = /^\uFEFF?---\r?\n([\s\S]*?)\r?\n---(?:\r?\n|$)/.exec(content);
|
|
5838
|
+
if (!match)
|
|
5733
5839
|
return {};
|
|
5734
|
-
|
|
5735
|
-
|
|
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 >
|
|
5849
|
+
if (depth > 8 || visited.size >= 2000) {
|
|
5753
5850
|
return;
|
|
5754
5851
|
}
|
|
5755
|
-
|
|
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
|
-
|
|
5758
|
-
|
|
5759
|
-
|
|
5760
|
-
|
|
5761
|
-
|
|
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
|
|
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(
|
|
5779
|
-
skillsCache.
|
|
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(
|
|
5900
|
+
function listSkillsUncached(roots) {
|
|
5786
5901
|
const seen = new Set;
|
|
5787
5902
|
const summaries = [];
|
|
5788
|
-
for (const root of
|
|
5903
|
+
for (const root of roots) {
|
|
5789
5904
|
for (const skillPath of findSkillFiles(root)) {
|
|
5790
|
-
|
|
5791
|
-
|
|
5792
|
-
|
|
5793
|
-
|
|
5794
|
-
|
|
5795
|
-
|
|
5796
|
-
|
|
5797
|
-
|
|
5798
|
-
|
|
5799
|
-
|
|
5800
|
-
|
|
5801
|
-
|
|
5802
|
-
|
|
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,
|
|
5925
|
+
function loadSkill(input, options = {}) {
|
|
5809
5926
|
const parsedInput = skillInputSchema.parse(input);
|
|
5810
5927
|
const normalizedName = normalizeSkillName(parsedInput.name);
|
|
5811
|
-
const skill = listSkills(
|
|
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:
|
|
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
|
|
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 (!
|
|
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(
|
|
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.
|
|
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.
|
|
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 ??
|
|
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
|
|
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
|
|
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 (!
|
|
8335
|
+
if (!existsSync5(credentialsPath)) {
|
|
8177
8336
|
return {};
|
|
8178
8337
|
}
|
|
8179
8338
|
try {
|
|
8180
|
-
const parsed = storedCredentialsSchema.safeParse(JSON.parse(
|
|
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 ??
|
|
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 ??
|
|
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
|
|
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 (!
|
|
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 (!
|
|
10172
|
+
if (!existsSync6(manifestPath)) {
|
|
10013
10173
|
continue;
|
|
10014
10174
|
}
|
|
10015
|
-
const manifest = pluginManifestSchema.parse(JSON.parse(
|
|
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(
|
|
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
|
|
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 (
|
|
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 (!
|
|
22192
|
+
if (!existsSync7(wasmPath) || !existsSync7(highlightsPath)) {
|
|
22030
22193
|
continue;
|
|
22031
22194
|
}
|
|
22032
22195
|
try {
|