agemon 0.1.0 → 6.3.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/README.md +983 -30
- package/assets/skills/architecture/agemon-architecture/LICENSE +21 -0
- package/assets/skills/architecture/agemon-architecture/NOTICE.md +9 -0
- package/assets/skills/architecture/agemon-architecture/SKILL.md +275 -0
- package/assets/skills/code-quality/agemon-clean-code/LICENSE +21 -0
- package/assets/skills/code-quality/agemon-clean-code/NOTICE.md +9 -0
- package/assets/skills/code-quality/agemon-clean-code/SKILL.md +78 -0
- package/assets/skills/code-quality/agemon-house-rules/SKILL.md +103 -0
- package/assets/skills/commit/agemon-commit/SKILL.md +87 -0
- package/assets/skills/design/agemon-design/LICENSE +21 -0
- package/assets/skills/design/agemon-design/NOTICE.md +43 -0
- package/assets/skills/design/agemon-design/SKILL.md +128 -0
- package/assets/skills/design/agemon-design/references/accessibility.md +182 -0
- package/assets/skills/design/agemon-design/references/actions.md +215 -0
- package/assets/skills/design/agemon-design/references/ai-chat.md +158 -0
- package/assets/skills/design/agemon-design/references/brief.md +190 -0
- package/assets/skills/design/agemon-design/references/color.md +216 -0
- package/assets/skills/design/agemon-design/references/components.md +91 -0
- package/assets/skills/design/agemon-design/references/copy.md +300 -0
- package/assets/skills/design/agemon-design/references/core-rules.md +166 -0
- package/assets/skills/design/agemon-design/references/craft-intent.md +162 -0
- package/assets/skills/design/agemon-design/references/dashboard.md +131 -0
- package/assets/skills/design/agemon-design/references/dataviz.md +197 -0
- package/assets/skills/design/agemon-design/references/finish-bar.md +243 -0
- package/assets/skills/design/agemon-design/references/forms.md +192 -0
- package/assets/skills/design/agemon-design/references/heuristics.md +319 -0
- package/assets/skills/design/agemon-design/references/inspiration.md +287 -0
- package/assets/skills/design/agemon-design/references/layout.md +236 -0
- package/assets/skills/design/agemon-design/references/loops.md +155 -0
- package/assets/skills/design/agemon-design/references/metadata.md +98 -0
- package/assets/skills/design/agemon-design/references/modern-css.md +448 -0
- package/assets/skills/design/agemon-design/references/motion.md +434 -0
- package/assets/skills/design/agemon-design/references/personas.md +206 -0
- package/assets/skills/design/agemon-design/references/preset-dashboard.md +109 -0
- package/assets/skills/design/agemon-design/references/preset-editorial.md +94 -0
- package/assets/skills/design/agemon-design/references/preset-minimal.md +93 -0
- package/assets/skills/design/agemon-design/references/principles-catalog.md +299 -0
- package/assets/skills/design/agemon-design/references/recipe-auth.md +88 -0
- package/assets/skills/design/agemon-design/references/recipe-dashboard.md +130 -0
- package/assets/skills/design/agemon-design/references/recipe-landing.md +120 -0
- package/assets/skills/design/agemon-design/references/responsive.md +168 -0
- package/assets/skills/design/agemon-design/references/review.md +340 -0
- package/assets/skills/design/agemon-design/references/routing.md +143 -0
- package/assets/skills/design/agemon-design/references/sound.md +150 -0
- package/assets/skills/design/agemon-design/references/spec.md +123 -0
- package/assets/skills/design/agemon-design/references/stack.md +522 -0
- package/assets/skills/design/agemon-design/references/stage-assets.md +106 -0
- package/assets/skills/design/agemon-design/references/stage-build.md +46 -0
- package/assets/skills/design/agemon-design/references/stage-critique.md +34 -0
- package/assets/skills/design/agemon-design/references/stage-directions.md +44 -0
- package/assets/skills/design/agemon-design/references/stage-intake.md +34 -0
- package/assets/skills/design/agemon-design/references/stage-verify.md +44 -0
- package/assets/skills/design/agemon-design/references/state-design.md +188 -0
- package/assets/skills/design/agemon-design/references/tells-2026-11.md +32 -0
- package/assets/skills/design/agemon-design/references/themes.md +233 -0
- package/assets/skills/design/agemon-design/references/third-party-licenses.md +323 -0
- package/assets/skills/design/agemon-design/references/tokens.md +350 -0
- package/assets/skills/design/agemon-design/references/typography.md +248 -0
- package/assets/skills/design/agemon-design/scripts/audit-page.js +532 -0
- package/assets/skills/design/agemon-design/scripts/check-source.mjs +358 -0
- package/assets/skills/design/agemon-design/scripts/deal.mjs +231 -0
- package/assets/skills/design/agemon-design/scripts/inventory.mjs +676 -0
- package/assets/skills/essentials/web-design-guidelines/LICENSE +21 -0
- package/assets/skills/essentials/web-design-guidelines/NOTICE.md +14 -0
- package/assets/skills/essentials/web-design-guidelines/SKILL.md +18 -0
- package/assets/skills/essentials/web-design-guidelines/references/guidelines.md +184 -0
- package/assets/skills/essentials/writing-guidelines/LICENSE +21 -0
- package/assets/skills/essentials/writing-guidelines/NOTICE.md +14 -0
- package/assets/skills/essentials/writing-guidelines/SKILL.md +18 -0
- package/assets/skills/essentials/writing-guidelines/references/guidelines.md +250 -0
- package/assets/skills/founder/agemon-founder-board/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-board/NOTICE.md +11 -0
- package/assets/skills/founder/agemon-founder-board/SKILL.md +111 -0
- package/assets/skills/founder/agemon-founder-board/lenses.md +84 -0
- package/assets/skills/founder/agemon-founder-brand/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-brand/NOTICE.md +12 -0
- package/assets/skills/founder/agemon-founder-brand/SKILL.md +77 -0
- package/assets/skills/founder/agemon-founder-cfo/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-cfo/NOTICE.md +12 -0
- package/assets/skills/founder/agemon-founder-cfo/SKILL.md +109 -0
- package/assets/skills/founder/agemon-founder-cfo/example.json +27 -0
- package/assets/skills/founder/agemon-founder-cfo/unit_economics.py +270 -0
- package/assets/skills/founder/agemon-founder-competitors/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-competitors/NOTICE.md +12 -0
- package/assets/skills/founder/agemon-founder-competitors/SKILL.md +84 -0
- package/assets/skills/founder/agemon-founder-consumer/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-consumer/NOTICE.md +11 -0
- package/assets/skills/founder/agemon-founder-consumer/SKILL.md +126 -0
- package/assets/skills/founder/agemon-founder-consumer/customer.example.json +34 -0
- package/assets/skills/founder/agemon-founder-consumer/panel.py +424 -0
- package/assets/skills/founder/agemon-founder-consumer/pitch.example.md +3 -0
- package/assets/skills/founder/agemon-founder-launch/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-launch/NOTICE.md +11 -0
- package/assets/skills/founder/agemon-founder-launch/SKILL.md +65 -0
- package/assets/skills/founder/agemon-founder-marketing/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-marketing/NOTICE.md +11 -0
- package/assets/skills/founder/agemon-founder-marketing/SKILL.md +80 -0
- package/assets/skills/founder/agemon-founder-offer/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-offer/NOTICE.md +11 -0
- package/assets/skills/founder/agemon-founder-offer/SKILL.md +75 -0
- package/assets/skills/founder/agemon-founder-ops/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-ops/NOTICE.md +12 -0
- package/assets/skills/founder/agemon-founder-ops/SKILL.md +77 -0
- package/assets/skills/founder/agemon-founder-plan/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-plan/NOTICE.md +11 -0
- package/assets/skills/founder/agemon-founder-plan/SKILL.md +88 -0
- package/assets/skills/founder/agemon-founder-plan/compile.py +178 -0
- package/assets/skills/founder/agemon-founder-pricing/LICENSE +21 -0
- package/assets/skills/founder/agemon-founder-pricing/NOTICE.md +12 -0
- package/assets/skills/founder/agemon-founder-pricing/SKILL.md +92 -0
- package/assets/skills/founder/agemon-founder-pricing/van_westendorp.py +172 -0
- package/assets/skills/geo-seo/geo/LICENSE +21 -0
- package/assets/skills/geo-seo/geo/NOTICE.md +18 -0
- package/assets/skills/geo-seo/geo/SKILL.md +145 -0
- package/assets/skills/geo-seo/geo-audit/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-audit/NOTICE.md +17 -0
- package/assets/skills/geo-seo/geo-audit/SKILL.md +371 -0
- package/assets/skills/geo-seo/geo-audit/references/ai-visibility.md +233 -0
- package/assets/skills/geo-seo/geo-audit/references/content.md +329 -0
- package/assets/skills/geo-seo/geo-audit/references/platform-analysis.md +294 -0
- package/assets/skills/geo-seo/geo-audit/references/schema.md +383 -0
- package/assets/skills/geo-seo/geo-audit/references/technical.md +368 -0
- package/assets/skills/geo-seo/geo-brand-mentions/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-brand-mentions/NOTICE.md +15 -0
- package/assets/skills/geo-seo/geo-brand-mentions/SKILL.md +459 -0
- package/assets/skills/geo-seo/geo-citability/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-citability/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-citability/SKILL.md +320 -0
- package/assets/skills/geo-seo/geo-content/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-content/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-content/SKILL.md +352 -0
- package/assets/skills/geo-seo/geo-crawlers/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-crawlers/NOTICE.md +15 -0
- package/assets/skills/geo-seo/geo-crawlers/SKILL.md +388 -0
- package/assets/skills/geo-seo/geo-llmstxt/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-llmstxt/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-llmstxt/SKILL.md +433 -0
- package/assets/skills/geo-seo/geo-platform-optimizer/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-platform-optimizer/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-platform-optimizer/SKILL.md +282 -0
- package/assets/skills/geo-seo/geo-report/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-report/NOTICE.md +14 -0
- package/assets/skills/geo-seo/geo-report/SKILL.md +406 -0
- package/assets/skills/geo-seo/geo-report-pdf/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-report-pdf/NOTICE.md +20 -0
- package/assets/skills/geo-seo/geo-report-pdf/SKILL.md +153 -0
- package/assets/skills/geo-seo/geo-report-pdf/templates/geo-report-template.html +548 -0
- package/assets/skills/geo-seo/geo-schema/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-schema/NOTICE.md +16 -0
- package/assets/skills/geo-seo/geo-schema/SKILL.md +396 -0
- package/assets/skills/geo-seo/geo-technical/LICENSE +21 -0
- package/assets/skills/geo-seo/geo-technical/NOTICE.md +15 -0
- package/assets/skills/geo-seo/geo-technical/SKILL.md +566 -0
- package/assets/skills/output-style/agemon-adhd/LICENSE +21 -0
- package/assets/skills/output-style/agemon-adhd/NOTICE.md +13 -0
- package/assets/skills/output-style/agemon-adhd/SKILL.md +138 -0
- package/assets/skills/performance/agemon-performance/NOTICE.md +1 -0
- package/assets/skills/performance/agemon-performance/SKILL.md +76 -0
- package/assets/skills/security/agemon-security/LICENSE +21 -0
- package/assets/skills/security/agemon-security/NOTICE.md +8 -0
- package/assets/skills/security/agemon-security/SKILL.md +356 -0
- package/assets/skills/security/agemon-security-audit/AI-AND-LLM.md +83 -0
- package/assets/skills/security/agemon-security-audit/ATTACK-CLASSES.md +130 -0
- package/assets/skills/security/agemon-security-audit/CLIENT-SIDE.md +83 -0
- package/assets/skills/security/agemon-security-audit/CLOUD-AND-DEPLOYMENT.md +86 -0
- package/assets/skills/security/agemon-security-audit/DATA-ISOLATION-AND-LIFECYCLE.md +84 -0
- package/assets/skills/security/agemon-security-audit/DESKTOP-MOBILE-AND-LOCAL-IPC.md +89 -0
- package/assets/skills/security/agemon-security-audit/HUNTING.md +251 -0
- package/assets/skills/security/agemon-security-audit/LICENSE +21 -0
- package/assets/skills/security/agemon-security-audit/MEMORY-SAFETY-AND-BINARY.md +101 -0
- package/assets/skills/security/agemon-security-audit/NOTICE.md +12 -0
- package/assets/skills/security/agemon-security-audit/PROTOCOLS-RPC-AND-MESSAGING.md +81 -0
- package/assets/skills/security/agemon-security-audit/RECONNAISSANCE.md +156 -0
- package/assets/skills/security/agemon-security-audit/RESOURCE-EXHAUSTION-AND-AVAILABILITY.md +78 -0
- package/assets/skills/security/agemon-security-audit/SKILL.md +192 -0
- package/assets/skills/security/agemon-security-audit/SUPPLY-CHAIN-AND-RELEASE.md +73 -0
- package/assets/skills/security/agemon-security-audit/VALIDATION-AND-REPORTING.md +186 -0
- package/assets/skills/security/agemon-security-audit/WEB-PROTOCOL-AND-AUTH.md +105 -0
- package/assets/skills/security/agemon-security-audit/report-schema.json +461 -0
- package/assets/skills/security/agemon-security-audit/validate-coverage-ledger.cjs +872 -0
- package/assets/skills/security/agemon-security-audit/validate-findings.cjs +773 -0
- package/assets/skills/self-review/agemon-review-intake/LICENSE +21 -0
- package/assets/skills/self-review/agemon-review-intake/NOTICE.md +9 -0
- package/assets/skills/self-review/agemon-review-intake/SKILL.md +205 -0
- package/assets/skills/self-review/agemon-review-request/LICENSE +21 -0
- package/assets/skills/self-review/agemon-review-request/NOTICE.md +9 -0
- package/assets/skills/self-review/agemon-review-request/SKILL.md +104 -0
- package/assets/skills/self-review/agemon-review-request/code-reviewer.md +188 -0
- package/assets/skills/self-review/agemon-root-cause/LICENSE +21 -0
- package/assets/skills/self-review/agemon-root-cause/NOTICE.md +10 -0
- package/assets/skills/self-review/agemon-root-cause/SKILL.md +283 -0
- package/assets/skills/self-review/agemon-root-cause/condition-based-waiting-example.ts +158 -0
- package/assets/skills/self-review/agemon-root-cause/condition-based-waiting.md +115 -0
- package/assets/skills/self-review/agemon-root-cause/defense-in-depth.md +122 -0
- package/assets/skills/self-review/agemon-root-cause/find-polluter.sh +72 -0
- package/assets/skills/self-review/agemon-root-cause/root-cause-tracing.md +169 -0
- package/assets/skills/self-review/agemon-root-cause/test-academic.md +14 -0
- package/assets/skills/self-review/agemon-root-cause/test-pressure-1.md +58 -0
- package/assets/skills/self-review/agemon-root-cause/test-pressure-2.md +68 -0
- package/assets/skills/self-review/agemon-root-cause/test-pressure-3.md +69 -0
- package/assets/skills/self-review/agemon-verify-before-done/LICENSE +21 -0
- package/assets/skills/self-review/agemon-verify-before-done/NOTICE.md +9 -0
- package/assets/skills/self-review/agemon-verify-before-done/SKILL.md +120 -0
- package/assets/skills/team-lead/agemon-plan-rfc/SKILL.md +317 -0
- package/assets/skills/team-lead/prd-generator/SKILL.md +437 -0
- package/assets/skills/team-lead/skill-headofit-audit/SKILL.md +72 -0
- package/assets/skills/team-lead/skill-incident-rca/SKILL.md +66 -0
- package/assets/skills/team-lead/skill-meeting-lifecycle/SKILL.md +63 -0
- package/assets/skills/team-lead/skill-obsidian-vault-sync/SKILL.md +69 -0
- package/assets/skills/team-lead/skill-prd-decomposer/SKILL.md +71 -0
- package/assets/skills/team-lead/skill-sec-check/SKILL.md +70 -0
- package/assets/skills/team-lead/skill-task-extractor/SKILL.md +79 -0
- package/assets/skills/team-lead/skill-tech-spike/SKILL.md +57 -0
- package/assets/skills/team-lead/skill-techlead-reviewer/SKILL.md +81 -0
- package/assets/skills/workflow/agemon-brainstorming/LICENSE +21 -0
- package/assets/skills/workflow/agemon-brainstorming/NOTICE.md +14 -0
- package/assets/skills/workflow/agemon-brainstorming/SKILL.md +287 -0
- package/assets/skills/workflow/agemon-brainstorming/builder-check-prompt.md +23 -0
- package/assets/skills/workflow/agemon-dispatching-parallel-agents/LICENSE +21 -0
- package/assets/skills/workflow/agemon-dispatching-parallel-agents/NOTICE.md +13 -0
- package/assets/skills/workflow/agemon-dispatching-parallel-agents/SKILL.md +167 -0
- package/assets/skills/workflow/agemon-executing-plans/LICENSE +21 -0
- package/assets/skills/workflow/agemon-executing-plans/NOTICE.md +15 -0
- package/assets/skills/workflow/agemon-executing-plans/SKILL.md +322 -0
- package/assets/skills/workflow/agemon-executing-plans/scripts/task-done +55 -0
- package/assets/skills/workflow/agemon-executing-plans/scripts/task-start +32 -0
- package/assets/skills/workflow/agemon-finishing-a-development-branch/LICENSE +21 -0
- package/assets/skills/workflow/agemon-finishing-a-development-branch/NOTICE.md +13 -0
- package/assets/skills/workflow/agemon-finishing-a-development-branch/SKILL.md +225 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/LICENSE +21 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/NOTICE.md +15 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/SKILL.md +339 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/code-reviewer.md +198 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/implementer-prompt.md +154 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/re-review-prompt.md +116 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/batching.md +11 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/cannot-verify.md +10 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/final-review.md +49 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/fix-loop.md +77 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/handling-reports.md +15 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/preflight.md +26 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/resuming.md +12 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/role-scout.md +18 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/references/role-worker.md +19 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/scripts/review-package +64 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/scripts/sdd-repo +54 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/scripts/sdd-workspace +110 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/scripts/task-brief +108 -0
- package/assets/skills/workflow/agemon-subagent-driven-development/task-reviewer-prompt.md +209 -0
- package/assets/skills/workflow/agemon-test-driven-development/LICENSE +21 -0
- package/assets/skills/workflow/agemon-test-driven-development/NOTICE.md +14 -0
- package/assets/skills/workflow/agemon-test-driven-development/SKILL.md +330 -0
- package/assets/skills/workflow/agemon-test-driven-development/writing-good-tests.md +198 -0
- package/assets/skills/workflow/agemon-using-git-worktrees/LICENSE +21 -0
- package/assets/skills/workflow/agemon-using-git-worktrees/NOTICE.md +13 -0
- package/assets/skills/workflow/agemon-using-git-worktrees/SKILL.md +167 -0
- package/assets/skills/workflow/agemon-writing-plans/LICENSE +21 -0
- package/assets/skills/workflow/agemon-writing-plans/NOTICE.md +13 -0
- package/assets/skills/workflow/agemon-writing-plans/SKILL.md +204 -0
- package/assets/tools/code-review-graph-constraints.txt +1418 -0
- package/assets/tools/package-lock.json +111 -0
- package/assets/tools/package.json +6 -0
- package/dist/chunk-ZZBF3GOV.js +3451 -0
- package/dist/chunk-ZZBF3GOV.js.map +1 -0
- package/dist/fake-subprocess-IV4ML77S.js +636 -0
- package/dist/fake-subprocess-IV4ML77S.js.map +1 -0
- package/dist/index.js +23836 -1945
- package/dist/index.js.map +1 -1
- package/package.json +37 -17
|
@@ -0,0 +1,3451 @@
|
|
|
1
|
+
var __create = Object.create;
|
|
2
|
+
var __defProp = Object.defineProperty;
|
|
3
|
+
var __getOwnPropDesc = Object.getOwnPropertyDescriptor;
|
|
4
|
+
var __getOwnPropNames = Object.getOwnPropertyNames;
|
|
5
|
+
var __getProtoOf = Object.getPrototypeOf;
|
|
6
|
+
var __hasOwnProp = Object.prototype.hasOwnProperty;
|
|
7
|
+
var __commonJS = (cb, mod) => function __require() {
|
|
8
|
+
try {
|
|
9
|
+
return mod || (0, cb[__getOwnPropNames(cb)[0]])((mod = { exports: {} }).exports, mod), mod.exports;
|
|
10
|
+
} catch (e) {
|
|
11
|
+
throw mod = 0, e;
|
|
12
|
+
}
|
|
13
|
+
};
|
|
14
|
+
var __copyProps = (to, from, except, desc) => {
|
|
15
|
+
if (from && typeof from === "object" || typeof from === "function") {
|
|
16
|
+
for (let key of __getOwnPropNames(from))
|
|
17
|
+
if (!__hasOwnProp.call(to, key) && key !== except)
|
|
18
|
+
__defProp(to, key, { get: () => from[key], enumerable: !(desc = __getOwnPropDesc(from, key)) || desc.enumerable });
|
|
19
|
+
}
|
|
20
|
+
return to;
|
|
21
|
+
};
|
|
22
|
+
var __toESM = (mod, isNodeMode, target) => (target = mod != null ? __create(__getProtoOf(mod)) : {}, __copyProps(
|
|
23
|
+
// If the importer is in node compatibility mode or this is not an ESM
|
|
24
|
+
// file that has been converted to a CommonJS file using a Babel-
|
|
25
|
+
// compatible transform (i.e. "__esModule" has not been set), then set
|
|
26
|
+
// "default" to the CommonJS "module.exports" for node compatibility.
|
|
27
|
+
isNodeMode || !mod || !mod.__esModule ? __defProp(target, "default", { value: mod, enumerable: true }) : target,
|
|
28
|
+
mod
|
|
29
|
+
));
|
|
30
|
+
|
|
31
|
+
// src/core/untrusted-text.ts
|
|
32
|
+
var TERMINAL_UNSAFE_CHARACTER_RANGES = "\\u0000-\\u0008\\u000b-\\u001f\\u007f-\\u009f\\u061c\\u200b-\\u200f\\u2028\\u2029\\u202a-\\u202e\\u2060\\u2066-\\u2069\\ufeff";
|
|
33
|
+
var TERMINAL_UNSAFE_CHARACTERS = new RegExp(
|
|
34
|
+
`[${TERMINAL_UNSAFE_CHARACTER_RANGES}]`,
|
|
35
|
+
"gu"
|
|
36
|
+
);
|
|
37
|
+
var IDENTIFIER_UNSAFE_CHARACTERS = new RegExp(
|
|
38
|
+
`[\\t\\n${TERMINAL_UNSAFE_CHARACTER_RANGES}]`,
|
|
39
|
+
"u"
|
|
40
|
+
);
|
|
41
|
+
function printable(value) {
|
|
42
|
+
return value.replace(TERMINAL_UNSAFE_CHARACTERS, "?");
|
|
43
|
+
}
|
|
44
|
+
function isSafeIdentifier(value) {
|
|
45
|
+
return !IDENTIFIER_UNSAFE_CHARACTERS.test(value);
|
|
46
|
+
}
|
|
47
|
+
function quoteForShlex(value) {
|
|
48
|
+
return `'${value.replaceAll("'", "'\\''")}'`;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
// src/core/repo-paths.ts
|
|
52
|
+
import { constants as constants2 } from "fs";
|
|
53
|
+
import { lstat as lstat2, mkdir, open as open2, realpath } from "fs/promises";
|
|
54
|
+
import { homedir } from "os";
|
|
55
|
+
import { dirname, isAbsolute, join, relative, resolve, sep } from "path";
|
|
56
|
+
|
|
57
|
+
// src/core/files.ts
|
|
58
|
+
import { createHash, randomUUID } from "crypto";
|
|
59
|
+
import { constants } from "fs";
|
|
60
|
+
import {
|
|
61
|
+
access,
|
|
62
|
+
lstat,
|
|
63
|
+
open,
|
|
64
|
+
rename,
|
|
65
|
+
rm
|
|
66
|
+
} from "fs/promises";
|
|
67
|
+
var OWNER_ONLY_MODE = 384;
|
|
68
|
+
var DEFAULT_NEW_FILE_MODE = 438;
|
|
69
|
+
var PERMISSION_BITS = 511;
|
|
70
|
+
var READ_CHUNK_BYTES = 64 * 1024;
|
|
71
|
+
var PRESERVED_ORIGINAL_SUFFIX = ".agemon-original";
|
|
72
|
+
var BYTES_PER_MEBIBYTE = 1024 * 1024;
|
|
73
|
+
var MAX_READ_FILE_BYTES = 8 * BYTES_PER_MEBIBYTE;
|
|
74
|
+
var MAX_READ_FILE_LABEL = `${MAX_READ_FILE_BYTES / BYTES_PER_MEBIBYTE} MiB`;
|
|
75
|
+
function isMissingFileError(error) {
|
|
76
|
+
return error.code === "ENOENT";
|
|
77
|
+
}
|
|
78
|
+
var RefusedFileError = class extends Error {
|
|
79
|
+
};
|
|
80
|
+
var NotARegularFileError = class extends RefusedFileError {
|
|
81
|
+
constructor(action, filePath) {
|
|
82
|
+
super(
|
|
83
|
+
`Refusing to ${action} ${filePath}: it is not a regular file (agemon does not ${action} through symlinks or special files).`
|
|
84
|
+
);
|
|
85
|
+
this.name = "NotARegularFileError";
|
|
86
|
+
}
|
|
87
|
+
};
|
|
88
|
+
var FileTooLargeError = class extends RefusedFileError {
|
|
89
|
+
constructor(filePath) {
|
|
90
|
+
super(
|
|
91
|
+
`Refusing to read ${filePath}: it is larger than ${MAX_READ_FILE_LABEL}.`
|
|
92
|
+
);
|
|
93
|
+
this.name = "FileTooLargeError";
|
|
94
|
+
}
|
|
95
|
+
};
|
|
96
|
+
var RefusedPathError = class extends RefusedFileError {
|
|
97
|
+
constructor(message) {
|
|
98
|
+
super(message);
|
|
99
|
+
this.name = "RefusedPathError";
|
|
100
|
+
}
|
|
101
|
+
};
|
|
102
|
+
async function readAtMostCap(handle, filePath) {
|
|
103
|
+
const chunks = [];
|
|
104
|
+
let totalBytes = 0;
|
|
105
|
+
for (; ; ) {
|
|
106
|
+
const { bytesRead, buffer } = await handle.read({
|
|
107
|
+
buffer: Buffer.alloc(READ_CHUNK_BYTES)
|
|
108
|
+
});
|
|
109
|
+
if (bytesRead === 0) {
|
|
110
|
+
return Buffer.concat(chunks, totalBytes).toString("utf8");
|
|
111
|
+
}
|
|
112
|
+
totalBytes += bytesRead;
|
|
113
|
+
if (totalBytes > MAX_READ_FILE_BYTES) {
|
|
114
|
+
throw new FileTooLargeError(filePath);
|
|
115
|
+
}
|
|
116
|
+
chunks.push(buffer.subarray(0, bytesRead));
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
async function readRegularFileIfExists(filePath) {
|
|
120
|
+
let handle;
|
|
121
|
+
try {
|
|
122
|
+
handle = await open(
|
|
123
|
+
filePath,
|
|
124
|
+
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
125
|
+
);
|
|
126
|
+
} catch (error) {
|
|
127
|
+
if (isMissingFileError(error)) {
|
|
128
|
+
return null;
|
|
129
|
+
}
|
|
130
|
+
if (error.code === "ELOOP") {
|
|
131
|
+
throw new NotARegularFileError("read", filePath);
|
|
132
|
+
}
|
|
133
|
+
throw error;
|
|
134
|
+
}
|
|
135
|
+
try {
|
|
136
|
+
if (!(await handle.stat()).isFile()) {
|
|
137
|
+
throw new NotARegularFileError("read", filePath);
|
|
138
|
+
}
|
|
139
|
+
return await readAtMostCap(handle, filePath);
|
|
140
|
+
} finally {
|
|
141
|
+
await handle.close();
|
|
142
|
+
}
|
|
143
|
+
}
|
|
144
|
+
async function sha256OfRegularFile(filePath, maxBytes) {
|
|
145
|
+
let handle;
|
|
146
|
+
try {
|
|
147
|
+
handle = await open(
|
|
148
|
+
filePath,
|
|
149
|
+
constants.O_RDONLY | constants.O_NOFOLLOW | constants.O_NONBLOCK
|
|
150
|
+
);
|
|
151
|
+
} catch (error) {
|
|
152
|
+
if (error.code === "ELOOP") {
|
|
153
|
+
throw new NotARegularFileError("read", filePath);
|
|
154
|
+
}
|
|
155
|
+
throw error;
|
|
156
|
+
}
|
|
157
|
+
try {
|
|
158
|
+
if (!(await handle.stat()).isFile()) {
|
|
159
|
+
throw new NotARegularFileError("read", filePath);
|
|
160
|
+
}
|
|
161
|
+
const hash = createHash("sha256");
|
|
162
|
+
let totalBytes = 0;
|
|
163
|
+
const buffer = Buffer.alloc(READ_CHUNK_BYTES);
|
|
164
|
+
for (; ; ) {
|
|
165
|
+
const { bytesRead } = await handle.read({ buffer });
|
|
166
|
+
if (bytesRead === 0) {
|
|
167
|
+
return hash.digest("hex");
|
|
168
|
+
}
|
|
169
|
+
totalBytes += bytesRead;
|
|
170
|
+
if (totalBytes > maxBytes) {
|
|
171
|
+
throw new Error(
|
|
172
|
+
`Refusing to hash ${filePath}: it is larger than expected.`
|
|
173
|
+
);
|
|
174
|
+
}
|
|
175
|
+
hash.update(buffer.subarray(0, bytesRead));
|
|
176
|
+
}
|
|
177
|
+
} finally {
|
|
178
|
+
await handle.close();
|
|
179
|
+
}
|
|
180
|
+
}
|
|
181
|
+
async function modeOfReplaceableFile(filePath) {
|
|
182
|
+
let existing;
|
|
183
|
+
try {
|
|
184
|
+
existing = await lstat(filePath);
|
|
185
|
+
} catch (error) {
|
|
186
|
+
if (isMissingFileError(error)) {
|
|
187
|
+
return null;
|
|
188
|
+
}
|
|
189
|
+
throw error;
|
|
190
|
+
}
|
|
191
|
+
if (!existing.isFile()) {
|
|
192
|
+
throw new NotARegularFileError("write", filePath);
|
|
193
|
+
}
|
|
194
|
+
try {
|
|
195
|
+
await access(filePath, constants.W_OK);
|
|
196
|
+
} catch {
|
|
197
|
+
throw new Error(
|
|
198
|
+
`Refusing to write ${filePath}: it is read-only for this user.`
|
|
199
|
+
);
|
|
200
|
+
}
|
|
201
|
+
return existing.mode & PERMISSION_BITS;
|
|
202
|
+
}
|
|
203
|
+
async function writeFileAtomic(filePath, contents, options = {}) {
|
|
204
|
+
const existingMode = await modeOfReplaceableFile(filePath);
|
|
205
|
+
const tempPath = `${filePath}.tmp-${process.pid}-${randomUUID()}`;
|
|
206
|
+
const handle = await open(
|
|
207
|
+
tempPath,
|
|
208
|
+
"wx",
|
|
209
|
+
existingMode === null ? options.newFileMode ?? DEFAULT_NEW_FILE_MODE : OWNER_ONLY_MODE
|
|
210
|
+
);
|
|
211
|
+
try {
|
|
212
|
+
try {
|
|
213
|
+
if (existingMode !== null) {
|
|
214
|
+
await handle.chmod(existingMode);
|
|
215
|
+
}
|
|
216
|
+
await handle.writeFile(contents, "utf8");
|
|
217
|
+
await handle.sync();
|
|
218
|
+
} finally {
|
|
219
|
+
await handle.close();
|
|
220
|
+
}
|
|
221
|
+
await rename(tempPath, filePath);
|
|
222
|
+
} catch (error) {
|
|
223
|
+
await rm(tempPath, { force: true });
|
|
224
|
+
throw error;
|
|
225
|
+
}
|
|
226
|
+
}
|
|
227
|
+
async function writeNewFile(filePath, contents) {
|
|
228
|
+
const handle = await open(filePath, "wx", OWNER_ONLY_MODE);
|
|
229
|
+
try {
|
|
230
|
+
await handle.writeFile(contents, "utf8");
|
|
231
|
+
await handle.sync();
|
|
232
|
+
} finally {
|
|
233
|
+
await handle.close();
|
|
234
|
+
}
|
|
235
|
+
}
|
|
236
|
+
async function writeNewFileWithFallback(preferredPath, fallbackPath, contents) {
|
|
237
|
+
try {
|
|
238
|
+
await writeNewFile(preferredPath, contents);
|
|
239
|
+
return preferredPath;
|
|
240
|
+
} catch (error) {
|
|
241
|
+
if (error.code !== "EEXIST") {
|
|
242
|
+
throw error;
|
|
243
|
+
}
|
|
244
|
+
}
|
|
245
|
+
await writeNewFile(fallbackPath, contents);
|
|
246
|
+
return fallbackPath;
|
|
247
|
+
}
|
|
248
|
+
async function saveVerifiedCopyBeside(targetPath, contents) {
|
|
249
|
+
const preferredPath = `${targetPath}${PRESERVED_ORIGINAL_SUFFIX}`;
|
|
250
|
+
const savedPath = await writeNewFileWithFallback(
|
|
251
|
+
preferredPath,
|
|
252
|
+
`${preferredPath}.${Date.now()}`,
|
|
253
|
+
contents
|
|
254
|
+
);
|
|
255
|
+
if (await readRegularFileIfExists(savedPath) !== contents) {
|
|
256
|
+
throw new Error(`Could not verify the copy saved at ${savedPath}`);
|
|
257
|
+
}
|
|
258
|
+
return savedPath;
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// src/core/repo-paths.ts
|
|
262
|
+
var AGEMON_STATE_DIRECTORY = ".agemon";
|
|
263
|
+
var GIT_DIRECTORY = ".git";
|
|
264
|
+
var IGNORABLE_CODE_POINTS = new RegExp("\\p{Default_Ignorable_Code_Point}", "gu");
|
|
265
|
+
var MAX_PATH_COMPONENT_BYTES = 255;
|
|
266
|
+
var GITDIR_FILE_PREFIX = "gitdir:";
|
|
267
|
+
var GITDIR_FILE_PROBE_BYTES = 4096;
|
|
268
|
+
function isMissingPathError(error) {
|
|
269
|
+
return error.code === "ENOENT";
|
|
270
|
+
}
|
|
271
|
+
function refuseRepoPath(relativePath, reason) {
|
|
272
|
+
throw new RefusedPathError(
|
|
273
|
+
`Refusing to use '${printable(relativePath)}': ${reason}. agemon only touches files inside the repository.`
|
|
274
|
+
);
|
|
275
|
+
}
|
|
276
|
+
async function resolveRealDirectory(repoRoot, components, options, createdComponents = []) {
|
|
277
|
+
let current = repoRoot;
|
|
278
|
+
for (const [index, component] of components.entries()) {
|
|
279
|
+
current = join(current, component);
|
|
280
|
+
let stats;
|
|
281
|
+
try {
|
|
282
|
+
stats = await lstat2(current);
|
|
283
|
+
} catch (error) {
|
|
284
|
+
if (!isMissingPathError(error)) {
|
|
285
|
+
throw error;
|
|
286
|
+
}
|
|
287
|
+
if (!options.create) {
|
|
288
|
+
return null;
|
|
289
|
+
}
|
|
290
|
+
await mkdir(current);
|
|
291
|
+
createdComponents.push(components.slice(0, index + 1).join("/"));
|
|
292
|
+
continue;
|
|
293
|
+
}
|
|
294
|
+
if (!stats.isDirectory()) {
|
|
295
|
+
throw new RefusedPathError(
|
|
296
|
+
`Refusing to use ${printable(current)}: it is a symlink or not a directory. agemon only uses real directories here \u2014 replace it with a real directory.`
|
|
297
|
+
);
|
|
298
|
+
}
|
|
299
|
+
}
|
|
300
|
+
return current;
|
|
301
|
+
}
|
|
302
|
+
function foldForCaseInsensitiveFilesystems(component) {
|
|
303
|
+
return component.replace(IGNORABLE_CODE_POINTS, "").toLowerCase();
|
|
304
|
+
}
|
|
305
|
+
async function resolveInsideRepo(repoRoot, relativePath) {
|
|
306
|
+
if (relativePath.length === 0 || relativePath.includes("\0")) {
|
|
307
|
+
refuseRepoPath(relativePath, "it is empty or contains a NUL byte");
|
|
308
|
+
}
|
|
309
|
+
if (isAbsolute(relativePath)) {
|
|
310
|
+
refuseRepoPath(relativePath, "it is an absolute path");
|
|
311
|
+
}
|
|
312
|
+
if (!isSafeIdentifier(relativePath)) {
|
|
313
|
+
refuseRepoPath(relativePath, "it contains control characters");
|
|
314
|
+
}
|
|
315
|
+
const repoRelativePath = relative(repoRoot, resolve(repoRoot, relativePath));
|
|
316
|
+
if (repoRelativePath === "" || repoRelativePath === ".." || repoRelativePath.startsWith(`..${sep}`)) {
|
|
317
|
+
refuseRepoPath(relativePath, "it resolves outside the repository");
|
|
318
|
+
}
|
|
319
|
+
const components = repoRelativePath.split(sep);
|
|
320
|
+
if (components.some(
|
|
321
|
+
(component) => Buffer.byteLength(component) > MAX_PATH_COMPONENT_BYTES
|
|
322
|
+
)) {
|
|
323
|
+
refuseRepoPath(relativePath, "a path component is longer than 255 bytes");
|
|
324
|
+
}
|
|
325
|
+
const foldedComponents = components.map(foldForCaseInsensitiveFilesystems);
|
|
326
|
+
if (foldedComponents.includes(GIT_DIRECTORY)) {
|
|
327
|
+
refuseRepoPath(relativePath, "it is inside a .git directory");
|
|
328
|
+
}
|
|
329
|
+
if (foldedComponents[0] === AGEMON_STATE_DIRECTORY) {
|
|
330
|
+
refuseRepoPath(relativePath, "it is inside agemon's own .agemon/ state");
|
|
331
|
+
}
|
|
332
|
+
await resolveRealDirectory(repoRoot, components.slice(0, -1), {
|
|
333
|
+
create: false
|
|
334
|
+
});
|
|
335
|
+
return join(repoRoot, repoRelativePath);
|
|
336
|
+
}
|
|
337
|
+
async function resolveStateDirectory(repoRoot, segments, options) {
|
|
338
|
+
return resolveRealDirectory(
|
|
339
|
+
repoRoot,
|
|
340
|
+
[AGEMON_STATE_DIRECTORY, ...segments],
|
|
341
|
+
options
|
|
342
|
+
);
|
|
343
|
+
}
|
|
344
|
+
async function resolveRepoDirectory(repoRoot, relativeDirectory, options = { create: false }) {
|
|
345
|
+
return resolveRealDirectory(repoRoot, relativeDirectory.split("/"), options);
|
|
346
|
+
}
|
|
347
|
+
async function ensureRepoDirectory(repoRoot, relativeDirectory) {
|
|
348
|
+
const createdDirectories = [];
|
|
349
|
+
await resolveRealDirectory(
|
|
350
|
+
repoRoot,
|
|
351
|
+
relativeDirectory.split("/"),
|
|
352
|
+
{ create: true },
|
|
353
|
+
createdDirectories
|
|
354
|
+
);
|
|
355
|
+
return createdDirectories;
|
|
356
|
+
}
|
|
357
|
+
async function readRegularFilePrefix(path, maxBytes) {
|
|
358
|
+
const handle = await open2(
|
|
359
|
+
path,
|
|
360
|
+
constants2.O_RDONLY | constants2.O_NOFOLLOW | constants2.O_NONBLOCK
|
|
361
|
+
);
|
|
362
|
+
try {
|
|
363
|
+
if (!(await handle.stat()).isFile()) {
|
|
364
|
+
return null;
|
|
365
|
+
}
|
|
366
|
+
const buffer = Buffer.alloc(maxBytes);
|
|
367
|
+
const { bytesRead } = await handle.read(buffer, 0, maxBytes, 0);
|
|
368
|
+
return buffer.subarray(0, bytesRead).toString("utf8");
|
|
369
|
+
} finally {
|
|
370
|
+
await handle.close();
|
|
371
|
+
}
|
|
372
|
+
}
|
|
373
|
+
async function classifyGitEntry(directory) {
|
|
374
|
+
const gitPath = join(directory, GIT_DIRECTORY);
|
|
375
|
+
try {
|
|
376
|
+
const stats = await lstat2(gitPath);
|
|
377
|
+
const entry = (kind) => ({
|
|
378
|
+
kind,
|
|
379
|
+
ownerUid: stats.uid
|
|
380
|
+
});
|
|
381
|
+
if (stats.isDirectory()) {
|
|
382
|
+
return entry("repository");
|
|
383
|
+
}
|
|
384
|
+
if (!stats.isFile()) {
|
|
385
|
+
return entry("unacceptable");
|
|
386
|
+
}
|
|
387
|
+
const prefix = await readRegularFilePrefix(
|
|
388
|
+
gitPath,
|
|
389
|
+
GITDIR_FILE_PROBE_BYTES
|
|
390
|
+
);
|
|
391
|
+
return entry(
|
|
392
|
+
prefix?.startsWith(GITDIR_FILE_PREFIX) ? "repository" : "unacceptable"
|
|
393
|
+
);
|
|
394
|
+
} catch (error) {
|
|
395
|
+
return {
|
|
396
|
+
kind: isMissingPathError(error) ? "none" : "unreadable",
|
|
397
|
+
ownerUid: null
|
|
398
|
+
};
|
|
399
|
+
}
|
|
400
|
+
}
|
|
401
|
+
async function hasGitEntry(directory) {
|
|
402
|
+
return (await classifyGitEntry(directory)).kind !== "none";
|
|
403
|
+
}
|
|
404
|
+
async function isOwnedBy(directory, gitEntry, uid) {
|
|
405
|
+
if (uid === void 0) {
|
|
406
|
+
return true;
|
|
407
|
+
}
|
|
408
|
+
return gitEntry.ownerUid === uid && (await lstat2(directory)).uid === uid;
|
|
409
|
+
}
|
|
410
|
+
async function realpathOrResolved(path) {
|
|
411
|
+
try {
|
|
412
|
+
return await realpath(path);
|
|
413
|
+
} catch {
|
|
414
|
+
return resolve(path);
|
|
415
|
+
}
|
|
416
|
+
}
|
|
417
|
+
function isSameOrInside(path, ancestor) {
|
|
418
|
+
const pathFromAncestor = relative(ancestor, path);
|
|
419
|
+
return pathFromAncestor === "" || pathFromAncestor !== ".." && !pathFromAncestor.startsWith(`..${sep}`) && !isAbsolute(pathFromAncestor);
|
|
420
|
+
}
|
|
421
|
+
async function resolveRepositoryRoot(cwd, homeDirectory = homedir(), currentUid = process.getuid?.()) {
|
|
422
|
+
const start = await realpath(cwd);
|
|
423
|
+
const home = await realpathOrResolved(homeDirectory);
|
|
424
|
+
const useStart = (notice) => ({
|
|
425
|
+
root: start,
|
|
426
|
+
notice
|
|
427
|
+
});
|
|
428
|
+
const notUsing = (directory, reason) => useStart(
|
|
429
|
+
`Not using ${printable(directory)} as the repository root (${reason}).`
|
|
430
|
+
);
|
|
431
|
+
for (let directory = start; ; directory = dirname(directory)) {
|
|
432
|
+
const gitEntry = await classifyGitEntry(directory);
|
|
433
|
+
if (gitEntry.kind === "unreadable") {
|
|
434
|
+
return useStart(null);
|
|
435
|
+
}
|
|
436
|
+
if (gitEntry.kind !== "none" && directory === start) {
|
|
437
|
+
return useStart(null);
|
|
438
|
+
}
|
|
439
|
+
if (gitEntry.kind === "unacceptable") {
|
|
440
|
+
return notUsing(directory, "its .git is not a directory or gitdir file");
|
|
441
|
+
}
|
|
442
|
+
if (gitEntry.kind === "repository") {
|
|
443
|
+
if (isSameOrInside(home, directory)) {
|
|
444
|
+
return useStart(
|
|
445
|
+
`Not using ${printable(directory)} as the repository root (it contains your home directory); run 'git init' here or run agemon from the repository root.`
|
|
446
|
+
);
|
|
447
|
+
}
|
|
448
|
+
if (!await isOwnedBy(directory, gitEntry, currentUid)) {
|
|
449
|
+
return notUsing(directory, "it is owned by another user");
|
|
450
|
+
}
|
|
451
|
+
return {
|
|
452
|
+
root: directory,
|
|
453
|
+
notice: `Using repository root ${printable(directory)} (not the current directory).`
|
|
454
|
+
};
|
|
455
|
+
}
|
|
456
|
+
if (dirname(directory) === directory) {
|
|
457
|
+
return useStart(null);
|
|
458
|
+
}
|
|
459
|
+
}
|
|
460
|
+
}
|
|
461
|
+
|
|
462
|
+
// src/platform/home.ts
|
|
463
|
+
import { realpath as realpath2 } from "fs/promises";
|
|
464
|
+
import { userInfo } from "os";
|
|
465
|
+
import { isAbsolute as isAbsolute2 } from "path";
|
|
466
|
+
function isUsableAbsolutePath(value) {
|
|
467
|
+
return isAbsolute2(value) && value === value.trim() && isSafeIdentifier(value);
|
|
468
|
+
}
|
|
469
|
+
function accountHomeDirectory() {
|
|
470
|
+
try {
|
|
471
|
+
return userInfo().homedir;
|
|
472
|
+
} catch {
|
|
473
|
+
return null;
|
|
474
|
+
}
|
|
475
|
+
}
|
|
476
|
+
function resolveHomeDirectory() {
|
|
477
|
+
for (const candidate of [process.env.HOME, accountHomeDirectory()]) {
|
|
478
|
+
if (candidate && isUsableAbsolutePath(candidate)) {
|
|
479
|
+
return candidate;
|
|
480
|
+
}
|
|
481
|
+
}
|
|
482
|
+
throw new Error(
|
|
483
|
+
"HOME is not an absolute path without control characters or surrounding spaces, and your account has no usable home directory. Set HOME to your home directory and run agemon again."
|
|
484
|
+
);
|
|
485
|
+
}
|
|
486
|
+
async function realpathOrNull(path) {
|
|
487
|
+
try {
|
|
488
|
+
return await realpath2(path);
|
|
489
|
+
} catch {
|
|
490
|
+
return null;
|
|
491
|
+
}
|
|
492
|
+
}
|
|
493
|
+
async function homeDirectoryRealpaths() {
|
|
494
|
+
const realpaths = [];
|
|
495
|
+
for (const candidate of [process.env.HOME, accountHomeDirectory()]) {
|
|
496
|
+
const real = candidate && isAbsolute2(candidate) ? await realpathOrNull(candidate) : null;
|
|
497
|
+
if (real !== null) {
|
|
498
|
+
realpaths.push(real);
|
|
499
|
+
}
|
|
500
|
+
}
|
|
501
|
+
return realpaths;
|
|
502
|
+
}
|
|
503
|
+
async function isHomeDirectory(realDirectory) {
|
|
504
|
+
return (await homeDirectoryRealpaths()).includes(realDirectory);
|
|
505
|
+
}
|
|
506
|
+
async function containsHomeDirectory(realDirectory) {
|
|
507
|
+
return (await homeDirectoryRealpaths()).some(
|
|
508
|
+
(home) => isSameOrInside(home, realDirectory)
|
|
509
|
+
);
|
|
510
|
+
}
|
|
511
|
+
|
|
512
|
+
// src/plugins/pins.ts
|
|
513
|
+
var CODE_REVIEW_GRAPH_VERSION = "2.3.9";
|
|
514
|
+
var CODE_REVIEW_GRAPH_PACKAGE_SPEC = `code-review-graph==${CODE_REVIEW_GRAPH_VERSION}`;
|
|
515
|
+
var AGNIX_VERSION = "0.58.0";
|
|
516
|
+
var AGNIX_RELEASE_ARCHIVE_SHA256 = {
|
|
517
|
+
"agnix-x86_64-unknown-linux-gnu.tar.gz": "68cbf435c87e89319133cd4fd4b8b0c8b0763b17862f231bf495ccb9d8ee074b",
|
|
518
|
+
"agnix-x86_64-unknown-linux-musl.tar.gz": "692d8e90017629e69d9256842ab010a6396affb1c482952f5988ade05fdbccd3",
|
|
519
|
+
"agnix-aarch64-unknown-linux-gnu.tar.gz": "66b1ad951232b7f112e36b2fd8d7dae01ed3a89f84eee2152f5a2baf4dd8b0a7",
|
|
520
|
+
"agnix-aarch64-apple-darwin.tar.gz": "a0585c5ad000d8c6861f545ff55d0a7b09460653cd29e56c15675bb9e6e62fe7"
|
|
521
|
+
};
|
|
522
|
+
var AGNIX_NPM_TARBALL = {
|
|
523
|
+
url: `https://registry.npmjs.org/agnix/-/agnix-${AGNIX_VERSION}.tgz`,
|
|
524
|
+
integrity: "sha512-DpGdAmG66EmwNSlX+JXiEUbCL+dxbOiDAjzniHgafFbfDfQKYGHyEEVBT1JcKAMPVapM3U2jCU1U+4UCOnT/TA=="
|
|
525
|
+
};
|
|
526
|
+
var AGNIX_PACKAGE_FILE_SHA256 = {
|
|
527
|
+
"package.json": "c214b20e9ba469c9fb318937a5aa2efbddff057797fd77eda49eefcdb869dd8b",
|
|
528
|
+
"bin/agnix": "34a7b789cea9872deb3c81fde838c003a3b4366f3924d06e9deb3cccc60228af"
|
|
529
|
+
};
|
|
530
|
+
var AGNIX_BINARY_SHA256 = [
|
|
531
|
+
"d220619c26e4b6f8448bad36816608dfeca0ae2b7c3153ea1d772d485eec6bc9",
|
|
532
|
+
"b4a3c1c630e36dbc3227da82a14b5b055e3add70f5d8384675fbab8e4ae317e6",
|
|
533
|
+
"fca697c810e5ea6598c48377171ad57fdae8b8b82ac9f33c6e6d1c7338ec2987",
|
|
534
|
+
"d9dc2071417d9f5c288e87031f861eba8689586f8f6df9c30a2a21348ceba598"
|
|
535
|
+
];
|
|
536
|
+
|
|
537
|
+
// src/core/fingerprint.ts
|
|
538
|
+
import { createHash as createHash2 } from "crypto";
|
|
539
|
+
function canonicalizeJsonValue(value) {
|
|
540
|
+
if (Array.isArray(value)) {
|
|
541
|
+
return value.map((entry) => canonicalizeJsonValue(entry));
|
|
542
|
+
}
|
|
543
|
+
if (value && typeof value === "object") {
|
|
544
|
+
const sortedEntries = Object.entries(value).filter(([, entryValue]) => entryValue !== void 0).sort(([left], [right]) => left.localeCompare(right));
|
|
545
|
+
return Object.fromEntries(
|
|
546
|
+
sortedEntries.map(([key, entryValue]) => [
|
|
547
|
+
key,
|
|
548
|
+
canonicalizeJsonValue(entryValue)
|
|
549
|
+
])
|
|
550
|
+
);
|
|
551
|
+
}
|
|
552
|
+
return value;
|
|
553
|
+
}
|
|
554
|
+
function sha256Hex(contents) {
|
|
555
|
+
return createHash2("sha256").update(contents).digest("hex");
|
|
556
|
+
}
|
|
557
|
+
function fingerprintJson(value) {
|
|
558
|
+
return sha256Hex(JSON.stringify(canonicalizeJsonValue(value)));
|
|
559
|
+
}
|
|
560
|
+
function fingerprintContent(contents) {
|
|
561
|
+
return sha256Hex(contents);
|
|
562
|
+
}
|
|
563
|
+
|
|
564
|
+
// src/plugins/skills/skill-tree.ts
|
|
565
|
+
import { constants as constants3 } from "fs";
|
|
566
|
+
import { lstat as lstat3, open as open3, opendir } from "fs/promises";
|
|
567
|
+
import { join as join2 } from "path";
|
|
568
|
+
var MAX_SKILL_ENTRIES = 200;
|
|
569
|
+
var MAX_SKILL_DEPTH = 8;
|
|
570
|
+
var MAX_SKILL_BYTES = 8 * 1024 * 1024;
|
|
571
|
+
var FINDER_METADATA_FILE = ".DS_Store";
|
|
572
|
+
var SKILLS_CLI_COPY_EXCLUSIONS = {
|
|
573
|
+
anyName: ["metadata.json"],
|
|
574
|
+
directoryNames: [".git", "__pycache__", "__pypackages__"]
|
|
575
|
+
};
|
|
576
|
+
var SkillTreeRefusal = class extends Error {
|
|
577
|
+
};
|
|
578
|
+
var keepEveryEntry = () => false;
|
|
579
|
+
function isLeftOutBySkillsCli(name, stats) {
|
|
580
|
+
return SKILLS_CLI_COPY_EXCLUSIONS.anyName.includes(name) || stats.isDirectory() && SKILLS_CLI_COPY_EXCLUSIONS.directoryNames.includes(
|
|
581
|
+
name
|
|
582
|
+
);
|
|
583
|
+
}
|
|
584
|
+
function isFinderMetadataFile(name, stats) {
|
|
585
|
+
return name === FINDER_METADATA_FILE && stats.isFile();
|
|
586
|
+
}
|
|
587
|
+
async function readRegularFileWithoutFollowing(path, maxBytes) {
|
|
588
|
+
const handle = await open3(
|
|
589
|
+
path,
|
|
590
|
+
constants3.O_RDONLY | constants3.O_NOFOLLOW | constants3.O_NONBLOCK
|
|
591
|
+
);
|
|
592
|
+
try {
|
|
593
|
+
const stats = await handle.stat();
|
|
594
|
+
if (!stats.isFile()) {
|
|
595
|
+
return null;
|
|
596
|
+
}
|
|
597
|
+
if (stats.size > maxBytes) {
|
|
598
|
+
throw new SkillTreeRefusal("it is larger than 8 MiB");
|
|
599
|
+
}
|
|
600
|
+
return await handle.readFile();
|
|
601
|
+
} finally {
|
|
602
|
+
await handle.close();
|
|
603
|
+
}
|
|
604
|
+
}
|
|
605
|
+
async function sortedNamesUpToCap(directory) {
|
|
606
|
+
const names = [];
|
|
607
|
+
for await (const entry of await opendir(directory)) {
|
|
608
|
+
names.push(entry.name);
|
|
609
|
+
if (names.length > MAX_SKILL_ENTRIES) {
|
|
610
|
+
throw new SkillTreeRefusal(
|
|
611
|
+
`it has more than ${MAX_SKILL_ENTRIES} entries`
|
|
612
|
+
);
|
|
613
|
+
}
|
|
614
|
+
}
|
|
615
|
+
return names.sort();
|
|
616
|
+
}
|
|
617
|
+
async function readSkillTree(directory, skipEntry = keepEveryEntry) {
|
|
618
|
+
const entries = [];
|
|
619
|
+
let totalBytes = 0;
|
|
620
|
+
const walk = async (relativeDirectory, depth) => {
|
|
621
|
+
if (depth > MAX_SKILL_DEPTH) {
|
|
622
|
+
throw new SkillTreeRefusal(
|
|
623
|
+
`it is nested deeper than ${MAX_SKILL_DEPTH} levels`
|
|
624
|
+
);
|
|
625
|
+
}
|
|
626
|
+
for (const name of await sortedNamesUpToCap(
|
|
627
|
+
join2(directory, relativeDirectory)
|
|
628
|
+
)) {
|
|
629
|
+
const relativePath = relativeDirectory === "" ? name : `${relativeDirectory}/${name}`;
|
|
630
|
+
const absolutePath = join2(directory, relativePath);
|
|
631
|
+
const stats = await lstat3(absolutePath);
|
|
632
|
+
if (skipEntry(name, stats)) {
|
|
633
|
+
continue;
|
|
634
|
+
}
|
|
635
|
+
if (entries.length >= MAX_SKILL_ENTRIES) {
|
|
636
|
+
throw new SkillTreeRefusal(
|
|
637
|
+
`it has more than ${MAX_SKILL_ENTRIES} entries`
|
|
638
|
+
);
|
|
639
|
+
}
|
|
640
|
+
if (stats.isDirectory()) {
|
|
641
|
+
entries.push({ relativePath, contents: null, mode: stats.mode });
|
|
642
|
+
await walk(relativePath, depth + 1);
|
|
643
|
+
continue;
|
|
644
|
+
}
|
|
645
|
+
const contents = stats.isFile() ? await readRegularFileWithoutFollowing(
|
|
646
|
+
absolutePath,
|
|
647
|
+
MAX_SKILL_BYTES - totalBytes
|
|
648
|
+
) : null;
|
|
649
|
+
if (contents === null) {
|
|
650
|
+
throw new SkillTreeRefusal(
|
|
651
|
+
`${printable(relativePath)} is a symlink or not a regular file`
|
|
652
|
+
);
|
|
653
|
+
}
|
|
654
|
+
totalBytes += contents.length;
|
|
655
|
+
if (totalBytes > MAX_SKILL_BYTES) {
|
|
656
|
+
throw new SkillTreeRefusal("it is larger than 8 MiB");
|
|
657
|
+
}
|
|
658
|
+
entries.push({ relativePath, contents, mode: stats.mode });
|
|
659
|
+
}
|
|
660
|
+
};
|
|
661
|
+
await walk("", 1);
|
|
662
|
+
return entries;
|
|
663
|
+
}
|
|
664
|
+
function fingerprintSkillTree(entries) {
|
|
665
|
+
return fingerprintJson(
|
|
666
|
+
entries.map((entry) => [
|
|
667
|
+
entry.relativePath,
|
|
668
|
+
entry.contents === null ? null : sha256Hex(entry.contents)
|
|
669
|
+
])
|
|
670
|
+
);
|
|
671
|
+
}
|
|
672
|
+
async function fingerprintSkillSource(directory) {
|
|
673
|
+
return fingerprintSkillTree(
|
|
674
|
+
await readSkillTree(directory, isLeftOutBySkillsCli)
|
|
675
|
+
);
|
|
676
|
+
}
|
|
677
|
+
function readInstalledSkill(directory) {
|
|
678
|
+
return readSkillTree(directory, isFinderMetadataFile);
|
|
679
|
+
}
|
|
680
|
+
async function fingerprintInstalledSkill(directory) {
|
|
681
|
+
return fingerprintSkillTree(await readInstalledSkill(directory));
|
|
682
|
+
}
|
|
683
|
+
|
|
684
|
+
// src/core/state-root.ts
|
|
685
|
+
import { constants as constants4 } from "fs";
|
|
686
|
+
import { mkdir as mkdir2, open as open5, readFile as readFile2, realpath as realpath3, stat } from "fs/promises";
|
|
687
|
+
import { homedir as homedir2, userInfo as userInfo2 } from "os";
|
|
688
|
+
import { dirname as dirname2, isAbsolute as isAbsolute3, join as join3 } from "path";
|
|
689
|
+
|
|
690
|
+
// src/core/file-lock.ts
|
|
691
|
+
import { AsyncLocalStorage } from "async_hooks";
|
|
692
|
+
import { execFile } from "child_process";
|
|
693
|
+
import { randomUUID as randomUUID2 } from "crypto";
|
|
694
|
+
import { lstat as lstat4, open as open4, readFile, rm as rm2 } from "fs/promises";
|
|
695
|
+
import { setTimeout as sleep } from "timers/promises";
|
|
696
|
+
var LOCK_FILE_MODE = 384;
|
|
697
|
+
var DEFAULT_POLL_MS = 200;
|
|
698
|
+
var UNREADABLE_LOCK_GRACE_MS = 3e4;
|
|
699
|
+
var STALE_BREAKER_AGE_MS = 6e4;
|
|
700
|
+
var BREAKER_SUFFIX = ".break";
|
|
701
|
+
var START_TIME_INDEX_AFTER_NAME = 19;
|
|
702
|
+
var SMALLEST_TRUSTED_PID = 2;
|
|
703
|
+
var SYSCTL_PATH = "/usr/sbin/sysctl";
|
|
704
|
+
var SYSCTL_TIMEOUT_MS = 5e3;
|
|
705
|
+
var SYSCTL_OUTPUT_LIMIT_BYTES = 1024;
|
|
706
|
+
var BOOT_SESSION_UUID_PATTERN = /^[0-9A-F]{8}(?:-[0-9A-F]{4}){3}-[0-9A-F]{12}$/iu;
|
|
707
|
+
var locksHeldByThisCallChain = new AsyncLocalStorage();
|
|
708
|
+
function parseProcessStartTime(statContents) {
|
|
709
|
+
const endOfName = statContents.lastIndexOf(")");
|
|
710
|
+
if (endOfName < 0) {
|
|
711
|
+
return null;
|
|
712
|
+
}
|
|
713
|
+
const fieldsAfterName = statContents.slice(endOfName + 1).trim().split(/\s+/u);
|
|
714
|
+
return fieldsAfterName[START_TIME_INDEX_AFTER_NAME] ?? null;
|
|
715
|
+
}
|
|
716
|
+
async function readTrimmedOrNull(path) {
|
|
717
|
+
try {
|
|
718
|
+
return (await readFile(path, "utf8")).trim() || null;
|
|
719
|
+
} catch {
|
|
720
|
+
return null;
|
|
721
|
+
}
|
|
722
|
+
}
|
|
723
|
+
function processExists(pid) {
|
|
724
|
+
try {
|
|
725
|
+
process.kill(pid, 0);
|
|
726
|
+
return true;
|
|
727
|
+
} catch (error) {
|
|
728
|
+
return error.code === "EPERM";
|
|
729
|
+
}
|
|
730
|
+
}
|
|
731
|
+
var linuxProcessProbe = {
|
|
732
|
+
bootId: () => readTrimmedOrNull("/proc/sys/kernel/random/boot_id"),
|
|
733
|
+
async startTime(pid) {
|
|
734
|
+
const stat2 = await readTrimmedOrNull(`/proc/${pid}/stat`);
|
|
735
|
+
return stat2 === null ? null : parseProcessStartTime(stat2);
|
|
736
|
+
},
|
|
737
|
+
exists: processExists
|
|
738
|
+
};
|
|
739
|
+
function readBootSessionUuidWithSysctl() {
|
|
740
|
+
return new Promise((resolve3, reject) => {
|
|
741
|
+
execFile(
|
|
742
|
+
SYSCTL_PATH,
|
|
743
|
+
["-n", "kern.bootsessionuuid"],
|
|
744
|
+
{ timeout: SYSCTL_TIMEOUT_MS, maxBuffer: SYSCTL_OUTPUT_LIMIT_BYTES },
|
|
745
|
+
(error, stdout) => error ? reject(error) : resolve3(stdout)
|
|
746
|
+
);
|
|
747
|
+
});
|
|
748
|
+
}
|
|
749
|
+
function createDarwinProcessProbe(readBootSessionUuid = readBootSessionUuidWithSysctl) {
|
|
750
|
+
let bootId;
|
|
751
|
+
return {
|
|
752
|
+
bootId() {
|
|
753
|
+
bootId ??= readBootSessionUuid().then(
|
|
754
|
+
(output) => BOOT_SESSION_UUID_PATTERN.test(output.trim()) ? output.trim() : null,
|
|
755
|
+
() => null
|
|
756
|
+
);
|
|
757
|
+
return bootId;
|
|
758
|
+
},
|
|
759
|
+
startTime: async () => null,
|
|
760
|
+
exists: processExists
|
|
761
|
+
};
|
|
762
|
+
}
|
|
763
|
+
var hostProcessProbe = process.platform === "darwin" ? createDarwinProcessProbe() : linuxProcessProbe;
|
|
764
|
+
function parseLockOwner(contents) {
|
|
765
|
+
let parsed;
|
|
766
|
+
try {
|
|
767
|
+
parsed = JSON.parse(contents);
|
|
768
|
+
} catch {
|
|
769
|
+
return null;
|
|
770
|
+
}
|
|
771
|
+
if (typeof parsed !== "object" || parsed === null) {
|
|
772
|
+
return null;
|
|
773
|
+
}
|
|
774
|
+
const owner = parsed;
|
|
775
|
+
const isNullableString = (value) => value === null || typeof value === "string";
|
|
776
|
+
const hasTrustedPid = Number.isSafeInteger(owner.pid) && owner.pid >= SMALLEST_TRUSTED_PID;
|
|
777
|
+
if (typeof owner.token !== "string" || !hasTrustedPid || !isNullableString(owner.bootId) || !isNullableString(owner.startTime) || typeof owner.acquiredAt !== "string") {
|
|
778
|
+
return null;
|
|
779
|
+
}
|
|
780
|
+
return owner;
|
|
781
|
+
}
|
|
782
|
+
async function isOwnerAlive(owner, probe) {
|
|
783
|
+
const currentBootId = await probe.bootId();
|
|
784
|
+
if (currentBootId !== null && owner.bootId !== null) {
|
|
785
|
+
if (owner.bootId !== currentBootId) {
|
|
786
|
+
return false;
|
|
787
|
+
}
|
|
788
|
+
if (owner.startTime !== null) {
|
|
789
|
+
return await probe.startTime(owner.pid) === owner.startTime;
|
|
790
|
+
}
|
|
791
|
+
}
|
|
792
|
+
return probe.exists(owner.pid);
|
|
793
|
+
}
|
|
794
|
+
async function isOlderThan(path, ageMs) {
|
|
795
|
+
try {
|
|
796
|
+
return Date.now() - (await lstat4(path)).mtimeMs > ageMs;
|
|
797
|
+
} catch {
|
|
798
|
+
return false;
|
|
799
|
+
}
|
|
800
|
+
}
|
|
801
|
+
async function isStaleLock(lockPath, contents, probe) {
|
|
802
|
+
const owner = parseLockOwner(contents);
|
|
803
|
+
if (owner === null) {
|
|
804
|
+
return isOlderThan(lockPath, UNREADABLE_LOCK_GRACE_MS);
|
|
805
|
+
}
|
|
806
|
+
return !await isOwnerAlive(owner, probe);
|
|
807
|
+
}
|
|
808
|
+
async function createLockFile(lockPath, contents) {
|
|
809
|
+
let handle;
|
|
810
|
+
try {
|
|
811
|
+
handle = await open4(lockPath, "wx", LOCK_FILE_MODE);
|
|
812
|
+
} catch (error) {
|
|
813
|
+
if (error.code === "EEXIST") {
|
|
814
|
+
return false;
|
|
815
|
+
}
|
|
816
|
+
throw error;
|
|
817
|
+
}
|
|
818
|
+
try {
|
|
819
|
+
await handle.writeFile(contents, "utf8");
|
|
820
|
+
await handle.sync();
|
|
821
|
+
} finally {
|
|
822
|
+
await handle.close();
|
|
823
|
+
}
|
|
824
|
+
return true;
|
|
825
|
+
}
|
|
826
|
+
async function breakStaleLock(lockPath, staleContents, probe) {
|
|
827
|
+
const breakerPath = `${lockPath}${BREAKER_SUFFIX}`;
|
|
828
|
+
if (!await createLockFile(breakerPath, String(process.pid))) {
|
|
829
|
+
if (await isOlderThan(breakerPath, STALE_BREAKER_AGE_MS)) {
|
|
830
|
+
await rm2(breakerPath, { force: true });
|
|
831
|
+
}
|
|
832
|
+
return false;
|
|
833
|
+
}
|
|
834
|
+
try {
|
|
835
|
+
if (await readRegularFileIfExists(lockPath) !== staleContents || !await isStaleLock(lockPath, staleContents, probe)) {
|
|
836
|
+
return false;
|
|
837
|
+
}
|
|
838
|
+
await rm2(lockPath, { force: true });
|
|
839
|
+
return true;
|
|
840
|
+
} finally {
|
|
841
|
+
await rm2(breakerPath, { force: true });
|
|
842
|
+
}
|
|
843
|
+
}
|
|
844
|
+
function describeHolder(contents) {
|
|
845
|
+
const owner = contents === null ? null : parseLockOwner(contents);
|
|
846
|
+
return owner === null ? "another agemon run" : `another agemon run (pid ${owner.pid}, since ${printable(owner.acquiredAt)})`;
|
|
847
|
+
}
|
|
848
|
+
async function acquireLock(lockPath, ownerContents, options, probe) {
|
|
849
|
+
const deadline = Date.now() + options.waitMs;
|
|
850
|
+
for (; ; ) {
|
|
851
|
+
if (await createLockFile(lockPath, ownerContents)) {
|
|
852
|
+
return;
|
|
853
|
+
}
|
|
854
|
+
const existing = await readRegularFileIfExists(lockPath);
|
|
855
|
+
if (existing === null) {
|
|
856
|
+
continue;
|
|
857
|
+
}
|
|
858
|
+
if (await isStaleLock(lockPath, existing, probe) && await breakStaleLock(lockPath, existing, probe)) {
|
|
859
|
+
continue;
|
|
860
|
+
}
|
|
861
|
+
if (Date.now() >= deadline) {
|
|
862
|
+
throw new Error(
|
|
863
|
+
`${describeHolder(existing)} holds ${printable(lockPath)}. Wait for it to finish; delete that file only if no agemon is running (an unreadable lock counts as stale after 30 s).`
|
|
864
|
+
);
|
|
865
|
+
}
|
|
866
|
+
await sleep(options.pollMs ?? DEFAULT_POLL_MS);
|
|
867
|
+
}
|
|
868
|
+
}
|
|
869
|
+
async function releaseLock(lockPath, ownerContents) {
|
|
870
|
+
if (await readRegularFileIfExists(lockPath) === ownerContents) {
|
|
871
|
+
await rm2(lockPath, { force: true });
|
|
872
|
+
}
|
|
873
|
+
}
|
|
874
|
+
async function withFileLock(lockPath, work, options) {
|
|
875
|
+
const heldLocks = locksHeldByThisCallChain.getStore() ?? /* @__PURE__ */ new Set();
|
|
876
|
+
if (heldLocks.has(lockPath)) {
|
|
877
|
+
throw new Error(
|
|
878
|
+
`agemon tried to take ${printable(lockPath)} while already holding it.`
|
|
879
|
+
);
|
|
880
|
+
}
|
|
881
|
+
const probe = options.probe ?? hostProcessProbe;
|
|
882
|
+
const owner = {
|
|
883
|
+
token: randomUUID2(),
|
|
884
|
+
pid: process.pid,
|
|
885
|
+
bootId: await probe.bootId(),
|
|
886
|
+
startTime: await probe.startTime(process.pid),
|
|
887
|
+
acquiredAt: (/* @__PURE__ */ new Date()).toISOString()
|
|
888
|
+
};
|
|
889
|
+
const ownerContents = JSON.stringify(owner);
|
|
890
|
+
await acquireLock(lockPath, ownerContents, options, probe);
|
|
891
|
+
const held = {
|
|
892
|
+
async assertStillHeld() {
|
|
893
|
+
if (await readRegularFileIfExists(lockPath) !== ownerContents) {
|
|
894
|
+
throw new Error(
|
|
895
|
+
`agemon's lock ${printable(lockPath)} was removed or replaced by another run; nothing more was changed. Run agemon again.`
|
|
896
|
+
);
|
|
897
|
+
}
|
|
898
|
+
}
|
|
899
|
+
};
|
|
900
|
+
try {
|
|
901
|
+
return await locksHeldByThisCallChain.run(
|
|
902
|
+
/* @__PURE__ */ new Set([...heldLocks, lockPath]),
|
|
903
|
+
() => work(held)
|
|
904
|
+
);
|
|
905
|
+
} finally {
|
|
906
|
+
await releaseLock(lockPath, ownerContents);
|
|
907
|
+
}
|
|
908
|
+
}
|
|
909
|
+
|
|
910
|
+
// src/core/state-root.ts
|
|
911
|
+
var PRIVATE_DIRECTORY_MODE = 448;
|
|
912
|
+
var PERMISSION_BITS2 = 511;
|
|
913
|
+
var GROUP_AND_OTHER_BITS = 63;
|
|
914
|
+
var GROUP_WRITE = 16;
|
|
915
|
+
var OTHER_WRITE = 2;
|
|
916
|
+
var STICKY_BIT = 512;
|
|
917
|
+
var ROOT_UID = 0;
|
|
918
|
+
var REPOSITORY_STATE_KEY_LENGTH = 16;
|
|
919
|
+
var RUN_LOCK_WAIT_MS = 0;
|
|
920
|
+
function currentAccounts() {
|
|
921
|
+
const user = userInfo2();
|
|
922
|
+
return {
|
|
923
|
+
uid: user.uid,
|
|
924
|
+
username: user.username,
|
|
925
|
+
groupFile: "/etc/group",
|
|
926
|
+
passwdFile: "/etc/passwd"
|
|
927
|
+
};
|
|
928
|
+
}
|
|
929
|
+
function resolveStateRoot(hostPlatform = process.platform) {
|
|
930
|
+
const xdgStateHome = process.env.XDG_STATE_HOME;
|
|
931
|
+
if (xdgStateHome && isAbsolute3(xdgStateHome)) {
|
|
932
|
+
return xdgStateHome;
|
|
933
|
+
}
|
|
934
|
+
const homeDirectory = homedir2();
|
|
935
|
+
if (!isAbsolute3(homeDirectory)) {
|
|
936
|
+
throw new Error(
|
|
937
|
+
"Cannot locate agemon's state directory: set HOME (or XDG_STATE_HOME) to an absolute path."
|
|
938
|
+
);
|
|
939
|
+
}
|
|
940
|
+
return hostPlatform === "darwin" ? join3(homeDirectory, "Library", "Application Support") : join3(homeDirectory, ".local", "state");
|
|
941
|
+
}
|
|
942
|
+
async function readColonSeparatedRows(path) {
|
|
943
|
+
return (await readFile2(path, "utf8")).split("\n").filter((line) => line.length > 0).map((line) => line.split(":"));
|
|
944
|
+
}
|
|
945
|
+
async function isUserPrivateGroup(gid, accounts) {
|
|
946
|
+
let groups;
|
|
947
|
+
let users;
|
|
948
|
+
try {
|
|
949
|
+
groups = await readColonSeparatedRows(accounts.groupFile);
|
|
950
|
+
users = await readColonSeparatedRows(accounts.passwdFile);
|
|
951
|
+
} catch {
|
|
952
|
+
return false;
|
|
953
|
+
}
|
|
954
|
+
const groupsWithGid = groups.filter((fields) => fields[2] === String(gid));
|
|
955
|
+
const isPrivateGroupRow = (fields) => fields[0] === accounts.username && (fields[3] ?? "").trim() === "";
|
|
956
|
+
if (groupsWithGid.length === 0 || !groupsWithGid.every(isPrivateGroupRow)) {
|
|
957
|
+
return false;
|
|
958
|
+
}
|
|
959
|
+
return !users.some(
|
|
960
|
+
(fields) => fields[3] === String(gid) && fields[0] !== accounts.username
|
|
961
|
+
);
|
|
962
|
+
}
|
|
963
|
+
function refuseDirectory(directory, reason) {
|
|
964
|
+
throw new Error(
|
|
965
|
+
`Refusing to use ${printable(directory)}: ${reason}. Fix it (for example 'chmod g-w,o-w ${printable(directory)}', or point XDG_STATE_HOME at a directory you own), then run agemon again. Groups served only by LDAP or NSS are not recognised as private.`
|
|
966
|
+
);
|
|
967
|
+
}
|
|
968
|
+
async function assertNotWritableByOthers(directory, mode, gid, accounts) {
|
|
969
|
+
if (mode & OTHER_WRITE) {
|
|
970
|
+
refuseDirectory(directory, "other users can write to it");
|
|
971
|
+
}
|
|
972
|
+
if (mode & GROUP_WRITE && !await isUserPrivateGroup(gid, accounts)) {
|
|
973
|
+
refuseDirectory(directory, "its group can write to it");
|
|
974
|
+
}
|
|
975
|
+
}
|
|
976
|
+
async function assertSafeStateRoot(stateRoot, accounts) {
|
|
977
|
+
let stats;
|
|
978
|
+
try {
|
|
979
|
+
stats = await stat(stateRoot);
|
|
980
|
+
} catch (error) {
|
|
981
|
+
if (error.code === "ENOENT") {
|
|
982
|
+
return;
|
|
983
|
+
}
|
|
984
|
+
throw error;
|
|
985
|
+
}
|
|
986
|
+
if (!stats.isDirectory()) {
|
|
987
|
+
refuseDirectory(stateRoot, "it is not a directory");
|
|
988
|
+
}
|
|
989
|
+
if (stats.uid !== accounts.uid && stats.uid !== ROOT_UID) {
|
|
990
|
+
refuseDirectory(stateRoot, "it is owned by another user");
|
|
991
|
+
}
|
|
992
|
+
if (!(stats.mode & STICKY_BIT)) {
|
|
993
|
+
await assertNotWritableByOthers(stateRoot, stats.mode, stats.gid, accounts);
|
|
994
|
+
}
|
|
995
|
+
}
|
|
996
|
+
async function openAgemonDirectory(directory, mode) {
|
|
997
|
+
const flags = constants4.O_RDONLY | constants4.O_DIRECTORY | constants4.O_NOFOLLOW;
|
|
998
|
+
try {
|
|
999
|
+
return await open5(directory, flags);
|
|
1000
|
+
} catch (error) {
|
|
1001
|
+
const code = error.code;
|
|
1002
|
+
if (code === "ELOOP" || code === "ENOTDIR") {
|
|
1003
|
+
refuseDirectory(directory, "it is not a real directory");
|
|
1004
|
+
}
|
|
1005
|
+
if (code !== "ENOENT") {
|
|
1006
|
+
throw error;
|
|
1007
|
+
}
|
|
1008
|
+
}
|
|
1009
|
+
if (mode === "check") {
|
|
1010
|
+
return null;
|
|
1011
|
+
}
|
|
1012
|
+
try {
|
|
1013
|
+
await mkdir2(directory, { mode: PRIVATE_DIRECTORY_MODE });
|
|
1014
|
+
} catch (error) {
|
|
1015
|
+
if (error.code !== "EEXIST") {
|
|
1016
|
+
throw error;
|
|
1017
|
+
}
|
|
1018
|
+
}
|
|
1019
|
+
return openAgemonDirectory(directory, "check");
|
|
1020
|
+
}
|
|
1021
|
+
async function resolveAgemonStateDirectory(mode, accounts = currentAccounts()) {
|
|
1022
|
+
const stateRoot = resolveStateRoot();
|
|
1023
|
+
const directory = join3(stateRoot, "agemon");
|
|
1024
|
+
if (mode === "prepare") {
|
|
1025
|
+
await mkdir2(stateRoot, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
|
|
1026
|
+
}
|
|
1027
|
+
await assertSafeStateRoot(stateRoot, accounts);
|
|
1028
|
+
const handle = await openAgemonDirectory(directory, mode);
|
|
1029
|
+
if (handle === null) {
|
|
1030
|
+
return { path: directory, tightenedFromMode: null };
|
|
1031
|
+
}
|
|
1032
|
+
try {
|
|
1033
|
+
const stats = await handle.stat();
|
|
1034
|
+
if (stats.uid !== accounts.uid) {
|
|
1035
|
+
refuseDirectory(directory, "it is owned by another user");
|
|
1036
|
+
}
|
|
1037
|
+
await assertNotWritableByOthers(directory, stats.mode, stats.gid, accounts);
|
|
1038
|
+
if (mode === "check" || !(stats.mode & GROUP_AND_OTHER_BITS)) {
|
|
1039
|
+
return { path: directory, tightenedFromMode: null };
|
|
1040
|
+
}
|
|
1041
|
+
await handle.chmod(PRIVATE_DIRECTORY_MODE);
|
|
1042
|
+
return {
|
|
1043
|
+
path: directory,
|
|
1044
|
+
tightenedFromMode: stats.mode & PERMISSION_BITS2
|
|
1045
|
+
};
|
|
1046
|
+
} finally {
|
|
1047
|
+
await handle.close();
|
|
1048
|
+
}
|
|
1049
|
+
}
|
|
1050
|
+
async function checkAgemonStateDirectoryBeforeRun(dryRun, report) {
|
|
1051
|
+
const directory = await resolveAgemonStateDirectory(
|
|
1052
|
+
dryRun ? "check" : "prepare"
|
|
1053
|
+
);
|
|
1054
|
+
if (directory.tightenedFromMode !== null) {
|
|
1055
|
+
report(
|
|
1056
|
+
`Restricted ${printable(directory.path)} to its owner (mode was ${directory.tightenedFromMode.toString(8)}).`
|
|
1057
|
+
);
|
|
1058
|
+
}
|
|
1059
|
+
}
|
|
1060
|
+
async function preparePrivateStateDirectory(name) {
|
|
1061
|
+
const directory = join3(
|
|
1062
|
+
(await resolveAgemonStateDirectory("prepare")).path,
|
|
1063
|
+
name
|
|
1064
|
+
);
|
|
1065
|
+
await mkdir2(directory, { recursive: true, mode: PRIVATE_DIRECTORY_MODE });
|
|
1066
|
+
return directory;
|
|
1067
|
+
}
|
|
1068
|
+
function prepareToolsRoot() {
|
|
1069
|
+
return preparePrivateStateDirectory("tools");
|
|
1070
|
+
}
|
|
1071
|
+
async function repositoryStateKey(repoRoot) {
|
|
1072
|
+
return sha256Hex(await realpath3(repoRoot)).slice(
|
|
1073
|
+
0,
|
|
1074
|
+
REPOSITORY_STATE_KEY_LENGTH
|
|
1075
|
+
);
|
|
1076
|
+
}
|
|
1077
|
+
async function withAgemonStateLock(lockRelativePath, work, waitMs) {
|
|
1078
|
+
const lockPath = join3(
|
|
1079
|
+
(await resolveAgemonStateDirectory("prepare")).path,
|
|
1080
|
+
lockRelativePath
|
|
1081
|
+
);
|
|
1082
|
+
await mkdir2(dirname2(lockPath), {
|
|
1083
|
+
recursive: true,
|
|
1084
|
+
mode: PRIVATE_DIRECTORY_MODE
|
|
1085
|
+
});
|
|
1086
|
+
return withFileLock(lockPath, work, { waitMs });
|
|
1087
|
+
}
|
|
1088
|
+
async function withRepositoryRunLock(repoRoot, work) {
|
|
1089
|
+
return withAgemonStateLock(
|
|
1090
|
+
join3("runs", `${await repositoryStateKey(repoRoot)}.lock`),
|
|
1091
|
+
() => work(),
|
|
1092
|
+
RUN_LOCK_WAIT_MS
|
|
1093
|
+
);
|
|
1094
|
+
}
|
|
1095
|
+
|
|
1096
|
+
// src/plugins/skills/skills-cli.ts
|
|
1097
|
+
import { existsSync as existsSync2 } from "fs";
|
|
1098
|
+
import {
|
|
1099
|
+
copyFile,
|
|
1100
|
+
mkdtemp,
|
|
1101
|
+
readdir,
|
|
1102
|
+
readFile as readFile3,
|
|
1103
|
+
rename as rename2,
|
|
1104
|
+
rm as rm3
|
|
1105
|
+
} from "fs/promises";
|
|
1106
|
+
import { join as join5 } from "path";
|
|
1107
|
+
|
|
1108
|
+
// src/core/package-assets.ts
|
|
1109
|
+
import { existsSync, lstatSync, readFileSync } from "fs";
|
|
1110
|
+
import { dirname as dirname3, join as join4 } from "path";
|
|
1111
|
+
import { fileURLToPath } from "url";
|
|
1112
|
+
var PACKAGE_NAME = "agemon";
|
|
1113
|
+
var PACKAGE_MANIFEST = "package.json";
|
|
1114
|
+
var MAX_PACKAGE_MANIFEST_BYTES = 1024 * 1024;
|
|
1115
|
+
var cachedPackageRoot;
|
|
1116
|
+
function readPackageName(manifestPath) {
|
|
1117
|
+
const stats = lstatSync(manifestPath);
|
|
1118
|
+
if (!stats.isFile() || stats.size > MAX_PACKAGE_MANIFEST_BYTES) {
|
|
1119
|
+
return null;
|
|
1120
|
+
}
|
|
1121
|
+
try {
|
|
1122
|
+
const manifest = JSON.parse(readFileSync(manifestPath, "utf8"));
|
|
1123
|
+
return typeof manifest === "object" && manifest !== null && "name" in manifest && typeof manifest.name === "string" ? manifest.name : null;
|
|
1124
|
+
} catch {
|
|
1125
|
+
return null;
|
|
1126
|
+
}
|
|
1127
|
+
}
|
|
1128
|
+
function findNearestPackageDirectory(startDirectory) {
|
|
1129
|
+
let currentDir = startDirectory;
|
|
1130
|
+
for (; ; ) {
|
|
1131
|
+
if (existsSync(join4(currentDir, PACKAGE_MANIFEST))) {
|
|
1132
|
+
return currentDir;
|
|
1133
|
+
}
|
|
1134
|
+
const parentDir = dirname3(currentDir);
|
|
1135
|
+
if (parentDir === currentDir) {
|
|
1136
|
+
return null;
|
|
1137
|
+
}
|
|
1138
|
+
currentDir = parentDir;
|
|
1139
|
+
}
|
|
1140
|
+
}
|
|
1141
|
+
function resolvePackageRootFrom(startDirectory) {
|
|
1142
|
+
const packageDirectory = findNearestPackageDirectory(startDirectory);
|
|
1143
|
+
if (packageDirectory === null || readPackageName(join4(packageDirectory, PACKAGE_MANIFEST)) !== PACKAGE_NAME || !existsSync(join4(packageDirectory, "assets", "skills"))) {
|
|
1144
|
+
throw new Error(
|
|
1145
|
+
"Unable to locate agemon's vendored assets/skills directory."
|
|
1146
|
+
);
|
|
1147
|
+
}
|
|
1148
|
+
return packageDirectory;
|
|
1149
|
+
}
|
|
1150
|
+
function resolvePackageRoot() {
|
|
1151
|
+
cachedPackageRoot ??= resolvePackageRootFrom(
|
|
1152
|
+
dirname3(fileURLToPath(import.meta.url))
|
|
1153
|
+
);
|
|
1154
|
+
return cachedPackageRoot;
|
|
1155
|
+
}
|
|
1156
|
+
function resolveVendoredSkillPath(groupId, skillId) {
|
|
1157
|
+
return join4(resolvePackageRoot(), "assets", "skills", groupId, skillId);
|
|
1158
|
+
}
|
|
1159
|
+
function resolveToolsManifestDirectory() {
|
|
1160
|
+
return join4(resolvePackageRoot(), "assets", "tools");
|
|
1161
|
+
}
|
|
1162
|
+
|
|
1163
|
+
// src/core/subprocess-runner.ts
|
|
1164
|
+
import { spawn } from "child_process";
|
|
1165
|
+
import { isAbsolute as isAbsolute4 } from "path";
|
|
1166
|
+
|
|
1167
|
+
// src/inspect/redact.ts
|
|
1168
|
+
var DENYLISTED_FILE_PATTERNS = [
|
|
1169
|
+
/(^|\/)\.env(\.[^/]*)?$/,
|
|
1170
|
+
/\.pem$/i,
|
|
1171
|
+
/(^|\/)id_(?:rsa|ed25519|ecdsa|dsa)/i,
|
|
1172
|
+
/\.key$/i,
|
|
1173
|
+
/\.p12$/i,
|
|
1174
|
+
/\.pfx$/i,
|
|
1175
|
+
/(^|\/)\.pypirc$/,
|
|
1176
|
+
/(^|\/)\.aws\/credentials$/,
|
|
1177
|
+
/(^|\/)\.docker\/config\.json$/,
|
|
1178
|
+
/(^|\/)\.npmrc$/,
|
|
1179
|
+
/(^|\/)\.netrc$/,
|
|
1180
|
+
/(^|\/)\.git-credentials$/,
|
|
1181
|
+
/\.local\.json$/,
|
|
1182
|
+
/(^|\/)\.kube\/config$/,
|
|
1183
|
+
/(^|\/)\.pgpass$/,
|
|
1184
|
+
/\.ppk$/i,
|
|
1185
|
+
/\.jks$/i,
|
|
1186
|
+
/\.keystore$/i,
|
|
1187
|
+
/(^|\/)credentials\.json$/,
|
|
1188
|
+
/(^|\/)\.config\/gh\/hosts\.yml$/,
|
|
1189
|
+
/(^|\/)\.vault-token$/,
|
|
1190
|
+
/(^|\/)credentials\.tfrc\.json$/,
|
|
1191
|
+
/(^|\/)application_default_credentials\.json$/,
|
|
1192
|
+
/(^|\/)\.azure\/accessTokens\.json$/,
|
|
1193
|
+
/\.kdbx$/i,
|
|
1194
|
+
/(^|\/)\.config\/gcloud\/credentials\.db$/,
|
|
1195
|
+
/(^|\/)\.gnupg\//,
|
|
1196
|
+
/(^|\/)\.terraformrc$/,
|
|
1197
|
+
/(^|\/)[^/]*service[-_]account[^/]*\.json$/i
|
|
1198
|
+
];
|
|
1199
|
+
var CREDENTIAL_KEY_PATTERN = /(?:^|[._-])(?:tokens?|secret|password|passwd|pwd|api[._-]?key|access[._-]?key|client[._-]?secret|auth|authorization|credentials?|private[._-]?key)(?:$|[._-])|_key$|^key$/i;
|
|
1200
|
+
var CAMEL_CASE_BOUNDARY = /([a-z0-9])([A-Z])/g;
|
|
1201
|
+
var CONCATENATED_CREDENTIAL_SUFFIX = /(?:token|secret|password|passwd|apikey)$/i;
|
|
1202
|
+
var PEM_DASHES = "-{4,5}";
|
|
1203
|
+
var PEM_LABEL = "[A-Z0-9 ]+";
|
|
1204
|
+
var PEM_BEGIN = `${PEM_DASHES} ?BEGIN ${PEM_LABEL}${PEM_DASHES}`;
|
|
1205
|
+
var PEM_END = `${PEM_DASHES} ?END ${PEM_LABEL}${PEM_DASHES}`;
|
|
1206
|
+
var PEM_PATTERN = new RegExp(PEM_BEGIN, "i");
|
|
1207
|
+
var JWT_PATTERN = /^eyJ[A-Za-z0-9_-]+=*\.eyJ[A-Za-z0-9_-]+=*\.[A-Za-z0-9_-]+=*$/;
|
|
1208
|
+
var URL_WITH_CREDENTIALS_PATTERN = /\b[a-z][a-z0-9+.-]{0,31}:\/\/[^/\s:@]+:[^/\s:@]+@/i;
|
|
1209
|
+
var MAX_REDACTED_LINE_CHARACTERS = 4096;
|
|
1210
|
+
var HIGH_ENTROPY_MIN_LENGTH = 32;
|
|
1211
|
+
var HIGH_ENTROPY_MIN_BITS_PER_CHAR = 3.5;
|
|
1212
|
+
var HIGH_ENTROPY_CHARSET = /^[A-Za-z0-9+/=_-]+$/;
|
|
1213
|
+
var MIN_PATH_SEGMENTS = 3;
|
|
1214
|
+
var MIXED_SEGMENT_MIN_LENGTH = 16;
|
|
1215
|
+
var WORD_SEPARATOR = /[-_]/;
|
|
1216
|
+
function isDenylistedPath(relativePath) {
|
|
1217
|
+
return DENYLISTED_FILE_PATTERNS.some((pattern) => pattern.test(relativePath));
|
|
1218
|
+
}
|
|
1219
|
+
function shannonEntropyBitsPerChar(value) {
|
|
1220
|
+
const characterCounts = /* @__PURE__ */ new Map();
|
|
1221
|
+
for (const character of value) {
|
|
1222
|
+
characterCounts.set(character, (characterCounts.get(character) ?? 0) + 1);
|
|
1223
|
+
}
|
|
1224
|
+
let bitsPerChar = 0;
|
|
1225
|
+
for (const count of characterCounts.values()) {
|
|
1226
|
+
const probability = count / value.length;
|
|
1227
|
+
bitsPerChar -= probability * Math.log2(probability);
|
|
1228
|
+
}
|
|
1229
|
+
return bitsPerChar;
|
|
1230
|
+
}
|
|
1231
|
+
function mixesCasesAndDigits(value) {
|
|
1232
|
+
return /[A-Z]/.test(value) && /[a-z]/.test(value) && /[0-9]/.test(value);
|
|
1233
|
+
}
|
|
1234
|
+
function looksLikeSecretPathSegment(segment) {
|
|
1235
|
+
if (shannonEntropyBitsPerChar(segment) < HIGH_ENTROPY_MIN_BITS_PER_CHAR) {
|
|
1236
|
+
return false;
|
|
1237
|
+
}
|
|
1238
|
+
return segment.length >= MIXED_SEGMENT_MIN_LENGTH && mixesCasesAndDigits(segment) || segment.length >= HIGH_ENTROPY_MIN_LENGTH && !WORD_SEPARATOR.test(segment);
|
|
1239
|
+
}
|
|
1240
|
+
function looksLikeWordPath(value) {
|
|
1241
|
+
const segments = value.split("/");
|
|
1242
|
+
return value.startsWith("/") && segments.length >= MIN_PATH_SEGMENTS && !segments.some(looksLikeSecretPathSegment);
|
|
1243
|
+
}
|
|
1244
|
+
function looksHighEntropy(value) {
|
|
1245
|
+
return value.length >= HIGH_ENTROPY_MIN_LENGTH && HIGH_ENTROPY_CHARSET.test(value) && shannonEntropyBitsPerChar(value) >= HIGH_ENTROPY_MIN_BITS_PER_CHAR && !looksLikeWordPath(value);
|
|
1246
|
+
}
|
|
1247
|
+
function stripKeyDecoration(rawKey) {
|
|
1248
|
+
return rawKey.replace(/^[-"']+/, "").replace(/["':]+$/, "");
|
|
1249
|
+
}
|
|
1250
|
+
function namesCredentialKey(rawKey) {
|
|
1251
|
+
const key = stripKeyDecoration(rawKey);
|
|
1252
|
+
return CREDENTIAL_KEY_PATTERN.test(key.replace(CAMEL_CASE_BOUNDARY, "$1_$2")) || CONCATENATED_CREDENTIAL_SUFFIX.test(key);
|
|
1253
|
+
}
|
|
1254
|
+
function classifyString(value) {
|
|
1255
|
+
if (PEM_PATTERN.test(value)) {
|
|
1256
|
+
return "pem";
|
|
1257
|
+
}
|
|
1258
|
+
if (JWT_PATTERN.test(value)) {
|
|
1259
|
+
return "jwt";
|
|
1260
|
+
}
|
|
1261
|
+
if (URL_WITH_CREDENTIALS_PATTERN.test(value)) {
|
|
1262
|
+
return "url-credentials";
|
|
1263
|
+
}
|
|
1264
|
+
if (looksHighEntropy(value)) {
|
|
1265
|
+
return "high-entropy";
|
|
1266
|
+
}
|
|
1267
|
+
return null;
|
|
1268
|
+
}
|
|
1269
|
+
function redactJsonValue(input) {
|
|
1270
|
+
const redactedPaths = [];
|
|
1271
|
+
const walk = (value, path, insideCredentialKey) => {
|
|
1272
|
+
if (typeof value === "string") {
|
|
1273
|
+
const reason = insideCredentialKey ? "credential-key" : classifyString(value.slice(0, MAX_REDACTED_LINE_CHARACTERS));
|
|
1274
|
+
if (reason === null) {
|
|
1275
|
+
return value;
|
|
1276
|
+
}
|
|
1277
|
+
redactedPaths.push(path);
|
|
1278
|
+
return redactedMarker(reason);
|
|
1279
|
+
}
|
|
1280
|
+
if (Array.isArray(value)) {
|
|
1281
|
+
return value.map(
|
|
1282
|
+
(entry, index) => walk(entry, `${path}[${index}]`, insideCredentialKey)
|
|
1283
|
+
);
|
|
1284
|
+
}
|
|
1285
|
+
if (value !== null && typeof value === "object") {
|
|
1286
|
+
return Object.fromEntries(
|
|
1287
|
+
Object.entries(value).map(
|
|
1288
|
+
([key, entryValue]) => [
|
|
1289
|
+
key,
|
|
1290
|
+
walk(
|
|
1291
|
+
entryValue,
|
|
1292
|
+
path === "" ? key : `${path}.${key}`,
|
|
1293
|
+
insideCredentialKey || namesCredentialKey(key)
|
|
1294
|
+
)
|
|
1295
|
+
]
|
|
1296
|
+
)
|
|
1297
|
+
);
|
|
1298
|
+
}
|
|
1299
|
+
return value;
|
|
1300
|
+
};
|
|
1301
|
+
return { value: walk(input, "", false), redactedPaths };
|
|
1302
|
+
}
|
|
1303
|
+
var PEM_BLOCK_PATTERN = new RegExp(
|
|
1304
|
+
`${PEM_BEGIN}[\\s\\S]*?(?:${PEM_END}|$)`,
|
|
1305
|
+
"gi"
|
|
1306
|
+
);
|
|
1307
|
+
var PEM_TAIL_WITHOUT_BEGIN_PATTERN = new RegExp(
|
|
1308
|
+
`^(?:[A-Za-z0-9+/=]+\\r?\\n)*${PEM_END}`,
|
|
1309
|
+
"gim"
|
|
1310
|
+
);
|
|
1311
|
+
var TOKEN_WRAPPER_PATTERN = /^([("'`[{<]*)(.*?)([)"'`\]}>,;.]*)$/s;
|
|
1312
|
+
var KEY_SEPARATOR_PATTERN = /([A-Za-z0-9_.-]+)(["']?[=:])/g;
|
|
1313
|
+
var ASSIGNMENT_PATTERN = /^([A-Za-z0-9_.-]+=)(.+)$/s;
|
|
1314
|
+
var KEY_PATH_SEPARATOR = /[/:?&]/;
|
|
1315
|
+
var STANDALONE_SEPARATORS = /* @__PURE__ */ new Set(["=", ":"]);
|
|
1316
|
+
var AUTH_SCHEME_PATTERN = /^(?:bearer|basic)$/i;
|
|
1317
|
+
var AUTHORIZATION_TOKEN_SCHEME = /^token$/i;
|
|
1318
|
+
function redactedMarker(reason) {
|
|
1319
|
+
return `<redacted:${reason}>`;
|
|
1320
|
+
}
|
|
1321
|
+
function announcesSecretInText(rawKey) {
|
|
1322
|
+
return namesCredentialKey(rawKey) || AUTH_SCHEME_PATTERN.test(stripKeyDecoration(rawKey));
|
|
1323
|
+
}
|
|
1324
|
+
function redactKeyValue(token) {
|
|
1325
|
+
for (const match of token.matchAll(KEY_SEPARATOR_PATTERN)) {
|
|
1326
|
+
if (!announcesSecretInText(match[1])) {
|
|
1327
|
+
continue;
|
|
1328
|
+
}
|
|
1329
|
+
const valueStart = (match.index ?? 0) + match[0].length;
|
|
1330
|
+
const upToValue = token.slice(0, valueStart);
|
|
1331
|
+
const [, valuePrefix, value, valueSuffix] = TOKEN_WRAPPER_PATTERN.exec(
|
|
1332
|
+
token.slice(valueStart)
|
|
1333
|
+
);
|
|
1334
|
+
if (value.length === 0 || AUTH_SCHEME_PATTERN.test(value)) {
|
|
1335
|
+
return { text: token, announcesSecret: true };
|
|
1336
|
+
}
|
|
1337
|
+
return {
|
|
1338
|
+
text: `${upToValue}${valuePrefix}${redactedMarker("credential-key")}${valueSuffix}`,
|
|
1339
|
+
announcesSecret: false
|
|
1340
|
+
};
|
|
1341
|
+
}
|
|
1342
|
+
return null;
|
|
1343
|
+
}
|
|
1344
|
+
function redactAssignedValue(core) {
|
|
1345
|
+
const assignment = ASSIGNMENT_PATTERN.exec(core);
|
|
1346
|
+
if (assignment === null) {
|
|
1347
|
+
return null;
|
|
1348
|
+
}
|
|
1349
|
+
const reason = classifyString(assignment[2]);
|
|
1350
|
+
return reason === null ? null : `${assignment[1]}${redactedMarker(reason)}`;
|
|
1351
|
+
}
|
|
1352
|
+
function redactToken(token, previousToken, secretWasAnnounced) {
|
|
1353
|
+
const [, prefix, core, suffix] = TOKEN_WRAPPER_PATTERN.exec(
|
|
1354
|
+
token
|
|
1355
|
+
);
|
|
1356
|
+
if (AUTH_SCHEME_PATTERN.test(core)) {
|
|
1357
|
+
return { text: token, announcesSecret: true };
|
|
1358
|
+
}
|
|
1359
|
+
if (secretWasAnnounced) {
|
|
1360
|
+
if (AUTHORIZATION_TOKEN_SCHEME.test(core)) {
|
|
1361
|
+
return { text: token, announcesSecret: true };
|
|
1362
|
+
}
|
|
1363
|
+
return {
|
|
1364
|
+
text: `${prefix}${redactedMarker("credential-key")}${suffix}`,
|
|
1365
|
+
announcesSecret: false
|
|
1366
|
+
};
|
|
1367
|
+
}
|
|
1368
|
+
if (STANDALONE_SEPARATORS.has(core)) {
|
|
1369
|
+
return {
|
|
1370
|
+
text: token,
|
|
1371
|
+
announcesSecret: announcesSecretInText(
|
|
1372
|
+
previousToken.split(KEY_PATH_SEPARATOR).at(-1) ?? ""
|
|
1373
|
+
)
|
|
1374
|
+
};
|
|
1375
|
+
}
|
|
1376
|
+
const keyValue = redactKeyValue(token);
|
|
1377
|
+
if (keyValue !== null) {
|
|
1378
|
+
return keyValue;
|
|
1379
|
+
}
|
|
1380
|
+
if (core.startsWith("-") && announcesSecretInText(core)) {
|
|
1381
|
+
return { text: token, announcesSecret: true };
|
|
1382
|
+
}
|
|
1383
|
+
const redactedAssignment = redactAssignedValue(core);
|
|
1384
|
+
if (redactedAssignment !== null) {
|
|
1385
|
+
return {
|
|
1386
|
+
text: `${prefix}${redactedAssignment}${suffix}`,
|
|
1387
|
+
announcesSecret: false
|
|
1388
|
+
};
|
|
1389
|
+
}
|
|
1390
|
+
const reason = classifyString(core);
|
|
1391
|
+
return {
|
|
1392
|
+
text: reason === null ? token : `${prefix}${redactedMarker(reason)}${suffix}`,
|
|
1393
|
+
announcesSecret: false
|
|
1394
|
+
};
|
|
1395
|
+
}
|
|
1396
|
+
function redactSecretsInText(text) {
|
|
1397
|
+
let previousToken = "";
|
|
1398
|
+
let secretWasAnnounced = false;
|
|
1399
|
+
return text.split("\n").map((line) => line.slice(0, MAX_REDACTED_LINE_CHARACTERS)).join("\n").replace(PEM_BLOCK_PATTERN, redactedMarker("pem")).replace(PEM_TAIL_WITHOUT_BEGIN_PATTERN, redactedMarker("pem")).split(/(\s+)/).map((token) => {
|
|
1400
|
+
if (token.length === 0 || /^\s+$/.test(token)) {
|
|
1401
|
+
return token;
|
|
1402
|
+
}
|
|
1403
|
+
const redaction = redactToken(token, previousToken, secretWasAnnounced);
|
|
1404
|
+
previousToken = token;
|
|
1405
|
+
secretWasAnnounced = redaction.announcesSecret;
|
|
1406
|
+
return redaction.text;
|
|
1407
|
+
}).join("");
|
|
1408
|
+
}
|
|
1409
|
+
|
|
1410
|
+
// src/core/subprocess-runner.ts
|
|
1411
|
+
var MAX_RAW_OUTPUT_LINE_CHARACTERS = MAX_REDACTED_LINE_CHARACTERS;
|
|
1412
|
+
var MAX_SHOWN_OUTPUT_LINE_CHARACTERS = 300;
|
|
1413
|
+
var FAILURE_OUTPUT_LINES = 10;
|
|
1414
|
+
var CONTROL_SEQUENCE_INTRODUCER = `(?:${String.fromCharCode(27)}\\[|${String.fromCharCode(155)})`;
|
|
1415
|
+
var TERMINAL_ESCAPE_SEQUENCE = new RegExp(
|
|
1416
|
+
`${CONTROL_SEQUENCE_INTRODUCER}[0-9;?]*[ -/]*[@-~]`,
|
|
1417
|
+
"g"
|
|
1418
|
+
);
|
|
1419
|
+
var SPAWN_ERROR_PREFIX = "process spawn error: ";
|
|
1420
|
+
function sanitizeOutput(text) {
|
|
1421
|
+
return printable(
|
|
1422
|
+
redactSecretsInText(text.replace(TERMINAL_ESCAPE_SEQUENCE, ""))
|
|
1423
|
+
);
|
|
1424
|
+
}
|
|
1425
|
+
function capShownLine(line) {
|
|
1426
|
+
return line.length > MAX_SHOWN_OUTPUT_LINE_CHARACTERS ? `${line.slice(0, MAX_SHOWN_OUTPUT_LINE_CHARACTERS)}\u2026` : line;
|
|
1427
|
+
}
|
|
1428
|
+
function safeOutputTail(stream, maxLines) {
|
|
1429
|
+
const rawLines = stream.split("\n");
|
|
1430
|
+
if (rawLines.at(-1) === "") {
|
|
1431
|
+
rawLines.pop();
|
|
1432
|
+
}
|
|
1433
|
+
const earlierLineCount = Math.max(rawLines.length - maxLines, 0);
|
|
1434
|
+
if (rawLines.length === 0) {
|
|
1435
|
+
return { earlierLineCount, lines: [] };
|
|
1436
|
+
}
|
|
1437
|
+
const keptRawText = rawLines.slice(earlierLineCount).map((line) => line.slice(0, MAX_RAW_OUTPUT_LINE_CHARACTERS)).join("\n");
|
|
1438
|
+
return {
|
|
1439
|
+
earlierLineCount,
|
|
1440
|
+
lines: sanitizeOutput(keptRawText).split("\n").map(capShownLine)
|
|
1441
|
+
};
|
|
1442
|
+
}
|
|
1443
|
+
function describeSubprocessFailure(result, command) {
|
|
1444
|
+
const tail = safeOutputTail(result.stderr.trim(), FAILURE_OUTPUT_LINES);
|
|
1445
|
+
if (tail.lines.every((line) => line.trim() === "")) {
|
|
1446
|
+
return `${command} exited ${result.code}`;
|
|
1447
|
+
}
|
|
1448
|
+
return [
|
|
1449
|
+
...tail.earlierLineCount > 0 ? [`\u2026 ${tail.earlierLineCount} earlier lines`] : [],
|
|
1450
|
+
...tail.lines
|
|
1451
|
+
].join("\n");
|
|
1452
|
+
}
|
|
1453
|
+
function isSpawnFailure(result) {
|
|
1454
|
+
return result.code === 1 && result.stderr.startsWith(SPAWN_ERROR_PREFIX);
|
|
1455
|
+
}
|
|
1456
|
+
var NPM_LAUNCH_MARKERS = [
|
|
1457
|
+
"npm_command",
|
|
1458
|
+
"npm_execpath",
|
|
1459
|
+
"npm_config_user_agent"
|
|
1460
|
+
];
|
|
1461
|
+
var NPM_INJECTED_VARIABLES = ["init_cwd", "node"];
|
|
1462
|
+
var NPM_INJECTED_PATH_SUFFIXES = ["/node_modules/.bin", "/node-gyp-bin"];
|
|
1463
|
+
function wasLaunchedByNpm(names) {
|
|
1464
|
+
return names.some((name) => NPM_LAUNCH_MARKERS.includes(name.toLowerCase()));
|
|
1465
|
+
}
|
|
1466
|
+
function isNpmInjectedVariable(name) {
|
|
1467
|
+
const lowerCaseName = name.toLowerCase();
|
|
1468
|
+
return lowerCaseName.startsWith("npm_") || NPM_INJECTED_VARIABLES.includes(lowerCaseName);
|
|
1469
|
+
}
|
|
1470
|
+
function keepSearchPathEntries(searchPath, launchedByNpm) {
|
|
1471
|
+
return searchPath.split(":").filter(
|
|
1472
|
+
(entry) => isAbsolute4(entry) && !(launchedByNpm && NPM_INJECTED_PATH_SUFFIXES.some(
|
|
1473
|
+
(suffix) => entry.replace(/\/+$/, "").endsWith(suffix)
|
|
1474
|
+
))
|
|
1475
|
+
).join(":");
|
|
1476
|
+
}
|
|
1477
|
+
function agemonSubprocessEnvironment(environment = process.env) {
|
|
1478
|
+
const names = Object.keys(environment);
|
|
1479
|
+
const launchedByNpm = wasLaunchedByNpm(names);
|
|
1480
|
+
const kept = {};
|
|
1481
|
+
for (const name of names) {
|
|
1482
|
+
const value = environment[name];
|
|
1483
|
+
if (value === void 0 || launchedByNpm && isNpmInjectedVariable(name)) {
|
|
1484
|
+
continue;
|
|
1485
|
+
}
|
|
1486
|
+
kept[name] = name === "PATH" ? keepSearchPathEntries(value, launchedByNpm) : value;
|
|
1487
|
+
}
|
|
1488
|
+
return kept;
|
|
1489
|
+
}
|
|
1490
|
+
var DEFAULT_SUBPROCESS_TIMEOUT_MS = 3e5;
|
|
1491
|
+
var MAX_CAPTURED_CHARACTERS_PER_STREAM = 10 * 1024 * 1024;
|
|
1492
|
+
var TRUNCATION_MARKER = "\n\u2026[truncated]";
|
|
1493
|
+
var HARD_KILL_GRACE_MS = 2e3;
|
|
1494
|
+
var STREAM_DRAIN_GRACE_MS = 2e3;
|
|
1495
|
+
var TIMEOUT_EXIT_CODE = 124;
|
|
1496
|
+
function sliceWithoutSplittingSurrogatePair(text, length) {
|
|
1497
|
+
const lastKeptCodeUnit = text.charCodeAt(length - 1);
|
|
1498
|
+
const endsWithHighSurrogate = lastKeptCodeUnit >= 55296 && lastKeptCodeUnit <= 56319;
|
|
1499
|
+
return text.slice(0, endsWithHighSurrogate ? length - 1 : length);
|
|
1500
|
+
}
|
|
1501
|
+
function createBoundedCapture() {
|
|
1502
|
+
let text = "";
|
|
1503
|
+
let truncated = false;
|
|
1504
|
+
return {
|
|
1505
|
+
append(chunk) {
|
|
1506
|
+
if (truncated) {
|
|
1507
|
+
return;
|
|
1508
|
+
}
|
|
1509
|
+
const remainingCharacters = MAX_CAPTURED_CHARACTERS_PER_STREAM - text.length;
|
|
1510
|
+
if (chunk.length <= remainingCharacters) {
|
|
1511
|
+
text += chunk;
|
|
1512
|
+
return;
|
|
1513
|
+
}
|
|
1514
|
+
text += `${sliceWithoutSplittingSurrogatePair(chunk, remainingCharacters)}${TRUNCATION_MARKER}`;
|
|
1515
|
+
truncated = true;
|
|
1516
|
+
},
|
|
1517
|
+
get text() {
|
|
1518
|
+
return text;
|
|
1519
|
+
}
|
|
1520
|
+
};
|
|
1521
|
+
}
|
|
1522
|
+
async function runSubprocess(command, args, options = {}) {
|
|
1523
|
+
if (process.env.AGEMON_FAKE_SUBPROCESS === "1") {
|
|
1524
|
+
const { runFakeSubprocess } = await import("./fake-subprocess-IV4ML77S.js");
|
|
1525
|
+
return runFakeSubprocess(command, args, options);
|
|
1526
|
+
}
|
|
1527
|
+
return runRealSubprocess(command, args, options);
|
|
1528
|
+
}
|
|
1529
|
+
async function runRealSubprocess(command, args, options = {}) {
|
|
1530
|
+
const timeoutMs = options.timeoutMs ?? DEFAULT_SUBPROCESS_TIMEOUT_MS;
|
|
1531
|
+
return await new Promise((resolve3) => {
|
|
1532
|
+
let settled = false;
|
|
1533
|
+
let timedOut = false;
|
|
1534
|
+
let timeoutHandle;
|
|
1535
|
+
let hardKillHandle;
|
|
1536
|
+
let drainHandle;
|
|
1537
|
+
const stdout = createBoundedCapture();
|
|
1538
|
+
const stderr = createBoundedCapture();
|
|
1539
|
+
const clearTimers = () => {
|
|
1540
|
+
clearTimeout(timeoutHandle);
|
|
1541
|
+
clearTimeout(hardKillHandle);
|
|
1542
|
+
clearTimeout(drainHandle);
|
|
1543
|
+
};
|
|
1544
|
+
const finalize = (result) => {
|
|
1545
|
+
if (settled) {
|
|
1546
|
+
return;
|
|
1547
|
+
}
|
|
1548
|
+
settled = true;
|
|
1549
|
+
clearTimers();
|
|
1550
|
+
resolve3(result);
|
|
1551
|
+
};
|
|
1552
|
+
const resultForExit = (code, signal) => {
|
|
1553
|
+
if (timedOut) {
|
|
1554
|
+
const timeoutMessage = `Command timed out after ${timeoutMs}ms.`;
|
|
1555
|
+
return {
|
|
1556
|
+
code: TIMEOUT_EXIT_CODE,
|
|
1557
|
+
stdout: stdout.text,
|
|
1558
|
+
stderr: stderr.text ? `${stderr.text}
|
|
1559
|
+
${timeoutMessage}` : timeoutMessage
|
|
1560
|
+
};
|
|
1561
|
+
}
|
|
1562
|
+
if (signal) {
|
|
1563
|
+
return {
|
|
1564
|
+
code: 1,
|
|
1565
|
+
stdout: stdout.text,
|
|
1566
|
+
stderr: `${stderr.text}Process terminated by signal: ${signal}`
|
|
1567
|
+
};
|
|
1568
|
+
}
|
|
1569
|
+
return { code: code ?? 1, stdout: stdout.text, stderr: stderr.text };
|
|
1570
|
+
};
|
|
1571
|
+
const finalizeAfterDrainGrace = (code, signal) => {
|
|
1572
|
+
clearTimeout(drainHandle);
|
|
1573
|
+
drainHandle = setTimeout(() => {
|
|
1574
|
+
childProcess.stdout.destroy();
|
|
1575
|
+
childProcess.stderr.destroy();
|
|
1576
|
+
finalize(resultForExit(code, signal));
|
|
1577
|
+
}, STREAM_DRAIN_GRACE_MS);
|
|
1578
|
+
};
|
|
1579
|
+
const environment = {
|
|
1580
|
+
...agemonSubprocessEnvironment(),
|
|
1581
|
+
...options.env
|
|
1582
|
+
};
|
|
1583
|
+
for (const name of options.unsetEnv ?? []) {
|
|
1584
|
+
delete environment[name];
|
|
1585
|
+
}
|
|
1586
|
+
const childProcess = spawn(command, args, {
|
|
1587
|
+
cwd: options.cwd ?? process.cwd(),
|
|
1588
|
+
env: environment,
|
|
1589
|
+
stdio: ["ignore", "pipe", "pipe"]
|
|
1590
|
+
});
|
|
1591
|
+
if (timeoutMs > 0) {
|
|
1592
|
+
timeoutHandle = setTimeout(() => {
|
|
1593
|
+
timedOut = true;
|
|
1594
|
+
const signal = options.killSignal ?? "SIGTERM";
|
|
1595
|
+
const terminated = childProcess.kill(signal);
|
|
1596
|
+
if (!terminated) {
|
|
1597
|
+
finalize({
|
|
1598
|
+
code: TIMEOUT_EXIT_CODE,
|
|
1599
|
+
stdout: stdout.text,
|
|
1600
|
+
stderr: `${stderr.text}
|
|
1601
|
+
Command timed out after ${timeoutMs}ms and could not be signaled with ${signal}.`
|
|
1602
|
+
});
|
|
1603
|
+
return;
|
|
1604
|
+
}
|
|
1605
|
+
hardKillHandle = setTimeout(() => {
|
|
1606
|
+
childProcess.kill("SIGKILL");
|
|
1607
|
+
finalizeAfterDrainGrace(null, "SIGKILL");
|
|
1608
|
+
}, HARD_KILL_GRACE_MS);
|
|
1609
|
+
}, timeoutMs);
|
|
1610
|
+
}
|
|
1611
|
+
childProcess.stdout.setEncoding("utf8");
|
|
1612
|
+
childProcess.stderr.setEncoding("utf8");
|
|
1613
|
+
childProcess.stdout.on("data", (chunk) => stdout.append(chunk));
|
|
1614
|
+
childProcess.stderr.on("data", (chunk) => stderr.append(chunk));
|
|
1615
|
+
childProcess.on("error", (error) => {
|
|
1616
|
+
const errorMessage = stderr.text ? `${stderr.text}
|
|
1617
|
+
${SPAWN_ERROR_PREFIX}${error.message}` : `${SPAWN_ERROR_PREFIX}${error.message}`;
|
|
1618
|
+
finalize({ code: 1, stdout: stdout.text, stderr: errorMessage });
|
|
1619
|
+
});
|
|
1620
|
+
childProcess.on(
|
|
1621
|
+
"exit",
|
|
1622
|
+
(code, signal) => {
|
|
1623
|
+
clearTimeout(timeoutHandle);
|
|
1624
|
+
clearTimeout(hardKillHandle);
|
|
1625
|
+
finalizeAfterDrainGrace(code, signal);
|
|
1626
|
+
}
|
|
1627
|
+
);
|
|
1628
|
+
childProcess.on(
|
|
1629
|
+
"close",
|
|
1630
|
+
(code, signal) => {
|
|
1631
|
+
finalize(resultForExit(code, signal));
|
|
1632
|
+
}
|
|
1633
|
+
);
|
|
1634
|
+
});
|
|
1635
|
+
}
|
|
1636
|
+
|
|
1637
|
+
// src/plugins/skills/skills-cli.ts
|
|
1638
|
+
var TOOLS_LOCK_HASH_LENGTH = 16;
|
|
1639
|
+
var TOOLS_INSTALL_TIMEOUT_MS = 18e4;
|
|
1640
|
+
var TOOLS_LOCK_WAIT_MS = 10 * 6e4;
|
|
1641
|
+
var TOOLS_MANIFEST_FILES = ["package.json", "package-lock.json"];
|
|
1642
|
+
var SKILLS_CLI_PATH_IN_TOOLS_DIR = join5(
|
|
1643
|
+
"node_modules",
|
|
1644
|
+
"skills",
|
|
1645
|
+
"bin",
|
|
1646
|
+
"cli.mjs"
|
|
1647
|
+
);
|
|
1648
|
+
function isLostInstallRace(error) {
|
|
1649
|
+
const code = error.code;
|
|
1650
|
+
return code === "EEXIST" || code === "ENOTEMPTY";
|
|
1651
|
+
}
|
|
1652
|
+
async function renameUnlessAnotherRunWon(stagingDirectory, toolsDirectory) {
|
|
1653
|
+
try {
|
|
1654
|
+
await rename2(stagingDirectory, toolsDirectory);
|
|
1655
|
+
return true;
|
|
1656
|
+
} catch (error) {
|
|
1657
|
+
if (isLostInstallRace(error)) {
|
|
1658
|
+
return false;
|
|
1659
|
+
}
|
|
1660
|
+
throw error;
|
|
1661
|
+
}
|
|
1662
|
+
}
|
|
1663
|
+
async function promoteStagingDirectory(stagingDirectory, toolsDirectory, cliPath) {
|
|
1664
|
+
if (await renameUnlessAnotherRunWon(stagingDirectory, toolsDirectory)) {
|
|
1665
|
+
return;
|
|
1666
|
+
}
|
|
1667
|
+
if (existsSync2(cliPath)) {
|
|
1668
|
+
return;
|
|
1669
|
+
}
|
|
1670
|
+
const brokenDirectory = `${stagingDirectory}.broken`;
|
|
1671
|
+
await rename2(toolsDirectory, brokenDirectory);
|
|
1672
|
+
await rm3(brokenDirectory, { recursive: true, force: true });
|
|
1673
|
+
await renameUnlessAnotherRunWon(stagingDirectory, toolsDirectory);
|
|
1674
|
+
}
|
|
1675
|
+
var ABANDONED_TOOLS_DIRECTORY_PATTERN = /^[0-9a-f]{16}\.tmp-[^/]+$/;
|
|
1676
|
+
async function removeAbandonedToolsDirectories(toolsRoot) {
|
|
1677
|
+
for (const entry of await readdir(toolsRoot)) {
|
|
1678
|
+
if (ABANDONED_TOOLS_DIRECTORY_PATTERN.test(entry)) {
|
|
1679
|
+
await rm3(join5(toolsRoot, entry), { recursive: true, force: true });
|
|
1680
|
+
}
|
|
1681
|
+
}
|
|
1682
|
+
}
|
|
1683
|
+
async function installToolsDirectory(ctx, toolsRoot, manifestDirectory, toolsDirectory, cliPath) {
|
|
1684
|
+
await removeAbandonedToolsDirectories(toolsRoot);
|
|
1685
|
+
const stagingDirectory = await mkdtemp(`${toolsDirectory}.tmp-`);
|
|
1686
|
+
try {
|
|
1687
|
+
for (const fileName of TOOLS_MANIFEST_FILES) {
|
|
1688
|
+
await copyFile(
|
|
1689
|
+
join5(manifestDirectory, fileName),
|
|
1690
|
+
join5(stagingDirectory, fileName)
|
|
1691
|
+
);
|
|
1692
|
+
}
|
|
1693
|
+
const installResult = await ctx.run(
|
|
1694
|
+
"npm",
|
|
1695
|
+
[
|
|
1696
|
+
"ci",
|
|
1697
|
+
"--ignore-scripts",
|
|
1698
|
+
"--no-audit",
|
|
1699
|
+
"--no-fund",
|
|
1700
|
+
"--prefix",
|
|
1701
|
+
stagingDirectory
|
|
1702
|
+
],
|
|
1703
|
+
{ cwd: stagingDirectory, timeoutMs: TOOLS_INSTALL_TIMEOUT_MS }
|
|
1704
|
+
);
|
|
1705
|
+
if (installResult.code !== 0) {
|
|
1706
|
+
throw new Error(
|
|
1707
|
+
`Installing the pinned skills CLI into ${toolsRoot} failed: ${describeSubprocessFailure(installResult, "npm ci")}`
|
|
1708
|
+
);
|
|
1709
|
+
}
|
|
1710
|
+
await promoteStagingDirectory(stagingDirectory, toolsDirectory, cliPath);
|
|
1711
|
+
} finally {
|
|
1712
|
+
await rm3(stagingDirectory, { recursive: true, force: true });
|
|
1713
|
+
}
|
|
1714
|
+
}
|
|
1715
|
+
async function ensureSkillsCli(ctx) {
|
|
1716
|
+
const manifestDirectory = resolveToolsManifestDirectory();
|
|
1717
|
+
const lockHash = sha256Hex(
|
|
1718
|
+
await readFile3(join5(manifestDirectory, "package-lock.json"))
|
|
1719
|
+
).slice(0, TOOLS_LOCK_HASH_LENGTH);
|
|
1720
|
+
const toolsRoot = await prepareToolsRoot();
|
|
1721
|
+
const toolsDirectory = join5(toolsRoot, lockHash);
|
|
1722
|
+
const cliPath = join5(toolsDirectory, SKILLS_CLI_PATH_IN_TOOLS_DIR);
|
|
1723
|
+
if (existsSync2(cliPath)) {
|
|
1724
|
+
return cliPath;
|
|
1725
|
+
}
|
|
1726
|
+
await withAgemonStateLock(
|
|
1727
|
+
"tools.lock",
|
|
1728
|
+
async () => {
|
|
1729
|
+
if (!existsSync2(cliPath)) {
|
|
1730
|
+
await installToolsDirectory(
|
|
1731
|
+
ctx,
|
|
1732
|
+
toolsRoot,
|
|
1733
|
+
manifestDirectory,
|
|
1734
|
+
toolsDirectory,
|
|
1735
|
+
cliPath
|
|
1736
|
+
);
|
|
1737
|
+
}
|
|
1738
|
+
},
|
|
1739
|
+
TOOLS_LOCK_WAIT_MS
|
|
1740
|
+
);
|
|
1741
|
+
return cliPath;
|
|
1742
|
+
}
|
|
1743
|
+
async function runSkillsCli(ctx, args, timeoutMs) {
|
|
1744
|
+
const cliPath = await ensureSkillsCli(ctx);
|
|
1745
|
+
return ctx.run(process.execPath, [cliPath, ...args], {
|
|
1746
|
+
cwd: ctx.cwd,
|
|
1747
|
+
timeoutMs
|
|
1748
|
+
});
|
|
1749
|
+
}
|
|
1750
|
+
|
|
1751
|
+
// src/core/config.ts
|
|
1752
|
+
import { join as join7, relative as relative2 } from "path";
|
|
1753
|
+
import { isDeepStrictEqual } from "util";
|
|
1754
|
+
|
|
1755
|
+
// node_modules/smol-toml/dist/error.js
|
|
1756
|
+
function getLineColFromPtr(string, ptr) {
|
|
1757
|
+
let lines = string.slice(0, ptr).split(/\r?\n/);
|
|
1758
|
+
return [lines.length, lines.pop().length + 1];
|
|
1759
|
+
}
|
|
1760
|
+
function makeCodeBlock(string, line, column) {
|
|
1761
|
+
let lines = string.split(/\r?\n/);
|
|
1762
|
+
let codeblock = "";
|
|
1763
|
+
let numberLen = (Math.log10(line + 1) | 0) + 1;
|
|
1764
|
+
for (let i = line - 1; i <= line + 1; i++) {
|
|
1765
|
+
let l = lines[i - 1];
|
|
1766
|
+
if (!l)
|
|
1767
|
+
continue;
|
|
1768
|
+
codeblock += i.toString().padEnd(numberLen, " ");
|
|
1769
|
+
codeblock += ": ";
|
|
1770
|
+
codeblock += l;
|
|
1771
|
+
codeblock += "\n";
|
|
1772
|
+
if (i === line) {
|
|
1773
|
+
codeblock += " ".repeat(numberLen + column + 2);
|
|
1774
|
+
codeblock += "^\n";
|
|
1775
|
+
}
|
|
1776
|
+
}
|
|
1777
|
+
return codeblock;
|
|
1778
|
+
}
|
|
1779
|
+
var TomlError = class _TomlError extends Error {
|
|
1780
|
+
line;
|
|
1781
|
+
column;
|
|
1782
|
+
codeblock;
|
|
1783
|
+
constructor(message, options) {
|
|
1784
|
+
const [line, column] = getLineColFromPtr(options.toml, options.ptr);
|
|
1785
|
+
const codeblock = makeCodeBlock(options.toml, line, column);
|
|
1786
|
+
super(`Invalid TOML document: ${message}
|
|
1787
|
+
|
|
1788
|
+
${codeblock}`, options);
|
|
1789
|
+
this.line = line;
|
|
1790
|
+
this.column = column;
|
|
1791
|
+
this.codeblock = codeblock;
|
|
1792
|
+
}
|
|
1793
|
+
/** @internal */
|
|
1794
|
+
static x(message, ctx, ptr) {
|
|
1795
|
+
throw new _TomlError(message, { toml: ctx.s, ptr: ptr ?? ctx.p });
|
|
1796
|
+
}
|
|
1797
|
+
};
|
|
1798
|
+
|
|
1799
|
+
// node_modules/smol-toml/dist/primitive.js
|
|
1800
|
+
function parseString(ctx) {
|
|
1801
|
+
let startPtr = ctx.p;
|
|
1802
|
+
let c = ctx.s.charCodeAt(ctx.p++);
|
|
1803
|
+
let first = c;
|
|
1804
|
+
let isLiteral = c === 39;
|
|
1805
|
+
let isMultiline = c === ctx.s.charCodeAt(ctx.p) && c === ctx.s.charCodeAt(ctx.p + 1);
|
|
1806
|
+
if (isMultiline) {
|
|
1807
|
+
if ((c = ctx.s.charCodeAt(ctx.p += 2)) === 10)
|
|
1808
|
+
ctx.p++;
|
|
1809
|
+
else if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)
|
|
1810
|
+
ctx.p += 2;
|
|
1811
|
+
}
|
|
1812
|
+
let parsed = "";
|
|
1813
|
+
let sliceStart = ctx.p;
|
|
1814
|
+
let state = 0;
|
|
1815
|
+
for (; ctx.p < ctx.s.length; ctx.p++) {
|
|
1816
|
+
c = ctx.s.charCodeAt(ctx.p);
|
|
1817
|
+
if (isMultiline && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)) {
|
|
1818
|
+
state = state && 3;
|
|
1819
|
+
} else if (c < 32 && c !== 9 || c === 127) {
|
|
1820
|
+
TomlError.x("control characters are not allowed in strings", ctx);
|
|
1821
|
+
} else if ((!state || state === 3) && c === first && (!isMultiline || ctx.s.charCodeAt(ctx.p + 1) === first && ctx.s.charCodeAt(ctx.p + 2) === first)) {
|
|
1822
|
+
if (isMultiline) {
|
|
1823
|
+
if (ctx.s.charCodeAt(ctx.p + 3) === first)
|
|
1824
|
+
ctx.p++;
|
|
1825
|
+
if (ctx.s.charCodeAt(ctx.p + 3) === first)
|
|
1826
|
+
ctx.p++;
|
|
1827
|
+
}
|
|
1828
|
+
if (!state) {
|
|
1829
|
+
let s = ctx.s.slice(sliceStart, ctx.p);
|
|
1830
|
+
parsed = parsed ? parsed + s : s;
|
|
1831
|
+
}
|
|
1832
|
+
ctx.p += isMultiline ? 3 : 1;
|
|
1833
|
+
return parsed;
|
|
1834
|
+
} else if (!state) {
|
|
1835
|
+
if (!isLiteral && c === 92) {
|
|
1836
|
+
parsed += ctx.s.slice(sliceStart, sliceStart = ctx.p);
|
|
1837
|
+
state = 1;
|
|
1838
|
+
}
|
|
1839
|
+
} else if (state === 1) {
|
|
1840
|
+
if (c === 120 || c === 117 || c === 85) {
|
|
1841
|
+
let errPtr = ctx.p++ - 1;
|
|
1842
|
+
let value = 0;
|
|
1843
|
+
let len = c === 120 ? 2 : c === 117 ? 4 : 8;
|
|
1844
|
+
for (let j = 0; j < len; j++, ctx.p++) {
|
|
1845
|
+
let hex = ctx.s.charCodeAt(ctx.p);
|
|
1846
|
+
let digit = (
|
|
1847
|
+
/* 0-9 */
|
|
1848
|
+
hex >= 48 && hex <= 57 ? hex - 48 : (
|
|
1849
|
+
/* A-F */
|
|
1850
|
+
hex >= 65 && hex <= 70 ? hex - 65 + 10 : (
|
|
1851
|
+
/* a-f */
|
|
1852
|
+
hex >= 97 && hex <= 102 ? hex - 97 + 10 : -1
|
|
1853
|
+
)
|
|
1854
|
+
)
|
|
1855
|
+
);
|
|
1856
|
+
if (digit < 0)
|
|
1857
|
+
TomlError.x("invalid non-hex character in unicode escape", ctx);
|
|
1858
|
+
value = value << 4 | digit;
|
|
1859
|
+
}
|
|
1860
|
+
if (value < 0 || value > 1114111 || value >= 55296 && value <= 57343) {
|
|
1861
|
+
TomlError.x("invalid unicode escape", ctx, errPtr);
|
|
1862
|
+
}
|
|
1863
|
+
parsed += String.fromCodePoint(value);
|
|
1864
|
+
sliceStart = ctx.p--;
|
|
1865
|
+
state = 0;
|
|
1866
|
+
} else if (isMultiline && (c === 32 || c === 9)) {
|
|
1867
|
+
state = 2;
|
|
1868
|
+
} else {
|
|
1869
|
+
if (c === 98)
|
|
1870
|
+
parsed += "\b";
|
|
1871
|
+
else if (c === 116)
|
|
1872
|
+
parsed += " ";
|
|
1873
|
+
else if (c === 110)
|
|
1874
|
+
parsed += "\n";
|
|
1875
|
+
else if (c === 102)
|
|
1876
|
+
parsed += "\f";
|
|
1877
|
+
else if (c === 114)
|
|
1878
|
+
parsed += "\r";
|
|
1879
|
+
else if (c === 101)
|
|
1880
|
+
parsed += "\x1B";
|
|
1881
|
+
else if (c === 34)
|
|
1882
|
+
parsed += '"';
|
|
1883
|
+
else if (c === 92)
|
|
1884
|
+
parsed += "\\";
|
|
1885
|
+
else
|
|
1886
|
+
TomlError.x("unrecognised escape sequence", ctx);
|
|
1887
|
+
sliceStart = ctx.p + 1;
|
|
1888
|
+
state = 0;
|
|
1889
|
+
}
|
|
1890
|
+
} else if (c !== 32 && c !== 9) {
|
|
1891
|
+
if (state === 2)
|
|
1892
|
+
TomlError.x("invalid escape: only line-ending whitespace may be escaped", ctx, sliceStart);
|
|
1893
|
+
state = !isLiteral && c === 92 ? 1 : 0;
|
|
1894
|
+
sliceStart = ctx.p;
|
|
1895
|
+
}
|
|
1896
|
+
}
|
|
1897
|
+
TomlError.x("unfinished string", ctx, startPtr);
|
|
1898
|
+
}
|
|
1899
|
+
|
|
1900
|
+
// node_modules/smol-toml/dist/date.js
|
|
1901
|
+
var DATE_TIME_RE = /^(\d{4}-\d{2}-\d{2})?[Tt ]?(?:(\d{2}):\d{2}(?::\d{2}(?:\.\d+)?)?)?(Z|z|[-+]\d{2}:\d{2})?$/i;
|
|
1902
|
+
var TomlDate = class _TomlDate extends Date {
|
|
1903
|
+
#hasDate = false;
|
|
1904
|
+
#hasTime = false;
|
|
1905
|
+
#offset = null;
|
|
1906
|
+
constructor(date, fasttype, unsafeDelim) {
|
|
1907
|
+
let hasDate = true;
|
|
1908
|
+
let hasTime = true;
|
|
1909
|
+
let offset = "Z";
|
|
1910
|
+
let c;
|
|
1911
|
+
if (typeof date === "string") {
|
|
1912
|
+
if (fasttype)
|
|
1913
|
+
prep: {
|
|
1914
|
+
if (fasttype < 3) {
|
|
1915
|
+
if (+date.slice(11, 13) > 23) {
|
|
1916
|
+
date = "";
|
|
1917
|
+
break prep;
|
|
1918
|
+
}
|
|
1919
|
+
if (fasttype === 2) {
|
|
1920
|
+
offset = null;
|
|
1921
|
+
date += "Z";
|
|
1922
|
+
} else if ((c = date.charCodeAt(date.length - 1)) !== 90 && c !== 122) {
|
|
1923
|
+
offset = date.slice(date.length - 6);
|
|
1924
|
+
}
|
|
1925
|
+
if (unsafeDelim)
|
|
1926
|
+
date = date.slice(0, 10) + "T" + date.slice(11);
|
|
1927
|
+
} else if (fasttype === 4) {
|
|
1928
|
+
date = +date.slice(0, 2) > 23 ? "" : `0000-01-01T${date}Z`;
|
|
1929
|
+
}
|
|
1930
|
+
hasDate = fasttype !== 4;
|
|
1931
|
+
hasTime = fasttype !== 3;
|
|
1932
|
+
}
|
|
1933
|
+
else {
|
|
1934
|
+
let match = date.match(DATE_TIME_RE);
|
|
1935
|
+
if (match) {
|
|
1936
|
+
if (!match[1]) {
|
|
1937
|
+
hasDate = false;
|
|
1938
|
+
date = `0000-01-01T${date}`;
|
|
1939
|
+
}
|
|
1940
|
+
hasTime = !!match[2];
|
|
1941
|
+
hasTime && date[10] === " " && (date = date.replace(" ", "T"));
|
|
1942
|
+
if (match[2] && +match[2] > 23) {
|
|
1943
|
+
date = "";
|
|
1944
|
+
} else {
|
|
1945
|
+
offset = match[3] || null;
|
|
1946
|
+
if (!offset && hasTime)
|
|
1947
|
+
date += "Z";
|
|
1948
|
+
}
|
|
1949
|
+
} else {
|
|
1950
|
+
date = "";
|
|
1951
|
+
}
|
|
1952
|
+
}
|
|
1953
|
+
}
|
|
1954
|
+
super(date);
|
|
1955
|
+
if (!isNaN(this.getTime())) {
|
|
1956
|
+
this.#hasDate = hasDate;
|
|
1957
|
+
this.#hasTime = hasTime;
|
|
1958
|
+
this.#offset = offset;
|
|
1959
|
+
}
|
|
1960
|
+
}
|
|
1961
|
+
isDateTime() {
|
|
1962
|
+
return this.#hasDate && this.#hasTime;
|
|
1963
|
+
}
|
|
1964
|
+
isLocal() {
|
|
1965
|
+
return !this.#hasDate || !this.#hasTime || !this.#offset;
|
|
1966
|
+
}
|
|
1967
|
+
isDate() {
|
|
1968
|
+
return this.#hasDate && !this.#hasTime;
|
|
1969
|
+
}
|
|
1970
|
+
isTime() {
|
|
1971
|
+
return this.#hasTime && !this.#hasDate;
|
|
1972
|
+
}
|
|
1973
|
+
isValid() {
|
|
1974
|
+
return this.#hasDate || this.#hasTime;
|
|
1975
|
+
}
|
|
1976
|
+
toISOString() {
|
|
1977
|
+
let iso = super.toISOString();
|
|
1978
|
+
if (this.isDate())
|
|
1979
|
+
return iso.slice(0, 10);
|
|
1980
|
+
if (this.isTime())
|
|
1981
|
+
return iso.slice(11, 23);
|
|
1982
|
+
if (this.#offset === null)
|
|
1983
|
+
return iso.slice(0, -1);
|
|
1984
|
+
if (this.#offset === "Z" || this.#offset === "z")
|
|
1985
|
+
return iso;
|
|
1986
|
+
let offset = +this.#offset.slice(1, 3) * 60 + +this.#offset.slice(4, 6);
|
|
1987
|
+
offset = this.#offset[0] === "-" ? offset : -offset;
|
|
1988
|
+
let offsetDate = new Date(this.getTime() - offset * 6e4);
|
|
1989
|
+
return offsetDate.toISOString().slice(0, -1) + this.#offset;
|
|
1990
|
+
}
|
|
1991
|
+
static wrapAsOffsetDateTime(jsDate, offset = "Z") {
|
|
1992
|
+
let date = new _TomlDate(jsDate);
|
|
1993
|
+
date.#offset = offset;
|
|
1994
|
+
return date;
|
|
1995
|
+
}
|
|
1996
|
+
static wrapAsLocalDateTime(jsDate) {
|
|
1997
|
+
let date = new _TomlDate(jsDate);
|
|
1998
|
+
date.#offset = null;
|
|
1999
|
+
return date;
|
|
2000
|
+
}
|
|
2001
|
+
static wrapAsLocalDate(jsDate) {
|
|
2002
|
+
let date = new _TomlDate(jsDate);
|
|
2003
|
+
date.#hasTime = false;
|
|
2004
|
+
date.#offset = null;
|
|
2005
|
+
return date;
|
|
2006
|
+
}
|
|
2007
|
+
static wrapAsLocalTime(jsDate) {
|
|
2008
|
+
let date = new _TomlDate(jsDate);
|
|
2009
|
+
date.#hasDate = false;
|
|
2010
|
+
date.#offset = null;
|
|
2011
|
+
return date;
|
|
2012
|
+
}
|
|
2013
|
+
};
|
|
2014
|
+
|
|
2015
|
+
// node_modules/smol-toml/dist/extract.js
|
|
2016
|
+
function isDigit(char, base = 10) {
|
|
2017
|
+
return base === 16 ? char > 47 && char < 58 || char > 64 && char < 71 || char > 96 && char < 103 : char > 47 && char < 48 + base;
|
|
2018
|
+
}
|
|
2019
|
+
function isEndOfValue(char, delim) {
|
|
2020
|
+
return char === 32 || char === 9 || char === 10 || char === 13 || // Structure end or next value delimiter
|
|
2021
|
+
delim && (char === delim || char === 44) || // Comment
|
|
2022
|
+
char === 35;
|
|
2023
|
+
}
|
|
2024
|
+
function extractValue(ctx, end) {
|
|
2025
|
+
let errPtr = ctx.p;
|
|
2026
|
+
let c = ctx.s.charCodeAt(ctx.p);
|
|
2027
|
+
if (c === 91 || c === 123) {
|
|
2028
|
+
ctx.d-- || TomlError.x("document contains excessively nested structures. aborting.", ctx);
|
|
2029
|
+
let value = c === 91 ? parseArray(ctx) : parseInlineTable(ctx);
|
|
2030
|
+
ctx.d++;
|
|
2031
|
+
return value;
|
|
2032
|
+
}
|
|
2033
|
+
if (c === 34 || c === 39) {
|
|
2034
|
+
return parseString(ctx);
|
|
2035
|
+
}
|
|
2036
|
+
if (c === 116) {
|
|
2037
|
+
if (ctx.s.charCodeAt(++ctx.p) !== 114 || ctx.s.charCodeAt(++ctx.p) !== 117 || ctx.s.charCodeAt(++ctx.p) !== 101)
|
|
2038
|
+
TomlError.x("invalid value", ctx, errPtr);
|
|
2039
|
+
return ctx.p++, true;
|
|
2040
|
+
}
|
|
2041
|
+
if (c === 102) {
|
|
2042
|
+
if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 108 || ctx.s.charCodeAt(++ctx.p) !== 115 || ctx.s.charCodeAt(++ctx.p) !== 101)
|
|
2043
|
+
TomlError.x("invalid value", ctx, errPtr);
|
|
2044
|
+
return ctx.p++, false;
|
|
2045
|
+
}
|
|
2046
|
+
if (c === 43 || c === 45) {
|
|
2047
|
+
return parseNumber(ctx, ctx.p, ctx.s.charCodeAt(++ctx.p), 44 - c, end);
|
|
2048
|
+
}
|
|
2049
|
+
if (ctx.s.charCodeAt(ctx.p + 4) === 45 && ctx.s.charCodeAt(ctx.p + 7) === 45) {
|
|
2050
|
+
return parseDate(ctx, c, end);
|
|
2051
|
+
}
|
|
2052
|
+
if (ctx.s.charCodeAt(ctx.p + 2) === 58) {
|
|
2053
|
+
return parseTime(ctx, c, end);
|
|
2054
|
+
}
|
|
2055
|
+
return parseNumber(ctx, ctx.p, c, 0, end);
|
|
2056
|
+
}
|
|
2057
|
+
function parseNumber(ctx, startPtr, startChr, sign, endChr) {
|
|
2058
|
+
let c = startChr;
|
|
2059
|
+
let state = 0;
|
|
2060
|
+
let hasUnderscores = false;
|
|
2061
|
+
if (c === 105) {
|
|
2062
|
+
if (ctx.s.charCodeAt(++ctx.p) !== 110 || ctx.s.charCodeAt(++ctx.p) !== 102)
|
|
2063
|
+
TomlError.x("invalid value", ctx, startPtr);
|
|
2064
|
+
return ctx.p++, (sign || 1) / 0;
|
|
2065
|
+
}
|
|
2066
|
+
if (c === 110) {
|
|
2067
|
+
if (ctx.s.charCodeAt(++ctx.p) !== 97 || ctx.s.charCodeAt(++ctx.p) !== 110)
|
|
2068
|
+
TomlError.x("invalid value", ctx, startPtr);
|
|
2069
|
+
return ctx.p++, NaN;
|
|
2070
|
+
}
|
|
2071
|
+
if (c === 48) {
|
|
2072
|
+
if (++ctx.p >= ctx.s.length || isEndOfValue(c = ctx.s.charCodeAt(ctx.p), endChr))
|
|
2073
|
+
return ctx.bi === true ? 0n : 0;
|
|
2074
|
+
if (!sign) {
|
|
2075
|
+
if (c === 120)
|
|
2076
|
+
return parseIntegerBaseN(ctx, startPtr, 16, endChr);
|
|
2077
|
+
else if (c === 98)
|
|
2078
|
+
return parseIntegerBaseN(ctx, startPtr, 2, endChr);
|
|
2079
|
+
else if (c === 111)
|
|
2080
|
+
return parseIntegerBaseN(ctx, startPtr, 8, endChr);
|
|
2081
|
+
}
|
|
2082
|
+
if (c === 46)
|
|
2083
|
+
state = 2;
|
|
2084
|
+
else if (c === 101 || c === 69)
|
|
2085
|
+
state = 4;
|
|
2086
|
+
else
|
|
2087
|
+
TomlError.x("illegal leading zero", ctx, startPtr);
|
|
2088
|
+
} else if (!isDigit(c))
|
|
2089
|
+
TomlError.x("invalid value", ctx, startPtr);
|
|
2090
|
+
while (++ctx.p < ctx.s.length && (c = ctx.s.charCodeAt(ctx.p), !isEndOfValue(c, endChr))) {
|
|
2091
|
+
if (!state)
|
|
2092
|
+
state = 1;
|
|
2093
|
+
if (c === 95) {
|
|
2094
|
+
if (!(state & 1))
|
|
2095
|
+
TomlError.x("illegal underscore", ctx);
|
|
2096
|
+
state += 11;
|
|
2097
|
+
hasUnderscores = true;
|
|
2098
|
+
} else if (state === 1 && c === 46)
|
|
2099
|
+
state = 2;
|
|
2100
|
+
else if ((state === 1 || state === 3) && (c === 101 || c === 69))
|
|
2101
|
+
state = 4;
|
|
2102
|
+
else if (state === 4 && (c === 43 || c === 45)) {
|
|
2103
|
+
state = 6;
|
|
2104
|
+
} else if (!isDigit(c))
|
|
2105
|
+
TomlError.x(`illegal character in numeric literal`, ctx);
|
|
2106
|
+
else if (state > 9)
|
|
2107
|
+
state -= 11;
|
|
2108
|
+
else if (!(state & 1))
|
|
2109
|
+
state++;
|
|
2110
|
+
}
|
|
2111
|
+
if (!state) {
|
|
2112
|
+
let val = (startChr - 48) * (sign || 1);
|
|
2113
|
+
return ctx.bi === true ? BigInt(val) : val;
|
|
2114
|
+
}
|
|
2115
|
+
if (!(state & 1))
|
|
2116
|
+
TomlError.x("unfinished numeric value", ctx, startPtr);
|
|
2117
|
+
let str = ctx.s.slice(startPtr, ctx.p);
|
|
2118
|
+
if (hasUnderscores)
|
|
2119
|
+
str = str.replaceAll("_", "");
|
|
2120
|
+
return state > 1 ? parseFloat(str) : parseInteger(ctx, str, 10, startPtr);
|
|
2121
|
+
}
|
|
2122
|
+
function parseIntegerBaseN(ctx, startPtr, base, endChr) {
|
|
2123
|
+
let c, underscore = 1;
|
|
2124
|
+
while (++ctx.p < ctx.s.length && (c = ctx.s.charCodeAt(ctx.p), !isEndOfValue(c, endChr))) {
|
|
2125
|
+
if (c === 95) {
|
|
2126
|
+
if (underscore & 1)
|
|
2127
|
+
TomlError.x("illegal underscore", ctx);
|
|
2128
|
+
underscore = 3;
|
|
2129
|
+
} else if (!isDigit(c, base))
|
|
2130
|
+
TomlError.x(`illegal character in numeric literal`, ctx);
|
|
2131
|
+
else if (underscore & 1)
|
|
2132
|
+
underscore--;
|
|
2133
|
+
}
|
|
2134
|
+
if (underscore & 1)
|
|
2135
|
+
TomlError.x("unfinished numeric value", ctx);
|
|
2136
|
+
let str = ctx.s.slice(startPtr + 2, ctx.p);
|
|
2137
|
+
if (underscore)
|
|
2138
|
+
str = str.replaceAll("_", "");
|
|
2139
|
+
return parseInteger(ctx, str, base, startPtr);
|
|
2140
|
+
}
|
|
2141
|
+
function parseInteger(ctx, str, base, startPtr) {
|
|
2142
|
+
if (ctx.bi !== true)
|
|
2143
|
+
int: {
|
|
2144
|
+
let val = parseInt(str, base);
|
|
2145
|
+
if (!Number.isSafeInteger(val)) {
|
|
2146
|
+
if (ctx.bi)
|
|
2147
|
+
break int;
|
|
2148
|
+
TomlError.x("integer value cannot be represented losslessly", ctx, startPtr);
|
|
2149
|
+
}
|
|
2150
|
+
return val;
|
|
2151
|
+
}
|
|
2152
|
+
return base === 10 ? BigInt(str) : BigInt((base === 2 ? "0b" : base === 8 ? "0o" : "0x") + str);
|
|
2153
|
+
}
|
|
2154
|
+
function parseDate(ctx, c, endChr) {
|
|
2155
|
+
let startPtr = ctx.p++, unsafeSeparator;
|
|
2156
|
+
if (!isDigit(c) || !isDigit(ctx.s.charCodeAt(ctx.p++)) || !isDigit(ctx.s.charCodeAt(ctx.p++)) || !isDigit(ctx.s.charCodeAt(ctx.p++))) {
|
|
2157
|
+
return parseNumber(ctx, ctx.p = startPtr, c, 0, endChr);
|
|
2158
|
+
}
|
|
2159
|
+
ctx.p += 5;
|
|
2160
|
+
if (!isDigit(ctx.s.charCodeAt(ctx.p++)))
|
|
2161
|
+
TomlError.x("invalid date-time: date part is malformed", ctx, startPtr);
|
|
2162
|
+
if (ctx.p >= ctx.s.length || ((c = ctx.s.charCodeAt(ctx.p)) !== 32 || (unsafeSeparator = true, !isDigit(ctx.s.charCodeAt(ctx.p + 1)))) && c !== 84 && c !== 116) {
|
|
2163
|
+
let t2 = ctx.s.slice(startPtr, ctx.p);
|
|
2164
|
+
return readDate(ctx, t2, 3, false, startPtr);
|
|
2165
|
+
}
|
|
2166
|
+
if (ctx.s.charCodeAt(ctx.p += 3) !== 58)
|
|
2167
|
+
TomlError.x("invalid date-time: time part is malformed", ctx, startPtr);
|
|
2168
|
+
if (ctx.s.charCodeAt(ctx.p += 3) === 58)
|
|
2169
|
+
ctx.p += 3;
|
|
2170
|
+
if (ctx.s.charCodeAt(ctx.p) === 46)
|
|
2171
|
+
while (isDigit(ctx.s.charCodeAt(++ctx.p)))
|
|
2172
|
+
;
|
|
2173
|
+
if (c = ctx.s.charCodeAt(ctx.p)) {
|
|
2174
|
+
if (c === 90 || c === 122) {
|
|
2175
|
+
let t2 = ctx.s.slice(startPtr, ++ctx.p);
|
|
2176
|
+
return readDate(ctx, t2, 1, unsafeSeparator, startPtr, "[+00:00]");
|
|
2177
|
+
}
|
|
2178
|
+
if (c === 43 || c === 45) {
|
|
2179
|
+
let t2 = ctx.s.slice(startPtr, ctx.p += 6);
|
|
2180
|
+
return readDate(ctx, t2, 1, unsafeSeparator, startPtr, !ctx.ld && "[" + ctx.s.slice(ctx.p - 6, ctx.p) + "]");
|
|
2181
|
+
}
|
|
2182
|
+
}
|
|
2183
|
+
let t = ctx.s.slice(startPtr, ctx.p);
|
|
2184
|
+
return readDate(ctx, t, 2, unsafeSeparator, startPtr);
|
|
2185
|
+
}
|
|
2186
|
+
function parseTime(ctx, c, endChr) {
|
|
2187
|
+
let start = ctx.p;
|
|
2188
|
+
if (!isDigit(c) || !isDigit(ctx.s.charCodeAt(++ctx.p))) {
|
|
2189
|
+
return parseNumber(ctx, --ctx.p, c, 0, endChr);
|
|
2190
|
+
}
|
|
2191
|
+
if (ctx.s.charCodeAt(ctx.p += 4) === 58)
|
|
2192
|
+
ctx.p += 3;
|
|
2193
|
+
if (ctx.s.charCodeAt(ctx.p) === 46)
|
|
2194
|
+
while (isDigit(ctx.s.charCodeAt(++ctx.p)))
|
|
2195
|
+
;
|
|
2196
|
+
let t = ctx.s.slice(start, ctx.p);
|
|
2197
|
+
return readDate(ctx, t, 4, false, start);
|
|
2198
|
+
}
|
|
2199
|
+
function readDate(ctx, str, type, unsafeDelim, errPtr, temporalSuffix) {
|
|
2200
|
+
if (ctx.ld) {
|
|
2201
|
+
let date = new TomlDate(str, type, unsafeDelim);
|
|
2202
|
+
if (!date.isValid())
|
|
2203
|
+
TomlError.x("invalid date", ctx, errPtr);
|
|
2204
|
+
return date;
|
|
2205
|
+
}
|
|
2206
|
+
try {
|
|
2207
|
+
if (temporalSuffix)
|
|
2208
|
+
str += temporalSuffix;
|
|
2209
|
+
switch (type) {
|
|
2210
|
+
case 1:
|
|
2211
|
+
return Temporal.ZonedDateTime.from(str);
|
|
2212
|
+
case 2:
|
|
2213
|
+
return Temporal.PlainDateTime.from(str);
|
|
2214
|
+
case 3:
|
|
2215
|
+
return Temporal.PlainDate.from(str);
|
|
2216
|
+
case 4:
|
|
2217
|
+
return Temporal.PlainTime.from(str);
|
|
2218
|
+
}
|
|
2219
|
+
} catch (e) {
|
|
2220
|
+
TomlError.x(e instanceof Error ? e.message : "" + e, ctx, errPtr);
|
|
2221
|
+
}
|
|
2222
|
+
}
|
|
2223
|
+
|
|
2224
|
+
// node_modules/smol-toml/dist/util.js
|
|
2225
|
+
function skipComment(ctx) {
|
|
2226
|
+
for (; ctx.p < ctx.s.length; ctx.p++) {
|
|
2227
|
+
let c = ctx.s.charCodeAt(ctx.p);
|
|
2228
|
+
if (c === 10)
|
|
2229
|
+
break;
|
|
2230
|
+
if (c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10) {
|
|
2231
|
+
ctx.p++;
|
|
2232
|
+
break;
|
|
2233
|
+
}
|
|
2234
|
+
if (c < 32 && c !== 9 || c === 127) {
|
|
2235
|
+
TomlError.x("control characters are not allowed in comments", ctx);
|
|
2236
|
+
}
|
|
2237
|
+
}
|
|
2238
|
+
}
|
|
2239
|
+
function skipVoid(ctx, banNewLines, banComments) {
|
|
2240
|
+
let c;
|
|
2241
|
+
while (ctx.p < ctx.s.length) {
|
|
2242
|
+
while (ctx.p < ctx.s.length && ((c = ctx.s.charCodeAt(ctx.p)) === 32 || c === 9 || !banNewLines && (c === 10 || c === 13 && ctx.s.charCodeAt(ctx.p + 1) === 10)))
|
|
2243
|
+
ctx.p++;
|
|
2244
|
+
if (banComments || c !== 35)
|
|
2245
|
+
break;
|
|
2246
|
+
skipComment(ctx);
|
|
2247
|
+
}
|
|
2248
|
+
}
|
|
2249
|
+
|
|
2250
|
+
// node_modules/smol-toml/dist/struct.js
|
|
2251
|
+
function parseKey(ctx, end = 61) {
|
|
2252
|
+
let startPtr;
|
|
2253
|
+
let state = 0;
|
|
2254
|
+
let parsed = [];
|
|
2255
|
+
let sliceStart;
|
|
2256
|
+
let c = ctx.s.charCodeAt(startPtr = ctx.p);
|
|
2257
|
+
do {
|
|
2258
|
+
if (c === end) {
|
|
2259
|
+
if (!state)
|
|
2260
|
+
TomlError.x("unexpected end of key", ctx);
|
|
2261
|
+
if (state === 1)
|
|
2262
|
+
parsed.push(ctx.s.slice(sliceStart, ctx.p));
|
|
2263
|
+
return ctx.p++, parsed;
|
|
2264
|
+
} else if (c === 46) {
|
|
2265
|
+
if (!state)
|
|
2266
|
+
TomlError.x("illegal empty bare key", ctx);
|
|
2267
|
+
if (state === 1)
|
|
2268
|
+
parsed.push(ctx.s.slice(sliceStart, ctx.p));
|
|
2269
|
+
state = 0;
|
|
2270
|
+
} else if (!state && (c === 34 || c === 39)) {
|
|
2271
|
+
if (c === ctx.s.charCodeAt(ctx.p + 1) && c === ctx.s.charCodeAt(ctx.p + 2))
|
|
2272
|
+
TomlError.x("illegal quoted key: multiline strings are not allowed", ctx);
|
|
2273
|
+
parsed.push(parseString(ctx));
|
|
2274
|
+
state = 2;
|
|
2275
|
+
ctx.p--;
|
|
2276
|
+
} else if (c === 32 || c === 9) {
|
|
2277
|
+
if (state === 1) {
|
|
2278
|
+
parsed.push(ctx.s.slice(sliceStart, ctx.p));
|
|
2279
|
+
state = 2;
|
|
2280
|
+
}
|
|
2281
|
+
} else if (state === 2 || c < 48 && c !== 45 || c > 57 && c < 65 || c > 90 && c < 97 && c !== 95 || c > 122) {
|
|
2282
|
+
TomlError.x("illegal character in key", ctx);
|
|
2283
|
+
} else if (!state) {
|
|
2284
|
+
state = 1;
|
|
2285
|
+
sliceStart = ctx.p;
|
|
2286
|
+
}
|
|
2287
|
+
} while (c = ctx.s.charCodeAt(++ctx.p));
|
|
2288
|
+
TomlError.x("incomplete key-value: cannot find end of key", ctx, startPtr);
|
|
2289
|
+
}
|
|
2290
|
+
function parseInlineTable(ctx) {
|
|
2291
|
+
let startPtr = ctx.p++;
|
|
2292
|
+
let res = /* @__PURE__ */ Object.create(null);
|
|
2293
|
+
let seen = /* @__PURE__ */ new Set();
|
|
2294
|
+
let c;
|
|
2295
|
+
while (ctx.p < ctx.s.length) {
|
|
2296
|
+
skipVoid(ctx);
|
|
2297
|
+
if ((c = ctx.s.charCodeAt(ctx.p)) === 125) {
|
|
2298
|
+
ctx.p++;
|
|
2299
|
+
return res;
|
|
2300
|
+
}
|
|
2301
|
+
let k;
|
|
2302
|
+
let t = res;
|
|
2303
|
+
let hasOwn = false;
|
|
2304
|
+
let errPtr = ctx.p;
|
|
2305
|
+
let key = parseKey(ctx);
|
|
2306
|
+
for (let i = 0; i < key.length; i++) {
|
|
2307
|
+
if (i)
|
|
2308
|
+
t = hasOwn ? t[k] : t[k] = /* @__PURE__ */ Object.create(null);
|
|
2309
|
+
k = key[i];
|
|
2310
|
+
if ((hasOwn = Object.hasOwn(t, k)) && (typeof t[k] !== "object" || seen.has(t[k]))) {
|
|
2311
|
+
TomlError.x("trying to redefine an already defined value", ctx, errPtr);
|
|
2312
|
+
}
|
|
2313
|
+
let unsafe = k === "__proto__";
|
|
2314
|
+
if (ctx.uk && (unsafe || k === "constructor")) {
|
|
2315
|
+
t = ctx.uk !== 1 && TomlError.x("document contains an unsafe property", ctx, errPtr);
|
|
2316
|
+
break;
|
|
2317
|
+
}
|
|
2318
|
+
if (!hasOwn && unsafe) {
|
|
2319
|
+
Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
|
|
2320
|
+
}
|
|
2321
|
+
}
|
|
2322
|
+
if (hasOwn) {
|
|
2323
|
+
TomlError.x("trying to redefine an already defined value", ctx, errPtr);
|
|
2324
|
+
}
|
|
2325
|
+
skipVoid(ctx, true, true);
|
|
2326
|
+
let value = extractValue(
|
|
2327
|
+
ctx,
|
|
2328
|
+
125
|
|
2329
|
+
/* } */
|
|
2330
|
+
);
|
|
2331
|
+
if (t && typeof (t[k] = value) === "object")
|
|
2332
|
+
seen.add(value);
|
|
2333
|
+
skipVoid(ctx);
|
|
2334
|
+
if ((c = ctx.s.charCodeAt(ctx.p++)) === 125) {
|
|
2335
|
+
return res;
|
|
2336
|
+
}
|
|
2337
|
+
if (c !== 44)
|
|
2338
|
+
TomlError.x("expected comma or end of structure", ctx, ctx.p - 1);
|
|
2339
|
+
}
|
|
2340
|
+
TomlError.x("unfinished table", ctx, startPtr);
|
|
2341
|
+
}
|
|
2342
|
+
function parseArray(ctx) {
|
|
2343
|
+
let startPtr = ctx.p++;
|
|
2344
|
+
let res = [];
|
|
2345
|
+
let c;
|
|
2346
|
+
while (ctx.p < ctx.s.length) {
|
|
2347
|
+
skipVoid(ctx);
|
|
2348
|
+
if ((c = ctx.s.charCodeAt(ctx.p)) === 93) {
|
|
2349
|
+
ctx.p++;
|
|
2350
|
+
return res;
|
|
2351
|
+
}
|
|
2352
|
+
res.push(extractValue(
|
|
2353
|
+
ctx,
|
|
2354
|
+
93
|
|
2355
|
+
/* ] */
|
|
2356
|
+
));
|
|
2357
|
+
skipVoid(ctx);
|
|
2358
|
+
if ((c = ctx.s.charCodeAt(ctx.p++)) === 93) {
|
|
2359
|
+
return res;
|
|
2360
|
+
}
|
|
2361
|
+
if (c !== 44)
|
|
2362
|
+
TomlError.x("expected comma or end of structure", ctx, ctx.p - 1);
|
|
2363
|
+
}
|
|
2364
|
+
TomlError.x("unfinished array", ctx, startPtr);
|
|
2365
|
+
}
|
|
2366
|
+
|
|
2367
|
+
// node_modules/smol-toml/dist/parse.js
|
|
2368
|
+
function peekTable(ctx, key, table, meta, type) {
|
|
2369
|
+
let t = table;
|
|
2370
|
+
let m = meta;
|
|
2371
|
+
let k;
|
|
2372
|
+
let hasOwn = false;
|
|
2373
|
+
let state;
|
|
2374
|
+
for (let i = 0; i < key.length; i++) {
|
|
2375
|
+
if (i) {
|
|
2376
|
+
t = hasOwn ? t[k] : t[k] = /* @__PURE__ */ Object.create(null);
|
|
2377
|
+
m = (state = m[k]).c;
|
|
2378
|
+
if (type === 0 && (state.t === 1 || state.t === 2)) {
|
|
2379
|
+
return null;
|
|
2380
|
+
}
|
|
2381
|
+
if (state.t === 2) {
|
|
2382
|
+
let l = t.length - 1;
|
|
2383
|
+
t = t[l];
|
|
2384
|
+
m = m[l].c;
|
|
2385
|
+
}
|
|
2386
|
+
}
|
|
2387
|
+
k = key[i];
|
|
2388
|
+
if ((hasOwn = Object.hasOwn(t, k)) && m[k]?.t === 0 && m[k]?.d) {
|
|
2389
|
+
return null;
|
|
2390
|
+
}
|
|
2391
|
+
if (!hasOwn) {
|
|
2392
|
+
let unsafe = k === "__proto__";
|
|
2393
|
+
if (ctx.uk && (unsafe || k === "constructor"))
|
|
2394
|
+
return false;
|
|
2395
|
+
if (unsafe) {
|
|
2396
|
+
Object.defineProperty(t, k, { enumerable: true, configurable: true, writable: true });
|
|
2397
|
+
Object.defineProperty(m, k, { enumerable: true, configurable: true, writable: true });
|
|
2398
|
+
}
|
|
2399
|
+
m[k] = {
|
|
2400
|
+
t: i < key.length - 1 && type === 2 ? 3 : type,
|
|
2401
|
+
d: false,
|
|
2402
|
+
i: 0,
|
|
2403
|
+
c: /* @__PURE__ */ Object.create(null)
|
|
2404
|
+
};
|
|
2405
|
+
}
|
|
2406
|
+
}
|
|
2407
|
+
state = m[k];
|
|
2408
|
+
if (state.t !== type && !(type === 1 && state.t === 3)) {
|
|
2409
|
+
return null;
|
|
2410
|
+
}
|
|
2411
|
+
if (type === 2) {
|
|
2412
|
+
if (!state.d) {
|
|
2413
|
+
state.d = true;
|
|
2414
|
+
t[k] = [];
|
|
2415
|
+
}
|
|
2416
|
+
t[k].push(t = /* @__PURE__ */ Object.create(null));
|
|
2417
|
+
state.c[state.i++] = state = { t: 1, d: false, i: 0, c: /* @__PURE__ */ Object.create(null) };
|
|
2418
|
+
}
|
|
2419
|
+
if (state.d) {
|
|
2420
|
+
return null;
|
|
2421
|
+
}
|
|
2422
|
+
state.d = true;
|
|
2423
|
+
if (type === 1) {
|
|
2424
|
+
t = hasOwn ? t[k] : t[k] = /* @__PURE__ */ Object.create(null);
|
|
2425
|
+
} else if (type === 0 && hasOwn) {
|
|
2426
|
+
return null;
|
|
2427
|
+
}
|
|
2428
|
+
return [k, t, state.c];
|
|
2429
|
+
}
|
|
2430
|
+
function validateTablePeek(ctx, peek, ptr) {
|
|
2431
|
+
if (peek === null || ctx.uk === 2)
|
|
2432
|
+
TomlError.x(peek === null ? "trying to redefine an already defined table or value" : "document contains an unsafe property", ctx, ptr);
|
|
2433
|
+
}
|
|
2434
|
+
function parse(toml, options = {}) {
|
|
2435
|
+
let ctx = {
|
|
2436
|
+
s: toml,
|
|
2437
|
+
p: 0,
|
|
2438
|
+
d: options.maxDepth ?? 1e3,
|
|
2439
|
+
bi: options.integersAsBigInt ?? false,
|
|
2440
|
+
ld: options.useLegacyDate ?? true,
|
|
2441
|
+
uk: options.unsafeKeyBehaviour === "throw" ? 2 : options.unsafeKeyBehaviour === "drop" ? 1 : 0
|
|
2442
|
+
};
|
|
2443
|
+
let res = /* @__PURE__ */ Object.create(null);
|
|
2444
|
+
let meta = /* @__PURE__ */ Object.create(null);
|
|
2445
|
+
let tmp;
|
|
2446
|
+
let skipping = false;
|
|
2447
|
+
let tbl = res;
|
|
2448
|
+
let m = meta;
|
|
2449
|
+
if (toml.charCodeAt(0) === 65279)
|
|
2450
|
+
ctx.p++;
|
|
2451
|
+
skipVoid(ctx);
|
|
2452
|
+
while (ctx.p < toml.length) {
|
|
2453
|
+
if (toml.charCodeAt(ctx.p) === 91) {
|
|
2454
|
+
let isTableArray = toml.charCodeAt(++ctx.p) === 91;
|
|
2455
|
+
tmp = ctx.p += +isTableArray;
|
|
2456
|
+
skipping = false;
|
|
2457
|
+
let k = parseKey(
|
|
2458
|
+
ctx,
|
|
2459
|
+
93
|
|
2460
|
+
/* ] */
|
|
2461
|
+
);
|
|
2462
|
+
if (isTableArray) {
|
|
2463
|
+
if (toml.charCodeAt(ctx.p) !== 93) {
|
|
2464
|
+
TomlError.x("expected end of table array declaration", ctx);
|
|
2465
|
+
}
|
|
2466
|
+
ctx.p++;
|
|
2467
|
+
}
|
|
2468
|
+
let p = peekTable(
|
|
2469
|
+
ctx,
|
|
2470
|
+
k,
|
|
2471
|
+
res,
|
|
2472
|
+
meta,
|
|
2473
|
+
isTableArray ? 2 : 1
|
|
2474
|
+
/* Type.EXPLICIT */
|
|
2475
|
+
);
|
|
2476
|
+
if (!p) {
|
|
2477
|
+
validateTablePeek(ctx, p, tmp);
|
|
2478
|
+
skipping = true;
|
|
2479
|
+
} else {
|
|
2480
|
+
m = p[2];
|
|
2481
|
+
tbl = p[1];
|
|
2482
|
+
}
|
|
2483
|
+
} else {
|
|
2484
|
+
tmp = ctx.p;
|
|
2485
|
+
let k = parseKey(ctx);
|
|
2486
|
+
let p = peekTable(
|
|
2487
|
+
ctx,
|
|
2488
|
+
k,
|
|
2489
|
+
tbl,
|
|
2490
|
+
m,
|
|
2491
|
+
0
|
|
2492
|
+
/* Type.DOTTED */
|
|
2493
|
+
);
|
|
2494
|
+
if (!p && !skipping)
|
|
2495
|
+
validateTablePeek(ctx, p, tmp);
|
|
2496
|
+
skipVoid(ctx, true, true);
|
|
2497
|
+
let v = extractValue(ctx, void 0);
|
|
2498
|
+
if (p && !skipping)
|
|
2499
|
+
p[1][p[0]] = v;
|
|
2500
|
+
}
|
|
2501
|
+
skipVoid(ctx, true);
|
|
2502
|
+
if (ctx.p < toml.length && (tmp = toml.charCodeAt(ctx.p)) !== 10 && (tmp !== 13 || toml.charCodeAt(ctx.p + 1) !== 10)) {
|
|
2503
|
+
TomlError.x("each key-value declaration must be followed by an end-of-line", ctx);
|
|
2504
|
+
}
|
|
2505
|
+
skipVoid(ctx);
|
|
2506
|
+
}
|
|
2507
|
+
return res;
|
|
2508
|
+
}
|
|
2509
|
+
|
|
2510
|
+
// node_modules/smol-toml/dist/stringify.js
|
|
2511
|
+
var BARE_KEY = /^[a-z0-9-_]+$/i;
|
|
2512
|
+
var HAS_WELLFORMED = !!"".isWellFormed;
|
|
2513
|
+
function extendedTypeOf(obj) {
|
|
2514
|
+
let type = typeof obj;
|
|
2515
|
+
if (type === "object") {
|
|
2516
|
+
if (Array.isArray(obj))
|
|
2517
|
+
return "array";
|
|
2518
|
+
if (typeof obj.getUTCDate === "function" && obj instanceof Date)
|
|
2519
|
+
return "date";
|
|
2520
|
+
if (globalThis.Temporal) {
|
|
2521
|
+
if (obj.until) {
|
|
2522
|
+
if (obj instanceof Temporal.ZonedDateTime)
|
|
2523
|
+
return "temporal/tz+uc";
|
|
2524
|
+
if (obj instanceof Temporal.PlainDateTime || obj instanceof Temporal.PlainDate)
|
|
2525
|
+
return "temporal/uc";
|
|
2526
|
+
if (obj instanceof Temporal.PlainTime || obj instanceof Temporal.Instant)
|
|
2527
|
+
return "temporal";
|
|
2528
|
+
if (obj instanceof Temporal.PlainYearMonth)
|
|
2529
|
+
return "temporal/x";
|
|
2530
|
+
} else if (obj.toPlainDate && obj instanceof Temporal.PlainMonthDay || obj.negated && obj instanceof Temporal.Duration) {
|
|
2531
|
+
return "temporal/x";
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
}
|
|
2535
|
+
return type;
|
|
2536
|
+
}
|
|
2537
|
+
function isArrayOfTables(obj) {
|
|
2538
|
+
for (let i = 0; i < obj.length; i++) {
|
|
2539
|
+
if (extendedTypeOf(obj[i]) !== "object")
|
|
2540
|
+
return false;
|
|
2541
|
+
}
|
|
2542
|
+
return obj.length != 0;
|
|
2543
|
+
}
|
|
2544
|
+
function formatWellFormedStringUnchecked(s) {
|
|
2545
|
+
return JSON.stringify(s).replaceAll("\x7F", "\\u007f");
|
|
2546
|
+
}
|
|
2547
|
+
function formatString(s) {
|
|
2548
|
+
return formatWellFormedStringUnchecked(HAS_WELLFORMED ? s.toWellFormed() : s);
|
|
2549
|
+
}
|
|
2550
|
+
function formatKey(s) {
|
|
2551
|
+
if (BARE_KEY.test(s))
|
|
2552
|
+
return s;
|
|
2553
|
+
if (HAS_WELLFORMED && !s.isWellFormed())
|
|
2554
|
+
throw new RangeError("key contains illegal lone surrogates");
|
|
2555
|
+
return formatWellFormedStringUnchecked(s);
|
|
2556
|
+
}
|
|
2557
|
+
function stringifyValue(val, type, depth, numberAsFloat, strictTemporal) {
|
|
2558
|
+
if (depth === 0) {
|
|
2559
|
+
throw new Error("Could not stringify the object: maximum object depth exceeded");
|
|
2560
|
+
}
|
|
2561
|
+
switch (type) {
|
|
2562
|
+
// @ts-expect-error -- intentional fallthrough case
|
|
2563
|
+
case "number":
|
|
2564
|
+
if (isNaN(val))
|
|
2565
|
+
return "nan";
|
|
2566
|
+
if (val === Infinity)
|
|
2567
|
+
return "inf";
|
|
2568
|
+
if (val === -Infinity)
|
|
2569
|
+
return "-inf";
|
|
2570
|
+
if (Number.isInteger(val) && (numberAsFloat || !Number.isSafeInteger(val)))
|
|
2571
|
+
return val.toFixed(1);
|
|
2572
|
+
case "bigint":
|
|
2573
|
+
case "boolean":
|
|
2574
|
+
case "temporal":
|
|
2575
|
+
return val.toString();
|
|
2576
|
+
case "string":
|
|
2577
|
+
return formatString(val);
|
|
2578
|
+
case "date":
|
|
2579
|
+
if (isNaN(val.getTime()))
|
|
2580
|
+
throw new TypeError("cannot serialize invalid date");
|
|
2581
|
+
return val.toISOString();
|
|
2582
|
+
case "object":
|
|
2583
|
+
return stringifyInlineTable(val, depth, numberAsFloat, strictTemporal);
|
|
2584
|
+
case "array":
|
|
2585
|
+
return stringifyArray(val, depth, numberAsFloat, strictTemporal);
|
|
2586
|
+
// @ts-expect-error -- intentional fallthrough case
|
|
2587
|
+
case "temporal/tz+uc":
|
|
2588
|
+
if (strictTemporal) {
|
|
2589
|
+
let tz = val.timeZoneId;
|
|
2590
|
+
let tzc = tz.charCodeAt(0);
|
|
2591
|
+
if (
|
|
2592
|
+
// Classic offset
|
|
2593
|
+
tzc !== 43 && tzc !== 45 && // Fast pre-check pass; see below for the actually accepted values
|
|
2594
|
+
(tzc !== 85 && tzc !== 71 && tzc !== 90 && tzc !== 69 || // UTC and its aliases; Temporal implementations don't all canonicalise unfortunately
|
|
2595
|
+
tz !== "UTC" && tz !== "UCT" && tz !== "Universal" && tz !== "Zulu" && // GMT is a TZ but it's for all intents and purposes equivalent to UTC. Safe to downgrade.
|
|
2596
|
+
!tz.startsWith("GMT") && tz !== "Greenwich" && // Etc/* are all safe to downgrade to offset (either UTC, GMT, or offset)
|
|
2597
|
+
!tz.startsWith("Etc/"))
|
|
2598
|
+
) {
|
|
2599
|
+
throw new TypeError("Temporal objects with an IANA timezone are not allowed in Temporal strict mode");
|
|
2600
|
+
}
|
|
2601
|
+
}
|
|
2602
|
+
case "temporal/uc":
|
|
2603
|
+
if (strictTemporal && val.calendarId !== "iso8601")
|
|
2604
|
+
throw new TypeError("Temporal objects with a non-default calendar are not allowed in Temporal strict mode");
|
|
2605
|
+
return val.toString({
|
|
2606
|
+
calendarName: "never",
|
|
2607
|
+
timeZoneName: "never"
|
|
2608
|
+
});
|
|
2609
|
+
case "temporal/x":
|
|
2610
|
+
throw new TypeError("Unsupported " + val[Symbol.toStringTag]);
|
|
2611
|
+
}
|
|
2612
|
+
}
|
|
2613
|
+
function stringifyInlineTable(obj, depth, numberAsFloat, strictTemporal) {
|
|
2614
|
+
let keys = Object.keys(obj);
|
|
2615
|
+
if (keys.length === 0)
|
|
2616
|
+
return "{}";
|
|
2617
|
+
let res = "{ ";
|
|
2618
|
+
for (let i = 0; i < keys.length; i++) {
|
|
2619
|
+
let k = keys[i];
|
|
2620
|
+
if (i)
|
|
2621
|
+
res += ", ";
|
|
2622
|
+
res += formatKey(k) + " = " + stringifyValue(obj[k], extendedTypeOf(obj[k]), depth - 1, numberAsFloat, strictTemporal);
|
|
2623
|
+
}
|
|
2624
|
+
return res + " }";
|
|
2625
|
+
}
|
|
2626
|
+
function stringifyArray(array, depth, numberAsFloat, strictTemporal) {
|
|
2627
|
+
if (array.length === 0)
|
|
2628
|
+
return "[]";
|
|
2629
|
+
let res = "[ ";
|
|
2630
|
+
for (let i = 0; i < array.length; i++) {
|
|
2631
|
+
if (i)
|
|
2632
|
+
res += ", ";
|
|
2633
|
+
if (array[i] === null || array[i] === void 0) {
|
|
2634
|
+
throw new TypeError("arrays cannot contain null or undefined values");
|
|
2635
|
+
}
|
|
2636
|
+
res += stringifyValue(array[i], extendedTypeOf(array[i]), depth - 1, numberAsFloat, strictTemporal);
|
|
2637
|
+
}
|
|
2638
|
+
return res + " ]";
|
|
2639
|
+
}
|
|
2640
|
+
function stringifyArrayTable(array, key, depth, numberAsFloat, strictTemporal) {
|
|
2641
|
+
if (depth === 0) {
|
|
2642
|
+
throw new Error("Could not stringify the object: maximum object depth exceeded");
|
|
2643
|
+
}
|
|
2644
|
+
let res = "";
|
|
2645
|
+
for (let i = 0; i < array.length; i++) {
|
|
2646
|
+
res += `${res && "\n"}[[${key}]]
|
|
2647
|
+
`;
|
|
2648
|
+
res += stringifyTable(0, array[i], key, depth, numberAsFloat, strictTemporal);
|
|
2649
|
+
}
|
|
2650
|
+
return res;
|
|
2651
|
+
}
|
|
2652
|
+
function stringifyTable(tableKey, obj, prefix, depth, numberAsFloat, strictTemporal) {
|
|
2653
|
+
if (depth === 0) {
|
|
2654
|
+
throw new Error("Could not stringify the object: maximum object depth exceeded");
|
|
2655
|
+
}
|
|
2656
|
+
let preamble = "";
|
|
2657
|
+
let tables = "";
|
|
2658
|
+
let keys = Object.keys(obj);
|
|
2659
|
+
for (let i = 0; i < keys.length; i++) {
|
|
2660
|
+
let k = keys[i];
|
|
2661
|
+
if (obj[k] !== null && obj[k] !== void 0) {
|
|
2662
|
+
let type = extendedTypeOf(obj[k]);
|
|
2663
|
+
if (type === "symbol" || type === "function") {
|
|
2664
|
+
throw new TypeError(`cannot serialize values of type '${type}'`);
|
|
2665
|
+
}
|
|
2666
|
+
let key = formatKey(k);
|
|
2667
|
+
if (type === "array" && isArrayOfTables(obj[k])) {
|
|
2668
|
+
tables += (tables && "\n") + stringifyArrayTable(obj[k], prefix ? `${prefix}.${key}` : key, depth - 1, numberAsFloat, strictTemporal);
|
|
2669
|
+
} else if (type === "object") {
|
|
2670
|
+
let tblKey = prefix ? `${prefix}.${key}` : key;
|
|
2671
|
+
tables += (tables && "\n") + stringifyTable(tblKey, obj[k], tblKey, depth - 1, numberAsFloat, strictTemporal);
|
|
2672
|
+
} else {
|
|
2673
|
+
preamble += key;
|
|
2674
|
+
preamble += " = ";
|
|
2675
|
+
preamble += stringifyValue(obj[k], type, depth, numberAsFloat, strictTemporal);
|
|
2676
|
+
preamble += "\n";
|
|
2677
|
+
}
|
|
2678
|
+
}
|
|
2679
|
+
}
|
|
2680
|
+
if (tableKey && (preamble || !tables))
|
|
2681
|
+
preamble = preamble ? `[${tableKey}]
|
|
2682
|
+
${preamble}` : `[${tableKey}]`;
|
|
2683
|
+
return preamble && tables ? `${preamble}
|
|
2684
|
+
${tables}` : preamble || tables;
|
|
2685
|
+
}
|
|
2686
|
+
function stringify(obj, { maxDepth = 1e3, numbersAsFloat = false, strictTemporal = false } = {}) {
|
|
2687
|
+
if (extendedTypeOf(obj) !== "object") {
|
|
2688
|
+
throw new TypeError("stringify can only be called with an object");
|
|
2689
|
+
}
|
|
2690
|
+
let str = stringifyTable(0, obj, "", maxDepth, numbersAsFloat, strictTemporal);
|
|
2691
|
+
if (str[str.length - 1] !== "\n")
|
|
2692
|
+
return str + "\n";
|
|
2693
|
+
return str;
|
|
2694
|
+
}
|
|
2695
|
+
|
|
2696
|
+
// src/core/backups.ts
|
|
2697
|
+
import { dirname as dirname4, isAbsolute as isAbsolute5, join as join6, resolve as resolve2 } from "path";
|
|
2698
|
+
var BACKUP_SEGMENTS = ["backups"];
|
|
2699
|
+
var V1_BACKUP_SEGMENTS = ["backups", "master-prompt"];
|
|
2700
|
+
var BACKUP_DIRECTORY = join6(AGEMON_STATE_DIRECTORY, ...BACKUP_SEGMENTS);
|
|
2701
|
+
var V1_BACKUP_DIRECTORY = join6(AGEMON_STATE_DIRECTORY, ...V1_BACKUP_SEGMENTS);
|
|
2702
|
+
var RESOURCE_ID_HASH_LENGTH = 12;
|
|
2703
|
+
function sanitizeForFileName(value) {
|
|
2704
|
+
return value.replace(/[^a-zA-Z0-9._-]/gu, "_");
|
|
2705
|
+
}
|
|
2706
|
+
function resolveImmutableBackupPath(cwd, resourceId) {
|
|
2707
|
+
const resourceIdHash = sha256Hex(resourceId).slice(
|
|
2708
|
+
0,
|
|
2709
|
+
RESOURCE_ID_HASH_LENGTH
|
|
2710
|
+
);
|
|
2711
|
+
return join6(
|
|
2712
|
+
cwd,
|
|
2713
|
+
BACKUP_DIRECTORY,
|
|
2714
|
+
`${sanitizeForFileName(resourceId)}-${resourceIdHash}.bak`
|
|
2715
|
+
);
|
|
2716
|
+
}
|
|
2717
|
+
function resolveLegacyBackupPath(cwd, resourceId) {
|
|
2718
|
+
return join6(cwd, BACKUP_DIRECTORY, `${sanitizeForFileName(resourceId)}.bak`);
|
|
2719
|
+
}
|
|
2720
|
+
function resolveV1RuleFileBackupPath(cwd, target) {
|
|
2721
|
+
return join6(cwd, V1_BACKUP_DIRECTORY, `${sanitizeForFileName(target)}.bak`);
|
|
2722
|
+
}
|
|
2723
|
+
function backupDirectorySegments(cwd, backupPath) {
|
|
2724
|
+
const directory = dirname4(backupPath);
|
|
2725
|
+
if (directory === join6(cwd, BACKUP_DIRECTORY)) {
|
|
2726
|
+
return BACKUP_SEGMENTS;
|
|
2727
|
+
}
|
|
2728
|
+
if (directory === join6(cwd, V1_BACKUP_DIRECTORY)) {
|
|
2729
|
+
return V1_BACKUP_SEGMENTS;
|
|
2730
|
+
}
|
|
2731
|
+
return null;
|
|
2732
|
+
}
|
|
2733
|
+
async function writeBackup(cwd, backupPath, contents) {
|
|
2734
|
+
await resolveStateDirectory(cwd, BACKUP_SEGMENTS, { create: true });
|
|
2735
|
+
await writeFileAtomic(backupPath, contents);
|
|
2736
|
+
return { path: backupPath, checksum: sha256Hex(contents) };
|
|
2737
|
+
}
|
|
2738
|
+
async function writeImmutableBackup(cwd, resourceId, contents) {
|
|
2739
|
+
const backupPath = resolveImmutableBackupPath(cwd, resourceId);
|
|
2740
|
+
const existingBackup = await readBackupFile(cwd, backupPath);
|
|
2741
|
+
if (existingBackup !== null) {
|
|
2742
|
+
return { path: backupPath, checksum: sha256Hex(existingBackup) };
|
|
2743
|
+
}
|
|
2744
|
+
return writeBackup(cwd, backupPath, contents);
|
|
2745
|
+
}
|
|
2746
|
+
var BackupPathOccupiedError = class extends Error {
|
|
2747
|
+
constructor(backupPath) {
|
|
2748
|
+
super(`${backupPath} already holds a different file`);
|
|
2749
|
+
this.backupPath = backupPath;
|
|
2750
|
+
}
|
|
2751
|
+
backupPath;
|
|
2752
|
+
};
|
|
2753
|
+
async function writeBackupOnce(cwd, resourceId, contents) {
|
|
2754
|
+
const backupPath = resolveImmutableBackupPath(cwd, resourceId);
|
|
2755
|
+
let existingBackup;
|
|
2756
|
+
try {
|
|
2757
|
+
existingBackup = await readBackupFile(cwd, backupPath);
|
|
2758
|
+
} catch (error) {
|
|
2759
|
+
if (error instanceof NotARegularFileError || error instanceof FileTooLargeError) {
|
|
2760
|
+
throw new BackupPathOccupiedError(backupPath);
|
|
2761
|
+
}
|
|
2762
|
+
throw error;
|
|
2763
|
+
}
|
|
2764
|
+
if (existingBackup === null) {
|
|
2765
|
+
return writeBackup(cwd, backupPath, contents);
|
|
2766
|
+
}
|
|
2767
|
+
if (existingBackup !== contents) {
|
|
2768
|
+
throw new BackupPathOccupiedError(backupPath);
|
|
2769
|
+
}
|
|
2770
|
+
return { path: backupPath, checksum: sha256Hex(contents) };
|
|
2771
|
+
}
|
|
2772
|
+
async function replaceStaleBackup(cwd, resourceId, contents) {
|
|
2773
|
+
const backupPath = resolveImmutableBackupPath(cwd, resourceId);
|
|
2774
|
+
if (await readBackupFile(cwd, backupPath) === contents) {
|
|
2775
|
+
return { path: backupPath, checksum: sha256Hex(contents) };
|
|
2776
|
+
}
|
|
2777
|
+
return writeBackup(cwd, backupPath, contents);
|
|
2778
|
+
}
|
|
2779
|
+
function immutableBackupCandidates(cwd, resourceId, recordedPath) {
|
|
2780
|
+
const ownBackupPaths = [
|
|
2781
|
+
resolveImmutableBackupPath(cwd, resourceId),
|
|
2782
|
+
resolveLegacyBackupPath(cwd, resourceId)
|
|
2783
|
+
];
|
|
2784
|
+
const recordedOwnPath = recordedPath !== null && isAbsolute5(recordedPath) ? ownBackupPaths.find((ownPath) => ownPath === resolve2(recordedPath)) : void 0;
|
|
2785
|
+
return recordedOwnPath === void 0 ? ownBackupPaths : [
|
|
2786
|
+
recordedOwnPath,
|
|
2787
|
+
...ownBackupPaths.filter((ownPath) => ownPath !== recordedOwnPath)
|
|
2788
|
+
];
|
|
2789
|
+
}
|
|
2790
|
+
async function readBackupFile(cwd, backupPath) {
|
|
2791
|
+
const segments = backupDirectorySegments(cwd, backupPath);
|
|
2792
|
+
if (segments === null) {
|
|
2793
|
+
throw new RefusedPathError(
|
|
2794
|
+
`Refusing to use ${printable(backupPath)}: it is not in agemon's backup directory.`
|
|
2795
|
+
);
|
|
2796
|
+
}
|
|
2797
|
+
const backupDirectory = await resolveStateDirectory(cwd, segments, {
|
|
2798
|
+
create: false
|
|
2799
|
+
});
|
|
2800
|
+
return backupDirectory === null ? null : readRegularFileIfExists(backupPath);
|
|
2801
|
+
}
|
|
2802
|
+
async function readFirstExistingBackup(cwd, candidatePaths) {
|
|
2803
|
+
for (const candidatePath of candidatePaths) {
|
|
2804
|
+
try {
|
|
2805
|
+
const contents = await readBackupFile(cwd, candidatePath);
|
|
2806
|
+
if (contents !== null) {
|
|
2807
|
+
return { path: candidatePath, contents };
|
|
2808
|
+
}
|
|
2809
|
+
} catch (error) {
|
|
2810
|
+
if (!(error instanceof NotARegularFileError) && !(error instanceof FileTooLargeError)) {
|
|
2811
|
+
throw error;
|
|
2812
|
+
}
|
|
2813
|
+
}
|
|
2814
|
+
}
|
|
2815
|
+
return null;
|
|
2816
|
+
}
|
|
2817
|
+
|
|
2818
|
+
// src/core/config.ts
|
|
2819
|
+
var CONFIG_FILE_NAME = "agemon.toml";
|
|
2820
|
+
var CONFIG_BACKUP_RESOURCE_PREFIX = "config:agemon.toml";
|
|
2821
|
+
var CONFIG_BACKUP_CONTENT_HASH_LENGTH = 16;
|
|
2822
|
+
var MAX_CUSTOM_GROUP_ID_CHARACTERS = 40;
|
|
2823
|
+
var CUSTOM_GROUP_ID_PATTERN = new RegExp(
|
|
2824
|
+
`^[a-z0-9][a-z0-9-]{0,${MAX_CUSTOM_GROUP_ID_CHARACTERS - 1}}$`,
|
|
2825
|
+
"u"
|
|
2826
|
+
);
|
|
2827
|
+
var RESERVED_GROUP_IDS = ["all", "none"];
|
|
2828
|
+
var MAX_CUSTOM_GROUP_LABEL_CHARACTERS = 80;
|
|
2829
|
+
var SKILL_NAME_PATTERN = /^[a-z0-9][a-z0-9._-]{0,63}$/u;
|
|
2830
|
+
var LOCAL_SOURCE_PATTERN = /^\.\/(?:[A-Za-z0-9._-]+\/)*([A-Za-z0-9._-]+)$/u;
|
|
2831
|
+
var AGENT_DIRECTORIES = [".agents", ".claude"];
|
|
2832
|
+
var GITHUB_SOURCE_PATTERN = /^github:([A-Za-z0-9][A-Za-z0-9_.-]{0,99})\/([A-Za-z0-9][A-Za-z0-9_.-]{0,99})$/u;
|
|
2833
|
+
var COMMIT_REF_PATTERN = /^[0-9a-f]{40}$/u;
|
|
2834
|
+
var MAX_CUSTOM_GROUPS = 20;
|
|
2835
|
+
var MAX_SKILLS_PER_CUSTOM_GROUP = 50;
|
|
2836
|
+
var MAX_REMOTE_REPOSITORIES = 10;
|
|
2837
|
+
var CONFLICT_DECISIONS = ["keep", "replace", "skip"];
|
|
2838
|
+
var SKILL_GROUPS_WHEN_ABSENT = "interactive prompt per group, or essentials-only when non-interactive";
|
|
2839
|
+
var AGEMON_TOML_TABLES = {
|
|
2840
|
+
topLevel: {
|
|
2841
|
+
heading: "Top-level keys",
|
|
2842
|
+
keys: [
|
|
2843
|
+
{
|
|
2844
|
+
name: "capabilities",
|
|
2845
|
+
type: "array of capability ids",
|
|
2846
|
+
whenAbsent: null,
|
|
2847
|
+
summary: "The capabilities agemon reconciles in this repository. An empty list or an unknown id is an error."
|
|
2848
|
+
},
|
|
2849
|
+
{
|
|
2850
|
+
name: "skill_groups",
|
|
2851
|
+
type: "string: comma-separated group ids, or all or none",
|
|
2852
|
+
whenAbsent: SKILL_GROUPS_WHEN_ABSENT,
|
|
2853
|
+
summary: "The skill groups to install, as with --skill-groups; the flag wins when both are given."
|
|
2854
|
+
},
|
|
2855
|
+
{
|
|
2856
|
+
name: "custom_skill_groups",
|
|
2857
|
+
type: "table of group id to custom group table",
|
|
2858
|
+
whenAbsent: "no project-defined groups",
|
|
2859
|
+
summary: `Project-defined skill groups. A group id is lower-case letters, digits and '-' (at most ${MAX_CUSTOM_GROUP_ID_CHARACTERS} characters), not all or none, and not a built-in group id.`
|
|
2860
|
+
},
|
|
2861
|
+
{
|
|
2862
|
+
name: "conflict_decisions",
|
|
2863
|
+
type: "table of resource id to decision",
|
|
2864
|
+
whenAbsent: "every conflict needs a decision in the run",
|
|
2865
|
+
summary: "What to do with a rule file that holds your own rules where agemon would write a pointer."
|
|
2866
|
+
},
|
|
2867
|
+
{
|
|
2868
|
+
name: "known_capabilities",
|
|
2869
|
+
type: "array of capability ids",
|
|
2870
|
+
whenAbsent: "the capabilities agemon had before this key existed",
|
|
2871
|
+
summary: "The capabilities the agemon that wrote the file knew about, so a capability added in a later release is offered once."
|
|
2872
|
+
}
|
|
2873
|
+
]
|
|
2874
|
+
},
|
|
2875
|
+
customSkillGroup: {
|
|
2876
|
+
heading: "Custom skill group tables",
|
|
2877
|
+
keys: [
|
|
2878
|
+
{
|
|
2879
|
+
name: "label",
|
|
2880
|
+
type: `string, one line, at most ${MAX_CUSTOM_GROUP_LABEL_CHARACTERS} characters`,
|
|
2881
|
+
whenAbsent: null,
|
|
2882
|
+
summary: "The name shown when agemon asks which groups to install."
|
|
2883
|
+
},
|
|
2884
|
+
{
|
|
2885
|
+
name: "skills",
|
|
2886
|
+
type: `array of 1 to ${MAX_SKILLS_PER_CUSTOM_GROUP} skill entries`,
|
|
2887
|
+
whenAbsent: null,
|
|
2888
|
+
summary: "The skills in the group, each a local or a GitHub source."
|
|
2889
|
+
}
|
|
2890
|
+
]
|
|
2891
|
+
},
|
|
2892
|
+
customSkill: {
|
|
2893
|
+
heading: "Custom skill entries",
|
|
2894
|
+
keys: [
|
|
2895
|
+
{
|
|
2896
|
+
name: "source",
|
|
2897
|
+
type: 'string: "./<path>" or "github:<owner>/<repository>"',
|
|
2898
|
+
whenAbsent: null,
|
|
2899
|
+
summary: "A directory in this repository (the skill's name is the directory name), or a GitHub repository."
|
|
2900
|
+
},
|
|
2901
|
+
{
|
|
2902
|
+
name: "ref",
|
|
2903
|
+
type: "string: a full 40-character commit sha",
|
|
2904
|
+
whenAbsent: null,
|
|
2905
|
+
summary: "The commit to fetch; branches, tags and short shas are refused. Only for GitHub sources.",
|
|
2906
|
+
githubSourceOnly: true
|
|
2907
|
+
},
|
|
2908
|
+
{
|
|
2909
|
+
name: "skill",
|
|
2910
|
+
type: "string: a skill name",
|
|
2911
|
+
whenAbsent: null,
|
|
2912
|
+
summary: "The skill to take from skills/<skill>/ in the repository. Only for GitHub sources.",
|
|
2913
|
+
githubSourceOnly: true
|
|
2914
|
+
}
|
|
2915
|
+
]
|
|
2916
|
+
}
|
|
2917
|
+
};
|
|
2918
|
+
var keyNames = (keys) => keys.map((key) => key.name);
|
|
2919
|
+
var TOP_LEVEL_KEYS = keyNames(AGEMON_TOML_TABLES.topLevel.keys);
|
|
2920
|
+
var CUSTOM_GROUP_KEYS = keyNames(AGEMON_TOML_TABLES.customSkillGroup.keys);
|
|
2921
|
+
var GITHUB_SKILL_KEYS = keyNames(AGEMON_TOML_TABLES.customSkill.keys);
|
|
2922
|
+
var LOCAL_SKILL_KEYS = keyNames(
|
|
2923
|
+
AGEMON_TOML_TABLES.customSkill.keys.filter((key) => !key.githubSourceOnly)
|
|
2924
|
+
);
|
|
2925
|
+
function resolveConfigPath(cwd) {
|
|
2926
|
+
return join7(cwd, CONFIG_FILE_NAME);
|
|
2927
|
+
}
|
|
2928
|
+
function fail(detail) {
|
|
2929
|
+
throw new Error(`${CONFIG_FILE_NAME} is invalid: ${printable(detail)}`);
|
|
2930
|
+
}
|
|
2931
|
+
function validateCapabilities(value) {
|
|
2932
|
+
if (!Array.isArray(value)) {
|
|
2933
|
+
fail("'capabilities' must be an array of capability ids");
|
|
2934
|
+
}
|
|
2935
|
+
if (value.length === 0) {
|
|
2936
|
+
fail("'capabilities' must list at least one capability id");
|
|
2937
|
+
}
|
|
2938
|
+
return value.map((entry) => {
|
|
2939
|
+
if (typeof entry !== "string" || entry.trim().length === 0) {
|
|
2940
|
+
fail("'capabilities' entries must be non-empty strings");
|
|
2941
|
+
}
|
|
2942
|
+
return entry;
|
|
2943
|
+
});
|
|
2944
|
+
}
|
|
2945
|
+
function validateKnownCapabilities(value) {
|
|
2946
|
+
if (value === void 0) {
|
|
2947
|
+
return null;
|
|
2948
|
+
}
|
|
2949
|
+
if (!Array.isArray(value) || value.some((entry) => typeof entry !== "string" || entry.length === 0)) {
|
|
2950
|
+
fail("'known_capabilities' must be an array of non-empty strings");
|
|
2951
|
+
}
|
|
2952
|
+
return value;
|
|
2953
|
+
}
|
|
2954
|
+
function validateKnownKeys(record) {
|
|
2955
|
+
const unknownKey = Object.keys(record).find(
|
|
2956
|
+
(key) => !TOP_LEVEL_KEYS.includes(key)
|
|
2957
|
+
);
|
|
2958
|
+
if (unknownKey !== void 0) {
|
|
2959
|
+
fail(
|
|
2960
|
+
`unknown key '${unknownKey}'. Valid keys: ${TOP_LEVEL_KEYS.join(", ")}.`
|
|
2961
|
+
);
|
|
2962
|
+
}
|
|
2963
|
+
}
|
|
2964
|
+
function validateSkillGroups(value) {
|
|
2965
|
+
if (value === void 0) {
|
|
2966
|
+
return null;
|
|
2967
|
+
}
|
|
2968
|
+
if (typeof value !== "string") {
|
|
2969
|
+
fail("'skill_groups' must be a string when present");
|
|
2970
|
+
}
|
|
2971
|
+
return value;
|
|
2972
|
+
}
|
|
2973
|
+
function asTable(value, field) {
|
|
2974
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
2975
|
+
fail(`'${field}' must be a table`);
|
|
2976
|
+
}
|
|
2977
|
+
return value;
|
|
2978
|
+
}
|
|
2979
|
+
function assertOnlyKeys(table, allowedKeys, field) {
|
|
2980
|
+
const unknownKey = Object.keys(table).find(
|
|
2981
|
+
(key) => !allowedKeys.includes(key)
|
|
2982
|
+
);
|
|
2983
|
+
if (unknownKey !== void 0) {
|
|
2984
|
+
fail(
|
|
2985
|
+
`'${field}' has unknown key '${unknownKey}'. Valid keys: ${allowedKeys.join(", ")}.`
|
|
2986
|
+
);
|
|
2987
|
+
}
|
|
2988
|
+
}
|
|
2989
|
+
function isPlainSkillName(value) {
|
|
2990
|
+
return SKILL_NAME_PATTERN.test(value);
|
|
2991
|
+
}
|
|
2992
|
+
function validateSkillName(value, field) {
|
|
2993
|
+
if (typeof value !== "string" || !isPlainSkillName(value)) {
|
|
2994
|
+
fail(
|
|
2995
|
+
`'${field}' must be a skill name of lower-case letters, digits, '.', '_' or '-' (at most 64 characters, not starting with '-' or '.')`
|
|
2996
|
+
);
|
|
2997
|
+
}
|
|
2998
|
+
return value;
|
|
2999
|
+
}
|
|
3000
|
+
function validateLocalSource(path, field) {
|
|
3001
|
+
const match = LOCAL_SOURCE_PATTERN.exec(path);
|
|
3002
|
+
const components = path.split("/").slice(1);
|
|
3003
|
+
if (match === null || components.some((component) => component === "." || component === "..")) {
|
|
3004
|
+
fail(
|
|
3005
|
+
`'${field}' must be a repository-relative path like "./tools/skills/<name>" without '.' or '..' components`
|
|
3006
|
+
);
|
|
3007
|
+
}
|
|
3008
|
+
if (AGENT_DIRECTORIES.includes(components[0].toLowerCase())) {
|
|
3009
|
+
fail(
|
|
3010
|
+
`'${field}' must not point into ${AGENT_DIRECTORIES.join(" or ")}, where installed skills live`
|
|
3011
|
+
);
|
|
3012
|
+
}
|
|
3013
|
+
return {
|
|
3014
|
+
kind: "local",
|
|
3015
|
+
path,
|
|
3016
|
+
skillName: validateSkillName(match[1], `${field} (last path component)`)
|
|
3017
|
+
};
|
|
3018
|
+
}
|
|
3019
|
+
function validateCustomSkill(value, field) {
|
|
3020
|
+
const skill = asTable(value, field);
|
|
3021
|
+
if (typeof skill.source !== "string") {
|
|
3022
|
+
fail(`'${field}.source' must be a string`);
|
|
3023
|
+
}
|
|
3024
|
+
if (!skill.source.startsWith("github:")) {
|
|
3025
|
+
assertOnlyKeys(skill, LOCAL_SKILL_KEYS, field);
|
|
3026
|
+
return validateLocalSource(skill.source, `${field}.source`);
|
|
3027
|
+
}
|
|
3028
|
+
assertOnlyKeys(skill, GITHUB_SKILL_KEYS, field);
|
|
3029
|
+
const match = GITHUB_SOURCE_PATTERN.exec(skill.source);
|
|
3030
|
+
if (match === null) {
|
|
3031
|
+
fail(`'${field}.source' must be "github:<owner>/<repository>"`);
|
|
3032
|
+
}
|
|
3033
|
+
if (typeof skill.ref !== "string" || !COMMIT_REF_PATTERN.test(skill.ref)) {
|
|
3034
|
+
fail(
|
|
3035
|
+
`'${field}.ref' must be a full 40-character commit sha; branches, tags and short shas are refused`
|
|
3036
|
+
);
|
|
3037
|
+
}
|
|
3038
|
+
return {
|
|
3039
|
+
kind: "github",
|
|
3040
|
+
owner: match[1],
|
|
3041
|
+
repository: match[2],
|
|
3042
|
+
ref: skill.ref,
|
|
3043
|
+
skillName: validateSkillName(skill.skill, `${field}.skill`)
|
|
3044
|
+
};
|
|
3045
|
+
}
|
|
3046
|
+
function validateCustomSkillGroup(id, value) {
|
|
3047
|
+
const field = `custom_skill_groups.${id}`;
|
|
3048
|
+
if (!CUSTOM_GROUP_ID_PATTERN.test(id) || RESERVED_GROUP_IDS.includes(id)) {
|
|
3049
|
+
fail(
|
|
3050
|
+
`'${field}': a group id is lower-case letters, digits and '-' (at most ${MAX_CUSTOM_GROUP_ID_CHARACTERS} characters), and not ${RESERVED_GROUP_IDS.join(" or ")}`
|
|
3051
|
+
);
|
|
3052
|
+
}
|
|
3053
|
+
const group = asTable(value, field);
|
|
3054
|
+
assertOnlyKeys(group, CUSTOM_GROUP_KEYS, field);
|
|
3055
|
+
if (typeof group.label !== "string" || group.label.trim().length === 0 || group.label.length > MAX_CUSTOM_GROUP_LABEL_CHARACTERS || !isSafeIdentifier(group.label)) {
|
|
3056
|
+
fail(
|
|
3057
|
+
`'${field}.label' must be a non-empty single-line string of at most ${MAX_CUSTOM_GROUP_LABEL_CHARACTERS} characters, without invisible or control characters`
|
|
3058
|
+
);
|
|
3059
|
+
}
|
|
3060
|
+
if (!Array.isArray(group.skills) || group.skills.length === 0 || group.skills.length > MAX_SKILLS_PER_CUSTOM_GROUP) {
|
|
3061
|
+
fail(
|
|
3062
|
+
`'${field}.skills' must list between 1 and ${MAX_SKILLS_PER_CUSTOM_GROUP} skills`
|
|
3063
|
+
);
|
|
3064
|
+
}
|
|
3065
|
+
return {
|
|
3066
|
+
id,
|
|
3067
|
+
label: group.label,
|
|
3068
|
+
skills: group.skills.map(
|
|
3069
|
+
(skill, index) => validateCustomSkill(skill, `${field}.skills[${index}]`)
|
|
3070
|
+
)
|
|
3071
|
+
};
|
|
3072
|
+
}
|
|
3073
|
+
function validateCustomSkillGroups(value) {
|
|
3074
|
+
if (value === void 0) {
|
|
3075
|
+
return [];
|
|
3076
|
+
}
|
|
3077
|
+
const groups = Object.entries(asTable(value, "custom_skill_groups")).map(
|
|
3078
|
+
([id, group]) => validateCustomSkillGroup(id, group)
|
|
3079
|
+
);
|
|
3080
|
+
if (groups.length > MAX_CUSTOM_GROUPS) {
|
|
3081
|
+
fail(
|
|
3082
|
+
`'custom_skill_groups' may define at most ${MAX_CUSTOM_GROUPS} groups`
|
|
3083
|
+
);
|
|
3084
|
+
}
|
|
3085
|
+
const skillNames = groups.flatMap(
|
|
3086
|
+
(group) => group.skills.map((skill) => skill.skillName)
|
|
3087
|
+
);
|
|
3088
|
+
const duplicateName = skillNames.find(
|
|
3089
|
+
(name, index) => skillNames.indexOf(name) !== index
|
|
3090
|
+
);
|
|
3091
|
+
if (duplicateName !== void 0) {
|
|
3092
|
+
fail(
|
|
3093
|
+
`skill '${duplicateName}' appears more than once in 'custom_skill_groups'`
|
|
3094
|
+
);
|
|
3095
|
+
}
|
|
3096
|
+
const remoteRepositories = new Set(
|
|
3097
|
+
groups.flatMap(
|
|
3098
|
+
(group) => group.skills.flatMap(
|
|
3099
|
+
(skill) => skill.kind === "github" ? [`${skill.owner}/${skill.repository}`] : []
|
|
3100
|
+
)
|
|
3101
|
+
)
|
|
3102
|
+
);
|
|
3103
|
+
if (remoteRepositories.size > MAX_REMOTE_REPOSITORIES) {
|
|
3104
|
+
fail(
|
|
3105
|
+
`'custom_skill_groups' may use at most ${MAX_REMOTE_REPOSITORIES} GitHub repositories`
|
|
3106
|
+
);
|
|
3107
|
+
}
|
|
3108
|
+
return groups;
|
|
3109
|
+
}
|
|
3110
|
+
var quotedDecisions = CONFLICT_DECISIONS.map(
|
|
3111
|
+
(decision) => JSON.stringify(decision)
|
|
3112
|
+
);
|
|
3113
|
+
var CONFLICT_DECISION_CHOICES = `${quotedDecisions.slice(0, -1).join(", ")}, or ${quotedDecisions.at(-1)}`;
|
|
3114
|
+
function isConflictDecision(value) {
|
|
3115
|
+
return CONFLICT_DECISIONS.some((decision) => decision === value);
|
|
3116
|
+
}
|
|
3117
|
+
function validateConflictDecisions(value) {
|
|
3118
|
+
const decisions = /* @__PURE__ */ new Map();
|
|
3119
|
+
if (value === void 0) {
|
|
3120
|
+
return decisions;
|
|
3121
|
+
}
|
|
3122
|
+
if (typeof value !== "object" || value === null || Array.isArray(value)) {
|
|
3123
|
+
fail("'conflict_decisions' must be a table of resource id to decision");
|
|
3124
|
+
}
|
|
3125
|
+
for (const [resourceId, decision] of Object.entries(
|
|
3126
|
+
value
|
|
3127
|
+
)) {
|
|
3128
|
+
if (!isConflictDecision(decision)) {
|
|
3129
|
+
fail(
|
|
3130
|
+
`'conflict_decisions.${resourceId}' must be ${CONFLICT_DECISION_CHOICES}, got ${JSON.stringify(decision)}`
|
|
3131
|
+
);
|
|
3132
|
+
}
|
|
3133
|
+
decisions.set(resourceId, decision);
|
|
3134
|
+
}
|
|
3135
|
+
return decisions;
|
|
3136
|
+
}
|
|
3137
|
+
async function loadConfig(cwd) {
|
|
3138
|
+
let rawContents;
|
|
3139
|
+
try {
|
|
3140
|
+
rawContents = await readRegularFileIfExists(resolveConfigPath(cwd));
|
|
3141
|
+
} catch (error) {
|
|
3142
|
+
if (error instanceof RefusedFileError) {
|
|
3143
|
+
fail(error.message);
|
|
3144
|
+
}
|
|
3145
|
+
throw new Error(`Unable to read ${CONFIG_FILE_NAME}: ${String(error)}`);
|
|
3146
|
+
}
|
|
3147
|
+
if (rawContents === null) {
|
|
3148
|
+
return null;
|
|
3149
|
+
}
|
|
3150
|
+
return validateConfigText(rawContents);
|
|
3151
|
+
}
|
|
3152
|
+
function parseConfigRecord(text) {
|
|
3153
|
+
try {
|
|
3154
|
+
return parse(text);
|
|
3155
|
+
} catch (error) {
|
|
3156
|
+
const detail = error instanceof TomlError ? error.message : String(error);
|
|
3157
|
+
fail(`not valid TOML \u2014 ${detail}`);
|
|
3158
|
+
}
|
|
3159
|
+
}
|
|
3160
|
+
function validateConfigText(text) {
|
|
3161
|
+
const record = parseConfigRecord(text);
|
|
3162
|
+
validateKnownKeys(record);
|
|
3163
|
+
return {
|
|
3164
|
+
capabilities: validateCapabilities(record.capabilities),
|
|
3165
|
+
skillGroups: validateSkillGroups(record.skill_groups),
|
|
3166
|
+
customSkillGroups: validateCustomSkillGroups(record.custom_skill_groups),
|
|
3167
|
+
conflictDecisions: validateConflictDecisions(record.conflict_decisions),
|
|
3168
|
+
knownCapabilities: validateKnownCapabilities(record.known_capabilities),
|
|
3169
|
+
sourceText: text
|
|
3170
|
+
};
|
|
3171
|
+
}
|
|
3172
|
+
function serializeConfigRecord(record) {
|
|
3173
|
+
return `${stringify(record)}
|
|
3174
|
+
`;
|
|
3175
|
+
}
|
|
3176
|
+
function configBackupResourceId(addedCapabilityIds, originalText) {
|
|
3177
|
+
const contentHash = sha256Hex(originalText).slice(
|
|
3178
|
+
0,
|
|
3179
|
+
CONFIG_BACKUP_CONTENT_HASH_LENGTH
|
|
3180
|
+
);
|
|
3181
|
+
return `${CONFIG_BACKUP_RESOURCE_PREFIX}:${addedCapabilityIds.join(",")}:${contentHash}`;
|
|
3182
|
+
}
|
|
3183
|
+
async function rewriteConfigCapabilities(cwd, config, update) {
|
|
3184
|
+
const configPath = resolveConfigPath(cwd);
|
|
3185
|
+
const currentText = await readRegularFileIfExists(configPath);
|
|
3186
|
+
if (currentText !== config.sourceText) {
|
|
3187
|
+
return { kind: "not-written", reason: "it changed during this run" };
|
|
3188
|
+
}
|
|
3189
|
+
const currentRecord = parseConfigRecord(currentText);
|
|
3190
|
+
const nextRecord = {
|
|
3191
|
+
...currentRecord,
|
|
3192
|
+
capabilities: update.capabilities,
|
|
3193
|
+
known_capabilities: [
|
|
3194
|
+
.../* @__PURE__ */ new Set([
|
|
3195
|
+
...config.knownCapabilities ?? [],
|
|
3196
|
+
...update.knownCapabilities
|
|
3197
|
+
])
|
|
3198
|
+
]
|
|
3199
|
+
};
|
|
3200
|
+
const nextText = serializeConfigRecord(nextRecord);
|
|
3201
|
+
if (!isDeepStrictEqual(
|
|
3202
|
+
structuredClone(parseConfigRecord(nextText)),
|
|
3203
|
+
structuredClone(nextRecord)
|
|
3204
|
+
)) {
|
|
3205
|
+
return {
|
|
3206
|
+
kind: "not-written",
|
|
3207
|
+
reason: "rewriting it would change values agemon did not mean to change"
|
|
3208
|
+
};
|
|
3209
|
+
}
|
|
3210
|
+
validateConfigText(nextText);
|
|
3211
|
+
const losesCommentsOrLayout = serializeConfigRecord(currentRecord) !== currentText;
|
|
3212
|
+
let backup = null;
|
|
3213
|
+
if (losesCommentsOrLayout) {
|
|
3214
|
+
const addedIds = update.capabilities.filter(
|
|
3215
|
+
(id) => !config.capabilities.includes(id)
|
|
3216
|
+
);
|
|
3217
|
+
try {
|
|
3218
|
+
backup = await writeBackupOnce(
|
|
3219
|
+
cwd,
|
|
3220
|
+
configBackupResourceId(addedIds, currentText),
|
|
3221
|
+
currentText
|
|
3222
|
+
);
|
|
3223
|
+
} catch (error) {
|
|
3224
|
+
if (!(error instanceof BackupPathOccupiedError)) {
|
|
3225
|
+
throw error;
|
|
3226
|
+
}
|
|
3227
|
+
return {
|
|
3228
|
+
kind: "not-written",
|
|
3229
|
+
reason: `agemon's backup path ${relative2(cwd, error.backupPath)} holds something else; move it aside and run agemon again`
|
|
3230
|
+
};
|
|
3231
|
+
}
|
|
3232
|
+
}
|
|
3233
|
+
await writeFileAtomic(configPath, nextText);
|
|
3234
|
+
return {
|
|
3235
|
+
kind: "written",
|
|
3236
|
+
backupPath: backup === null ? null : relative2(cwd, backup.path)
|
|
3237
|
+
};
|
|
3238
|
+
}
|
|
3239
|
+
async function writeConfig(cwd, config) {
|
|
3240
|
+
const configPath = resolveConfigPath(cwd);
|
|
3241
|
+
const payload = {
|
|
3242
|
+
capabilities: config.capabilities
|
|
3243
|
+
};
|
|
3244
|
+
if (config.skillGroups !== null) {
|
|
3245
|
+
payload.skill_groups = config.skillGroups;
|
|
3246
|
+
}
|
|
3247
|
+
payload.conflict_decisions = Object.fromEntries(config.conflictDecisions);
|
|
3248
|
+
payload.known_capabilities = config.knownCapabilities;
|
|
3249
|
+
await writeFileAtomic(configPath, `${stringify(payload)}
|
|
3250
|
+
`);
|
|
3251
|
+
return configPath;
|
|
3252
|
+
}
|
|
3253
|
+
|
|
3254
|
+
// src/plugins/skills/skills-cli-targets.ts
|
|
3255
|
+
import { lstat as lstat5, rm as rm4, unlink } from "fs/promises";
|
|
3256
|
+
import { join as join8 } from "path";
|
|
3257
|
+
var CANONICAL_SKILLS_DIRECTORY = ".agents/skills";
|
|
3258
|
+
var CLAUDE_SKILLS_DIRECTORY = ".claude/skills";
|
|
3259
|
+
var SKILLS_LOCK_FILE = "skills-lock.json";
|
|
3260
|
+
async function lstatIfPresent(path) {
|
|
3261
|
+
try {
|
|
3262
|
+
return await lstat5(path);
|
|
3263
|
+
} catch (error) {
|
|
3264
|
+
if (error.code === "ENOENT") {
|
|
3265
|
+
return null;
|
|
3266
|
+
}
|
|
3267
|
+
throw error;
|
|
3268
|
+
}
|
|
3269
|
+
}
|
|
3270
|
+
function refuse(relativePath, reason) {
|
|
3271
|
+
throw new RefusedPathError(
|
|
3272
|
+
`Refusing to run the skills CLI: ${printable(relativePath)} ${reason}. agemon only lets it write inside the repository \u2014 replace it with a real directory or file.`
|
|
3273
|
+
);
|
|
3274
|
+
}
|
|
3275
|
+
function assertPlainSkillName(skillName) {
|
|
3276
|
+
if (!isPlainSkillName(skillName)) {
|
|
3277
|
+
throw new RefusedPathError(
|
|
3278
|
+
`Refusing to run the skills CLI: the skill name '${printable(skillName)}' is not a plain skill name. The ledger in .agemon/state.json may have been edited; check it before running agemon again.`
|
|
3279
|
+
);
|
|
3280
|
+
}
|
|
3281
|
+
}
|
|
3282
|
+
async function prepareSkillsCliTargets(repoRoot, skillNames) {
|
|
3283
|
+
skillNames.forEach(assertPlainSkillName);
|
|
3284
|
+
const canonicalDirectory = await resolveRepoDirectory(
|
|
3285
|
+
repoRoot,
|
|
3286
|
+
CANONICAL_SKILLS_DIRECTORY,
|
|
3287
|
+
{ create: false }
|
|
3288
|
+
);
|
|
3289
|
+
if (canonicalDirectory !== null) {
|
|
3290
|
+
for (const skillName of skillNames) {
|
|
3291
|
+
const stats = await lstatIfPresent(join8(canonicalDirectory, skillName));
|
|
3292
|
+
if (stats?.isSymbolicLink()) {
|
|
3293
|
+
refuse(`${CANONICAL_SKILLS_DIRECTORY}/${skillName}`, "is a symlink");
|
|
3294
|
+
}
|
|
3295
|
+
}
|
|
3296
|
+
}
|
|
3297
|
+
const lockStats = await lstatIfPresent(join8(repoRoot, SKILLS_LOCK_FILE));
|
|
3298
|
+
if (lockStats !== null && !lockStats.isFile()) {
|
|
3299
|
+
refuse(SKILLS_LOCK_FILE, "is a symlink or not a regular file");
|
|
3300
|
+
}
|
|
3301
|
+
return ensureRepoDirectory(repoRoot, CLAUDE_SKILLS_DIRECTORY);
|
|
3302
|
+
}
|
|
3303
|
+
async function removeInstalledSkillCopy(repoRoot, skillName) {
|
|
3304
|
+
assertPlainSkillName(skillName);
|
|
3305
|
+
const skillsDirectory = await resolveRepoDirectory(
|
|
3306
|
+
repoRoot,
|
|
3307
|
+
CLAUDE_SKILLS_DIRECTORY
|
|
3308
|
+
);
|
|
3309
|
+
if (skillsDirectory === null) {
|
|
3310
|
+
return;
|
|
3311
|
+
}
|
|
3312
|
+
const copyPath = join8(skillsDirectory, skillName);
|
|
3313
|
+
const stats = await lstatIfPresent(copyPath);
|
|
3314
|
+
if (stats?.isSymbolicLink()) {
|
|
3315
|
+
await unlink(copyPath);
|
|
3316
|
+
} else if (stats !== null) {
|
|
3317
|
+
await rm4(copyPath, { recursive: true, force: true });
|
|
3318
|
+
}
|
|
3319
|
+
}
|
|
3320
|
+
|
|
3321
|
+
export {
|
|
3322
|
+
__commonJS,
|
|
3323
|
+
__toESM,
|
|
3324
|
+
MAX_READ_FILE_BYTES,
|
|
3325
|
+
MAX_READ_FILE_LABEL,
|
|
3326
|
+
RefusedFileError,
|
|
3327
|
+
FileTooLargeError,
|
|
3328
|
+
RefusedPathError,
|
|
3329
|
+
readRegularFileIfExists,
|
|
3330
|
+
sha256OfRegularFile,
|
|
3331
|
+
writeFileAtomic,
|
|
3332
|
+
writeNewFileWithFallback,
|
|
3333
|
+
saveVerifiedCopyBeside,
|
|
3334
|
+
printable,
|
|
3335
|
+
isSafeIdentifier,
|
|
3336
|
+
quoteForShlex,
|
|
3337
|
+
AGEMON_STATE_DIRECTORY,
|
|
3338
|
+
isMissingPathError,
|
|
3339
|
+
resolveInsideRepo,
|
|
3340
|
+
resolveStateDirectory,
|
|
3341
|
+
resolveRepoDirectory,
|
|
3342
|
+
ensureRepoDirectory,
|
|
3343
|
+
readRegularFilePrefix,
|
|
3344
|
+
hasGitEntry,
|
|
3345
|
+
isSameOrInside,
|
|
3346
|
+
resolveRepositoryRoot,
|
|
3347
|
+
isDenylistedPath,
|
|
3348
|
+
redactJsonValue,
|
|
3349
|
+
redactSecretsInText,
|
|
3350
|
+
isUsableAbsolutePath,
|
|
3351
|
+
resolveHomeDirectory,
|
|
3352
|
+
isHomeDirectory,
|
|
3353
|
+
containsHomeDirectory,
|
|
3354
|
+
CODE_REVIEW_GRAPH_VERSION,
|
|
3355
|
+
CODE_REVIEW_GRAPH_PACKAGE_SPEC,
|
|
3356
|
+
AGNIX_VERSION,
|
|
3357
|
+
AGNIX_RELEASE_ARCHIVE_SHA256,
|
|
3358
|
+
AGNIX_NPM_TARBALL,
|
|
3359
|
+
AGNIX_PACKAGE_FILE_SHA256,
|
|
3360
|
+
AGNIX_BINARY_SHA256,
|
|
3361
|
+
sha256Hex,
|
|
3362
|
+
fingerprintJson,
|
|
3363
|
+
fingerprintContent,
|
|
3364
|
+
SkillTreeRefusal,
|
|
3365
|
+
isLeftOutBySkillsCli,
|
|
3366
|
+
readSkillTree,
|
|
3367
|
+
fingerprintSkillTree,
|
|
3368
|
+
fingerprintSkillSource,
|
|
3369
|
+
readInstalledSkill,
|
|
3370
|
+
fingerprintInstalledSkill,
|
|
3371
|
+
resolveVendoredSkillPath,
|
|
3372
|
+
resolveToolsManifestDirectory,
|
|
3373
|
+
withFileLock,
|
|
3374
|
+
resolveStateRoot,
|
|
3375
|
+
resolveAgemonStateDirectory,
|
|
3376
|
+
checkAgemonStateDirectoryBeforeRun,
|
|
3377
|
+
preparePrivateStateDirectory,
|
|
3378
|
+
prepareToolsRoot,
|
|
3379
|
+
repositoryStateKey,
|
|
3380
|
+
withAgemonStateLock,
|
|
3381
|
+
withRepositoryRunLock,
|
|
3382
|
+
SKILLS_CLI_PATH_IN_TOOLS_DIR,
|
|
3383
|
+
runSkillsCli,
|
|
3384
|
+
resolveImmutableBackupPath,
|
|
3385
|
+
resolveV1RuleFileBackupPath,
|
|
3386
|
+
writeImmutableBackup,
|
|
3387
|
+
replaceStaleBackup,
|
|
3388
|
+
immutableBackupCandidates,
|
|
3389
|
+
readFirstExistingBackup,
|
|
3390
|
+
SKILL_GROUPS_WHEN_ABSENT,
|
|
3391
|
+
resolveConfigPath,
|
|
3392
|
+
isPlainSkillName,
|
|
3393
|
+
loadConfig,
|
|
3394
|
+
rewriteConfigCapabilities,
|
|
3395
|
+
writeConfig,
|
|
3396
|
+
CANONICAL_SKILLS_DIRECTORY,
|
|
3397
|
+
CLAUDE_SKILLS_DIRECTORY,
|
|
3398
|
+
SKILLS_LOCK_FILE,
|
|
3399
|
+
prepareSkillsCliTargets,
|
|
3400
|
+
removeInstalledSkillCopy,
|
|
3401
|
+
MAX_RAW_OUTPUT_LINE_CHARACTERS,
|
|
3402
|
+
sanitizeOutput,
|
|
3403
|
+
capShownLine,
|
|
3404
|
+
safeOutputTail,
|
|
3405
|
+
describeSubprocessFailure,
|
|
3406
|
+
isSpawnFailure,
|
|
3407
|
+
agemonSubprocessEnvironment,
|
|
3408
|
+
TRUNCATION_MARKER,
|
|
3409
|
+
runSubprocess,
|
|
3410
|
+
runRealSubprocess
|
|
3411
|
+
};
|
|
3412
|
+
/*! Bundled license information:
|
|
3413
|
+
|
|
3414
|
+
smol-toml/dist/error.js:
|
|
3415
|
+
smol-toml/dist/primitive.js:
|
|
3416
|
+
smol-toml/dist/date.js:
|
|
3417
|
+
smol-toml/dist/extract.js:
|
|
3418
|
+
smol-toml/dist/util.js:
|
|
3419
|
+
smol-toml/dist/struct.js:
|
|
3420
|
+
smol-toml/dist/parse.js:
|
|
3421
|
+
smol-toml/dist/stringify.js:
|
|
3422
|
+
smol-toml/dist/index.js:
|
|
3423
|
+
(*!
|
|
3424
|
+
* Copyright (c) Squirrel Chat et al., All rights reserved.
|
|
3425
|
+
* SPDX-License-Identifier: BSD-3-Clause
|
|
3426
|
+
*
|
|
3427
|
+
* Redistribution and use in source and binary forms, with or without
|
|
3428
|
+
* modification, are permitted provided that the following conditions are met:
|
|
3429
|
+
*
|
|
3430
|
+
* 1. Redistributions of source code must retain the above copyright notice, this
|
|
3431
|
+
* list of conditions and the following disclaimer.
|
|
3432
|
+
* 2. Redistributions in binary form must reproduce the above copyright notice,
|
|
3433
|
+
* this list of conditions and the following disclaimer in the
|
|
3434
|
+
* documentation and/or other materials provided with the distribution.
|
|
3435
|
+
* 3. Neither the name of the copyright holder nor the names of its contributors
|
|
3436
|
+
* may be used to endorse or promote products derived from this software without
|
|
3437
|
+
* specific prior written permission.
|
|
3438
|
+
*
|
|
3439
|
+
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
|
3440
|
+
* ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
|
3441
|
+
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
|
3442
|
+
* DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
|
|
3443
|
+
* FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
|
|
3444
|
+
* DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
|
|
3445
|
+
* SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
|
|
3446
|
+
* CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
|
|
3447
|
+
* OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
|
|
3448
|
+
* OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
|
3449
|
+
*)
|
|
3450
|
+
*/
|
|
3451
|
+
//# sourceMappingURL=chunk-ZZBF3GOV.js.map
|