@askexenow/exe-os 0.9.69 → 0.9.70
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/deploy/stack-manifests/v0.9.json +96 -16
- package/dist/bin/agentic-ontology-backfill.js +6 -0
- package/dist/bin/agentic-reflection-backfill.js +6 -0
- package/dist/bin/agentic-semantic-label.js +6 -0
- package/dist/bin/backfill-conversations.js +6 -0
- package/dist/bin/backfill-responses.js +6 -0
- package/dist/bin/backfill-vectors.js +6 -0
- package/dist/bin/bulk-sync-postgres.js +6 -0
- package/dist/bin/cleanup-stale-review-tasks.js +6 -0
- package/dist/bin/cli.js +1257 -178
- package/dist/bin/exe-agent.js +6 -0
- package/dist/bin/exe-assign.js +6 -0
- package/dist/bin/exe-boot.js +6 -0
- package/dist/bin/exe-call.js +6 -0
- package/dist/bin/exe-cloud.js +6 -0
- package/dist/bin/exe-dispatch.js +6 -0
- package/dist/bin/exe-doctor.js +6 -0
- package/dist/bin/exe-export-behaviors.js +6 -0
- package/dist/bin/exe-forget.js +6 -0
- package/dist/bin/exe-gateway.js +151 -110
- package/dist/bin/exe-heartbeat.js +6 -0
- package/dist/bin/exe-kill.js +6 -0
- package/dist/bin/exe-launch-agent.js +6 -0
- package/dist/bin/exe-new-employee.js +6 -0
- package/dist/bin/exe-pending-messages.js +6 -0
- package/dist/bin/exe-pending-notifications.js +6 -0
- package/dist/bin/exe-pending-reviews.js +6 -0
- package/dist/bin/exe-rename.js +13 -4
- package/dist/bin/exe-review.js +6 -0
- package/dist/bin/exe-search.js +6 -0
- package/dist/bin/exe-session-cleanup.js +6 -0
- package/dist/bin/exe-start-codex.js +6 -0
- package/dist/bin/exe-start-opencode.js +6 -0
- package/dist/bin/exe-status.js +6 -0
- package/dist/bin/exe-team.js +6 -0
- package/dist/bin/git-sweep.js +6 -0
- package/dist/bin/graph-backfill.js +150 -110
- package/dist/bin/graph-export.js +6 -0
- package/dist/bin/intercom-check.js +6 -0
- package/dist/bin/registry-proxy.js +207 -0
- package/dist/bin/scan-tasks.js +6 -0
- package/dist/bin/setup.js +6 -0
- package/dist/bin/shard-migrate.js +6 -0
- package/dist/bin/stack-update.js +128 -0
- package/dist/gateway/index.js +151 -110
- package/dist/hooks/bug-report-worker.js +6 -0
- package/dist/hooks/codex-stop-task-finalizer.js +6 -0
- package/dist/hooks/commit-complete.js +6 -0
- package/dist/hooks/error-recall.js +6 -0
- package/dist/hooks/ingest.js +6 -0
- package/dist/hooks/instructions-loaded.js +6 -0
- package/dist/hooks/notification.js +6 -0
- package/dist/hooks/post-compact.js +6 -0
- package/dist/hooks/post-tool-combined.js +6 -0
- package/dist/hooks/pre-compact.js +6 -0
- package/dist/hooks/pre-tool-use.js +6 -0
- package/dist/hooks/prompt-submit.js +6 -0
- package/dist/hooks/session-end.js +6 -0
- package/dist/hooks/session-start.js +6 -0
- package/dist/hooks/stop.js +6 -0
- package/dist/hooks/subagent-stop.js +6 -0
- package/dist/hooks/summary-worker.js +6 -0
- package/dist/index.js +151 -110
- package/dist/lib/employee-templates.js +6 -0
- package/dist/lib/exe-daemon.js +382 -234
- package/dist/lib/hybrid-search.js +6 -0
- package/dist/lib/registry-proxy.js +162 -0
- package/dist/lib/schedules.js +6 -0
- package/dist/lib/store.js +6 -0
- package/dist/mcp/server.js +318 -222
- package/dist/runtime/index.js +6 -0
- package/dist/tui/App.js +6 -0
- package/package.json +3 -2
- package/stack.release.json +6 -4
- package/stack.release.schema.json +89 -18
package/dist/bin/exe-search.js
CHANGED
|
@@ -4124,6 +4124,12 @@ var init_platform_procedures = __esm({
|
|
|
4124
4124
|
priority: "p0",
|
|
4125
4125
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
4126
4126
|
},
|
|
4127
|
+
{
|
|
4128
|
+
title: "Code context first for repository orientation",
|
|
4129
|
+
domain: "workflow",
|
|
4130
|
+
priority: "p1",
|
|
4131
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
4132
|
+
},
|
|
4127
4133
|
{
|
|
4128
4134
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
4129
4135
|
domain: "workflow",
|
|
@@ -4168,6 +4168,12 @@ var init_platform_procedures = __esm({
|
|
|
4168
4168
|
priority: "p0",
|
|
4169
4169
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
4170
4170
|
},
|
|
4171
|
+
{
|
|
4172
|
+
title: "Code context first for repository orientation",
|
|
4173
|
+
domain: "workflow",
|
|
4174
|
+
priority: "p1",
|
|
4175
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
4176
|
+
},
|
|
4171
4177
|
{
|
|
4172
4178
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
4173
4179
|
domain: "workflow",
|
|
@@ -3440,6 +3440,12 @@ var init_platform_procedures = __esm({
|
|
|
3440
3440
|
priority: "p0",
|
|
3441
3441
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
3442
3442
|
},
|
|
3443
|
+
{
|
|
3444
|
+
title: "Code context first for repository orientation",
|
|
3445
|
+
domain: "workflow",
|
|
3446
|
+
priority: "p1",
|
|
3447
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
3448
|
+
},
|
|
3443
3449
|
{
|
|
3444
3450
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
3445
3451
|
domain: "workflow",
|
|
@@ -3440,6 +3440,12 @@ var init_platform_procedures = __esm({
|
|
|
3440
3440
|
priority: "p0",
|
|
3441
3441
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
3442
3442
|
},
|
|
3443
|
+
{
|
|
3444
|
+
title: "Code context first for repository orientation",
|
|
3445
|
+
domain: "workflow",
|
|
3446
|
+
priority: "p1",
|
|
3447
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
3448
|
+
},
|
|
3443
3449
|
{
|
|
3444
3450
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
3445
3451
|
domain: "workflow",
|
package/dist/bin/exe-status.js
CHANGED
|
@@ -4147,6 +4147,12 @@ var init_platform_procedures = __esm({
|
|
|
4147
4147
|
priority: "p0",
|
|
4148
4148
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
4149
4149
|
},
|
|
4150
|
+
{
|
|
4151
|
+
title: "Code context first for repository orientation",
|
|
4152
|
+
domain: "workflow",
|
|
4153
|
+
priority: "p1",
|
|
4154
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
4155
|
+
},
|
|
4150
4156
|
{
|
|
4151
4157
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
4152
4158
|
domain: "workflow",
|
package/dist/bin/exe-team.js
CHANGED
|
@@ -4136,6 +4136,12 @@ var init_platform_procedures = __esm({
|
|
|
4136
4136
|
priority: "p0",
|
|
4137
4137
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
4138
4138
|
},
|
|
4139
|
+
{
|
|
4140
|
+
title: "Code context first for repository orientation",
|
|
4141
|
+
domain: "workflow",
|
|
4142
|
+
priority: "p1",
|
|
4143
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
4144
|
+
},
|
|
4139
4145
|
{
|
|
4140
4146
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
4141
4147
|
domain: "workflow",
|
package/dist/bin/git-sweep.js
CHANGED
|
@@ -7841,6 +7841,12 @@ var init_platform_procedures = __esm({
|
|
|
7841
7841
|
priority: "p0",
|
|
7842
7842
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
7843
7843
|
},
|
|
7844
|
+
{
|
|
7845
|
+
title: "Code context first for repository orientation",
|
|
7846
|
+
domain: "workflow",
|
|
7847
|
+
priority: "p1",
|
|
7848
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
7849
|
+
},
|
|
7844
7850
|
{
|
|
7845
7851
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
7846
7852
|
domain: "workflow",
|
|
@@ -3353,6 +3353,12 @@ var init_platform_procedures = __esm({
|
|
|
3353
3353
|
priority: "p0",
|
|
3354
3354
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
3355
3355
|
},
|
|
3356
|
+
{
|
|
3357
|
+
title: "Code context first for repository orientation",
|
|
3358
|
+
domain: "workflow",
|
|
3359
|
+
priority: "p1",
|
|
3360
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
3361
|
+
},
|
|
3356
3362
|
{
|
|
3357
3363
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
3358
3364
|
domain: "workflow",
|
|
@@ -4662,13 +4668,75 @@ import crypto2 from "crypto";
|
|
|
4662
4668
|
|
|
4663
4669
|
// src/lib/code-chunker.ts
|
|
4664
4670
|
import ts from "typescript";
|
|
4671
|
+
var LANGUAGE_BY_EXTENSION = {
|
|
4672
|
+
c: "c",
|
|
4673
|
+
cc: "cpp",
|
|
4674
|
+
cpp: "cpp",
|
|
4675
|
+
cxx: "cpp",
|
|
4676
|
+
h: "c",
|
|
4677
|
+
hh: "cpp",
|
|
4678
|
+
hpp: "cpp",
|
|
4679
|
+
cs: "csharp",
|
|
4680
|
+
css: "css",
|
|
4681
|
+
scss: "scss",
|
|
4682
|
+
f: "fortran",
|
|
4683
|
+
f90: "fortran",
|
|
4684
|
+
f95: "fortran",
|
|
4685
|
+
go: "go",
|
|
4686
|
+
html: "html",
|
|
4687
|
+
htm: "html",
|
|
4688
|
+
java: "java",
|
|
4689
|
+
js: "javascript",
|
|
4690
|
+
jsx: "javascript",
|
|
4691
|
+
json: "json",
|
|
4692
|
+
kt: "kotlin",
|
|
4693
|
+
kts: "kotlin",
|
|
4694
|
+
lua: "lua",
|
|
4695
|
+
md: "markdown",
|
|
4696
|
+
mdx: "mdx",
|
|
4697
|
+
pas: "pascal",
|
|
4698
|
+
php: "php",
|
|
4699
|
+
py: "python",
|
|
4700
|
+
r: "r",
|
|
4701
|
+
rb: "ruby",
|
|
4702
|
+
rs: "rust",
|
|
4703
|
+
scala: "scala",
|
|
4704
|
+
sc: "scala",
|
|
4705
|
+
sol: "solidity",
|
|
4706
|
+
sql: "sql",
|
|
4707
|
+
svelte: "svelte",
|
|
4708
|
+
swift: "swift",
|
|
4709
|
+
toml: "toml",
|
|
4710
|
+
ts: "typescript",
|
|
4711
|
+
tsx: "typescript",
|
|
4712
|
+
vue: "vue",
|
|
4713
|
+
xml: "xml",
|
|
4714
|
+
yaml: "yaml",
|
|
4715
|
+
yml: "yaml",
|
|
4716
|
+
sh: "shell",
|
|
4717
|
+
bash: "shell",
|
|
4718
|
+
zsh: "shell"
|
|
4719
|
+
};
|
|
4720
|
+
var TEXT_LIKE_LANGUAGES = /* @__PURE__ */ new Set(["json", "markdown", "mdx", "toml", "yaml", "xml", "html", "css", "scss", "sql"]);
|
|
4721
|
+
function languageForFile(filePath) {
|
|
4722
|
+
const base = filePath.split(/[\\/]/).pop()?.toLowerCase() ?? "";
|
|
4723
|
+
if (["dockerfile", "makefile", "rakefile", "gemfile"].includes(base)) return base;
|
|
4724
|
+
const ext = base.includes(".") ? base.split(".").pop() : void 0;
|
|
4725
|
+
return ext ? LANGUAGE_BY_EXTENSION[ext] : void 0;
|
|
4726
|
+
}
|
|
4665
4727
|
function chunkSourceFile(source, fileName = "file.ts") {
|
|
4728
|
+
const language = languageForFile(fileName);
|
|
4729
|
+
if (language === "typescript" || language === "javascript") {
|
|
4730
|
+
return chunkTypeScriptLike(source, fileName);
|
|
4731
|
+
}
|
|
4732
|
+
return chunkGenericSource(source, fileName, language);
|
|
4733
|
+
}
|
|
4734
|
+
function chunkTypeScriptLike(source, fileName) {
|
|
4666
4735
|
const sourceFile = ts.createSourceFile(
|
|
4667
4736
|
fileName,
|
|
4668
4737
|
source,
|
|
4669
4738
|
ts.ScriptTarget.Latest,
|
|
4670
4739
|
true,
|
|
4671
|
-
// setParentNodes
|
|
4672
4740
|
fileName.endsWith(".tsx") || fileName.endsWith(".jsx") ? ts.ScriptKind.TSX : ts.ScriptKind.TS
|
|
4673
4741
|
);
|
|
4674
4742
|
const chunks = [];
|
|
@@ -4690,149 +4758,121 @@ function chunkSourceFile(source, fileName = "file.ts") {
|
|
|
4690
4758
|
if (ts.isFunctionDeclaration(node) || ts.isFunctionExpression(node)) {
|
|
4691
4759
|
return node.name?.getText(sourceFile) ?? "(anonymous)";
|
|
4692
4760
|
}
|
|
4693
|
-
if (ts.isClassDeclaration(node))
|
|
4694
|
-
|
|
4695
|
-
|
|
4696
|
-
if (ts.
|
|
4697
|
-
|
|
4698
|
-
|
|
4699
|
-
if (ts.isTypeAliasDeclaration(node)) {
|
|
4700
|
-
return node.name.getText(sourceFile);
|
|
4701
|
-
}
|
|
4702
|
-
if (ts.isEnumDeclaration(node)) {
|
|
4703
|
-
return node.name.getText(sourceFile);
|
|
4704
|
-
}
|
|
4705
|
-
if (ts.isVariableStatement(node)) {
|
|
4706
|
-
const decls = node.declarationList.declarations;
|
|
4707
|
-
return decls.map((d) => d.name.getText(sourceFile)).join(", ");
|
|
4708
|
-
}
|
|
4709
|
-
if (ts.isExportAssignment(node)) {
|
|
4710
|
-
return "default export";
|
|
4711
|
-
}
|
|
4761
|
+
if (ts.isClassDeclaration(node)) return node.name?.getText(sourceFile) ?? "(anonymous class)";
|
|
4762
|
+
if (ts.isInterfaceDeclaration(node)) return node.name.getText(sourceFile);
|
|
4763
|
+
if (ts.isTypeAliasDeclaration(node)) return node.name.getText(sourceFile);
|
|
4764
|
+
if (ts.isEnumDeclaration(node)) return node.name.getText(sourceFile);
|
|
4765
|
+
if (ts.isVariableStatement(node)) return node.declarationList.declarations.map((d) => d.name.getText(sourceFile)).join(", ");
|
|
4766
|
+
if (ts.isExportAssignment(node)) return "default export";
|
|
4712
4767
|
return "(unknown)";
|
|
4713
4768
|
}
|
|
4714
4769
|
function visitTopLevel(node) {
|
|
4715
4770
|
if (ts.isImportDeclaration(node)) {
|
|
4716
|
-
importLines.push({
|
|
4717
|
-
start: getLineNumber(node.getStart(sourceFile)),
|
|
4718
|
-
end: getLineNumber(node.getEnd())
|
|
4719
|
-
});
|
|
4771
|
+
importLines.push({ start: getLineNumber(node.getStart(sourceFile)), end: getLineNumber(node.getEnd()) });
|
|
4720
4772
|
return;
|
|
4721
4773
|
}
|
|
4722
4774
|
if (ts.isFunctionDeclaration(node)) {
|
|
4723
|
-
chunks.push({
|
|
4724
|
-
kind: "function",
|
|
4725
|
-
name: getName(node),
|
|
4726
|
-
text: getNodeText(node),
|
|
4727
|
-
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
4728
|
-
endLine: getLineNumber(node.getEnd()),
|
|
4729
|
-
comment: getLeadingComment(node)
|
|
4730
|
-
});
|
|
4775
|
+
chunks.push({ kind: "function", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
4731
4776
|
return;
|
|
4732
4777
|
}
|
|
4733
4778
|
if (ts.isClassDeclaration(node)) {
|
|
4734
|
-
chunks.push({
|
|
4735
|
-
kind: "class",
|
|
4736
|
-
name: getName(node),
|
|
4737
|
-
text: getNodeText(node),
|
|
4738
|
-
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
4739
|
-
endLine: getLineNumber(node.getEnd()),
|
|
4740
|
-
comment: getLeadingComment(node)
|
|
4741
|
-
});
|
|
4779
|
+
chunks.push({ kind: "class", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
4742
4780
|
return;
|
|
4743
4781
|
}
|
|
4744
|
-
if (ts.isInterfaceDeclaration(node)) {
|
|
4745
|
-
chunks.push({
|
|
4746
|
-
kind: "type",
|
|
4747
|
-
name: getName(node),
|
|
4748
|
-
text: getNodeText(node),
|
|
4749
|
-
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
4750
|
-
endLine: getLineNumber(node.getEnd()),
|
|
4751
|
-
comment: getLeadingComment(node)
|
|
4752
|
-
});
|
|
4753
|
-
return;
|
|
4754
|
-
}
|
|
4755
|
-
if (ts.isTypeAliasDeclaration(node)) {
|
|
4756
|
-
chunks.push({
|
|
4757
|
-
kind: "type",
|
|
4758
|
-
name: getName(node),
|
|
4759
|
-
text: getNodeText(node),
|
|
4760
|
-
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
4761
|
-
endLine: getLineNumber(node.getEnd()),
|
|
4762
|
-
comment: getLeadingComment(node)
|
|
4763
|
-
});
|
|
4764
|
-
return;
|
|
4765
|
-
}
|
|
4766
|
-
if (ts.isEnumDeclaration(node)) {
|
|
4767
|
-
chunks.push({
|
|
4768
|
-
kind: "type",
|
|
4769
|
-
name: getName(node),
|
|
4770
|
-
text: getNodeText(node),
|
|
4771
|
-
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
4772
|
-
endLine: getLineNumber(node.getEnd()),
|
|
4773
|
-
comment: getLeadingComment(node)
|
|
4774
|
-
});
|
|
4782
|
+
if (ts.isInterfaceDeclaration(node) || ts.isTypeAliasDeclaration(node) || ts.isEnumDeclaration(node)) {
|
|
4783
|
+
chunks.push({ kind: "type", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
4775
4784
|
return;
|
|
4776
4785
|
}
|
|
4777
4786
|
if (ts.isVariableStatement(node)) {
|
|
4778
|
-
const
|
|
4779
|
-
|
|
4780
|
-
(d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer))
|
|
4781
|
-
);
|
|
4782
|
-
chunks.push({
|
|
4783
|
-
kind: isFnLike ? "function" : "variable",
|
|
4784
|
-
name: getName(node),
|
|
4785
|
-
text: getNodeText(node),
|
|
4786
|
-
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
4787
|
-
endLine: getLineNumber(node.getEnd()),
|
|
4788
|
-
comment: getLeadingComment(node)
|
|
4789
|
-
});
|
|
4787
|
+
const isFnLike = node.declarationList.declarations.some((d) => d.initializer && (ts.isArrowFunction(d.initializer) || ts.isFunctionExpression(d.initializer)));
|
|
4788
|
+
chunks.push({ kind: isFnLike ? "function" : "variable", name: getName(node), text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
4790
4789
|
return;
|
|
4791
4790
|
}
|
|
4792
4791
|
if (ts.isExportAssignment(node)) {
|
|
4793
|
-
chunks.push({
|
|
4794
|
-
kind: "export",
|
|
4795
|
-
name: "default export",
|
|
4796
|
-
text: getNodeText(node),
|
|
4797
|
-
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
4798
|
-
endLine: getLineNumber(node.getEnd()),
|
|
4799
|
-
comment: getLeadingComment(node)
|
|
4800
|
-
});
|
|
4792
|
+
chunks.push({ kind: "export", name: "default export", text: getNodeText(node), startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()), comment: getLeadingComment(node) });
|
|
4801
4793
|
return;
|
|
4802
4794
|
}
|
|
4803
4795
|
if (ts.isExpressionStatement(node)) {
|
|
4804
4796
|
const text = getNodeText(node);
|
|
4805
|
-
if (text.length > 10) {
|
|
4806
|
-
chunks.push({
|
|
4807
|
-
kind: "other",
|
|
4808
|
-
name: text.slice(0, 40).replace(/\n/g, " "),
|
|
4809
|
-
text,
|
|
4810
|
-
startLine: getLineNumber(node.getStart(sourceFile)),
|
|
4811
|
-
endLine: getLineNumber(node.getEnd())
|
|
4812
|
-
});
|
|
4813
|
-
}
|
|
4814
|
-
return;
|
|
4797
|
+
if (text.length > 10) chunks.push({ kind: "other", name: text.slice(0, 40).replace(/\n/g, " "), text, startLine: getLineNumber(node.getStart(sourceFile)), endLine: getLineNumber(node.getEnd()) });
|
|
4815
4798
|
}
|
|
4816
4799
|
}
|
|
4817
4800
|
sourceFile.statements.forEach(visitTopLevel);
|
|
4818
4801
|
if (importLines.length > 0) {
|
|
4819
4802
|
const startLine = importLines[0].start;
|
|
4820
4803
|
const endLine = importLines[importLines.length - 1].end;
|
|
4821
|
-
|
|
4822
|
-
|
|
4823
|
-
|
|
4824
|
-
|
|
4825
|
-
|
|
4826
|
-
|
|
4804
|
+
chunks.unshift({ kind: "import", name: `${importLines.length} imports`, text: lines.slice(startLine - 1, endLine).join("\n"), startLine, endLine });
|
|
4805
|
+
}
|
|
4806
|
+
chunks.sort((a, b) => a.startLine - b.startLine);
|
|
4807
|
+
return chunks.length > 0 ? chunks : chunkByWindows(source, 80);
|
|
4808
|
+
}
|
|
4809
|
+
function chunkGenericSource(source, _fileName, language) {
|
|
4810
|
+
const lines = source.split("\n");
|
|
4811
|
+
if (source.trim().length === 0) return [];
|
|
4812
|
+
if (language && TEXT_LIKE_LANGUAGES.has(language)) return chunkTextLike(lines, language);
|
|
4813
|
+
const chunks = [];
|
|
4814
|
+
const patterns = [
|
|
4815
|
+
{ kind: "function", regex: /^\s*(?:async\s+)?def\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
|
|
4816
|
+
{ kind: "class", regex: /^\s*class\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
|
|
4817
|
+
{ kind: "function", regex: /^\s*(?:pub\s+)?fn\s+([A-Za-z_][\w]*)\s*[<(]/, name: (m) => m[1] },
|
|
4818
|
+
{ kind: "class", regex: /^\s*(?:pub\s+)?(?:struct|enum|trait)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
|
|
4819
|
+
{ kind: "function", regex: /^\s*func\s+(?:\([^)]*\)\s*)?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
|
|
4820
|
+
{ kind: "function", regex: /^\s*(?:public|private|protected|static|final|suspend|fun|override|open|internal|export|async|func|function|subroutine)\s+.*?([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
|
|
4821
|
+
{ kind: "function", regex: /^\s*function\s+([A-Za-z_][\w]*)\s*\(/, name: (m) => m[1] },
|
|
4822
|
+
{ kind: "type", regex: /^\s*(?:interface|type|enum|record|data\s+class|case\s+class|contract|library)\s+([A-Za-z_][\w]*)\b/, name: (m) => m[1] },
|
|
4823
|
+
{ kind: "section", regex: /^\s{0,3}#{1,6}\s+(.+)$/, name: (m) => m[1].trim() }
|
|
4824
|
+
];
|
|
4825
|
+
const starts = [];
|
|
4826
|
+
for (let i = 0; i < lines.length; i++) {
|
|
4827
|
+
const line = lines[i];
|
|
4828
|
+
for (const pattern of patterns) {
|
|
4829
|
+
const match = line.match(pattern.regex);
|
|
4830
|
+
if (match) {
|
|
4831
|
+
starts.push({ line: i + 1, kind: pattern.kind, name: pattern.name(match) });
|
|
4832
|
+
break;
|
|
4833
|
+
}
|
|
4834
|
+
}
|
|
4835
|
+
}
|
|
4836
|
+
if (starts.length === 0) return chunkByWindows(source, 80);
|
|
4837
|
+
for (let i = 0; i < starts.length; i++) {
|
|
4838
|
+
const start = starts[i];
|
|
4839
|
+
const next = starts[i + 1]?.line ?? lines.length + 1;
|
|
4840
|
+
const endLine = Math.max(start.line, next - 1);
|
|
4841
|
+
chunks.push({
|
|
4842
|
+
kind: start.kind,
|
|
4843
|
+
name: start.name,
|
|
4844
|
+
text: lines.slice(start.line - 1, endLine).join("\n"),
|
|
4845
|
+
startLine: start.line,
|
|
4827
4846
|
endLine
|
|
4828
4847
|
});
|
|
4829
4848
|
}
|
|
4830
|
-
chunks
|
|
4849
|
+
return chunks;
|
|
4850
|
+
}
|
|
4851
|
+
function chunkTextLike(lines, language) {
|
|
4852
|
+
if (language === "markdown" || language === "mdx") {
|
|
4853
|
+
const starts = lines.map((line, i) => ({ line, i })).filter(({ line }) => /^\s{0,3}#{1,6}\s+/.test(line));
|
|
4854
|
+
if (starts.length > 0) {
|
|
4855
|
+
return starts.map((start, idx) => {
|
|
4856
|
+
const end = starts[idx + 1]?.i ?? lines.length;
|
|
4857
|
+
const title = start.line.replace(/^\s{0,3}#{1,6}\s+/, "").trim();
|
|
4858
|
+
return { kind: "section", name: title, text: lines.slice(start.i, end).join("\n"), startLine: start.i + 1, endLine: end };
|
|
4859
|
+
});
|
|
4860
|
+
}
|
|
4861
|
+
}
|
|
4862
|
+
return chunkByWindows(lines.join("\n"), 80);
|
|
4863
|
+
}
|
|
4864
|
+
function chunkByWindows(source, windowLines) {
|
|
4865
|
+
const lines = source.split("\n");
|
|
4866
|
+
const chunks = [];
|
|
4867
|
+
for (let i = 0; i < lines.length; i += windowLines) {
|
|
4868
|
+
const end = Math.min(lines.length, i + windowLines);
|
|
4869
|
+
const text = lines.slice(i, end).join("\n");
|
|
4870
|
+
if (text.trim()) chunks.push({ kind: "other", name: `lines ${i + 1}-${end}`, text, startLine: i + 1, endLine: end });
|
|
4871
|
+
}
|
|
4831
4872
|
return chunks;
|
|
4832
4873
|
}
|
|
4833
4874
|
function isChunkable(filePath) {
|
|
4834
|
-
|
|
4835
|
-
return ext === "ts" || ext === "tsx" || ext === "js" || ext === "jsx";
|
|
4875
|
+
return Boolean(languageForFile(filePath));
|
|
4836
4876
|
}
|
|
4837
4877
|
|
|
4838
4878
|
// src/lib/graph-rag.ts
|
package/dist/bin/graph-export.js
CHANGED
|
@@ -4125,6 +4125,12 @@ var init_platform_procedures = __esm({
|
|
|
4125
4125
|
priority: "p0",
|
|
4126
4126
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
4127
4127
|
},
|
|
4128
|
+
{
|
|
4129
|
+
title: "Code context first for repository orientation",
|
|
4130
|
+
domain: "workflow",
|
|
4131
|
+
priority: "p1",
|
|
4132
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
4133
|
+
},
|
|
4128
4134
|
{
|
|
4129
4135
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
4130
4136
|
domain: "workflow",
|
|
@@ -4234,6 +4234,12 @@ var init_platform_procedures = __esm({
|
|
|
4234
4234
|
priority: "p0",
|
|
4235
4235
|
content: "exe-build-adv is MANDATORY for ALL work touching 3+ files. Run /exe-build-adv --auto BEFORE implementation. Pipeline: Spec \u2192 AC \u2192 Tests \u2192 Evaluate \u2192 Fix. No multi-file feature ships without pipeline artifacts. No exceptions \u2014 managers reject work without them."
|
|
4236
4236
|
},
|
|
4237
|
+
{
|
|
4238
|
+
title: "Code context first for repository orientation",
|
|
4239
|
+
domain: "workflow",
|
|
4240
|
+
priority: "p1",
|
|
4241
|
+
content: "Before broad repo exploration, symbol tracing, blast-radius review, or codebase Q&A, agents should use the consolidated code_context MCP tool instead of manual grep/read loops. Use action=index or stats to refresh/check the index; action=search with query, limit, offset, languages, paths, refresh_index for fresh multi-language code/doc search; action=trace for symbol imports/dependents; action=blast_radius for impact analysis before edits. CLI parity exists via exe-os code-context init|index|status|stats|search|doctor. Keep code_context separate from durable employee memory: promote only validated decisions, procedures, or lessons into store_memory/commit_memory."
|
|
4242
|
+
},
|
|
4237
4243
|
{
|
|
4238
4244
|
title: "Commit discipline \u2014 never leave verified work floating",
|
|
4239
4245
|
domain: "workflow",
|
|
@@ -0,0 +1,207 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
|
|
3
|
+
// src/lib/is-main.ts
|
|
4
|
+
import { realpathSync } from "fs";
|
|
5
|
+
import { fileURLToPath } from "url";
|
|
6
|
+
function isMainModule(importMetaUrl) {
|
|
7
|
+
if (process.argv[1] == null) return false;
|
|
8
|
+
if (process.argv[1].includes("mcp/server")) return false;
|
|
9
|
+
try {
|
|
10
|
+
const scriptPath = realpathSync(process.argv[1]);
|
|
11
|
+
const modulePath = realpathSync(fileURLToPath(importMetaUrl));
|
|
12
|
+
return scriptPath === modulePath;
|
|
13
|
+
} catch {
|
|
14
|
+
return importMetaUrl === `file://${process.argv[1]}` || importMetaUrl === new URL(process.argv[1], "file://").href;
|
|
15
|
+
}
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
// src/lib/registry-proxy.ts
|
|
19
|
+
import { createServer } from "http";
|
|
20
|
+
import { Readable } from "stream";
|
|
21
|
+
function parsePullTokens(raw) {
|
|
22
|
+
return (raw ?? "").split(/[\n,]/).map((s) => s.trim()).filter(Boolean);
|
|
23
|
+
}
|
|
24
|
+
function registryProxyOptionsFromEnv(env = process.env) {
|
|
25
|
+
const upstreamToken = env.EXE_REGISTRY_PROXY_UPSTREAM_TOKEN || env.GHCR_TOKEN || "";
|
|
26
|
+
const pullTokens = parsePullTokens(env.EXE_REGISTRY_PROXY_PULL_TOKENS);
|
|
27
|
+
return {
|
|
28
|
+
port: Number(env.EXE_REGISTRY_PROXY_PORT || 3201),
|
|
29
|
+
host: env.EXE_REGISTRY_PROXY_HOST || "0.0.0.0",
|
|
30
|
+
upstream: env.EXE_REGISTRY_PROXY_UPSTREAM || "https://ghcr.io",
|
|
31
|
+
upstreamUsername: env.EXE_REGISTRY_PROXY_UPSTREAM_USERNAME || env.GHCR_USERNAME || "askexe",
|
|
32
|
+
upstreamToken,
|
|
33
|
+
pullTokens,
|
|
34
|
+
allowedNamespace: env.EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE || "askexe"
|
|
35
|
+
};
|
|
36
|
+
}
|
|
37
|
+
function assertRegistryProxyConfig(options) {
|
|
38
|
+
if (!options.upstreamToken) throw new Error("EXE_REGISTRY_PROXY_UPSTREAM_TOKEN or GHCR_TOKEN is required");
|
|
39
|
+
if (options.pullTokens.length === 0) throw new Error("EXE_REGISTRY_PROXY_PULL_TOKENS is required");
|
|
40
|
+
if (!options.allowedNamespace || !/^[a-z0-9._-]+$/i.test(options.allowedNamespace)) {
|
|
41
|
+
throw new Error("EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE must be a registry-safe namespace");
|
|
42
|
+
}
|
|
43
|
+
}
|
|
44
|
+
function timingSafeIncludes(values, candidate) {
|
|
45
|
+
return values.some((value) => value === candidate);
|
|
46
|
+
}
|
|
47
|
+
function parseBasicAuth(header) {
|
|
48
|
+
if (!header?.startsWith("Basic ")) return null;
|
|
49
|
+
try {
|
|
50
|
+
const decoded = Buffer.from(header.slice("Basic ".length), "base64").toString("utf8");
|
|
51
|
+
const idx = decoded.indexOf(":");
|
|
52
|
+
if (idx < 0) return null;
|
|
53
|
+
return { username: decoded.slice(0, idx), password: decoded.slice(idx + 1) };
|
|
54
|
+
} catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function unauthorized(res) {
|
|
59
|
+
res.writeHead(401, {
|
|
60
|
+
"www-authenticate": 'Basic realm="AskExe Registry Proxy"',
|
|
61
|
+
"content-type": "application/json"
|
|
62
|
+
});
|
|
63
|
+
res.end(JSON.stringify({ error: "registry proxy authentication required" }));
|
|
64
|
+
}
|
|
65
|
+
function isAllowedPath(pathname, namespace) {
|
|
66
|
+
if (pathname === "/health") return true;
|
|
67
|
+
if (pathname === "/v2/" || pathname === "/v2") return true;
|
|
68
|
+
const prefix = `/v2/${namespace}/`;
|
|
69
|
+
if (!pathname.startsWith(prefix)) return false;
|
|
70
|
+
if (pathname.includes("..") || /%2f/i.test(pathname)) return false;
|
|
71
|
+
return true;
|
|
72
|
+
}
|
|
73
|
+
function parseBearerChallenge(header) {
|
|
74
|
+
if (!header?.toLowerCase().startsWith("bearer ")) return null;
|
|
75
|
+
const raw = header.slice("Bearer ".length);
|
|
76
|
+
const params = new URLSearchParams();
|
|
77
|
+
for (const part of raw.match(/(?:[^,"]+|"[^"]*")+/g) ?? []) {
|
|
78
|
+
const idx = part.indexOf("=");
|
|
79
|
+
if (idx < 0) continue;
|
|
80
|
+
const key = part.slice(0, idx).trim();
|
|
81
|
+
const value = part.slice(idx + 1).trim().replace(/^"|"$/g, "");
|
|
82
|
+
params.set(key, value);
|
|
83
|
+
}
|
|
84
|
+
const realm = params.get("realm");
|
|
85
|
+
if (!realm) return null;
|
|
86
|
+
params.delete("realm");
|
|
87
|
+
return { realm, params };
|
|
88
|
+
}
|
|
89
|
+
async function fetchUpstreamWithRegistryAuth(url, init, upstreamBasicAuth) {
|
|
90
|
+
const firstHeaders = new Headers(init.headers);
|
|
91
|
+
firstHeaders.set("authorization", `Basic ${upstreamBasicAuth}`);
|
|
92
|
+
const first = await fetch(url, { ...init, headers: firstHeaders });
|
|
93
|
+
if (first.status !== 401) return first;
|
|
94
|
+
const challenge = parseBearerChallenge(first.headers.get("www-authenticate"));
|
|
95
|
+
if (!challenge) return first;
|
|
96
|
+
const tokenUrl = new URL(challenge.realm);
|
|
97
|
+
for (const [key, value] of challenge.params.entries()) tokenUrl.searchParams.set(key, value);
|
|
98
|
+
const tokenRes = await fetch(tokenUrl, { headers: { authorization: `Basic ${upstreamBasicAuth}` } });
|
|
99
|
+
if (!tokenRes.ok) return first;
|
|
100
|
+
const tokenJson = await tokenRes.json();
|
|
101
|
+
const token = tokenJson.token ?? tokenJson.access_token;
|
|
102
|
+
if (!token) return first;
|
|
103
|
+
const retryHeaders = new Headers(init.headers);
|
|
104
|
+
retryHeaders.set("authorization", `Bearer ${token}`);
|
|
105
|
+
return fetch(url, { ...init, headers: retryHeaders });
|
|
106
|
+
}
|
|
107
|
+
function copyResponseHeaders(from, to) {
|
|
108
|
+
for (const [key, value] of from.entries()) {
|
|
109
|
+
const lower = key.toLowerCase();
|
|
110
|
+
if (["connection", "keep-alive", "transfer-encoding", "content-encoding"].includes(lower)) continue;
|
|
111
|
+
to.setHeader(key, value);
|
|
112
|
+
}
|
|
113
|
+
}
|
|
114
|
+
function createRegistryProxyServer(options) {
|
|
115
|
+
assertRegistryProxyConfig(options);
|
|
116
|
+
const upstream = new URL(options.upstream ?? "https://ghcr.io");
|
|
117
|
+
const namespace = options.allowedNamespace ?? "askexe";
|
|
118
|
+
const upstreamAuth = Buffer.from(`${options.upstreamUsername ?? "askexe"}:${options.upstreamToken}`).toString("base64");
|
|
119
|
+
return createServer(async (req, res) => {
|
|
120
|
+
const requestUrl = new URL(req.url ?? "/", `http://${req.headers.host ?? "localhost"}`);
|
|
121
|
+
if (requestUrl.pathname === "/health") {
|
|
122
|
+
res.writeHead(200, { "content-type": "application/json" });
|
|
123
|
+
res.end(JSON.stringify({ ok: true, service: "exe-registry-proxy", upstream: upstream.origin, namespace }));
|
|
124
|
+
return;
|
|
125
|
+
}
|
|
126
|
+
if (!isAllowedPath(requestUrl.pathname, namespace)) {
|
|
127
|
+
res.writeHead(404, { "content-type": "application/json" });
|
|
128
|
+
res.end(JSON.stringify({ error: "registry path not allowed" }));
|
|
129
|
+
return;
|
|
130
|
+
}
|
|
131
|
+
const auth = parseBasicAuth(req.headers.authorization);
|
|
132
|
+
if (!auth || !timingSafeIncludes(options.pullTokens, auth.password)) {
|
|
133
|
+
unauthorized(res);
|
|
134
|
+
return;
|
|
135
|
+
}
|
|
136
|
+
const upstreamUrl = new URL(requestUrl.pathname + requestUrl.search, upstream.origin);
|
|
137
|
+
const headers = new Headers();
|
|
138
|
+
for (const [key, value] of Object.entries(req.headers)) {
|
|
139
|
+
if (!value) continue;
|
|
140
|
+
const lower = key.toLowerCase();
|
|
141
|
+
if (["host", "connection", "authorization", "content-length"].includes(lower)) continue;
|
|
142
|
+
headers.set(key, Array.isArray(value) ? value.join(",") : value);
|
|
143
|
+
}
|
|
144
|
+
headers.set("authorization", `Basic ${upstreamAuth}`);
|
|
145
|
+
try {
|
|
146
|
+
const upstreamRes = await fetchUpstreamWithRegistryAuth(upstreamUrl, {
|
|
147
|
+
method: req.method,
|
|
148
|
+
headers,
|
|
149
|
+
body: req.method === "GET" || req.method === "HEAD" ? void 0 : req,
|
|
150
|
+
// Required by undici when streaming request bodies.
|
|
151
|
+
duplex: "half"
|
|
152
|
+
}, upstreamAuth);
|
|
153
|
+
res.statusCode = upstreamRes.status;
|
|
154
|
+
res.statusMessage = upstreamRes.statusText;
|
|
155
|
+
copyResponseHeaders(upstreamRes.headers, res);
|
|
156
|
+
if (!upstreamRes.body || req.method === "HEAD") {
|
|
157
|
+
res.end();
|
|
158
|
+
return;
|
|
159
|
+
}
|
|
160
|
+
Readable.fromWeb(upstreamRes.body).pipe(res);
|
|
161
|
+
} catch (err) {
|
|
162
|
+
res.writeHead(502, { "content-type": "application/json" });
|
|
163
|
+
res.end(JSON.stringify({ error: "upstream registry proxy failed", detail: err instanceof Error ? err.message : String(err) }));
|
|
164
|
+
}
|
|
165
|
+
});
|
|
166
|
+
}
|
|
167
|
+
async function runRegistryProxy(options = registryProxyOptionsFromEnv()) {
|
|
168
|
+
const server = createRegistryProxyServer(options);
|
|
169
|
+
await new Promise((resolve) => server.listen(options.port, options.host, resolve));
|
|
170
|
+
console.log(`exe-registry-proxy listening on ${options.host ?? "0.0.0.0"}:${options.port}`);
|
|
171
|
+
console.log(`proxying /v2/${options.allowedNamespace ?? "askexe"}/* -> ${options.upstream ?? "https://ghcr.io"}`);
|
|
172
|
+
}
|
|
173
|
+
|
|
174
|
+
// src/bin/registry-proxy.ts
|
|
175
|
+
function printHelp() {
|
|
176
|
+
console.log(`exe-os registry-proxy \u2014 authenticated pull-through proxy for AskExe customer images
|
|
177
|
+
|
|
178
|
+
Environment:
|
|
179
|
+
EXE_REGISTRY_PROXY_PORT=3201
|
|
180
|
+
EXE_REGISTRY_PROXY_HOST=0.0.0.0
|
|
181
|
+
EXE_REGISTRY_PROXY_UPSTREAM=https://ghcr.io
|
|
182
|
+
EXE_REGISTRY_PROXY_UPSTREAM_USERNAME=<github-user>
|
|
183
|
+
EXE_REGISTRY_PROXY_UPSTREAM_TOKEN=<askexe-ghcr-read-token>
|
|
184
|
+
EXE_REGISTRY_PROXY_PULL_TOKENS=<customer-token-1,customer-token-2>
|
|
185
|
+
EXE_REGISTRY_PROXY_ALLOWED_NAMESPACE=askexe
|
|
186
|
+
|
|
187
|
+
Docker image shape for clients:
|
|
188
|
+
registry.askexe.com/askexe/exed:v0.9.3
|
|
189
|
+
registry.askexe.com/askexe/exe-crm:v0.9.3
|
|
190
|
+
`);
|
|
191
|
+
}
|
|
192
|
+
async function main(args = process.argv.slice(2)) {
|
|
193
|
+
if (args.includes("--help") || args.includes("-h")) {
|
|
194
|
+
printHelp();
|
|
195
|
+
return;
|
|
196
|
+
}
|
|
197
|
+
await runRegistryProxy(registryProxyOptionsFromEnv());
|
|
198
|
+
}
|
|
199
|
+
if (isMainModule(import.meta.url)) {
|
|
200
|
+
main().catch((err) => {
|
|
201
|
+
console.error(err instanceof Error ? err.message : String(err));
|
|
202
|
+
process.exit(1);
|
|
203
|
+
});
|
|
204
|
+
}
|
|
205
|
+
export {
|
|
206
|
+
main
|
|
207
|
+
};
|