@macintacos/caret 1.1.1 → 1.2.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/.claude-plugin/plugin.json +1 -1
- package/README.md +8 -4
- package/bin/caret-launcher +36 -8
- package/dist/cli.js +1262 -339
- package/opencode/caret.core.ts +458 -0
- package/opencode/caret.plugin.ts +83 -456
- package/opencode/caret.plugin.v2.ts +243 -0
- package/opencode/caret.tui.ts +75 -0
- package/opencode/index.ts +6 -5
- package/opencode/permission.ts +102 -0
- package/package.json +2 -1
- package/ui/dist/assets/{angular-html-BECXXPyr.js → angular-html-BVXNFElL.js} +1 -1
- package/ui/dist/assets/{angular-ts-CJKAKY9F.js → angular-ts-BBdNNVOV.js} +1 -1
- package/ui/dist/assets/{apl-CCcfDDqZ.js → apl-DN-9LSnm.js} +1 -1
- package/ui/dist/assets/{astro-DKCnAnVR.js → astro-DuMkehjz.js} +1 -1
- package/ui/dist/assets/{blade-DQ_JdT2N.js → blade-B_5LVqnt.js} +1 -1
- package/ui/dist/assets/{c-D3TwszVj.js → c-5LpWn0DJ.js} +1 -1
- package/ui/dist/assets/{chapel-C-2sPcCX.js → chapel-qw3-lXMw.js} +1 -1
- package/ui/dist/assets/{cobol-BPmo_4f_.js → cobol-BAnr3Azd.js} +1 -1
- package/ui/dist/assets/{coffee-DMTsNDVt.js → coffee-CGRFpAgb.js} +1 -1
- package/ui/dist/assets/{cpp-BJr6_Y8R.js → cpp-olfuBwwP.js} +1 -1
- package/ui/dist/assets/{crystal-pIWESUkD.js → crystal-qe9b46tp.js} +1 -1
- package/ui/dist/assets/{css-DRd9Q4Li.js → css-DkXCh44l.js} +1 -1
- package/ui/dist/assets/{dist-aKlRxpD_.js → dist-B1RcczPQ.js} +1 -1
- package/ui/dist/assets/{dist-BGfsV7n2.js → dist-BnIbJslb.js} +1 -1
- package/ui/dist/assets/{dist-BJ7SLVNC.js → dist-BnbtGh0F.js} +1 -1
- package/ui/dist/assets/{dist-BFQBMwmz.js → dist-CFbPxPI-.js} +1 -1
- package/ui/dist/assets/{dist-DoJTqy3z.js → dist-CHZr2-kE.js} +1 -1
- package/ui/dist/assets/{dist-BNr0c85A.js → dist-CMOjVm1q.js} +1 -1
- package/ui/dist/assets/{dist-C6t67BjE.js → dist-CfHYS987.js} +1 -1
- package/ui/dist/assets/{dist-CU5aMlNo.js → dist-D8lZm-8I.js} +1 -1
- package/ui/dist/assets/{dist-CD25BkNS.js → dist-KWgwFCxG.js} +1 -1
- package/ui/dist/assets/{dist-RKKeH1zs.js → dist-iPRbc51X.js} +1 -1
- package/ui/dist/assets/{edge-DeNk4F2G.js → edge-hytDa3H3.js} +1 -1
- package/ui/dist/assets/{elixir-DHc-DOO8.js → elixir-DnjerbFh.js} +1 -1
- package/ui/dist/assets/{elm-CJRqEcvz.js → elm-Cy60APyX.js} +1 -1
- package/ui/dist/assets/{erb-BEWGTHDA.js → erb-Rclm0zaT.js} +1 -1
- package/ui/dist/assets/{git-rebase-DzlcWhg9.js → git-rebase-Be7W4GuM.js} +1 -1
- package/ui/dist/assets/{glimmer-js-B-aWCRIZ.js → glimmer-js-DAN9CyDo.js} +1 -1
- package/ui/dist/assets/{glimmer-ts-DcjmPyQn.js → glimmer-ts-Bd4Ltw2M.js} +1 -1
- package/ui/dist/assets/{glsl-C9FOT-Uv.js → glsl-DIrrPSk8.js} +1 -1
- package/ui/dist/assets/{graphql-BgKSoRp0.js → graphql-CBXqF557.js} +1 -1
- package/ui/dist/assets/{hack-BOedpUUE.js → hack-D_K_3nhB.js} +1 -1
- package/ui/dist/assets/{haml-Cpngw1vd.js → haml-g7mpgDGW.js} +1 -1
- package/ui/dist/assets/{handlebars-CpW1IyBg.js → handlebars-DRWwCyqd.js} +1 -1
- package/ui/dist/assets/{html-CV142D8s.js → html-Byvm88Y2.js} +1 -1
- package/ui/dist/assets/{html-derivative-Df7h1Nuw.js → html-derivative-DpLoxBsM.js} +1 -1
- package/ui/dist/assets/{http-Cneddv9N.js → http-DGq_mf9b.js} +1 -1
- package/ui/dist/assets/{hurl-D6TS55N6.js → hurl-qkfW8ihO.js} +1 -1
- package/ui/dist/assets/{index-Ci9yCNtV.js → index-BwtwrzNC.js} +4 -4
- package/ui/dist/assets/{java-BicD3zQH.js → java-BHSPy0gM.js} +1 -1
- package/ui/dist/assets/{javascript-l6vKHWBV.js → javascript-BGu3ZEma.js} +1 -1
- package/ui/dist/assets/{jinja-BJF2iot2.js → jinja-BkUiOuUL.js} +1 -1
- package/ui/dist/assets/{jison-DiXPFJAT.js → jison-DQ_FQMYl.js} +1 -1
- package/ui/dist/assets/{json-BDRuI4UV.js → json-BHyU93c5.js} +1 -1
- package/ui/dist/assets/{jsx-BvfdkIOy.js → jsx-DhxI-Xjj.js} +1 -1
- package/ui/dist/assets/{julia-bXAhreI5.js → julia-BpY5TdkA.js} +1 -1
- package/ui/dist/assets/{just-BOxCfXDg.js → just-Dy9X30SL.js} +1 -1
- package/ui/dist/assets/{latex-B9OiTBLF.js → latex-DnqjVTTq.js} +1 -1
- package/ui/dist/assets/{liquid-DuLKDeWE.js → liquid-DSLF1wIz.js} +1 -1
- package/ui/dist/assets/{lua-Dbh9FQm_.js → lua-CWQ1tn9q.js} +1 -1
- package/ui/dist/assets/{marko--tjEBgDM.js → marko-CVOaD8Lr.js} +1 -1
- package/ui/dist/assets/{mdc-ByNJpl3m.js → mdc-CorrsEtK.js} +1 -1
- package/ui/dist/assets/{nginx-D4wsmFK9.js → nginx--n2A1zuK.js} +1 -1
- package/ui/dist/assets/{nim-Cy8RxisP.js → nim-eb5p3yB7.js} +1 -1
- package/ui/dist/assets/{org-CAm7o61d.js → org-Cnlel0O3.js} +1 -1
- package/ui/dist/assets/{perl-BAvaFGKe.js → perl-DDWGMuBV.js} +1 -1
- package/ui/dist/assets/{php-DpAf3w7u.js → php-Bz2SRtKk.js} +1 -1
- package/ui/dist/assets/{pug-Dm7otyJU.js → pug-cGPMemK2.js} +1 -1
- package/ui/dist/assets/{qml-D4rWQQKC.js → qml-B4Hbmvdi.js} +1 -1
- package/ui/dist/assets/{r-DMKUX8qf.js → r-BuuNGonz.js} +1 -1
- package/ui/dist/assets/{razor-BIUJkuf_.js → razor-CHpWr94T.js} +1 -1
- package/ui/dist/assets/{regexp-D8W_pVHj.js → regexp-9s1znDWq.js} +1 -1
- package/ui/dist/assets/{rst-aFr0DP5H.js → rst-BBsXWDuP.js} +1 -1
- package/ui/dist/assets/{ruby-BzH6kUKr.js → ruby-pQqViN30.js} +1 -1
- package/ui/dist/assets/{sas-CDk9DyCV.js → sas-C4SSIJLx.js} +1 -1
- package/ui/dist/assets/{scss-DABbvU5W.js → scss-D8e130Cq.js} +1 -1
- package/ui/dist/assets/{shellscript-B0ObP3K-.js → shellscript-CHUzbqv1.js} +1 -1
- package/ui/dist/assets/{shellsession-2sv3Jq0j.js → shellsession-BulTL_bH.js} +1 -1
- package/ui/dist/assets/{soy-irSRV4oq.js → soy-zGj4V7Ec.js} +1 -1
- package/ui/dist/assets/{sql-DJCHoKvP.js → sql-CnJYFgwb.js} +1 -1
- package/ui/dist/assets/{stata-a8YnzbIc.js → stata-BXDojqs4.js} +1 -1
- package/ui/dist/assets/{surrealql-DdXPOg8s.js → surrealql-3hRFCpnH.js} +1 -1
- package/ui/dist/assets/{svelte-ClAKUEZR.js → svelte-C8TzlKVd.js} +1 -1
- package/ui/dist/assets/{templ-NUCvvUBA.js → templ-D5KJNb8h.js} +1 -1
- package/ui/dist/assets/{tex-DwZeSAfC.js → tex-szX76TU8.js} +1 -1
- package/ui/dist/assets/{ts-tags-CFJEHYY_.js → ts-tags-H1v1spsZ.js} +1 -1
- package/ui/dist/assets/{tsx-B934yX2T.js → tsx-CoyAoKjY.js} +1 -1
- package/ui/dist/assets/{twig-NFkkOj3w.js → twig-pqXRFgM9.js} +1 -1
- package/ui/dist/assets/{typescript-7bmMC_xU.js → typescript-CNl_4SRH.js} +1 -1
- package/ui/dist/assets/{typst-BQEjp9tn.js → typst-Dr_K1Jqf.js} +1 -1
- package/ui/dist/assets/{vue-JqD9ieOF.js → vue-Wl1CPRGX.js} +1 -1
- package/ui/dist/assets/{vue-html-CT4XQRX1.js → vue-html-F5NdFsoh.js} +1 -1
- package/ui/dist/assets/{vue-vine-DIR27Dmn.js → vue-vine-YWSKUX3v.js} +1 -1
- package/ui/dist/assets/{xml-DhsMbw-1.js → xml-crSJzq2-.js} +1 -1
- package/ui/dist/assets/{xsl-BvqsVUg9.js → xsl-D-YjJFxJ.js} +1 -1
- package/ui/dist/assets/{yaml-C8eWjK_Q.js → yaml-BZWjhwN4.js} +1 -1
- package/ui/dist/index.html +1 -1
package/dist/cli.js
CHANGED
|
@@ -2748,6 +2748,43 @@ var init_edit = __esm(() => {
|
|
|
2748
2748
|
});
|
|
2749
2749
|
|
|
2750
2750
|
// node_modules/jsonc-parser/lib/esm/main.js
|
|
2751
|
+
function printParseErrorCode(code) {
|
|
2752
|
+
switch (code) {
|
|
2753
|
+
case 1:
|
|
2754
|
+
return "InvalidSymbol";
|
|
2755
|
+
case 2:
|
|
2756
|
+
return "InvalidNumberFormat";
|
|
2757
|
+
case 3:
|
|
2758
|
+
return "PropertyNameExpected";
|
|
2759
|
+
case 4:
|
|
2760
|
+
return "ValueExpected";
|
|
2761
|
+
case 5:
|
|
2762
|
+
return "ColonExpected";
|
|
2763
|
+
case 6:
|
|
2764
|
+
return "CommaExpected";
|
|
2765
|
+
case 7:
|
|
2766
|
+
return "CloseBraceExpected";
|
|
2767
|
+
case 8:
|
|
2768
|
+
return "CloseBracketExpected";
|
|
2769
|
+
case 9:
|
|
2770
|
+
return "EndOfFileExpected";
|
|
2771
|
+
case 10:
|
|
2772
|
+
return "InvalidCommentToken";
|
|
2773
|
+
case 11:
|
|
2774
|
+
return "UnexpectedEndOfComment";
|
|
2775
|
+
case 12:
|
|
2776
|
+
return "UnexpectedEndOfString";
|
|
2777
|
+
case 13:
|
|
2778
|
+
return "UnexpectedEndOfNumber";
|
|
2779
|
+
case 14:
|
|
2780
|
+
return "InvalidUnicode";
|
|
2781
|
+
case 15:
|
|
2782
|
+
return "InvalidEscapeCharacter";
|
|
2783
|
+
case 16:
|
|
2784
|
+
return "InvalidCharacter";
|
|
2785
|
+
}
|
|
2786
|
+
return "<unknown ParseErrorCode>";
|
|
2787
|
+
}
|
|
2751
2788
|
function modify(text, path, value, options) {
|
|
2752
2789
|
return setProperty(text, path, value, options);
|
|
2753
2790
|
}
|
|
@@ -2771,12 +2808,13 @@ function applyEdits(text, edits) {
|
|
|
2771
2808
|
}
|
|
2772
2809
|
return text;
|
|
2773
2810
|
}
|
|
2774
|
-
var ScanError, SyntaxKind, parse3, ParseErrorCode;
|
|
2811
|
+
var createScanner2, ScanError, SyntaxKind, parse3, parseTree2, findNodeAtLocation2, ParseErrorCode;
|
|
2775
2812
|
var init_main = __esm(() => {
|
|
2776
2813
|
init_format();
|
|
2777
2814
|
init_edit();
|
|
2778
2815
|
init_scanner();
|
|
2779
2816
|
init_parser();
|
|
2817
|
+
createScanner2 = createScanner;
|
|
2780
2818
|
(function(ScanError) {
|
|
2781
2819
|
ScanError[ScanError["None"] = 0] = "None";
|
|
2782
2820
|
ScanError[ScanError["UnexpectedEndOfComment"] = 1] = "UnexpectedEndOfComment";
|
|
@@ -2806,6 +2844,8 @@ var init_main = __esm(() => {
|
|
|
2806
2844
|
SyntaxKind[SyntaxKind["EOF"] = 17] = "EOF";
|
|
2807
2845
|
})(SyntaxKind || (SyntaxKind = {}));
|
|
2808
2846
|
parse3 = parse2;
|
|
2847
|
+
parseTree2 = parseTree;
|
|
2848
|
+
findNodeAtLocation2 = findNodeAtLocation;
|
|
2809
2849
|
(function(ParseErrorCode) {
|
|
2810
2850
|
ParseErrorCode[ParseErrorCode["InvalidSymbol"] = 1] = "InvalidSymbol";
|
|
2811
2851
|
ParseErrorCode[ParseErrorCode["InvalidNumberFormat"] = 2] = "InvalidNumberFormat";
|
|
@@ -2827,7 +2867,7 @@ var init_main = __esm(() => {
|
|
|
2827
2867
|
});
|
|
2828
2868
|
|
|
2829
2869
|
// src/adapters/opencode/paths.ts
|
|
2830
|
-
import { existsSync, readdirSync } from "fs";
|
|
2870
|
+
import { existsSync, readdirSync, realpathSync } from "fs";
|
|
2831
2871
|
import { homedir as homedir3 } from "os";
|
|
2832
2872
|
import { basename, dirname, join as join5 } from "path";
|
|
2833
2873
|
function localPluginSpecifier(repoDir) {
|
|
@@ -2849,14 +2889,24 @@ function opencodeConfigDir() {
|
|
|
2849
2889
|
const xdg = process.env.XDG_CONFIG_HOME?.trim();
|
|
2850
2890
|
return join5(xdg || join5(homedir3(), ".config"), "opencode");
|
|
2851
2891
|
}
|
|
2852
|
-
function resolveConfigFile(configDir) {
|
|
2853
|
-
for (const name of
|
|
2892
|
+
function resolveConfigFile(configDir, names) {
|
|
2893
|
+
for (const name of names) {
|
|
2854
2894
|
const p = join5(configDir, name);
|
|
2855
2895
|
if (existsSync(p))
|
|
2856
2896
|
return p;
|
|
2857
2897
|
}
|
|
2858
2898
|
return join5(configDir, "opencode.json");
|
|
2859
2899
|
}
|
|
2900
|
+
function existingConfigFiles(configDir) {
|
|
2901
|
+
const seen = new Set;
|
|
2902
|
+
return CONFIG_FILENAMES.map((name) => join5(configDir, name)).filter((p) => existsSync(p)).filter((p) => {
|
|
2903
|
+
const real = realpathSync(p);
|
|
2904
|
+
if (seen.has(real))
|
|
2905
|
+
return false;
|
|
2906
|
+
seen.add(real);
|
|
2907
|
+
return true;
|
|
2908
|
+
});
|
|
2909
|
+
}
|
|
2860
2910
|
function commandDir(configDir) {
|
|
2861
2911
|
return join5(configDir, COMMAND_DIRNAME);
|
|
2862
2912
|
}
|
|
@@ -2874,15 +2924,48 @@ function existingLegacyInstallFiles(configDir) {
|
|
|
2874
2924
|
const commands = entries.filter((e) => !e.isDirectory() && e.name.startsWith(COMMAND_NAMESPACE)).filter((e) => e.name.endsWith(".md")).map((e) => join5(configDir, LEGACY_COMMAND_DIRNAME, e.name)).sort();
|
|
2875
2925
|
return [...plugins, ...commands].filter((p) => existsSync(p));
|
|
2876
2926
|
}
|
|
2877
|
-
function
|
|
2927
|
+
function opencodeCacheRoot() {
|
|
2878
2928
|
const xdg = process.env.XDG_CACHE_HOME?.trim();
|
|
2879
|
-
return join5(xdg || join5(homedir3(), ".cache"), "opencode"
|
|
2929
|
+
return join5(xdg || join5(homedir3(), ".cache"), "opencode");
|
|
2930
|
+
}
|
|
2931
|
+
function opencodeCachePackagesDir() {
|
|
2932
|
+
return join5(opencodeCacheRoot(), "packages");
|
|
2933
|
+
}
|
|
2934
|
+
function opencodeNpmDir() {
|
|
2935
|
+
return join5(opencodeCacheRoot(), "npm");
|
|
2936
|
+
}
|
|
2937
|
+
function opencodeNpmCacheDir(pkg, version) {
|
|
2938
|
+
return join5(opencodeNpmDir(), `${pkg}@${version ?? "latest"}`);
|
|
2939
|
+
}
|
|
2940
|
+
function opencodeNpmLocalCacheDir(spec) {
|
|
2941
|
+
return join5(opencodeNpmDir(), spec);
|
|
2942
|
+
}
|
|
2943
|
+
function liveGenerationDir(dir) {
|
|
2944
|
+
let names;
|
|
2945
|
+
try {
|
|
2946
|
+
names = readdirSync(dir);
|
|
2947
|
+
} catch {
|
|
2948
|
+
return null;
|
|
2949
|
+
}
|
|
2950
|
+
const generations = names.filter((n) => /^\d+$/.test(n));
|
|
2951
|
+
if (generations.length === 0)
|
|
2952
|
+
return null;
|
|
2953
|
+
return join5(dir, generations.reduce((newest, name) => Number(name) > Number(newest) ? name : newest));
|
|
2880
2954
|
}
|
|
2881
2955
|
function opencodeCachePackageDir(specifier = CARET_PACKAGE) {
|
|
2882
2956
|
return join5(opencodeCachePackagesDir(), specifier);
|
|
2883
2957
|
}
|
|
2884
2958
|
function existingOpencodeCachePackageDirs(pkg = CARET_PACKAGE) {
|
|
2885
|
-
|
|
2959
|
+
return [
|
|
2960
|
+
...listCacheDirs(opencodeCachePackageDir(pkg)),
|
|
2961
|
+
...listCacheDirs(join5(opencodeNpmDir(), pkg))
|
|
2962
|
+
];
|
|
2963
|
+
}
|
|
2964
|
+
function opencodeRootPaths(pkg = CARET_PACKAGE) {
|
|
2965
|
+
const liveGenerations = listCacheDirs(join5(opencodeNpmDir(), pkg)).map(liveGenerationDir).filter((d) => d !== null);
|
|
2966
|
+
return [...listCacheDirs(opencodeCachePackageDir(pkg)), ...liveGenerations].map((d) => join5(d, "node_modules", pkg));
|
|
2967
|
+
}
|
|
2968
|
+
function listCacheDirs(bare) {
|
|
2886
2969
|
const parent = dirname(bare);
|
|
2887
2970
|
const leaf = basename(bare);
|
|
2888
2971
|
let names;
|
|
@@ -2901,9 +2984,53 @@ var init_paths2 = __esm(() => {
|
|
|
2901
2984
|
});
|
|
2902
2985
|
|
|
2903
2986
|
// src/adapters/opencode/config-plugin.ts
|
|
2904
|
-
|
|
2987
|
+
import { isDeepStrictEqual } from "util";
|
|
2988
|
+
function formattingOf(text) {
|
|
2989
|
+
const firstProperty = parseTree2(text)?.children?.[0];
|
|
2990
|
+
const indent = firstProperty ? text.slice(text.lastIndexOf(`
|
|
2991
|
+
`, firstProperty.offset) + 1, firstProperty.offset) : "";
|
|
2992
|
+
if (indent.startsWith("\t"))
|
|
2993
|
+
return { insertSpaces: false, tabSize: 1 };
|
|
2994
|
+
return { insertSpaces: true, tabSize: /^ +$/.test(indent) ? indent.length : 2 };
|
|
2995
|
+
}
|
|
2996
|
+
function configParseError(text) {
|
|
2997
|
+
const { root, errors } = lenientParse(text);
|
|
2998
|
+
const [first] = errors;
|
|
2999
|
+
if (first !== undefined)
|
|
3000
|
+
return `${printParseErrorCode(first.error)} at offset ${first.offset}`;
|
|
3001
|
+
const isObject = typeof root === "object" && root !== null && !Array.isArray(root);
|
|
3002
|
+
return root === undefined || isObject ? null : "not a JSON object";
|
|
3003
|
+
}
|
|
3004
|
+
function lenientParse(text) {
|
|
3005
|
+
const errors = [];
|
|
3006
|
+
const root = parse3(text.replace(/^\uFEFF/, ""), errors, {
|
|
3007
|
+
allowTrailingComma: true,
|
|
3008
|
+
allowEmptyContent: true
|
|
3009
|
+
});
|
|
3010
|
+
return { root, errors };
|
|
3011
|
+
}
|
|
3012
|
+
function checked(existing, edited, intent) {
|
|
3013
|
+
const { key, value } = intent;
|
|
3014
|
+
const { [key]: _, ...rest } = lenientParse(existing).root ?? {};
|
|
3015
|
+
const intended = value === undefined ? rest : { ...rest, [key]: value };
|
|
3016
|
+
const { root, errors } = lenientParse(edited);
|
|
3017
|
+
if (errors.length > 0 || !isDeepStrictEqual(root ?? {}, intended)) {
|
|
3018
|
+
throw new Error(`editing "${key}" would not give the intended config; is "${key}" listed more than once?`);
|
|
3019
|
+
}
|
|
3020
|
+
return edited;
|
|
3021
|
+
}
|
|
3022
|
+
function pluginArray(text, key) {
|
|
2905
3023
|
const cfg = parse3(text);
|
|
2906
|
-
|
|
3024
|
+
const arr = cfg?.[key];
|
|
3025
|
+
return Array.isArray(arr) ? arr : [];
|
|
3026
|
+
}
|
|
3027
|
+
function itemSpec(item) {
|
|
3028
|
+
if (typeof item === "string")
|
|
3029
|
+
return item;
|
|
3030
|
+
if (typeof item === "object" && item !== null && "package" in item && typeof item.package === "string") {
|
|
3031
|
+
return item.package;
|
|
3032
|
+
}
|
|
3033
|
+
return null;
|
|
2907
3034
|
}
|
|
2908
3035
|
function splitPluginSpecifier(spec) {
|
|
2909
3036
|
if (isLocalPluginSpecifier(spec))
|
|
@@ -2918,82 +3045,157 @@ function packageName(spec) {
|
|
|
2918
3045
|
return splitPluginSpecifier(spec).pkg;
|
|
2919
3046
|
}
|
|
2920
3047
|
function entryNames(entry, pkg) {
|
|
2921
|
-
|
|
3048
|
+
const spec = itemSpec(entry);
|
|
3049
|
+
return spec !== null && packageName(spec) === packageName(pkg);
|
|
2922
3050
|
}
|
|
2923
|
-
function addPluginToConfigText(existing, pkg) {
|
|
3051
|
+
function addPluginToConfigText(existing, pkg, key) {
|
|
2924
3052
|
const text = existing ?? `{}
|
|
2925
3053
|
`;
|
|
2926
|
-
const current = parse3(text)?.
|
|
3054
|
+
const current = parse3(text)?.[key];
|
|
2927
3055
|
if (Array.isArray(current)) {
|
|
2928
3056
|
if (current.some((e) => entryNames(e, pkg)))
|
|
2929
3057
|
return text;
|
|
2930
|
-
const path = [
|
|
3058
|
+
const path = [key, current.length];
|
|
2931
3059
|
const edits = modify(text, path, pkg, {
|
|
2932
3060
|
isArrayInsertion: true,
|
|
2933
|
-
formattingOptions:
|
|
3061
|
+
formattingOptions: formattingOf(text)
|
|
2934
3062
|
});
|
|
2935
|
-
return applyEdits(text, edits);
|
|
3063
|
+
return checked(text, applyEdits(text, edits), { key, value: [...current, pkg] });
|
|
2936
3064
|
}
|
|
2937
|
-
const edits = modify(text, [
|
|
2938
|
-
return applyEdits(text, edits);
|
|
2939
|
-
}
|
|
2940
|
-
function findPluginEntry(existing, pkg) {
|
|
2941
|
-
if (existing === null)
|
|
2942
|
-
return null;
|
|
2943
|
-
const entry = pluginArray(existing).find((e) => entryNames(e, pkg));
|
|
2944
|
-
return typeof entry === "string" ? entry : null;
|
|
3065
|
+
const edits = modify(text, [key], [pkg], { formattingOptions: formattingOf(text) });
|
|
3066
|
+
return checked(text, applyEdits(text, edits), { key, value: [pkg] });
|
|
2945
3067
|
}
|
|
2946
|
-
function
|
|
2947
|
-
return
|
|
3068
|
+
function pluginItemSpecs(existing, key) {
|
|
3069
|
+
return pluginArray(existing, key).map(itemSpec);
|
|
2948
3070
|
}
|
|
2949
|
-
function setPluginVersionInConfigText(existing,
|
|
2950
|
-
const
|
|
2951
|
-
const
|
|
2952
|
-
|
|
3071
|
+
function setPluginVersionInConfigText(existing, target) {
|
|
3072
|
+
const { pkg, version, key } = target;
|
|
3073
|
+
const arr = pluginArray(existing, key);
|
|
3074
|
+
const index = arr.findIndex((e) => entryNames(e, pkg));
|
|
3075
|
+
if (index === -1)
|
|
2953
3076
|
return existing;
|
|
2954
|
-
const
|
|
2955
|
-
|
|
2956
|
-
|
|
2957
|
-
|
|
2958
|
-
|
|
2959
|
-
const arr = pluginArray(existing);
|
|
2960
|
-
|
|
3077
|
+
const spec = `${packageName(itemSpec(arr[index]) ?? pkg)}@${version}`;
|
|
3078
|
+
return setPluginItemSpec(existing, { key, index, spec });
|
|
3079
|
+
}
|
|
3080
|
+
function setPluginItemSpec(existing, target) {
|
|
3081
|
+
const { key, index, spec } = target;
|
|
3082
|
+
const arr = pluginArray(existing, key);
|
|
3083
|
+
const item = arr[index];
|
|
3084
|
+
const isObject = typeof item === "object" && item !== null;
|
|
3085
|
+
const path = isObject ? [key, index, "package"] : [key, index];
|
|
3086
|
+
const edits = modify(existing, path, spec, { formattingOptions: formattingOf(existing) });
|
|
3087
|
+
const swapped = isObject ? { ...item, package: spec } : spec;
|
|
3088
|
+
const next = arr.map((e, i) => i === index ? swapped : e);
|
|
3089
|
+
return checked(existing, applyEdits(existing, edits), { key, value: next });
|
|
3090
|
+
}
|
|
3091
|
+
function rewritePluginArray(existing, key, keep) {
|
|
3092
|
+
const arr = pluginArray(existing, key);
|
|
3093
|
+
const dropped = arr.flatMap((item, i) => keep(item, i) ? [] : [i]);
|
|
3094
|
+
if (dropped.length === 0)
|
|
2961
3095
|
return existing;
|
|
2962
|
-
const next = arr.filter((
|
|
2963
|
-
|
|
2964
|
-
|
|
3096
|
+
const next = arr.filter((_, i) => !dropped.includes(i));
|
|
3097
|
+
if (key === "plugins" && next.length === 0) {
|
|
3098
|
+
return checked(existing, deleteNode(existing, [key]), { key, value: undefined });
|
|
3099
|
+
}
|
|
3100
|
+
const edited = dropped.reduceRight((text, i) => deleteNode(text, [key, i]), existing);
|
|
3101
|
+
return checked(existing, edited, { key, value: next });
|
|
3102
|
+
}
|
|
3103
|
+
function commaAfter(text, from) {
|
|
3104
|
+
const scanner = createScanner2(text, true);
|
|
3105
|
+
scanner.setPosition(from);
|
|
3106
|
+
scanner.scan();
|
|
3107
|
+
return text[scanner.getTokenOffset()] === "," ? scanner.getTokenOffset() : null;
|
|
3108
|
+
}
|
|
3109
|
+
function wholeLine(text, start, end) {
|
|
3110
|
+
let lineStart = start;
|
|
3111
|
+
let lineEnd = end;
|
|
3112
|
+
while (lineStart > 0 && (text[lineStart - 1] === " " || text[lineStart - 1] === "\t"))
|
|
3113
|
+
lineStart--;
|
|
3114
|
+
while (lineEnd < text.length && (text[lineEnd] === " " || text[lineEnd] === "\t"))
|
|
3115
|
+
lineEnd++;
|
|
3116
|
+
const eol = text.startsWith(`\r
|
|
3117
|
+
`, lineEnd) ? 2 : text[lineEnd] === `
|
|
3118
|
+
` ? 1 : 0;
|
|
3119
|
+
const aloneOnLine = (lineStart === 0 || text[lineStart - 1] === `
|
|
3120
|
+
`) && (lineEnd === text.length || eol > 0);
|
|
3121
|
+
return aloneOnLine ? { offset: lineStart, length: lineEnd + eol - lineStart, content: "" } : { offset: start, length: end - start, content: "" };
|
|
3122
|
+
}
|
|
3123
|
+
function deleteNode(text, path) {
|
|
3124
|
+
const root = parseTree2(text);
|
|
3125
|
+
const found = root && findNodeAtLocation2(root, path);
|
|
3126
|
+
const node = typeof path.at(-1) === "number" ? found : found?.parent;
|
|
3127
|
+
const siblings = node?.parent?.children;
|
|
3128
|
+
if (!node || !siblings)
|
|
3129
|
+
return text;
|
|
3130
|
+
const i = siblings.indexOf(node);
|
|
3131
|
+
const end = node.offset + node.length;
|
|
3132
|
+
const edits = [];
|
|
3133
|
+
const trailingComma = commaAfter(text, end);
|
|
3134
|
+
if (trailingComma !== null && text.slice(end, trailingComma).trim() === "") {
|
|
3135
|
+
edits.push(wholeLine(text, node.offset, trailingComma + 1));
|
|
3136
|
+
} else {
|
|
3137
|
+
edits.push(wholeLine(text, node.offset, end));
|
|
3138
|
+
const previousSibling = siblings[i - 1];
|
|
3139
|
+
const comma = trailingComma ?? (previousSibling ? commaAfter(text, previousSibling.offset + previousSibling.length) : null);
|
|
3140
|
+
if (comma !== null)
|
|
3141
|
+
edits.push({ offset: comma, length: 1, content: "" });
|
|
3142
|
+
}
|
|
3143
|
+
return applyEdits(text, edits);
|
|
2965
3144
|
}
|
|
2966
|
-
var
|
|
3145
|
+
var PLUGIN_KEYS;
|
|
2967
3146
|
var init_config_plugin = __esm(() => {
|
|
2968
3147
|
init_main();
|
|
2969
3148
|
init_paths2();
|
|
2970
|
-
|
|
3149
|
+
PLUGIN_KEYS = ["plugin", "plugins"];
|
|
2971
3150
|
});
|
|
2972
3151
|
|
|
2973
3152
|
// src/adapters/opencode/entries.ts
|
|
2974
3153
|
import { existsSync as existsSync2, readFileSync as readFileSync3 } from "fs";
|
|
2975
|
-
import { join as join6 } from "path";
|
|
3154
|
+
import { basename as basename2, join as join6 } from "path";
|
|
2976
3155
|
function isCaretCheckout(dir) {
|
|
2977
3156
|
return existsSync2(join6(dir, "opencode", "caret.plugin.ts"));
|
|
2978
3157
|
}
|
|
2979
3158
|
function caretEntries(text, isCheckout) {
|
|
2980
|
-
|
|
2981
|
-
|
|
2982
|
-
|
|
2983
|
-
|
|
3159
|
+
if (text === null)
|
|
3160
|
+
return [];
|
|
3161
|
+
return PLUGIN_KEYS.flatMap((key) => pluginItemSpecs(text, key).flatMap((spec, index) => spec !== null && isCaretSpec(spec, isCheckout) ? [{ key, index, spec }] : []));
|
|
3162
|
+
}
|
|
3163
|
+
function isCaretSpec(spec, isCheckout) {
|
|
3164
|
+
const path = localSpecifierPath(spec);
|
|
3165
|
+
if (path === undefined)
|
|
3166
|
+
return splitPluginSpecifier(spec).pkg === CARET_PACKAGE;
|
|
3167
|
+
return isCaretTarball(path) || isCheckout(path);
|
|
3168
|
+
}
|
|
3169
|
+
function isCaretTarball(path) {
|
|
3170
|
+
const filename = basename2(path);
|
|
3171
|
+
return filename.startsWith(CARET_TARBALL_PREFIX) && filename.endsWith(".tgz");
|
|
2984
3172
|
}
|
|
2985
|
-
function
|
|
2986
|
-
return
|
|
3173
|
+
function dropEntries(text, drop) {
|
|
3174
|
+
return PLUGIN_KEYS.reduce((acc, key) => {
|
|
3175
|
+
const droppedIndices = new Set(drop.filter((e) => e.key === key).map((e) => e.index));
|
|
3176
|
+
return droppedIndices.size === 0 ? acc : rewritePluginArray(acc, key, (_, i) => !droppedIndices.has(i));
|
|
3177
|
+
}, text);
|
|
2987
3178
|
}
|
|
2988
|
-
function
|
|
2989
|
-
return caretEntries(
|
|
3179
|
+
function caretPackageEntry(text) {
|
|
3180
|
+
return caretEntries(text, () => false).find((e) => !isLocalPluginSpecifier(e.spec)) ?? null;
|
|
3181
|
+
}
|
|
3182
|
+
function readCaretEntries(configFiles, isCheckout) {
|
|
3183
|
+
return configFiles.flatMap((f) => caretEntries(readConfigText(f), isCheckout));
|
|
3184
|
+
}
|
|
3185
|
+
function readCaretEntry(configFiles) {
|
|
3186
|
+
return configFiles.map((f) => caretPackageEntry(readConfigText(f))).find((e) => e !== null) ?? null;
|
|
3187
|
+
}
|
|
3188
|
+
function readLoadedCaretEntry(configFiles) {
|
|
3189
|
+
return readCaretEntries(configFiles, isCaretCheckout)[0] ?? null;
|
|
2990
3190
|
}
|
|
2991
3191
|
function readConfigText(path) {
|
|
2992
3192
|
return existsSync2(path) ? readFileSync3(path, "utf-8") : null;
|
|
2993
3193
|
}
|
|
3194
|
+
var CARET_TARBALL_PREFIX;
|
|
2994
3195
|
var init_entries = __esm(() => {
|
|
2995
3196
|
init_config_plugin();
|
|
2996
3197
|
init_paths2();
|
|
3198
|
+
CARET_TARBALL_PREFIX = `${CARET_PACKAGE.slice(1).replace("/", "-")}-`;
|
|
2997
3199
|
});
|
|
2998
3200
|
|
|
2999
3201
|
// src/lib/semver.ts
|
|
@@ -3015,6 +3217,126 @@ function isNewer(latest, current) {
|
|
|
3015
3217
|
return a2 > b2;
|
|
3016
3218
|
}
|
|
3017
3219
|
|
|
3220
|
+
// src/adapters/opencode/host.ts
|
|
3221
|
+
import { accessSync, constants, realpathSync as realpathSync2, statSync } from "fs";
|
|
3222
|
+
import { basename as basename3, delimiter, isAbsolute, join as join7 } from "path";
|
|
3223
|
+
function parseOpencodeVersion(stdout) {
|
|
3224
|
+
return parseVersionTriple(stdout.trim().replace(/^opencode /, ""));
|
|
3225
|
+
}
|
|
3226
|
+
function readOpencodeVersion({
|
|
3227
|
+
bin,
|
|
3228
|
+
timeoutMs = OPENCODE_VERSION_TIMEOUT_MS
|
|
3229
|
+
}) {
|
|
3230
|
+
try {
|
|
3231
|
+
const res = Bun.spawnSync([bin, "--version"], {
|
|
3232
|
+
timeout: timeoutMs,
|
|
3233
|
+
stdin: "ignore",
|
|
3234
|
+
stderr: "ignore"
|
|
3235
|
+
});
|
|
3236
|
+
return res.exitCode === 0 ? parseOpencodeVersion(res.stdout.toString()) : null;
|
|
3237
|
+
} catch {
|
|
3238
|
+
return null;
|
|
3239
|
+
}
|
|
3240
|
+
}
|
|
3241
|
+
function opencodeBinsOnPath(path) {
|
|
3242
|
+
const seen = new Set;
|
|
3243
|
+
return path.split(delimiter).filter((dir) => isAbsolute(dir)).map((dir) => join7(dir, "opencode")).filter((bin) => {
|
|
3244
|
+
const real = executableRealpath(bin);
|
|
3245
|
+
if (real === null || seen.has(real))
|
|
3246
|
+
return false;
|
|
3247
|
+
seen.add(real);
|
|
3248
|
+
return true;
|
|
3249
|
+
});
|
|
3250
|
+
}
|
|
3251
|
+
function executableRealpath(bin) {
|
|
3252
|
+
try {
|
|
3253
|
+
if (!statSync(bin).isFile())
|
|
3254
|
+
return null;
|
|
3255
|
+
accessSync(bin, constants.X_OK);
|
|
3256
|
+
return realpathSync2(bin);
|
|
3257
|
+
} catch {
|
|
3258
|
+
return null;
|
|
3259
|
+
}
|
|
3260
|
+
}
|
|
3261
|
+
function readOpencodeHosts(path = process.env.PATH ?? "", timeoutMs) {
|
|
3262
|
+
return opencodeBinsOnPath(path).map((bin) => ({
|
|
3263
|
+
bin,
|
|
3264
|
+
version: readOpencodeVersion({ bin, timeoutMs })
|
|
3265
|
+
}));
|
|
3266
|
+
}
|
|
3267
|
+
function sharedHost(hosts) {
|
|
3268
|
+
const firstVersion = hosts[0]?.version ?? null;
|
|
3269
|
+
if (firstVersion === null)
|
|
3270
|
+
return null;
|
|
3271
|
+
return hosts.every((h) => h.version !== null && isV2Host(h.version) === isV2Host(firstVersion)) ? firstVersion : null;
|
|
3272
|
+
}
|
|
3273
|
+
function describeHosts(hosts) {
|
|
3274
|
+
if (hosts.length === 0)
|
|
3275
|
+
return "no `opencode` on PATH";
|
|
3276
|
+
return hosts.map(({ bin, version }) => version === null ? `couldn't read \`${bin} --version\`` : `OpenCode ${version.join(".")} at ${bin}`).join(", ");
|
|
3277
|
+
}
|
|
3278
|
+
function isV2Host(version) {
|
|
3279
|
+
return version !== null && version[0] >= 2;
|
|
3280
|
+
}
|
|
3281
|
+
function pluginKeyFor(version) {
|
|
3282
|
+
return isV2Host(version) ? "plugins" : "plugin";
|
|
3283
|
+
}
|
|
3284
|
+
function hostConfigFilenames(host) {
|
|
3285
|
+
return host !== null && !isV2Host(host) ? CONFIG_FILENAMES : CONFIG_FILENAMES.filter((name) => name !== "config.json");
|
|
3286
|
+
}
|
|
3287
|
+
function loadedConfigFiles(configFiles, host) {
|
|
3288
|
+
const names = hostConfigFilenames(host);
|
|
3289
|
+
return configFiles.filter((p) => names.includes(basename3(p)));
|
|
3290
|
+
}
|
|
3291
|
+
function hostCheck(hosts, keys, ignoredFiles) {
|
|
3292
|
+
const base = { id: "opencode-host", title: "OpenCode host" };
|
|
3293
|
+
const found = describeHosts(hosts);
|
|
3294
|
+
if (hosts.every((h) => h.version === null)) {
|
|
3295
|
+
return { ...base, status: "unknown", detail: "", reason: found };
|
|
3296
|
+
}
|
|
3297
|
+
const faults = [];
|
|
3298
|
+
const remedies = [];
|
|
3299
|
+
for (const { bin, version } of hosts) {
|
|
3300
|
+
if (version === null || isV2Host(version))
|
|
3301
|
+
continue;
|
|
3302
|
+
const shown = version.join(".");
|
|
3303
|
+
if (isNewer(V1_FLOOR, shown)) {
|
|
3304
|
+
faults.push(`OpenCode ${shown} at ${bin} is older than ${V1_FLOOR}, the first that loads caret`);
|
|
3305
|
+
remedies.push(`upgrade OpenCode to ${V1_FLOOR} or later`);
|
|
3306
|
+
}
|
|
3307
|
+
}
|
|
3308
|
+
const wrongKey = keys.find((k) => k !== pluginKeyFor(sharedHost(hosts)));
|
|
3309
|
+
if (wrongKey === "plugins") {
|
|
3310
|
+
faults.push("caret is in `plugins`, which only OpenCode v2 loads, and not every `opencode` on PATH reads as v2 (v1 before 1.18.16 refuses to start with the key)");
|
|
3311
|
+
remedies.push("run `caret install`");
|
|
3312
|
+
} else if (wrongKey === "plugin") {
|
|
3313
|
+
faults.push("caret is in the legacy `plugin` key on OpenCode v2");
|
|
3314
|
+
remedies.push("run `caret install`");
|
|
3315
|
+
}
|
|
3316
|
+
if (ignoredFiles.length > 0) {
|
|
3317
|
+
const names = ignoredFiles.map((p) => basename3(p)).join(", ");
|
|
3318
|
+
faults.push(`caret is in ${names}, which OpenCode v2 doesn't load`);
|
|
3319
|
+
remedies.push("run `caret install`");
|
|
3320
|
+
}
|
|
3321
|
+
if (faults.length === 0) {
|
|
3322
|
+
return {
|
|
3323
|
+
...base,
|
|
3324
|
+
status: "pass",
|
|
3325
|
+
detail: `${found} \u2014 loads caret from \`${keys.join("`, `")}\``
|
|
3326
|
+
};
|
|
3327
|
+
}
|
|
3328
|
+
return {
|
|
3329
|
+
...base,
|
|
3330
|
+
status: "fail",
|
|
3331
|
+
detail: `${found} \u2014 ${faults.join("; ")}`,
|
|
3332
|
+
remedy: [...new Set(remedies)].join("; ")
|
|
3333
|
+
};
|
|
3334
|
+
}
|
|
3335
|
+
var OPENCODE_VERSION_TIMEOUT_MS = 5000, V1_FLOOR = "1.3.4";
|
|
3336
|
+
var init_host = __esm(() => {
|
|
3337
|
+
init_paths2();
|
|
3338
|
+
});
|
|
3339
|
+
|
|
3018
3340
|
// node_modules/zod/v4/core/util.js
|
|
3019
3341
|
function getEnumValues(entries) {
|
|
3020
3342
|
const numericValues = Object.values(entries).filter((v) => typeof v === "number");
|
|
@@ -3656,9 +3978,12 @@ function config(newConfig) {
|
|
|
3656
3978
|
Object.assign(globalConfig, newConfig);
|
|
3657
3979
|
return globalConfig;
|
|
3658
3980
|
}
|
|
3659
|
-
var _a, _zodDesc, _E, $ZodAsyncError, $ZodEncodeError, globalConfig;
|
|
3981
|
+
var _a, NEVER, _zodDesc, _E, $ZodAsyncError, $ZodEncodeError, globalConfig;
|
|
3660
3982
|
var init_core = __esm(() => {
|
|
3661
3983
|
init_util2();
|
|
3984
|
+
NEVER = /* @__PURE__ */ Object.freeze({
|
|
3985
|
+
status: "aborted"
|
|
3986
|
+
});
|
|
3662
3987
|
_zodDesc = { value: undefined, enumerable: false };
|
|
3663
3988
|
_E = "captureStackTrace" in Error ? Error : null;
|
|
3664
3989
|
$ZodAsyncError = class $ZodAsyncError extends Error {
|
|
@@ -9260,6 +9585,7 @@ var init_coerce = __esm(() => {
|
|
|
9260
9585
|
|
|
9261
9586
|
// node_modules/zod/v4/classic/external.js
|
|
9262
9587
|
var init_external = __esm(() => {
|
|
9588
|
+
init_core2();
|
|
9263
9589
|
init_core2();
|
|
9264
9590
|
init_locales();
|
|
9265
9591
|
init_iso();
|
|
@@ -9282,7 +9608,7 @@ var package_default;
|
|
|
9282
9608
|
var init_package = __esm(() => {
|
|
9283
9609
|
package_default = {
|
|
9284
9610
|
name: "@macintacos/caret",
|
|
9285
|
-
version: "1.
|
|
9611
|
+
version: "1.2.0",
|
|
9286
9612
|
license: "MIT",
|
|
9287
9613
|
type: "module",
|
|
9288
9614
|
description: "A Claude Code plugin that replaces the terminal plan-approval prompt with a local web UI for inline review, annotation, and approval.",
|
|
@@ -9333,6 +9659,7 @@ var init_package = __esm(() => {
|
|
|
9333
9659
|
"@laststance/tailwind-suggest-canonical-classes": "^0.1.1",
|
|
9334
9660
|
"@lezer/highlight": "^1.2.3",
|
|
9335
9661
|
"@modelcontextprotocol/sdk": "^1.30.0",
|
|
9662
|
+
"@opencode/plugin": "^2.0.18",
|
|
9336
9663
|
"@pierre/diffs": "1.3.6",
|
|
9337
9664
|
"@pierre/trees": "1.0.0-beta.6",
|
|
9338
9665
|
"@playwright/test": "^1.62.1",
|
|
@@ -9445,7 +9772,7 @@ var init_upstream = __esm(() => {
|
|
|
9445
9772
|
|
|
9446
9773
|
// src/adapters/opencode/upgrade.ts
|
|
9447
9774
|
import { existsSync as existsSync3, rmSync } from "fs";
|
|
9448
|
-
import { join as
|
|
9775
|
+
import { join as join8 } from "path";
|
|
9449
9776
|
function upgradeVerdict(input) {
|
|
9450
9777
|
const { entry, cached, published } = input;
|
|
9451
9778
|
if (published === null) {
|
|
@@ -9504,25 +9831,34 @@ function upgradeCheck(verdict) {
|
|
|
9504
9831
|
}
|
|
9505
9832
|
}
|
|
9506
9833
|
async function readUpgradeVerdict(deps) {
|
|
9507
|
-
const entry = readCaretEntry(deps.
|
|
9834
|
+
const entry = readCaretEntry(deps.configFiles);
|
|
9835
|
+
const cacheDir = deps.cacheDir ?? ((e) => caretCacheDir(e, deps.host));
|
|
9508
9836
|
return upgradeVerdict({
|
|
9509
|
-
entry,
|
|
9510
|
-
cached: readEntryCachedVersion(entry,
|
|
9837
|
+
entry: entry?.spec ?? null,
|
|
9838
|
+
cached: readEntryCachedVersion(entry, cacheDir),
|
|
9511
9839
|
published: await (deps.published ?? publishedCaretVersion)()
|
|
9512
9840
|
});
|
|
9513
9841
|
}
|
|
9514
|
-
function
|
|
9515
|
-
|
|
9842
|
+
function caretCacheDir(entry, host) {
|
|
9843
|
+
if (entry.key === "plugin" && !isV2Host(host ?? null)) {
|
|
9844
|
+
return opencodeCachePackageDir(entry.spec);
|
|
9845
|
+
}
|
|
9846
|
+
if (isLocalPluginSpecifier(entry.spec)) {
|
|
9847
|
+
return liveGenerationDir(opencodeNpmLocalCacheDir(entry.spec));
|
|
9848
|
+
}
|
|
9849
|
+
const { pkg, version } = splitPluginSpecifier(entry.spec);
|
|
9850
|
+
return liveGenerationDir(opencodeNpmCacheDir(pkg, version));
|
|
9516
9851
|
}
|
|
9517
|
-
function readEntryCachedVersion(entry, cacheDir =
|
|
9518
|
-
|
|
9852
|
+
function readEntryCachedVersion(entry, cacheDir = caretCacheDir) {
|
|
9853
|
+
const dir = entry === null ? null : cacheDir(entry);
|
|
9854
|
+
return dir === null ? null : readCachedCaretVersion(dir);
|
|
9519
9855
|
}
|
|
9520
9856
|
function readCachedCaretVersion(dir) {
|
|
9521
|
-
const installed = readJsonFileSync(
|
|
9857
|
+
const installed = readJsonFileSync(join8(dir, "node_modules", CARET_PACKAGE, "package.json"));
|
|
9522
9858
|
if (typeof installed?.version === "string" && installed.version.length > 0) {
|
|
9523
9859
|
return installed.version;
|
|
9524
9860
|
}
|
|
9525
|
-
const shim = readJsonFileSync(
|
|
9861
|
+
const shim = readJsonFileSync(join8(dir, "package.json"));
|
|
9526
9862
|
const requested = shim?.dependencies?.[CARET_PACKAGE];
|
|
9527
9863
|
return typeof requested === "string" && requested.length > 0 ? requested : null;
|
|
9528
9864
|
}
|
|
@@ -9539,6 +9875,7 @@ function clearCachedCaret(dirs = existingOpencodeCachePackageDirs()) {
|
|
|
9539
9875
|
var init_upgrade = __esm(() => {
|
|
9540
9876
|
init_config_plugin();
|
|
9541
9877
|
init_entries();
|
|
9878
|
+
init_host();
|
|
9542
9879
|
init_paths2();
|
|
9543
9880
|
init_json_file();
|
|
9544
9881
|
init_upstream();
|
|
@@ -9546,7 +9883,7 @@ var init_upgrade = __esm(() => {
|
|
|
9546
9883
|
|
|
9547
9884
|
// src/adapters/opencode/install.ts
|
|
9548
9885
|
import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
|
|
9549
|
-
import { join as
|
|
9886
|
+
import { join as join9 } from "path";
|
|
9550
9887
|
function readOpencodeInstallState() {
|
|
9551
9888
|
const dir = opencodeConfigDir();
|
|
9552
9889
|
if (!existsSync4(dir)) {
|
|
@@ -9554,12 +9891,12 @@ function readOpencodeInstallState() {
|
|
|
9554
9891
|
}
|
|
9555
9892
|
let entry;
|
|
9556
9893
|
try {
|
|
9557
|
-
entry = readLoadedCaretEntry(
|
|
9894
|
+
entry = readLoadedCaretEntry(existingConfigFiles(dir));
|
|
9558
9895
|
} catch {
|
|
9559
9896
|
return {
|
|
9560
9897
|
pluginVersion: "unknown",
|
|
9561
9898
|
pluginEnabled: "unknown",
|
|
9562
|
-
hookInUserSettings:
|
|
9899
|
+
hookInUserSettings: readCaretInPluginLists(dir)
|
|
9563
9900
|
};
|
|
9564
9901
|
}
|
|
9565
9902
|
const cached = readEntryCachedVersion(entry);
|
|
@@ -9567,13 +9904,13 @@ function readOpencodeInstallState() {
|
|
|
9567
9904
|
return {
|
|
9568
9905
|
pluginVersion: version,
|
|
9569
9906
|
pluginEnabled: version !== "unknown",
|
|
9570
|
-
hookInUserSettings:
|
|
9907
|
+
hookInUserSettings: readCaretInPluginLists(dir)
|
|
9571
9908
|
};
|
|
9572
9909
|
}
|
|
9573
|
-
function
|
|
9910
|
+
function readCaretInPluginLists(dir) {
|
|
9574
9911
|
let sawConfig = false;
|
|
9575
9912
|
for (const name of CONFIG_FILENAMES) {
|
|
9576
|
-
const path =
|
|
9913
|
+
const path = join9(dir, name);
|
|
9577
9914
|
if (!existsSync4(path))
|
|
9578
9915
|
continue;
|
|
9579
9916
|
let text;
|
|
@@ -9851,6 +10188,9 @@ function daemonStderrLogFile() {
|
|
|
9851
10188
|
function daemonLock() {
|
|
9852
10189
|
return `${stateDir()}/daemon.lock`;
|
|
9853
10190
|
}
|
|
10191
|
+
function daemonTokenFile() {
|
|
10192
|
+
return `${stateDir()}/daemon.token`;
|
|
10193
|
+
}
|
|
9854
10194
|
function daemonBootMarker() {
|
|
9855
10195
|
return `${stateDir()}/daemon.boot`;
|
|
9856
10196
|
}
|
|
@@ -9880,7 +10220,7 @@ var init_paths3 = () => {};
|
|
|
9880
10220
|
|
|
9881
10221
|
// src/ui/assets.ts
|
|
9882
10222
|
import { readdirSync as readdirSync2 } from "fs";
|
|
9883
|
-
import { dirname as dirname2, join as
|
|
10223
|
+
import { dirname as dirname2, join as join10, relative } from "path";
|
|
9884
10224
|
import { fileURLToPath } from "url";
|
|
9885
10225
|
function fromPathMap(map) {
|
|
9886
10226
|
const paths = Object.keys(map).sort();
|
|
@@ -9896,7 +10236,7 @@ function walkDist(distDir) {
|
|
|
9896
10236
|
const found = [];
|
|
9897
10237
|
const walk = (dir) => {
|
|
9898
10238
|
for (const ent of readdirSync2(dir, { withFileTypes: true })) {
|
|
9899
|
-
const full =
|
|
10239
|
+
const full = join10(dir, ent.name);
|
|
9900
10240
|
if (ent.isDirectory())
|
|
9901
10241
|
walk(full);
|
|
9902
10242
|
else if (ent.isFile())
|
|
@@ -9934,7 +10274,7 @@ async function loadUiAssets() {
|
|
|
9934
10274
|
if (fromDist)
|
|
9935
10275
|
return fromDist;
|
|
9936
10276
|
}
|
|
9937
|
-
const besideDist =
|
|
10277
|
+
const besideDist = join10(dirname2(process.execPath), "ui");
|
|
9938
10278
|
return assetsFromDist(besideDist);
|
|
9939
10279
|
}
|
|
9940
10280
|
var init_assets = () => {};
|
|
@@ -14434,18 +14774,18 @@ import {
|
|
|
14434
14774
|
openSync,
|
|
14435
14775
|
readdirSync as readdirSync3,
|
|
14436
14776
|
readFileSync as readFileSync5,
|
|
14437
|
-
statSync,
|
|
14777
|
+
statSync as statSync2,
|
|
14438
14778
|
truncateSync,
|
|
14439
14779
|
unlinkSync,
|
|
14440
14780
|
writeFileSync
|
|
14441
14781
|
} from "fs";
|
|
14442
|
-
import { basename as
|
|
14782
|
+
import { basename as basename4 } from "path";
|
|
14443
14783
|
function stamp() {
|
|
14444
14784
|
return new Date().toISOString().replace(/[-:.]/g, "");
|
|
14445
14785
|
}
|
|
14446
14786
|
function sizeOf(path) {
|
|
14447
14787
|
try {
|
|
14448
|
-
return
|
|
14788
|
+
return statSync2(path).size;
|
|
14449
14789
|
} catch {
|
|
14450
14790
|
return -1;
|
|
14451
14791
|
}
|
|
@@ -14457,7 +14797,7 @@ function claimLock(lock) {
|
|
|
14457
14797
|
} catch (e) {
|
|
14458
14798
|
if (e.code !== "EEXIST")
|
|
14459
14799
|
return false;
|
|
14460
|
-
if (Date.now() -
|
|
14800
|
+
if (Date.now() - statSync2(lock).mtimeMs >= STALE_LOCK_MS)
|
|
14461
14801
|
unlinkSync(lock);
|
|
14462
14802
|
return false;
|
|
14463
14803
|
}
|
|
@@ -14476,7 +14816,7 @@ function rotateIfOversized(path, maxSize, keep) {
|
|
|
14476
14816
|
try {
|
|
14477
14817
|
if (sizeOf(path) <= maxSize)
|
|
14478
14818
|
return;
|
|
14479
|
-
const name =
|
|
14819
|
+
const name = basename4(path, ".log");
|
|
14480
14820
|
const lock = `${logsDir()}/.rotate-${name}.lock`;
|
|
14481
14821
|
if (!claimLock(lock))
|
|
14482
14822
|
return;
|
|
@@ -14569,7 +14909,7 @@ function shortId(id) {
|
|
|
14569
14909
|
}
|
|
14570
14910
|
var CENSOR = "<redacted>", DENY_KEYS, MAX_DEPTH = 6;
|
|
14571
14911
|
var init_core3 = __esm(() => {
|
|
14572
|
-
DENY_KEYS = new Set(["plan", "prompt", "feedback"]);
|
|
14912
|
+
DENY_KEYS = new Set(["plan", "prompt", "feedback", "hostnames"]);
|
|
14573
14913
|
});
|
|
14574
14914
|
|
|
14575
14915
|
// src/redact/node.ts
|
|
@@ -14769,9 +15109,11 @@ var init_log = __esm(() => {
|
|
|
14769
15109
|
});
|
|
14770
15110
|
|
|
14771
15111
|
// src/config/settings.ts
|
|
14772
|
-
import { readFileSync as readFileSync7, statSync as
|
|
15112
|
+
import { readFileSync as readFileSync7, statSync as statSync3 } from "fs";
|
|
15113
|
+
import { isIP } from "net";
|
|
14773
15114
|
function freeze(s) {
|
|
14774
15115
|
Object.freeze(s.logging);
|
|
15116
|
+
Object.freeze(s.daemon.hostnames);
|
|
14775
15117
|
Object.freeze(s.daemon);
|
|
14776
15118
|
Object.freeze(s.review);
|
|
14777
15119
|
Object.freeze(s.opencode);
|
|
@@ -14807,7 +15149,7 @@ function loadSettings(file = configFile(), isCompiled = isCompiledBinary()) {
|
|
|
14807
15149
|
} catch {
|
|
14808
15150
|
return DEFAULTS;
|
|
14809
15151
|
}
|
|
14810
|
-
return parseAndValidate(text, isCompiled) ??
|
|
15152
|
+
return parseAndValidate(text, isCompiled) ?? FAIL_CLOSED_DEFAULTS;
|
|
14811
15153
|
}
|
|
14812
15154
|
function createSettings(file = configFile(), isCompiled = isCompiledBinary()) {
|
|
14813
15155
|
let lastGood = DEFAULTS;
|
|
@@ -14816,7 +15158,7 @@ function createSettings(file = configFile(), isCompiled = isCompiledBinary()) {
|
|
|
14816
15158
|
current() {
|
|
14817
15159
|
let st;
|
|
14818
15160
|
try {
|
|
14819
|
-
st =
|
|
15161
|
+
st = statSync3(file);
|
|
14820
15162
|
} catch {
|
|
14821
15163
|
return lastGood;
|
|
14822
15164
|
}
|
|
@@ -14833,6 +15175,8 @@ function createSettings(file = configFile(), isCompiled = isCompiledBinary()) {
|
|
|
14833
15175
|
lastSig = { mtimeMs: st.mtimeMs, size: st.size };
|
|
14834
15176
|
if (next !== null)
|
|
14835
15177
|
lastGood = next;
|
|
15178
|
+
else if (lastGood === DEFAULTS)
|
|
15179
|
+
lastGood = FAIL_CLOSED_DEFAULTS;
|
|
14836
15180
|
return lastGood;
|
|
14837
15181
|
}
|
|
14838
15182
|
};
|
|
@@ -14842,15 +15186,24 @@ function diffSettings(prev, next) {
|
|
|
14842
15186
|
for (const table of SETTINGS_TABLES) {
|
|
14843
15187
|
const prevTable = prev[table];
|
|
14844
15188
|
const nextTable = next[table];
|
|
14845
|
-
|
|
15189
|
+
const keys = new Set([...Object.keys(prevTable), ...Object.keys(nextTable)]);
|
|
15190
|
+
for (const key of keys) {
|
|
14846
15191
|
const before = prevTable[key];
|
|
14847
15192
|
const after = nextTable[key];
|
|
14848
|
-
|
|
14849
|
-
|
|
15193
|
+
const [beforeJson, afterJson] = [JSON.stringify(before), JSON.stringify(after)];
|
|
15194
|
+
if (beforeJson === afterJson)
|
|
15195
|
+
continue;
|
|
15196
|
+
if (DENY_KEYS.has(key))
|
|
15197
|
+
changes.push(`${table}.${key}: changed`);
|
|
15198
|
+
else
|
|
15199
|
+
changes.push(`${table}.${key}: ${shown(before, beforeJson)} \u2192 ${shown(after, afterJson)}`);
|
|
14850
15200
|
}
|
|
14851
15201
|
}
|
|
14852
15202
|
return changes;
|
|
14853
15203
|
}
|
|
15204
|
+
function shown(value, json) {
|
|
15205
|
+
return typeof value === "object" && value !== null ? String(json) : String(value);
|
|
15206
|
+
}
|
|
14854
15207
|
function watchSettings(inner, onChange) {
|
|
14855
15208
|
let prev = null;
|
|
14856
15209
|
return {
|
|
@@ -14918,7 +15271,7 @@ function logMaxSize(s = settings().current()) {
|
|
|
14918
15271
|
function logKeep(s = settings().current()) {
|
|
14919
15272
|
return envValue("CARET_LOG_KEEP", LogKeep) ?? s.logging.keep;
|
|
14920
15273
|
}
|
|
14921
|
-
var Port, TimeoutS, IdleMs, HeartbeatMs, LogMaxSize, LogKeep, DevIntervalMs, Dev, SettingsSchema, DEFAULTS, SETTINGS_TABLES, singleton, ENV_VARS, envCache;
|
|
15274
|
+
var Port, TimeoutS, IdleMs, HeartbeatMs, LogMaxSize, LogKeep, Host, Hostname, DevIntervalMs, Dev, SettingsSchema, DEFAULTS, FAIL_CLOSED_DEFAULTS, SETTINGS_TABLES, singleton, ENV_VARS, envCache;
|
|
14922
15275
|
var init_settings = __esm(() => {
|
|
14923
15276
|
init_dist();
|
|
14924
15277
|
init_zod();
|
|
@@ -14926,12 +15279,23 @@ var init_settings = __esm(() => {
|
|
|
14926
15279
|
init_paths3();
|
|
14927
15280
|
init_build_id();
|
|
14928
15281
|
init_log();
|
|
15282
|
+
init_core3();
|
|
14929
15283
|
Port = number2().int().positive();
|
|
14930
15284
|
TimeoutS = number2().positive().lt(HOOK_TIMEOUT_S);
|
|
14931
15285
|
IdleMs = number2().int().nonnegative();
|
|
14932
15286
|
HeartbeatMs = number2().int().positive().lt(MAX_HEARTBEAT_MS);
|
|
14933
15287
|
LogMaxSize = number2().int().min(MIN_LOG_MAX_SIZE).max(MAX_LOG_MAX_SIZE);
|
|
14934
15288
|
LogKeep = number2().int().nonnegative();
|
|
15289
|
+
Host = string2().refine((h) => h === "localhost" || isIP(h) !== 0 && !h.includes("%"), "an IP address or localhost").transform((h) => h === "localhost" ? "127.0.0.1" : h);
|
|
15290
|
+
Hostname = string2().transform((raw, ctx) => {
|
|
15291
|
+
const lower = raw.toLowerCase();
|
|
15292
|
+
try {
|
|
15293
|
+
if (lower !== "" && new URL(`http://${raw}`).hostname === lower)
|
|
15294
|
+
return lower;
|
|
15295
|
+
} catch {}
|
|
15296
|
+
ctx.addIssue({ code: "custom", message: "a bare hostname or IP address" });
|
|
15297
|
+
return NEVER;
|
|
15298
|
+
});
|
|
14935
15299
|
DevIntervalMs = number2().int().positive();
|
|
14936
15300
|
Dev = object({
|
|
14937
15301
|
port: Port.optional(),
|
|
@@ -14952,7 +15316,10 @@ var init_settings = __esm(() => {
|
|
|
14952
15316
|
daemon: object({
|
|
14953
15317
|
port: Port.default(DEFAULT_PORT),
|
|
14954
15318
|
idle_ms: IdleMs.default(60000),
|
|
14955
|
-
heartbeat_ms: HeartbeatMs.default(8000)
|
|
15319
|
+
heartbeat_ms: HeartbeatMs.default(8000),
|
|
15320
|
+
host: Host.default("127.0.0.1"),
|
|
15321
|
+
hostnames: array(Hostname).default([]),
|
|
15322
|
+
auth: _enum(["token", "none"]).optional()
|
|
14956
15323
|
}).prefault({}),
|
|
14957
15324
|
review: object({
|
|
14958
15325
|
timeout_s: TimeoutS.default(3600)
|
|
@@ -14966,6 +15333,10 @@ var init_settings = __esm(() => {
|
|
|
14966
15333
|
dev: Dev
|
|
14967
15334
|
});
|
|
14968
15335
|
DEFAULTS = freeze(SettingsSchema.parse({}));
|
|
15336
|
+
FAIL_CLOSED_DEFAULTS = freeze({
|
|
15337
|
+
...DEFAULTS,
|
|
15338
|
+
daemon: { ...DEFAULTS.daemon, auth: "token" }
|
|
15339
|
+
});
|
|
14969
15340
|
SETTINGS_TABLES = ["logging", "daemon", "review", "updates"];
|
|
14970
15341
|
ENV_VARS = [
|
|
14971
15342
|
["CARET_PORT", Port],
|
|
@@ -14994,21 +15365,170 @@ var init_boot = __esm(() => {
|
|
|
14994
15365
|
init_log();
|
|
14995
15366
|
});
|
|
14996
15367
|
|
|
15368
|
+
// src/daemon/address.ts
|
|
15369
|
+
function isExposed(host) {
|
|
15370
|
+
return !(host === "localhost" || host === "::1" || /^127\.\d+\.\d+\.\d+$/.test(host));
|
|
15371
|
+
}
|
|
15372
|
+
function authEnabled(daemon) {
|
|
15373
|
+
return daemon.auth === undefined ? isExposed(daemon.host) : daemon.auth === "token";
|
|
15374
|
+
}
|
|
15375
|
+
function connectHostname(host) {
|
|
15376
|
+
if (host === "0.0.0.0")
|
|
15377
|
+
return "127.0.0.1";
|
|
15378
|
+
if (host === "::")
|
|
15379
|
+
return "[::1]";
|
|
15380
|
+
return new URL(`http://${host.includes(":") ? `[${host}]` : host}`).hostname;
|
|
15381
|
+
}
|
|
15382
|
+
function baseUrlFor(host, port) {
|
|
15383
|
+
return `http://${connectHostname(host)}:${port}`;
|
|
15384
|
+
}
|
|
15385
|
+
function daemonBaseUrl(s) {
|
|
15386
|
+
return baseUrlFor(s.daemon.host, getPort(s));
|
|
15387
|
+
}
|
|
15388
|
+
function localHostname(host) {
|
|
15389
|
+
const name = connectHostname(host);
|
|
15390
|
+
return name === "127.0.0.1" || name === "localhost" ? VANITY_HOST : name;
|
|
15391
|
+
}
|
|
15392
|
+
function publicHostname(daemon) {
|
|
15393
|
+
return daemon.hostnames[0] ?? localHostname(daemon.host);
|
|
15394
|
+
}
|
|
15395
|
+
function publicUrl(daemon, port) {
|
|
15396
|
+
return `http://${publicHostname(daemon)}:${port}`;
|
|
15397
|
+
}
|
|
15398
|
+
function loginLink(url, token) {
|
|
15399
|
+
const u = new URL(url);
|
|
15400
|
+
u.searchParams.set("token", token);
|
|
15401
|
+
return u.href;
|
|
15402
|
+
}
|
|
15403
|
+
var init_address = __esm(() => {
|
|
15404
|
+
init_constants();
|
|
15405
|
+
init_settings();
|
|
15406
|
+
});
|
|
15407
|
+
|
|
15408
|
+
// src/daemon/auth.ts
|
|
15409
|
+
import { timingSafeEqual } from "crypto";
|
|
15410
|
+
function authCookieName(port) {
|
|
15411
|
+
return `caret-auth-${port}`;
|
|
15412
|
+
}
|
|
15413
|
+
function matches(candidate, token) {
|
|
15414
|
+
if (!candidate)
|
|
15415
|
+
return false;
|
|
15416
|
+
const a = Buffer.from(candidate);
|
|
15417
|
+
const b = Buffer.from(token);
|
|
15418
|
+
return a.length === b.length && timingSafeEqual(a, b);
|
|
15419
|
+
}
|
|
15420
|
+
function bearerToken(req) {
|
|
15421
|
+
const header = req.headers.get("authorization");
|
|
15422
|
+
return header?.startsWith("Bearer ") ? header.slice(7) : null;
|
|
15423
|
+
}
|
|
15424
|
+
function reject(url, extra) {
|
|
15425
|
+
const headers = new Headers(extra);
|
|
15426
|
+
headers.set("WWW-Authenticate", AUTH_CHALLENGE);
|
|
15427
|
+
if (url.pathname.startsWith("/api/")) {
|
|
15428
|
+
return Response.json({ error: "caret daemon token required" }, { status: 401, headers });
|
|
15429
|
+
}
|
|
15430
|
+
headers.set("Content-Type", "text/html; charset=utf-8");
|
|
15431
|
+
return new Response(REJECTION_PAGE, { status: 401, headers });
|
|
15432
|
+
}
|
|
15433
|
+
function login(url, presented, auth) {
|
|
15434
|
+
const noReferrer = { "Referrer-Policy": "no-referrer" };
|
|
15435
|
+
if (!matches(presented, auth.token))
|
|
15436
|
+
return reject(url, noReferrer);
|
|
15437
|
+
const params = new URLSearchParams(url.search);
|
|
15438
|
+
params.delete("token");
|
|
15439
|
+
const query = params.size > 0 ? `?${params}` : "";
|
|
15440
|
+
const location = `/${url.pathname.replace(/^\/+/, "")}${query}`;
|
|
15441
|
+
const cookie = new Bun.Cookie(authCookieName(auth.port), auth.token, {
|
|
15442
|
+
httpOnly: true,
|
|
15443
|
+
sameSite: "lax",
|
|
15444
|
+
secure: auth.secure,
|
|
15445
|
+
path: "/",
|
|
15446
|
+
maxAge: COOKIE_MAX_AGE_S
|
|
15447
|
+
});
|
|
15448
|
+
return new Response(null, {
|
|
15449
|
+
status: 303,
|
|
15450
|
+
headers: { ...noReferrer, Location: location, "Set-Cookie": cookie.serialize() }
|
|
15451
|
+
});
|
|
15452
|
+
}
|
|
15453
|
+
function authGate(req, url, auth) {
|
|
15454
|
+
const presented = url.searchParams.get("token");
|
|
15455
|
+
if (req.method === "GET" && presented !== null)
|
|
15456
|
+
return login(url, presented, auth);
|
|
15457
|
+
const cookieToken = new Bun.CookieMap(req.headers.get("cookie") ?? "").get(authCookieName(auth.port));
|
|
15458
|
+
if (matches(bearerToken(req), auth.token) || matches(cookieToken, auth.token))
|
|
15459
|
+
return null;
|
|
15460
|
+
return reject(url);
|
|
15461
|
+
}
|
|
15462
|
+
var COOKIE_MAX_AGE_S = 34560000, REJECTION_PAGE, AUTH_CHALLENGE = 'Bearer realm="caret"';
|
|
15463
|
+
var init_auth = __esm(() => {
|
|
15464
|
+
REJECTION_PAGE = "<!doctype html><title>caret</title><p>This caret daemon requires sign-in. " + "Open caret's login link to continue.</p>";
|
|
15465
|
+
});
|
|
15466
|
+
|
|
15467
|
+
// src/daemon/token.ts
|
|
15468
|
+
import { randomBytes } from "crypto";
|
|
15469
|
+
import { linkSync, readFileSync as readFileSync8, rmSync as rmSync2, writeFileSync as writeFileSync3 } from "fs";
|
|
15470
|
+
import { dirname as dirname5 } from "path";
|
|
15471
|
+
function readToken(file) {
|
|
15472
|
+
try {
|
|
15473
|
+
return readFileSync8(file, "utf8").trim() || null;
|
|
15474
|
+
} catch {
|
|
15475
|
+
return null;
|
|
15476
|
+
}
|
|
15477
|
+
}
|
|
15478
|
+
function loadOrMintToken(file) {
|
|
15479
|
+
const existing = readToken(file);
|
|
15480
|
+
if (existing !== null)
|
|
15481
|
+
return existing;
|
|
15482
|
+
const token = randomBytes(32).toString("base64url");
|
|
15483
|
+
ensureStateDir(dirname5(file));
|
|
15484
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
15485
|
+
writeFileSync3(tmp, token, { mode: 384 });
|
|
15486
|
+
try {
|
|
15487
|
+
linkSync(tmp, file);
|
|
15488
|
+
return token;
|
|
15489
|
+
} catch (e) {
|
|
15490
|
+
if (e.code !== "EEXIST")
|
|
15491
|
+
throw e;
|
|
15492
|
+
const winner = readToken(file);
|
|
15493
|
+
if (winner === null)
|
|
15494
|
+
throw new Error(`caret daemon token file ${file} exists but holds no readable token`);
|
|
15495
|
+
return winner;
|
|
15496
|
+
} finally {
|
|
15497
|
+
rmSync2(tmp, { force: true });
|
|
15498
|
+
}
|
|
15499
|
+
}
|
|
15500
|
+
var init_token = __esm(() => {
|
|
15501
|
+
init_paths3();
|
|
15502
|
+
});
|
|
15503
|
+
|
|
14997
15504
|
// src/daemon/client.ts
|
|
15505
|
+
async function daemonFetch(url, init = {}) {
|
|
15506
|
+
const tokenFile = daemonTokenFile();
|
|
15507
|
+
const token = readToken(tokenFile);
|
|
15508
|
+
const headers = new Headers(init.headers);
|
|
15509
|
+
if (token !== null)
|
|
15510
|
+
headers.set("Authorization", `Bearer ${token}`);
|
|
15511
|
+
const res = await fetch(url, { ...init, headers });
|
|
15512
|
+
if (res.status !== 401 || res.headers.get("www-authenticate") !== AUTH_CHALLENGE)
|
|
15513
|
+
return res;
|
|
15514
|
+
throw new DaemonAuthError(token === null ? `the daemon requires a token, but ${tokenFile} does not exist or cannot be read; restart the caret daemon to mint one, or check that XDG_STATE_HOME matches the caret service's` : `the daemon rejected the token in ${tokenFile}; check that XDG_STATE_HOME matches the caret service's`);
|
|
15515
|
+
}
|
|
14998
15516
|
async function httpHealth(baseUrl) {
|
|
14999
15517
|
try {
|
|
15000
|
-
const res = await
|
|
15518
|
+
const res = await daemonFetch(`${baseUrl}/api/health`, {
|
|
15001
15519
|
signal: AbortSignal.timeout(500)
|
|
15002
15520
|
});
|
|
15003
15521
|
if (!res.ok)
|
|
15004
15522
|
return null;
|
|
15005
15523
|
return await res.json();
|
|
15006
|
-
} catch {
|
|
15524
|
+
} catch (e) {
|
|
15525
|
+
if (e instanceof DaemonAuthError)
|
|
15526
|
+
throw e;
|
|
15007
15527
|
return null;
|
|
15008
15528
|
}
|
|
15009
15529
|
}
|
|
15010
15530
|
async function postReview(baseUrl, input) {
|
|
15011
|
-
const res = await
|
|
15531
|
+
const res = await daemonFetch(`${baseUrl}/api/reviews`, {
|
|
15012
15532
|
method: "POST",
|
|
15013
15533
|
headers: { "Content-Type": "application/json" },
|
|
15014
15534
|
body: JSON.stringify(input)
|
|
@@ -15023,7 +15543,7 @@ function versionSearch(version) {
|
|
|
15023
15543
|
return version === undefined ? "" : `?version=${version}`;
|
|
15024
15544
|
}
|
|
15025
15545
|
async function expireReview(baseUrl, id, version) {
|
|
15026
|
-
const res = await
|
|
15546
|
+
const res = await daemonFetch(`${baseUrl}/api/reviews/${id}/expire${versionSearch(version)}`, {
|
|
15027
15547
|
method: "POST",
|
|
15028
15548
|
signal: AbortSignal.timeout(1000)
|
|
15029
15549
|
});
|
|
@@ -15032,7 +15552,7 @@ async function expireReview(baseUrl, id, version) {
|
|
|
15032
15552
|
}
|
|
15033
15553
|
}
|
|
15034
15554
|
async function longPoll(baseUrl, id, version) {
|
|
15035
|
-
const res = await
|
|
15555
|
+
const res = await daemonFetch(`${baseUrl}/api/reviews/${id}/decision${versionSearch(version)}`);
|
|
15036
15556
|
if (res.status === 204)
|
|
15037
15557
|
return null;
|
|
15038
15558
|
if (res.status === 409)
|
|
@@ -15042,13 +15562,13 @@ async function longPoll(baseUrl, id, version) {
|
|
|
15042
15562
|
return await res.json();
|
|
15043
15563
|
}
|
|
15044
15564
|
async function listReviews(baseUrl) {
|
|
15045
|
-
const res = await
|
|
15565
|
+
const res = await daemonFetch(`${baseUrl}/api/reviews`, { signal: AbortSignal.timeout(1000) });
|
|
15046
15566
|
if (!res.ok)
|
|
15047
15567
|
throw new Error(`GET /api/reviews failed: ${res.status}`);
|
|
15048
15568
|
return await res.json();
|
|
15049
15569
|
}
|
|
15050
15570
|
async function resolveReview(baseUrl, id, body) {
|
|
15051
|
-
const res = await
|
|
15571
|
+
const res = await daemonFetch(`${baseUrl}/api/reviews/${id}/resolve`, {
|
|
15052
15572
|
method: "POST",
|
|
15053
15573
|
headers: { "Content-Type": "application/json" },
|
|
15054
15574
|
body: JSON.stringify(body),
|
|
@@ -15057,28 +15577,44 @@ async function resolveReview(baseUrl, id, body) {
|
|
|
15057
15577
|
if (!res.ok)
|
|
15058
15578
|
throw new Error(`POST /resolve failed: ${res.status}`);
|
|
15059
15579
|
}
|
|
15580
|
+
var DaemonAuthError;
|
|
15581
|
+
var init_client = __esm(() => {
|
|
15582
|
+
init_paths3();
|
|
15583
|
+
init_auth();
|
|
15584
|
+
init_token();
|
|
15585
|
+
DaemonAuthError = class DaemonAuthError extends Error {
|
|
15586
|
+
};
|
|
15587
|
+
});
|
|
15060
15588
|
|
|
15061
15589
|
// src/daemon/lifecycle.ts
|
|
15062
15590
|
import {
|
|
15063
|
-
accessSync,
|
|
15591
|
+
accessSync as accessSync2,
|
|
15064
15592
|
chmodSync as chmodSync2,
|
|
15065
|
-
constants,
|
|
15593
|
+
constants as constants2,
|
|
15066
15594
|
existsSync as existsSync7,
|
|
15067
|
-
linkSync,
|
|
15595
|
+
linkSync as linkSync2,
|
|
15068
15596
|
openSync as openSync2,
|
|
15069
|
-
readFileSync as
|
|
15597
|
+
readFileSync as readFileSync9,
|
|
15070
15598
|
renameSync as renameSync2,
|
|
15071
|
-
statSync as
|
|
15599
|
+
statSync as statSync4,
|
|
15072
15600
|
unlinkSync as unlinkSync2,
|
|
15073
|
-
writeFileSync as
|
|
15601
|
+
writeFileSync as writeFileSync4
|
|
15074
15602
|
} from "fs";
|
|
15075
|
-
import {
|
|
15603
|
+
import { createServer as createNetServer } from "net";
|
|
15604
|
+
import { join as join11, normalize } from "path";
|
|
15076
15605
|
function isAddrInUse(e) {
|
|
15077
15606
|
if (e && typeof e === "object" && "code" in e) {
|
|
15078
15607
|
return e.code === "EADDRINUSE";
|
|
15079
15608
|
}
|
|
15080
15609
|
return e instanceof Error && /EADDRINUSE/.test(e.message);
|
|
15081
15610
|
}
|
|
15611
|
+
function isAddrNotAvail(host) {
|
|
15612
|
+
return new Promise((resolve) => {
|
|
15613
|
+
const probe = createNetServer();
|
|
15614
|
+
probe.once("error", (e) => resolve(e.code === "EADDRNOTAVAIL"));
|
|
15615
|
+
probe.listen({ host, port: 0 }, () => probe.close(() => resolve(false)));
|
|
15616
|
+
});
|
|
15617
|
+
}
|
|
15082
15618
|
function sameWorldPath(a, b) {
|
|
15083
15619
|
return normalize(a) === normalize(b);
|
|
15084
15620
|
}
|
|
@@ -15089,7 +15625,7 @@ async function ensureDaemon(deps, mode = "takeover") {
|
|
|
15089
15625
|
const { timing } = deps;
|
|
15090
15626
|
const windowEnd = timing.now() + timing.windowMs;
|
|
15091
15627
|
const deadline = windowEnd + timing.reserveMs;
|
|
15092
|
-
let windowSpent = mode === "successor" ? await awaitDrained(deps, windowEnd) :
|
|
15628
|
+
let windowSpent = mode === "successor" ? await awaitDrained(deps, windowEnd) : mode === "takeover" && await moveStray(deps, windowEnd);
|
|
15093
15629
|
let supervised;
|
|
15094
15630
|
let spawnedPid;
|
|
15095
15631
|
let waitingOn;
|
|
@@ -15110,7 +15646,7 @@ async function ensureDaemon(deps, mode = "takeover") {
|
|
|
15110
15646
|
if (deps.pinned || isNewer(h.version ?? "", deps.currentVersion))
|
|
15111
15647
|
return deps.baseUrl;
|
|
15112
15648
|
windowSpent = true;
|
|
15113
|
-
if (await restartService(deps.service, h)) {
|
|
15649
|
+
if (await restartService(deps.service, h, "stale daemon build")) {
|
|
15114
15650
|
await awaitSuccessor(deps, { prev: h, step: "service", until: windowEnd });
|
|
15115
15651
|
}
|
|
15116
15652
|
continue;
|
|
@@ -15125,6 +15661,11 @@ async function ensureDaemon(deps, mode = "takeover") {
|
|
|
15125
15661
|
if (h && h.service !== "caret") {
|
|
15126
15662
|
throw new Error(`port is held by a non-caret process \u2014 set CARET_PORT to a free port`);
|
|
15127
15663
|
}
|
|
15664
|
+
if (mode !== "takeover") {
|
|
15665
|
+
const stray = await strayDaemon(deps.readLock(), deps.baseUrl, deps.health);
|
|
15666
|
+
if (stray)
|
|
15667
|
+
return stray.url;
|
|
15668
|
+
}
|
|
15128
15669
|
if (!windowSpent && deps.service) {
|
|
15129
15670
|
supervised ??= await supervisorExpected(deps.service);
|
|
15130
15671
|
if (supervised) {
|
|
@@ -15206,9 +15747,9 @@ async function awaitSuccessor(deps, { prev, step, until }) {
|
|
|
15206
15747
|
logWarn(step, "no daemon took the port in time", { instanceId: prev?.instanceId });
|
|
15207
15748
|
return false;
|
|
15208
15749
|
}
|
|
15209
|
-
async function restartService(service, stale) {
|
|
15750
|
+
async function restartService(service, stale, reason) {
|
|
15210
15751
|
const ctx = { instanceId: stale.instanceId, build: stale.build };
|
|
15211
|
-
logInfo("service",
|
|
15752
|
+
logInfo("service", `restarting service: ${reason}`, ctx);
|
|
15212
15753
|
try {
|
|
15213
15754
|
await service.restart();
|
|
15214
15755
|
return true;
|
|
@@ -15217,27 +15758,74 @@ async function restartService(service, stale) {
|
|
|
15217
15758
|
return false;
|
|
15218
15759
|
}
|
|
15219
15760
|
}
|
|
15761
|
+
async function strayDaemon(lock, baseUrl, health) {
|
|
15762
|
+
if (typeof lock?.host !== "string" || lock.instanceId === undefined)
|
|
15763
|
+
return null;
|
|
15764
|
+
let lockUrl;
|
|
15765
|
+
try {
|
|
15766
|
+
lockUrl = baseUrlFor(lock.host, lock.port);
|
|
15767
|
+
} catch {
|
|
15768
|
+
return null;
|
|
15769
|
+
}
|
|
15770
|
+
if (lockUrl === baseUrl)
|
|
15771
|
+
return null;
|
|
15772
|
+
const h = await health(lockUrl);
|
|
15773
|
+
if (h?.service !== "caret" || h.instanceId !== lock.instanceId)
|
|
15774
|
+
return null;
|
|
15775
|
+
if ((await health(baseUrl))?.instanceId === lock.instanceId)
|
|
15776
|
+
return null;
|
|
15777
|
+
return { url: lockUrl, health: h };
|
|
15778
|
+
}
|
|
15779
|
+
async function moveStray(deps, windowEnd) {
|
|
15780
|
+
const lock = deps.readLock();
|
|
15781
|
+
const stray = await strayDaemon(lock, deps.baseUrl, deps.health);
|
|
15782
|
+
if (!stray)
|
|
15783
|
+
return false;
|
|
15784
|
+
if (deps.service && (stray.health.supervised ?? stray.health.resident) === true) {
|
|
15785
|
+
if (await restartService(deps.service, stray.health, "daemon on a stale address")) {
|
|
15786
|
+
await awaitSuccessor(deps, { prev: null, step: "service", until: windowEnd });
|
|
15787
|
+
}
|
|
15788
|
+
return true;
|
|
15789
|
+
}
|
|
15790
|
+
const ctx = { instanceId: stray.health.instanceId };
|
|
15791
|
+
if (await deps.retire(stray.url, lock)) {
|
|
15792
|
+
logInfo("retire", "daemon on a stale address retiring", ctx);
|
|
15793
|
+
} else {
|
|
15794
|
+
logWarn("retire", "daemon on a stale address not retired", ctx);
|
|
15795
|
+
}
|
|
15796
|
+
return false;
|
|
15797
|
+
}
|
|
15220
15798
|
async function supervisorExpected(service) {
|
|
15221
15799
|
const status = await service.status().catch(() => null);
|
|
15222
15800
|
return status?.keepsAlive === true;
|
|
15223
15801
|
}
|
|
15224
15802
|
async function vacatePort(deps) {
|
|
15803
|
+
try {
|
|
15804
|
+
return await vacate(deps);
|
|
15805
|
+
} catch (e) {
|
|
15806
|
+
if (e instanceof DaemonAuthError)
|
|
15807
|
+
return e.message;
|
|
15808
|
+
throw e;
|
|
15809
|
+
}
|
|
15810
|
+
}
|
|
15811
|
+
async function vacate(deps) {
|
|
15225
15812
|
const deadline = deps.now() + deps.deadlineMs;
|
|
15226
15813
|
let retired;
|
|
15814
|
+
const url = (await strayDaemon(deps.readLock(), deps.baseUrl, deps.health))?.url ?? deps.baseUrl;
|
|
15227
15815
|
for (;; ) {
|
|
15228
|
-
const h = await deps.health(
|
|
15816
|
+
const h = await deps.health(url);
|
|
15229
15817
|
if (h?.service !== "caret")
|
|
15230
15818
|
return null;
|
|
15231
15819
|
if (isForeignWorld(h, deps.currentStateDir))
|
|
15232
15820
|
return FOREIGN_WORLD_ERROR;
|
|
15233
15821
|
if ((h.supervised ?? h.resident) === true) {
|
|
15234
|
-
return "caret's service already serves the review UI
|
|
15822
|
+
return "caret's service already serves the review UI \u2014 run `caret install` and answer \"I'll run it myself\" to serve it from a terminal instead";
|
|
15235
15823
|
}
|
|
15236
15824
|
if (deps.now() >= deadline)
|
|
15237
|
-
return "the caret daemon
|
|
15825
|
+
return "the caret daemon did not stop in time";
|
|
15238
15826
|
if (h.instanceId === undefined || h.instanceId !== retired) {
|
|
15239
|
-
if (!await deps.retire(
|
|
15240
|
-
return "the caret daemon
|
|
15827
|
+
if (!await deps.retire(url)) {
|
|
15828
|
+
return "the caret daemon could not be asked to stop";
|
|
15241
15829
|
}
|
|
15242
15830
|
retired = h.instanceId;
|
|
15243
15831
|
}
|
|
@@ -15260,7 +15848,7 @@ function fileBootMarker() {
|
|
|
15260
15848
|
const write = (marker, place) => {
|
|
15261
15849
|
const path = daemonBootMarker();
|
|
15262
15850
|
const tmp = `${path}.${process.pid}.tmp`;
|
|
15263
|
-
|
|
15851
|
+
writeFileSync4(tmp, JSON.stringify(marker), { mode: 384 });
|
|
15264
15852
|
try {
|
|
15265
15853
|
place(tmp, path);
|
|
15266
15854
|
} finally {
|
|
@@ -15271,7 +15859,7 @@ function fileBootMarker() {
|
|
|
15271
15859
|
claim: (marker) => {
|
|
15272
15860
|
try {
|
|
15273
15861
|
ensureStateDir();
|
|
15274
|
-
write(marker,
|
|
15862
|
+
write(marker, linkSync2);
|
|
15275
15863
|
return true;
|
|
15276
15864
|
} catch (e) {
|
|
15277
15865
|
return e.code !== "EEXIST";
|
|
@@ -15312,13 +15900,16 @@ function removeOwnDaemonLock() {
|
|
|
15312
15900
|
}
|
|
15313
15901
|
async function retireDaemon(baseUrl, lock, currentStateDir, kill = (pid, sig) => process.kill(pid, sig)) {
|
|
15314
15902
|
try {
|
|
15315
|
-
const res = await
|
|
15903
|
+
const res = await daemonFetch(`${baseUrl}/api/retire`, {
|
|
15316
15904
|
method: "POST",
|
|
15317
15905
|
signal: AbortSignal.timeout(1000)
|
|
15318
15906
|
});
|
|
15319
15907
|
if (res.ok)
|
|
15320
15908
|
return true;
|
|
15321
|
-
} catch {
|
|
15909
|
+
} catch (e) {
|
|
15910
|
+
if (e instanceof DaemonAuthError)
|
|
15911
|
+
throw e;
|
|
15912
|
+
}
|
|
15322
15913
|
const sameWorld = lock?.stateDir === undefined || sameWorldPath(lock.stateDir, currentStateDir);
|
|
15323
15914
|
if (lock && sameWorld && isPidAlive(lock.pid)) {
|
|
15324
15915
|
try {
|
|
@@ -15372,7 +15963,7 @@ function isLauncherPinned() {
|
|
|
15372
15963
|
}
|
|
15373
15964
|
function readPinnedRoot() {
|
|
15374
15965
|
try {
|
|
15375
|
-
const text =
|
|
15966
|
+
const text = readFileSync9(launcherPinnedRootFile(), "utf8");
|
|
15376
15967
|
const nl = text.indexOf(`
|
|
15377
15968
|
`);
|
|
15378
15969
|
if (nl < 0)
|
|
@@ -15384,12 +15975,12 @@ function readPinnedRoot() {
|
|
|
15384
15975
|
}
|
|
15385
15976
|
function isRunnableRoot(root) {
|
|
15386
15977
|
try {
|
|
15387
|
-
|
|
15978
|
+
accessSync2(join11(root, "bin", "caret"), constants2.X_OK);
|
|
15388
15979
|
try {
|
|
15389
|
-
|
|
15980
|
+
accessSync2(join11(root, "bin", "caret-native"), constants2.X_OK);
|
|
15390
15981
|
return true;
|
|
15391
15982
|
} catch {
|
|
15392
|
-
return
|
|
15983
|
+
return statSync4(join11(root, "ui", "dist", "index.html")).isFile();
|
|
15393
15984
|
}
|
|
15394
15985
|
} catch {
|
|
15395
15986
|
return false;
|
|
@@ -15400,7 +15991,7 @@ async function prodEnsureDeps(s, service, reserveMs) {
|
|
|
15400
15991
|
return {
|
|
15401
15992
|
service: existsSync7(launcherServiceFile()) ? service() : undefined,
|
|
15402
15993
|
pinned: isLauncherPinned(),
|
|
15403
|
-
baseUrl:
|
|
15994
|
+
baseUrl: daemonBaseUrl(s),
|
|
15404
15995
|
currentBuild: await currentBuildId(),
|
|
15405
15996
|
currentVersion: VERSION,
|
|
15406
15997
|
currentStateDir: world,
|
|
@@ -15425,6 +16016,8 @@ var BOOT_MARKER_TTL_MS = 30000, FOREIGN_WORLD_ERROR = "port serves a different c
|
|
|
15425
16016
|
var init_lifecycle = __esm(() => {
|
|
15426
16017
|
init_paths3();
|
|
15427
16018
|
init_settings();
|
|
16019
|
+
init_address();
|
|
16020
|
+
init_client();
|
|
15428
16021
|
init_build_id();
|
|
15429
16022
|
init_json_file();
|
|
15430
16023
|
init_log();
|
|
@@ -16816,7 +17409,7 @@ __export(exports_dist, {
|
|
|
16816
17409
|
import { styleText as styleText2, stripVTControlCharacters } from "util";
|
|
16817
17410
|
import process$1 from "process";
|
|
16818
17411
|
import { existsSync as existsSync11, lstatSync, readdirSync as readdirSync4 } from "fs";
|
|
16819
|
-
import { dirname as
|
|
17412
|
+
import { dirname as dirname9, join as join17 } from "path";
|
|
16820
17413
|
function isUnicodeSupported() {
|
|
16821
17414
|
if (process$1.platform !== "win32") {
|
|
16822
17415
|
return process$1.env.TERM !== "linux";
|
|
@@ -17527,10 +18120,10 @@ ${n}
|
|
|
17527
18120
|
return [];
|
|
17528
18121
|
try {
|
|
17529
18122
|
let i;
|
|
17530
|
-
existsSync11(t) ? lstatSync(t).isDirectory() && (!e.directory || t.endsWith("/")) ? i = t : i =
|
|
18123
|
+
existsSync11(t) ? lstatSync(t).isDirectory() && (!e.directory || t.endsWith("/")) ? i = t : i = dirname9(t) : i = dirname9(t);
|
|
17531
18124
|
const c = t.length > 1 && t.endsWith("/") ? t.slice(0, -1) : t;
|
|
17532
18125
|
return readdirSync4(i).map((r) => {
|
|
17533
|
-
const n =
|
|
18126
|
+
const n = join17(i, r), m = lstatSync(n);
|
|
17534
18127
|
return {
|
|
17535
18128
|
name: r,
|
|
17536
18129
|
path: n,
|
|
@@ -33764,7 +34357,7 @@ init_dist();
|
|
|
33764
34357
|
init_paths3();
|
|
33765
34358
|
import { mkdir, readFile as readFile3, stat as stat2 } from "fs/promises";
|
|
33766
34359
|
import { dirname as dirname4 } from "path";
|
|
33767
|
-
import { isDeepStrictEqual } from "util";
|
|
34360
|
+
import { isDeepStrictEqual as isDeepStrictEqual2 } from "util";
|
|
33768
34361
|
|
|
33769
34362
|
// src/lib/atomic-write.ts
|
|
33770
34363
|
import { realpath as realpath2, rename, rm, writeFile } from "fs/promises";
|
|
@@ -33858,7 +34451,7 @@ function withUpdatesCheck(text, check) {
|
|
|
33858
34451
|
return null;
|
|
33859
34452
|
if (after.updates?.check !== check)
|
|
33860
34453
|
return null;
|
|
33861
|
-
if (!
|
|
34454
|
+
if (!isDeepStrictEqual2(withoutUpdatesCheck(before), withoutUpdatesCheck(after)))
|
|
33862
34455
|
return null;
|
|
33863
34456
|
return next;
|
|
33864
34457
|
}
|
|
@@ -33933,6 +34526,7 @@ function optedOut(text) {
|
|
|
33933
34526
|
|
|
33934
34527
|
// src/commands/daemon.ts
|
|
33935
34528
|
init_settings();
|
|
34529
|
+
init_address();
|
|
33936
34530
|
|
|
33937
34531
|
// src/daemon/update-changes.ts
|
|
33938
34532
|
var COMMIT_CAP = 50;
|
|
@@ -34056,14 +34650,17 @@ function buildDiagnostics(deps) {
|
|
|
34056
34650
|
init_lifecycle();
|
|
34057
34651
|
|
|
34058
34652
|
// src/daemon/server.ts
|
|
34059
|
-
import { renameSync as renameSync4, unlinkSync as unlinkSync3, writeFileSync as
|
|
34060
|
-
import { dirname as
|
|
34653
|
+
import { renameSync as renameSync4, unlinkSync as unlinkSync3, writeFileSync as writeFileSync7 } from "fs";
|
|
34654
|
+
import { dirname as dirname7 } from "path";
|
|
34061
34655
|
init_constants();
|
|
34062
34656
|
init_paths3();
|
|
34063
34657
|
init_settings();
|
|
34658
|
+
init_address();
|
|
34659
|
+
init_auth();
|
|
34064
34660
|
|
|
34065
34661
|
// src/daemon/guards.ts
|
|
34066
34662
|
init_constants();
|
|
34663
|
+
init_address();
|
|
34067
34664
|
var LIVE_CLIENT_WINDOW_MS = 120000;
|
|
34068
34665
|
function isClientLive(lastPollAt, now, windowMs) {
|
|
34069
34666
|
return lastPollAt !== 0 && now - lastPollAt < windowMs;
|
|
@@ -34071,38 +34668,53 @@ function isClientLive(lastPollAt, now, windowMs) {
|
|
|
34071
34668
|
function isSafeMethod(method) {
|
|
34072
34669
|
return method === "GET" || method === "HEAD";
|
|
34073
34670
|
}
|
|
34074
|
-
var
|
|
34075
|
-
|
|
34671
|
+
var BUILT_IN_NAMES = ["127.0.0.1", "localhost", VANITY_HOST];
|
|
34672
|
+
var LOOPBACK_NAMES = ownNames("127.0.0.1", []);
|
|
34673
|
+
function ownNames(connectHostname, hostnames) {
|
|
34674
|
+
const anyPortSafe = (h) => !BUILT_IN_NAMES.includes(h) && isExposed(h.replace(/^\[(.*)\]$/, "$1"));
|
|
34675
|
+
return {
|
|
34676
|
+
exact: new Set([...BUILT_IN_NAMES, connectHostname]),
|
|
34677
|
+
anyPort: new Set(hostnames.filter(anyPortSafe))
|
|
34678
|
+
};
|
|
34679
|
+
}
|
|
34680
|
+
function isOwnAuthority(authority, port, names) {
|
|
34076
34681
|
try {
|
|
34077
34682
|
const u = new URL(`http://${authority}`);
|
|
34078
34683
|
if (u.href !== `http://${u.host}/`)
|
|
34079
34684
|
return false;
|
|
34080
|
-
|
|
34685
|
+
if (names.anyPort.has(u.hostname))
|
|
34686
|
+
return true;
|
|
34687
|
+
return names.exact.has(u.hostname) && (u.port || "80") === String(port);
|
|
34081
34688
|
} catch {
|
|
34082
34689
|
return false;
|
|
34083
34690
|
}
|
|
34084
34691
|
}
|
|
34085
|
-
function isForeignHost(req, port) {
|
|
34692
|
+
function isForeignHost(req, port, names = LOOPBACK_NAMES) {
|
|
34086
34693
|
const host = req.headers.get("host");
|
|
34087
|
-
return host === null || !isOwnAuthority(host, port);
|
|
34694
|
+
return host === null || !isOwnAuthority(host, port, names);
|
|
34088
34695
|
}
|
|
34089
|
-
function isOwnOrigin(origin, port) {
|
|
34696
|
+
function isOwnOrigin(origin, port, names) {
|
|
34090
34697
|
try {
|
|
34091
34698
|
const u = new URL(origin);
|
|
34092
|
-
|
|
34699
|
+
if (u.protocol === "https:")
|
|
34700
|
+
return names.anyPort.has(u.hostname);
|
|
34701
|
+
return u.protocol === "http:" && isOwnAuthority(u.host, port, names);
|
|
34093
34702
|
} catch {
|
|
34094
34703
|
return false;
|
|
34095
34704
|
}
|
|
34096
34705
|
}
|
|
34097
|
-
function isCrossOrigin(req, port) {
|
|
34706
|
+
function isCrossOrigin(req, port, names = LOOPBACK_NAMES) {
|
|
34098
34707
|
const origin = req.headers.get("origin");
|
|
34099
|
-
if (origin && !isOwnOrigin(origin, port))
|
|
34708
|
+
if (origin && !isOwnOrigin(origin, port, names))
|
|
34100
34709
|
return true;
|
|
34101
34710
|
const site = req.headers.get("sec-fetch-site");
|
|
34102
34711
|
if (site && site !== "same-origin" && site !== "none")
|
|
34103
34712
|
return true;
|
|
34104
34713
|
return false;
|
|
34105
34714
|
}
|
|
34715
|
+
function isForwardedHttps(req, bindHost) {
|
|
34716
|
+
return !isExposed(bindHost) && req.headers.get("x-forwarded-proto") === "https";
|
|
34717
|
+
}
|
|
34106
34718
|
|
|
34107
34719
|
// src/daemon/liveness.ts
|
|
34108
34720
|
function createLiveness(deps) {
|
|
@@ -34271,6 +34883,7 @@ async function parseBody(req, schema) {
|
|
|
34271
34883
|
}
|
|
34272
34884
|
|
|
34273
34885
|
// src/daemon/server.ts
|
|
34886
|
+
init_token();
|
|
34274
34887
|
init_build_id();
|
|
34275
34888
|
|
|
34276
34889
|
// src/lib/cmux.ts
|
|
@@ -34304,13 +34917,13 @@ init_log();
|
|
|
34304
34917
|
|
|
34305
34918
|
// src/plan/directory.ts
|
|
34306
34919
|
import { readdir as readdir4 } from "fs/promises";
|
|
34307
|
-
import { join as
|
|
34920
|
+
import { join as join13, relative as relative3, sep as sep4 } from "path";
|
|
34308
34921
|
|
|
34309
34922
|
// src/plan/excerpt.ts
|
|
34310
34923
|
init_constants();
|
|
34311
|
-
import { readFileSync as
|
|
34924
|
+
import { readFileSync as readFileSync10 } from "fs";
|
|
34312
34925
|
import { readdir as readdir3, realpath as realpath3, stat as stat3 } from "fs/promises";
|
|
34313
|
-
import { extname, isAbsolute, join as
|
|
34926
|
+
import { extname, isAbsolute as isAbsolute2, join as join12, relative as relative2, resolve, sep as sep3 } from "path";
|
|
34314
34927
|
var EXCERPT_HEAD_LINES = 60;
|
|
34315
34928
|
var MAX_EXCERPT_BYTES = 10 * 1024 * 1024;
|
|
34316
34929
|
var MAX_SCAN_ENTRIES = 5000;
|
|
@@ -34401,16 +35014,16 @@ async function basenameSearch(cwdReal, name) {
|
|
|
34401
35014
|
return null;
|
|
34402
35015
|
if (entry.isFile()) {
|
|
34403
35016
|
if (entry.name === name)
|
|
34404
|
-
return
|
|
35017
|
+
return join12(currentDir, entry.name);
|
|
34405
35018
|
} else if (entry.isDirectory() && !SKIP_DIRS.has(entry.name) && !entry.name.startsWith(".")) {
|
|
34406
|
-
queue.push(
|
|
35019
|
+
queue.push(join12(currentDir, entry.name));
|
|
34407
35020
|
}
|
|
34408
35021
|
}
|
|
34409
35022
|
}
|
|
34410
35023
|
return null;
|
|
34411
35024
|
}
|
|
34412
35025
|
async function resolveInCwd(cwd, candidate) {
|
|
34413
|
-
if (cwd === "" || !
|
|
35026
|
+
if (cwd === "" || !isAbsolute2(cwd))
|
|
34414
35027
|
return null;
|
|
34415
35028
|
const cwdReal = await safeRealpath(cwd);
|
|
34416
35029
|
if (cwdReal === null)
|
|
@@ -34450,7 +35063,7 @@ function readLines(abs, stats) {
|
|
|
34450
35063
|
return memo2.lines;
|
|
34451
35064
|
let content;
|
|
34452
35065
|
try {
|
|
34453
|
-
content =
|
|
35066
|
+
content = readFileSync10(abs, "utf-8");
|
|
34454
35067
|
} catch {
|
|
34455
35068
|
return null;
|
|
34456
35069
|
}
|
|
@@ -34535,7 +35148,7 @@ async function listDirectory(cwd, root, path) {
|
|
|
34535
35148
|
else if (d.isSymbolicLink())
|
|
34536
35149
|
links.push(d);
|
|
34537
35150
|
}
|
|
34538
|
-
const targets = await Promise.all(links.map((d) => resolveInCwd(cwd,
|
|
35151
|
+
const targets = await Promise.all(links.map((d) => resolveInCwd(cwd, join13(target.path, d.name))));
|
|
34539
35152
|
targets.forEach((hit, i) => {
|
|
34540
35153
|
const link = links[i];
|
|
34541
35154
|
if (hit !== null && link !== undefined)
|
|
@@ -34552,7 +35165,7 @@ async function listDirectory(cwd, root, path) {
|
|
|
34552
35165
|
|
|
34553
35166
|
// src/plan/file-search.ts
|
|
34554
35167
|
import { readdir as readdir5 } from "fs/promises";
|
|
34555
|
-
import { isAbsolute as
|
|
35168
|
+
import { isAbsolute as isAbsolute3, join as join14 } from "path";
|
|
34556
35169
|
var SEARCH_BUDGET = { dirents: 20000, results: 50 };
|
|
34557
35170
|
var MAX_SEARCH_QUERY_CHARS = 128;
|
|
34558
35171
|
function subsequence(haystack, needle) {
|
|
@@ -34587,7 +35200,7 @@ function partition(dirents, allowance) {
|
|
|
34587
35200
|
return { files, dirs, scanned, cut: false };
|
|
34588
35201
|
}
|
|
34589
35202
|
async function searchFiles(cwd, query, budget = SEARCH_BUDGET) {
|
|
34590
|
-
if (cwd === "" || !
|
|
35203
|
+
if (cwd === "" || !isAbsolute3(cwd))
|
|
34591
35204
|
return null;
|
|
34592
35205
|
const cwdReal = await safeRealpath(cwd);
|
|
34593
35206
|
if (cwdReal === null)
|
|
@@ -34600,7 +35213,7 @@ async function searchFiles(cwd, query, budget = SEARCH_BUDGET) {
|
|
|
34600
35213
|
const rel = queue.shift();
|
|
34601
35214
|
let dirents;
|
|
34602
35215
|
try {
|
|
34603
|
-
dirents = await readdir5(
|
|
35216
|
+
dirents = await readdir5(join14(cwdReal, rel), { withFileTypes: true });
|
|
34604
35217
|
} catch {
|
|
34605
35218
|
continue;
|
|
34606
35219
|
}
|
|
@@ -34673,7 +35286,7 @@ function createDecisions(log = noopLogger) {
|
|
|
34673
35286
|
}
|
|
34674
35287
|
|
|
34675
35288
|
// src/review/threading.ts
|
|
34676
|
-
import { randomBytes } from "crypto";
|
|
35289
|
+
import { randomBytes as randomBytes2 } from "crypto";
|
|
34677
35290
|
|
|
34678
35291
|
// node_modules/marked/lib/marked.esm.js
|
|
34679
35292
|
function A() {
|
|
@@ -36055,13 +36668,13 @@ var un = x.lex;
|
|
|
36055
36668
|
init_log();
|
|
36056
36669
|
|
|
36057
36670
|
// src/plan/canonical-file.ts
|
|
36058
|
-
import { appendFileSync, existsSync as existsSync8, readFileSync as
|
|
36671
|
+
import { appendFileSync, existsSync as existsSync8, readFileSync as readFileSync11, statSync as statSync5, writeFileSync as writeFileSync5 } from "fs";
|
|
36059
36672
|
function isPlanFile(path) {
|
|
36060
|
-
return path.endsWith(".md") && existsSync8(path) &&
|
|
36673
|
+
return path.endsWith(".md") && existsSync8(path) && statSync5(path).isFile();
|
|
36061
36674
|
}
|
|
36062
36675
|
function readPlanFile(path) {
|
|
36063
36676
|
try {
|
|
36064
|
-
return isPlanFile(path) ?
|
|
36677
|
+
return isPlanFile(path) ? readFileSync11(path, "utf8") : undefined;
|
|
36065
36678
|
} catch {
|
|
36066
36679
|
return;
|
|
36067
36680
|
}
|
|
@@ -36079,11 +36692,11 @@ function guardedPlanFileWrite(planFilePath, log, failMsg, write) {
|
|
|
36079
36692
|
}
|
|
36080
36693
|
function writeCanonicalPlanFile(input, canonical, log) {
|
|
36081
36694
|
return guardedPlanFileWrite(input.planFilePath, log, "plan file canonicalize failed", (p) => {
|
|
36082
|
-
if (
|
|
36695
|
+
if (readFileSync11(p, "utf8") !== (input.plan ?? "")) {
|
|
36083
36696
|
log.info("review", "plan file changed; rewrite skipped");
|
|
36084
36697
|
return "changed";
|
|
36085
36698
|
}
|
|
36086
|
-
|
|
36699
|
+
writeFileSync5(p, canonical);
|
|
36087
36700
|
return "written";
|
|
36088
36701
|
}) ?? "skipped";
|
|
36089
36702
|
}
|
|
@@ -36100,10 +36713,10 @@ init_log();
|
|
|
36100
36713
|
// src/plan/rumdl.ts
|
|
36101
36714
|
init_paths3();
|
|
36102
36715
|
import { createHash as createHash2 } from "crypto";
|
|
36103
|
-
import { chmodSync as chmodSync3, existsSync as existsSync9, mkdirSync as mkdirSync2, renameSync as renameSync3, writeFileSync as
|
|
36716
|
+
import { chmodSync as chmodSync3, existsSync as existsSync9, mkdirSync as mkdirSync2, renameSync as renameSync3, writeFileSync as writeFileSync6 } from "fs";
|
|
36104
36717
|
import { mkdtemp, rm as rm3 } from "fs/promises";
|
|
36105
36718
|
import { tmpdir } from "os";
|
|
36106
|
-
import { dirname as
|
|
36719
|
+
import { dirname as dirname6, join as join15 } from "path";
|
|
36107
36720
|
var RUMDL_VERSION = "0.2.73";
|
|
36108
36721
|
var ASSETS = {
|
|
36109
36722
|
"darwin-arm64": {
|
|
@@ -36146,17 +36759,17 @@ async function downloadRumdl(binPath, asset, fetchImpl = fetch) {
|
|
|
36146
36759
|
if (actual !== asset.sha256) {
|
|
36147
36760
|
throw new Error(`rumdl: checksum mismatch for ${asset.url} (expected ${asset.sha256}, got ${actual})`);
|
|
36148
36761
|
}
|
|
36149
|
-
const work = await mkdtemp(
|
|
36762
|
+
const work = await mkdtemp(join15(tmpdir(), "caret-rumdl-"));
|
|
36150
36763
|
try {
|
|
36151
|
-
const archive =
|
|
36764
|
+
const archive = join15(work, "rumdl.tar.gz");
|
|
36152
36765
|
await Bun.write(archive, bytes);
|
|
36153
36766
|
const tar = Bun.spawn(["tar", "-xzf", archive, "-C", work], { stderr: "pipe" });
|
|
36154
36767
|
if (await tar.exited !== 0)
|
|
36155
36768
|
throw new Error(`rumdl: failed to extract ${asset.url}`);
|
|
36156
|
-
const extracted =
|
|
36769
|
+
const extracted = join15(work, "rumdl");
|
|
36157
36770
|
if (!existsSync9(extracted))
|
|
36158
36771
|
throw new Error(`rumdl: binary not found in ${asset.url}`);
|
|
36159
|
-
mkdirSync2(
|
|
36772
|
+
mkdirSync2(dirname6(binPath), { recursive: true });
|
|
36160
36773
|
const tmp = `${binPath}.${process.pid}.tmp`;
|
|
36161
36774
|
await Bun.write(tmp, Bun.file(extracted));
|
|
36162
36775
|
chmodSync3(tmp, 493);
|
|
@@ -36185,7 +36798,7 @@ var acquisition = { cooldownUntil: 0 };
|
|
|
36185
36798
|
async function ensureRumdl(install = installPinnedRumdl, now = Date.now, state = acquisition) {
|
|
36186
36799
|
ensureStateDir(rumdlDir());
|
|
36187
36800
|
const config = rumdlConfig();
|
|
36188
|
-
|
|
36801
|
+
writeFileSync6(config, RUMDL_CONFIG);
|
|
36189
36802
|
const override = process.env.CARET_RUMDL_BIN?.trim();
|
|
36190
36803
|
if (override && await installedVersion(override) === RUMDL_VERSION) {
|
|
36191
36804
|
return { bin: override, config, installed: false };
|
|
@@ -36255,7 +36868,7 @@ async function formatPlanMarkdown(plan, log = noopLogger, doFormat = rumdlFormat
|
|
|
36255
36868
|
|
|
36256
36869
|
// src/review/threading.ts
|
|
36257
36870
|
function newReviewId() {
|
|
36258
|
-
return
|
|
36871
|
+
return randomBytes2(8).toString("base64url");
|
|
36259
36872
|
}
|
|
36260
36873
|
function deriveTitle(plan) {
|
|
36261
36874
|
const tokens = new x({ gfm: true }).blockTokens(plan.replace(/\r\n?/g, `
|
|
@@ -36441,6 +37054,9 @@ function resolveOptions(opts) {
|
|
|
36441
37054
|
routePlan: opts.routePlan ?? routeIncomingPlan,
|
|
36442
37055
|
configPath: opts.configPath ?? configFile(),
|
|
36443
37056
|
lockPath: opts.lockPath,
|
|
37057
|
+
tokenFile: opts.tokenFile,
|
|
37058
|
+
hostname: opts.hostname ?? "127.0.0.1",
|
|
37059
|
+
hostnames: opts.hostnames ?? [],
|
|
36444
37060
|
buildId: opts.buildId,
|
|
36445
37061
|
assetDigest: opts.assetDigest,
|
|
36446
37062
|
commit: opts.commit,
|
|
@@ -36496,6 +37112,8 @@ function createServer(opts) {
|
|
|
36496
37112
|
const { approveVariants, source, lockPath } = cfg;
|
|
36497
37113
|
const { awaitDecision, resolveDecision, clearDecision, openDecisionCount, unreadDecisionCount } = createDecisions(log);
|
|
36498
37114
|
const configWriter = createConfigWriter(configPath);
|
|
37115
|
+
const token = cfg.tokenFile === undefined ? null : loadOrMintToken(cfg.tokenFile);
|
|
37116
|
+
const names = ownNames(connectHostname(cfg.hostname), cfg.hostnames);
|
|
36499
37117
|
function raceDecision(id, ms) {
|
|
36500
37118
|
return new Promise((resolve) => {
|
|
36501
37119
|
const t = setTimeout(() => resolve(null), ms);
|
|
@@ -36962,19 +37580,29 @@ function createServer(opts) {
|
|
|
36962
37580
|
return notFound();
|
|
36963
37581
|
}
|
|
36964
37582
|
async function handle(req, self) {
|
|
36965
|
-
|
|
37583
|
+
let end;
|
|
36966
37584
|
let failureLog = log;
|
|
36967
37585
|
try {
|
|
36968
37586
|
const port = self.port ?? -1;
|
|
36969
|
-
if (isForeignHost(req, port)) {
|
|
37587
|
+
if (isForeignHost(req, port, names)) {
|
|
36970
37588
|
return new Response("host not recognized", { status: 403 });
|
|
36971
37589
|
}
|
|
36972
37590
|
const url = new URL(req.url);
|
|
36973
37591
|
const path = url.pathname;
|
|
36974
37592
|
const method = req.method;
|
|
36975
|
-
if (
|
|
37593
|
+
if (token !== null) {
|
|
37594
|
+
const denied = authGate(req, url, {
|
|
37595
|
+
token,
|
|
37596
|
+
port,
|
|
37597
|
+
secure: isForwardedHttps(req, cfg.hostname)
|
|
37598
|
+
});
|
|
37599
|
+
if (denied)
|
|
37600
|
+
return denied;
|
|
37601
|
+
}
|
|
37602
|
+
if (!isSafeMethod(method) && isCrossOrigin(req, port, names)) {
|
|
36976
37603
|
return new Response("cross-origin request blocked", { status: 403 });
|
|
36977
37604
|
}
|
|
37605
|
+
end = liveness.begin(method);
|
|
36978
37606
|
const idRoute = matchIdRoute(path);
|
|
36979
37607
|
const route = idRoute && { ...idRoute, log: log.child({ reviewId: idRoute.id }) };
|
|
36980
37608
|
if (route)
|
|
@@ -36988,16 +37616,16 @@ function createServer(opts) {
|
|
|
36988
37616
|
} catch {}
|
|
36989
37617
|
return new Response("internal error", { status: 500 });
|
|
36990
37618
|
} finally {
|
|
36991
|
-
end();
|
|
37619
|
+
end?.();
|
|
36992
37620
|
}
|
|
36993
37621
|
}
|
|
36994
37622
|
const server = Bun.serve({
|
|
36995
37623
|
port: opts.port ?? 0,
|
|
36996
|
-
hostname:
|
|
37624
|
+
hostname: cfg.hostname,
|
|
36997
37625
|
idleTimeout: deriveIdleTimeoutSec(heartbeat),
|
|
36998
37626
|
fetch: handle
|
|
36999
37627
|
});
|
|
37000
|
-
log.info("listen", `listening on
|
|
37628
|
+
log.info("listen", `listening on ${cfg.hostname}:${server.port}`, {
|
|
37001
37629
|
build: buildId,
|
|
37002
37630
|
version: IDENTITY.version,
|
|
37003
37631
|
commit,
|
|
@@ -37007,10 +37635,11 @@ function createServer(opts) {
|
|
|
37007
37635
|
if (!lockPath)
|
|
37008
37636
|
return;
|
|
37009
37637
|
try {
|
|
37010
|
-
ensureStateDir(
|
|
37638
|
+
ensureStateDir(dirname7(lockPath));
|
|
37011
37639
|
const lock = {
|
|
37012
37640
|
pid: process.pid,
|
|
37013
37641
|
port: server.port ?? 0,
|
|
37642
|
+
host: cfg.hostname,
|
|
37014
37643
|
build: buildId,
|
|
37015
37644
|
version: IDENTITY.version,
|
|
37016
37645
|
startedAt: Date.now(),
|
|
@@ -37018,7 +37647,7 @@ function createServer(opts) {
|
|
|
37018
37647
|
instanceId
|
|
37019
37648
|
};
|
|
37020
37649
|
const tmp = `${lockPath}.tmp.${process.pid}`;
|
|
37021
|
-
|
|
37650
|
+
writeFileSync7(tmp, JSON.stringify(lock));
|
|
37022
37651
|
renameSync4(tmp, lockPath);
|
|
37023
37652
|
} catch {}
|
|
37024
37653
|
}
|
|
@@ -37042,11 +37671,14 @@ function createServer(opts) {
|
|
|
37042
37671
|
return { port: server.port ?? 0, stop, drain: () => liveness.drain() };
|
|
37043
37672
|
}
|
|
37044
37673
|
|
|
37674
|
+
// src/commands/daemon.ts
|
|
37675
|
+
init_token();
|
|
37676
|
+
|
|
37045
37677
|
// src/daemon/update-check.ts
|
|
37046
37678
|
init_paths3();
|
|
37047
37679
|
init_json_file();
|
|
37048
|
-
import { writeFileSync as
|
|
37049
|
-
import { dirname as
|
|
37680
|
+
import { writeFileSync as writeFileSync8 } from "fs";
|
|
37681
|
+
import { dirname as dirname8 } from "path";
|
|
37050
37682
|
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
37051
37683
|
var REFRESH_COMMAND = "bunx --no-cache @macintacos/caret@latest install --refresh";
|
|
37052
37684
|
var REBUILD_COMMAND = "mise run build --install";
|
|
@@ -37114,8 +37746,8 @@ function fileUpdateCache(path) {
|
|
|
37114
37746
|
read: () => asRecord(readJsonFileSync(path)),
|
|
37115
37747
|
write: (entry) => {
|
|
37116
37748
|
try {
|
|
37117
|
-
ensureStateDir(
|
|
37118
|
-
|
|
37749
|
+
ensureStateDir(dirname8(path));
|
|
37750
|
+
writeFileSync8(path, JSON.stringify(entry, null, 2), { mode: 384 });
|
|
37119
37751
|
} catch {}
|
|
37120
37752
|
}
|
|
37121
37753
|
};
|
|
@@ -37182,7 +37814,7 @@ init_upstream();
|
|
|
37182
37814
|
// src/review/store.ts
|
|
37183
37815
|
init_paths3();
|
|
37184
37816
|
import { readdir as readdir6, readFile as readFile5, stat as stat4 } from "fs/promises";
|
|
37185
|
-
import { join as
|
|
37817
|
+
import { join as join16 } from "path";
|
|
37186
37818
|
init_json_file();
|
|
37187
37819
|
init_log();
|
|
37188
37820
|
var STALE_AFTER_MS = 7 * 24 * 60 * 60 * 1000;
|
|
@@ -37192,7 +37824,7 @@ function createStore(dir, log = noopLogger) {
|
|
|
37192
37824
|
function persist(review) {
|
|
37193
37825
|
return writes.run(review.id, async () => {
|
|
37194
37826
|
ensureStateDir(dir);
|
|
37195
|
-
await writeFileAtomic(
|
|
37827
|
+
await writeFileAtomic(join16(dir, `${review.id}.json`), JSON.stringify(review, null, 2), {
|
|
37196
37828
|
mode: 384
|
|
37197
37829
|
});
|
|
37198
37830
|
log.debug("store", `review persisted: ${shortId(review.id)}`, { reviewId: review.id });
|
|
@@ -37240,7 +37872,7 @@ function createStore(dir, log = noopLogger) {
|
|
|
37240
37872
|
return review;
|
|
37241
37873
|
},
|
|
37242
37874
|
async persisted(id) {
|
|
37243
|
-
return await readJsonFile(
|
|
37875
|
+
return await readJsonFile(join16(dir, `${id}.json`)) ?? undefined;
|
|
37244
37876
|
},
|
|
37245
37877
|
size() {
|
|
37246
37878
|
return reviews.size;
|
|
@@ -37273,7 +37905,7 @@ function createStore(dir, log = noopLogger) {
|
|
|
37273
37905
|
for (const file of files) {
|
|
37274
37906
|
if (!file.endsWith(".json"))
|
|
37275
37907
|
continue;
|
|
37276
|
-
const path =
|
|
37908
|
+
const path = join16(dir, file);
|
|
37277
37909
|
try {
|
|
37278
37910
|
if ((await stat4(path)).mtimeMs < now - STALE_AFTER_MS)
|
|
37279
37911
|
continue;
|
|
@@ -37372,6 +38004,14 @@ async function runDaemon(opts) {
|
|
|
37372
38004
|
const buildId = await currentBuildId();
|
|
37373
38005
|
const assetDigest = await buildHash(assets);
|
|
37374
38006
|
let armedUpkeep = [];
|
|
38007
|
+
const tokenFile = authEnabled(boot.daemon) ? daemonTokenFile() : undefined;
|
|
38008
|
+
if (tokenFile !== undefined) {
|
|
38009
|
+
try {
|
|
38010
|
+
loadOrMintToken(tokenFile);
|
|
38011
|
+
} catch (e) {
|
|
38012
|
+
exitTerminal(`cannot use the daemon token file ${tokenFile}`, "daemon-token-unusable", e);
|
|
38013
|
+
}
|
|
38014
|
+
}
|
|
37375
38015
|
try {
|
|
37376
38016
|
server = createServer({
|
|
37377
38017
|
store,
|
|
@@ -37381,6 +38021,9 @@ async function runDaemon(opts) {
|
|
|
37381
38021
|
resident,
|
|
37382
38022
|
assets,
|
|
37383
38023
|
lockPath: daemonLock(),
|
|
38024
|
+
tokenFile,
|
|
38025
|
+
hostname: boot.daemon.host,
|
|
38026
|
+
hostnames: boot.daemon.hostnames,
|
|
37384
38027
|
buildId,
|
|
37385
38028
|
assetDigest,
|
|
37386
38029
|
commit: built.commit,
|
|
@@ -37412,12 +38055,12 @@ async function runDaemon(opts) {
|
|
|
37412
38055
|
log
|
|
37413
38056
|
});
|
|
37414
38057
|
} catch (e) {
|
|
37415
|
-
if (isAddrInUse(e)) {
|
|
38058
|
+
if (isAddrInUse(e) && !await isAddrNotAvail(boot.daemon.host)) {
|
|
37416
38059
|
process.stderr.write(`caret: another daemon won the port; exiting.
|
|
37417
38060
|
`);
|
|
37418
38061
|
process.exit(0);
|
|
37419
38062
|
}
|
|
37420
|
-
exitTerminal(
|
|
38063
|
+
exitTerminal(`cannot bind the daemon to ${boot.daemon.host}:${getPort(boot)}`, "daemon-bind-failed", e);
|
|
37421
38064
|
}
|
|
37422
38065
|
removeOwnBootMarker();
|
|
37423
38066
|
const onFatal = (label) => (err) => {
|
|
@@ -37437,16 +38080,43 @@ async function runDaemon(opts) {
|
|
|
37437
38080
|
upkeep.push({ name: "stderr-rotate", run: () => rotateDaemonStderr(svc.current()) });
|
|
37438
38081
|
}
|
|
37439
38082
|
armedUpkeep = startUpkeep({ tasks: upkeep, log });
|
|
37440
|
-
return server.port;
|
|
38083
|
+
return { port: server.port, settings: boot };
|
|
37441
38084
|
}
|
|
37442
38085
|
|
|
37443
38086
|
// src/commands/doctor.ts
|
|
37444
38087
|
init_adapters();
|
|
37445
|
-
init_paths2();
|
|
37446
|
-
init_upgrade();
|
|
37447
38088
|
import { existsSync as existsSync12 } from "fs";
|
|
37448
38089
|
import { release } from "os";
|
|
37449
38090
|
|
|
38091
|
+
// src/adapters/opencode/checks.ts
|
|
38092
|
+
init_entries();
|
|
38093
|
+
init_host();
|
|
38094
|
+
init_paths2();
|
|
38095
|
+
init_upgrade();
|
|
38096
|
+
async function readOpencodeChecks(deps) {
|
|
38097
|
+
if (readCaretEntries(deps.configFiles, isCaretCheckout).length === 0)
|
|
38098
|
+
return [];
|
|
38099
|
+
const hosts = deps.opencodeHosts();
|
|
38100
|
+
const v2Host = hosts.find((h) => isV2Host(h.version));
|
|
38101
|
+
const loadedFiles = v2Host === undefined ? deps.configFiles : loadedConfigFiles(deps.configFiles, v2Host.version);
|
|
38102
|
+
const ignored = deps.configFiles.filter((f) => !loadedFiles.includes(f) && readCaretEntries([f], isCaretCheckout).length > 0);
|
|
38103
|
+
const entries = readCaretEntries(loadedFiles, isCaretCheckout);
|
|
38104
|
+
const hostResult = hostCheck(hosts, [...new Set(entries.map((e) => e.key))], ignored);
|
|
38105
|
+
if (entries.length === 0 || entries.every((e) => isLocalPluginSpecifier(e.spec)))
|
|
38106
|
+
return [hostResult];
|
|
38107
|
+
const verdict = await readUpgradeVerdict({
|
|
38108
|
+
configFiles: loadedFiles,
|
|
38109
|
+
host: sharedHost(hosts),
|
|
38110
|
+
published: deps.published
|
|
38111
|
+
});
|
|
38112
|
+
return [hostResult, upgradeCheck(verdict)];
|
|
38113
|
+
}
|
|
38114
|
+
|
|
38115
|
+
// src/commands/doctor.ts
|
|
38116
|
+
init_host();
|
|
38117
|
+
init_paths2();
|
|
38118
|
+
init_upgrade();
|
|
38119
|
+
|
|
37450
38120
|
// src/commands/install/ui.ts
|
|
37451
38121
|
function isTerminal() {
|
|
37452
38122
|
return process.stdin.isTTY === true && process.stdout.isTTY === true;
|
|
@@ -37530,9 +38200,9 @@ function servicePlatform(platform = process.platform) {
|
|
|
37530
38200
|
|
|
37531
38201
|
// src/service/launchd-manager.ts
|
|
37532
38202
|
init_log();
|
|
37533
|
-
import { mkdirSync as mkdirSync3, rmSync as
|
|
38203
|
+
import { mkdirSync as mkdirSync3, rmSync as rmSync3, writeFileSync as writeFileSync9 } from "fs";
|
|
37534
38204
|
import { homedir as homedir6 } from "os";
|
|
37535
|
-
import { join as
|
|
38205
|
+
import { join as join18 } from "path";
|
|
37536
38206
|
|
|
37537
38207
|
// src/service/launchd.ts
|
|
37538
38208
|
var EXIT_TIMEOUT_SEC = 20;
|
|
@@ -37593,10 +38263,10 @@ function commandError(tool, verb, result) {
|
|
|
37593
38263
|
}
|
|
37594
38264
|
|
|
37595
38265
|
// src/service/unit-file.ts
|
|
37596
|
-
import { readFileSync as
|
|
38266
|
+
import { readFileSync as readFileSync12 } from "fs";
|
|
37597
38267
|
function unitUnchanged(path, text) {
|
|
37598
38268
|
try {
|
|
37599
|
-
return
|
|
38269
|
+
return readFileSync12(path, "utf-8") === text;
|
|
37600
38270
|
} catch {
|
|
37601
38271
|
return false;
|
|
37602
38272
|
}
|
|
@@ -37608,14 +38278,14 @@ var RUNNING = [/^\s*state\s*=\s*running\b/m, /^\s*pid\s*=\s*\d+/m];
|
|
|
37608
38278
|
var DISABLED2 = new RegExp(`"${LAUNCHD_LABEL.replace(/\./g, "\\.")}"\\s*=>\\s*(?:true|disabled)\\b`);
|
|
37609
38279
|
var launchctlError = (action, result) => commandError("launchctl", action, result);
|
|
37610
38280
|
function createLaunchdManager(deps = {}) {
|
|
37611
|
-
const launchAgentsDir = deps.launchAgentsDir ??
|
|
38281
|
+
const launchAgentsDir = deps.launchAgentsDir ?? join18(homedir6(), "Library", "LaunchAgents");
|
|
37612
38282
|
const launchctl = deps.launchctl ?? spawnLaunchctl;
|
|
37613
38283
|
const uid = deps.uid ?? process.getuid?.();
|
|
37614
38284
|
if (uid === undefined)
|
|
37615
38285
|
throw new Error("caret service: launchd needs a POSIX uid");
|
|
37616
38286
|
const domain = `gui/${uid}`;
|
|
37617
38287
|
const target = `${domain}/${LAUNCHD_LABEL}`;
|
|
37618
|
-
const plistPath =
|
|
38288
|
+
const plistPath = join18(launchAgentsDir, `${LAUNCHD_LABEL}.plist`);
|
|
37619
38289
|
async function readStatus() {
|
|
37620
38290
|
const [printed, printDisabled] = await Promise.all([
|
|
37621
38291
|
launchctl(["print", target]),
|
|
@@ -37644,7 +38314,7 @@ function createLaunchdManager(deps = {}) {
|
|
|
37644
38314
|
}
|
|
37645
38315
|
}
|
|
37646
38316
|
mkdirSync3(launchAgentsDir, { recursive: true });
|
|
37647
|
-
|
|
38317
|
+
writeFileSync9(plistPath, text);
|
|
37648
38318
|
await launchctl(["bootout", target]);
|
|
37649
38319
|
const bootstrapped = await launchctl(["bootstrap", domain, plistPath]);
|
|
37650
38320
|
if (bootstrapped.code !== 0)
|
|
@@ -37652,7 +38322,7 @@ function createLaunchdManager(deps = {}) {
|
|
|
37652
38322
|
},
|
|
37653
38323
|
async uninstall() {
|
|
37654
38324
|
await launchctl(["bootout", target]);
|
|
37655
|
-
|
|
38325
|
+
rmSync3(plistPath, { force: true });
|
|
37656
38326
|
},
|
|
37657
38327
|
status: readStatus,
|
|
37658
38328
|
async restart() {
|
|
@@ -37665,9 +38335,9 @@ function createLaunchdManager(deps = {}) {
|
|
|
37665
38335
|
|
|
37666
38336
|
// src/service/systemd-manager.ts
|
|
37667
38337
|
init_log();
|
|
37668
|
-
import { mkdirSync as mkdirSync4, rmSync as
|
|
38338
|
+
import { mkdirSync as mkdirSync4, rmSync as rmSync4, writeFileSync as writeFileSync10 } from "fs";
|
|
37669
38339
|
import { homedir as homedir7 } from "os";
|
|
37670
|
-
import { join as
|
|
38340
|
+
import { join as join19 } from "path";
|
|
37671
38341
|
|
|
37672
38342
|
// src/service/systemd.ts
|
|
37673
38343
|
var START_LIMIT_INTERVAL_SEC = 60;
|
|
@@ -37709,10 +38379,10 @@ WantedBy=default.target
|
|
|
37709
38379
|
|
|
37710
38380
|
// src/service/systemd-manager.ts
|
|
37711
38381
|
function createSystemdManager(deps = {}) {
|
|
37712
|
-
const configHome = process.env.XDG_CONFIG_HOME ||
|
|
37713
|
-
const unitDir = deps.unitDir ??
|
|
38382
|
+
const configHome = process.env.XDG_CONFIG_HOME || join19(homedir7(), ".config");
|
|
38383
|
+
const unitDir = deps.unitDir ?? join19(configHome, "systemd", "user");
|
|
37714
38384
|
const run = deps.run ?? runCommand;
|
|
37715
|
-
const unitPath =
|
|
38385
|
+
const unitPath = join19(unitDir, SYSTEMD_UNIT);
|
|
37716
38386
|
const systemctl = (...args) => run(["systemctl", "--user", ...args]);
|
|
37717
38387
|
async function probeSystemd() {
|
|
37718
38388
|
const shown = await systemctl("show-environment");
|
|
@@ -37771,7 +38441,7 @@ function createSystemdManager(deps = {}) {
|
|
|
37771
38441
|
}
|
|
37772
38442
|
mkdirSync4(unitDir, { recursive: true });
|
|
37773
38443
|
try {
|
|
37774
|
-
|
|
38444
|
+
writeFileSync10(unitPath, text);
|
|
37775
38445
|
const reloaded = await systemctl("daemon-reload");
|
|
37776
38446
|
if (reloaded.code !== 0)
|
|
37777
38447
|
throw commandError("systemctl", "daemon-reload", reloaded);
|
|
@@ -37783,14 +38453,14 @@ function createSystemdManager(deps = {}) {
|
|
|
37783
38453
|
if (restarted.code !== 0)
|
|
37784
38454
|
throw commandError("systemctl", "restart", restarted);
|
|
37785
38455
|
} catch (err) {
|
|
37786
|
-
|
|
38456
|
+
rmSync4(unitPath, { force: true });
|
|
37787
38457
|
throw err;
|
|
37788
38458
|
}
|
|
37789
38459
|
await enableLinger();
|
|
37790
38460
|
},
|
|
37791
38461
|
async uninstall() {
|
|
37792
38462
|
await systemctl("disable", "--now", SYSTEMD_UNIT);
|
|
37793
|
-
|
|
38463
|
+
rmSync4(unitPath, { force: true });
|
|
37794
38464
|
await systemctl("daemon-reload");
|
|
37795
38465
|
},
|
|
37796
38466
|
status: readStatus,
|
|
@@ -37826,34 +38496,36 @@ function prodService() {
|
|
|
37826
38496
|
// src/commands/doctor.ts
|
|
37827
38497
|
init_paths3();
|
|
37828
38498
|
init_settings();
|
|
38499
|
+
init_address();
|
|
38500
|
+
init_client();
|
|
37829
38501
|
init_lifecycle();
|
|
37830
38502
|
|
|
37831
38503
|
// src/doctor/bundle.ts
|
|
37832
|
-
import { readdirSync as readdirSync5, readFileSync as
|
|
37833
|
-
import { basename as
|
|
38504
|
+
import { readdirSync as readdirSync5, readFileSync as readFileSync13 } from "fs";
|
|
38505
|
+
import { basename as basename5, join as join20 } from "path";
|
|
37834
38506
|
function stamp2(now) {
|
|
37835
38507
|
return now.toISOString().replace(/[-:.]/g, "");
|
|
37836
38508
|
}
|
|
37837
38509
|
function bundlePath(stateDir, now) {
|
|
37838
|
-
return
|
|
38510
|
+
return join20(stateDir, `doctor-${stamp2(now)}.zip`);
|
|
37839
38511
|
}
|
|
37840
38512
|
function member(path, name) {
|
|
37841
38513
|
try {
|
|
37842
|
-
return { name, data:
|
|
38514
|
+
return { name, data: readFileSync13(path) };
|
|
37843
38515
|
} catch {
|
|
37844
38516
|
return null;
|
|
37845
38517
|
}
|
|
37846
38518
|
}
|
|
37847
38519
|
var MAX_REVIEW_MEMBERS = 5000;
|
|
37848
38520
|
function entriesFor(deps) {
|
|
37849
|
-
const logs = deps.logPaths.map((p) => member(p, `logs/${
|
|
38521
|
+
const logs = deps.logPaths.map((p) => member(p, `logs/${basename5(p)}`));
|
|
37850
38522
|
let reviews;
|
|
37851
38523
|
try {
|
|
37852
38524
|
reviews = readdirSync5(deps.reviewsDir).filter((f) => f.endsWith(".json"));
|
|
37853
38525
|
} catch {
|
|
37854
38526
|
reviews = [];
|
|
37855
38527
|
}
|
|
37856
|
-
const records = reviews.slice(0, MAX_REVIEW_MEMBERS).map((f) => member(
|
|
38528
|
+
const records = reviews.slice(0, MAX_REVIEW_MEMBERS).map((f) => member(join20(deps.reviewsDir, f), `reviews/${f}`));
|
|
37857
38529
|
return [...logs, ...records].filter((e) => e !== null);
|
|
37858
38530
|
}
|
|
37859
38531
|
async function runBundle(opts, deps) {
|
|
@@ -37878,8 +38550,8 @@ init_paths3();
|
|
|
37878
38550
|
init_json_file();
|
|
37879
38551
|
init_log();
|
|
37880
38552
|
init_node();
|
|
37881
|
-
import { readdirSync as readdirSync6, statSync as
|
|
37882
|
-
import { basename as
|
|
38553
|
+
import { readdirSync as readdirSync6, statSync as statSync6 } from "fs";
|
|
38554
|
+
import { basename as basename6, join as join21 } from "path";
|
|
37883
38555
|
var CARET_BIN = "caret-native";
|
|
37884
38556
|
var ERROR_RECORD_FIELDS = ["time", "source", "step", "code", "reviewId", "sessionId"];
|
|
37885
38557
|
async function safe(build) {
|
|
@@ -38161,7 +38833,7 @@ function parsePsLines(text) {
|
|
|
38161
38833
|
const [, pidStr, comm] = m ?? [];
|
|
38162
38834
|
if (pidStr === undefined || comm === undefined)
|
|
38163
38835
|
continue;
|
|
38164
|
-
const name =
|
|
38836
|
+
const name = basename6(comm.trim());
|
|
38165
38837
|
if (name === CARET_BIN)
|
|
38166
38838
|
out.push({ pid: Number(pidStr), name });
|
|
38167
38839
|
}
|
|
@@ -38191,7 +38863,7 @@ function listReviewFiles() {
|
|
|
38191
38863
|
continue;
|
|
38192
38864
|
if (out.length >= 5000)
|
|
38193
38865
|
break;
|
|
38194
|
-
const raw = readJsonFileSync(
|
|
38866
|
+
const raw = readJsonFileSync(join21(dir, file));
|
|
38195
38867
|
if (raw && typeof raw.id === "string" && typeof raw.status === "string") {
|
|
38196
38868
|
out.push({ id: raw.id, status: raw.status });
|
|
38197
38869
|
}
|
|
@@ -38202,7 +38874,7 @@ var TAIL_BYTES = 256 * 1024;
|
|
|
38202
38874
|
async function logStats(path) {
|
|
38203
38875
|
let size;
|
|
38204
38876
|
try {
|
|
38205
|
-
size =
|
|
38877
|
+
size = statSync6(path).size;
|
|
38206
38878
|
} catch {
|
|
38207
38879
|
return { path, exists: false, size: 0, errors: 0, warns: 0 };
|
|
38208
38880
|
}
|
|
@@ -38221,7 +38893,7 @@ async function readTail(path, size) {
|
|
|
38221
38893
|
async function logErrorRecords(path) {
|
|
38222
38894
|
let size;
|
|
38223
38895
|
try {
|
|
38224
|
-
size =
|
|
38896
|
+
size = statSync6(path).size;
|
|
38225
38897
|
} catch {
|
|
38226
38898
|
return [];
|
|
38227
38899
|
}
|
|
@@ -38467,7 +39139,7 @@ function runChecks(report, adapterChecks = []) {
|
|
|
38467
39139
|
}
|
|
38468
39140
|
|
|
38469
39141
|
// src/doctor/zip.ts
|
|
38470
|
-
import { closeSync as closeSync2, openSync as openSync3, rmSync as
|
|
39142
|
+
import { closeSync as closeSync2, openSync as openSync3, rmSync as rmSync5, writeSync } from "fs";
|
|
38471
39143
|
import { crc32 } from "zlib";
|
|
38472
39144
|
var LOCAL_SIG = 67324752;
|
|
38473
39145
|
var CENTRAL_SIG = 33639248;
|
|
@@ -38565,7 +39237,7 @@ function writeZip(path, entries, now) {
|
|
|
38565
39237
|
}
|
|
38566
39238
|
put(eocd(records.length, offset - directoryStart, directoryStart));
|
|
38567
39239
|
} catch (e) {
|
|
38568
|
-
|
|
39240
|
+
rmSync5(path, { force: true });
|
|
38569
39241
|
throw e;
|
|
38570
39242
|
} finally {
|
|
38571
39243
|
closeSync2(fd);
|
|
@@ -38594,7 +39266,7 @@ function prodDoctorDeps(s) {
|
|
|
38594
39266
|
reviewTimeoutMs: reviewTimeoutMs(s),
|
|
38595
39267
|
heartbeatMs: heartbeatMs(s)
|
|
38596
39268
|
}),
|
|
38597
|
-
baseUrl:
|
|
39269
|
+
baseUrl: daemonBaseUrl(s),
|
|
38598
39270
|
health: httpHealth,
|
|
38599
39271
|
serviceInstalled: () => existsSync12(launcherServiceFile()),
|
|
38600
39272
|
serviceStatus: async () => prodService().manager.status(),
|
|
@@ -38619,10 +39291,10 @@ function prodDoctorDeps(s) {
|
|
|
38619
39291
|
}
|
|
38620
39292
|
async function readAdapterChecks() {
|
|
38621
39293
|
try {
|
|
38622
|
-
|
|
38623
|
-
|
|
38624
|
-
|
|
38625
|
-
|
|
39294
|
+
return await readOpencodeChecks({
|
|
39295
|
+
configFiles: existingConfigFiles(opencodeConfigDir()),
|
|
39296
|
+
opencodeHosts: readOpencodeHosts
|
|
39297
|
+
});
|
|
38626
39298
|
} catch (e) {
|
|
38627
39299
|
return [upgradeCheck({ kind: "unknown", reason: errorMessage(e) })];
|
|
38628
39300
|
}
|
|
@@ -38685,27 +39357,27 @@ import { resolve as resolve3 } from "path";
|
|
|
38685
39357
|
|
|
38686
39358
|
// src/commands/install/local.ts
|
|
38687
39359
|
import {
|
|
38688
|
-
accessSync as
|
|
38689
|
-
constants as
|
|
39360
|
+
accessSync as accessSync3,
|
|
39361
|
+
constants as constants3,
|
|
38690
39362
|
existsSync as existsSync13,
|
|
38691
39363
|
mkdirSync as mkdirSync5,
|
|
38692
|
-
rmSync as
|
|
38693
|
-
statSync as
|
|
39364
|
+
rmSync as rmSync6,
|
|
39365
|
+
statSync as statSync7,
|
|
38694
39366
|
symlinkSync,
|
|
38695
|
-
writeFileSync as
|
|
39367
|
+
writeFileSync as writeFileSync11
|
|
38696
39368
|
} from "fs";
|
|
38697
|
-
import { join as
|
|
39369
|
+
import { join as join23, resolve as resolve2 } from "path";
|
|
38698
39370
|
|
|
38699
39371
|
// src/adapters/opencode/packaging.ts
|
|
38700
39372
|
init_entries();
|
|
38701
|
-
import { readdirSync as readdirSync7, readFileSync as
|
|
38702
|
-
import { dirname as
|
|
39373
|
+
import { readdirSync as readdirSync7, readFileSync as readFileSync14 } from "fs";
|
|
39374
|
+
import { dirname as dirname10, join as join22 } from "path";
|
|
38703
39375
|
function resolveCaretRoot() {
|
|
38704
39376
|
const candidates = [];
|
|
38705
39377
|
const script = process.argv[1];
|
|
38706
39378
|
if (script)
|
|
38707
|
-
candidates.push(
|
|
38708
|
-
candidates.push(
|
|
39379
|
+
candidates.push(join22(dirname10(script), ".."));
|
|
39380
|
+
candidates.push(join22(dirname10(process.execPath), ".."));
|
|
38709
39381
|
for (const root of candidates) {
|
|
38710
39382
|
if (isCaretCheckout(root))
|
|
38711
39383
|
return root;
|
|
@@ -38714,12 +39386,12 @@ function resolveCaretRoot() {
|
|
|
38714
39386
|
}
|
|
38715
39387
|
var DEMO_TEMPLATE = "templates/demo.md";
|
|
38716
39388
|
function loadOpencodePackaging(root = resolveCaretRoot()) {
|
|
38717
|
-
const binPath =
|
|
38718
|
-
const demoTemplate =
|
|
38719
|
-
const commandsDir =
|
|
39389
|
+
const binPath = join22(root, "bin", "caret");
|
|
39390
|
+
const demoTemplate = readFileSync14(join22(root, DEMO_TEMPLATE), "utf-8");
|
|
39391
|
+
const commandsDir = join22(root, "opencode", "commands");
|
|
38720
39392
|
let commands = [];
|
|
38721
39393
|
try {
|
|
38722
|
-
commands = readdirSync7(commandsDir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f, contents:
|
|
39394
|
+
commands = readdirSync7(commandsDir).filter((f) => f.endsWith(".md")).sort().map((f) => ({ name: f, contents: readFileSync14(join22(commandsDir, f), "utf-8") }));
|
|
38723
39395
|
} catch {
|
|
38724
39396
|
commands = [];
|
|
38725
39397
|
}
|
|
@@ -38729,17 +39401,17 @@ function loadOpencodePackaging(root = resolveCaretRoot()) {
|
|
|
38729
39401
|
// src/commands/install/local.ts
|
|
38730
39402
|
init_paths3();
|
|
38731
39403
|
function devMarketplaceDir() {
|
|
38732
|
-
return
|
|
39404
|
+
return join23(stateDir(), "dev-marketplace");
|
|
38733
39405
|
}
|
|
38734
39406
|
function resolveLocalCheckout(opts = {}, deps = {}) {
|
|
38735
39407
|
const repoDir = resolve2(caretRoot(deps));
|
|
38736
|
-
if (!existsSync13(
|
|
39408
|
+
if (!existsSync13(join23(repoDir, ".claude-plugin", "marketplace.json"))) {
|
|
38737
39409
|
throw new Error(`--from-local must run from a caret checkout (no .claude-plugin/marketplace.json at ${repoDir}). A published caret installs with a plain \`caret install\`.`);
|
|
38738
39410
|
}
|
|
38739
39411
|
if (opts.requireArtifacts !== false) {
|
|
38740
39412
|
const missing = [
|
|
38741
|
-
isExecutable(
|
|
38742
|
-
isDirectory(
|
|
39413
|
+
isExecutable(join23(repoDir, "bin", "caret-native")) ? null : "bin/caret-native",
|
|
39414
|
+
isDirectory(join23(repoDir, "bin", "ui")) ? null : "bin/ui"
|
|
38743
39415
|
].filter((m) => m !== null);
|
|
38744
39416
|
if (missing.length > 0) {
|
|
38745
39417
|
throw new Error(`--from-local reuses the build artifacts bin/caret-native + bin/ui, and ${repoDir} is missing ${missing.join(" + ")} \u2014 run \`mise run build\` first.`);
|
|
@@ -38757,10 +39429,10 @@ function caretRoot(deps) {
|
|
|
38757
39429
|
}
|
|
38758
39430
|
}
|
|
38759
39431
|
function writeDevMarketplace(repoDir, outDir) {
|
|
38760
|
-
|
|
38761
|
-
mkdirSync5(
|
|
38762
|
-
symlinkSync(resolve2(repoDir),
|
|
38763
|
-
|
|
39432
|
+
rmSync6(outDir, { recursive: true, force: true });
|
|
39433
|
+
mkdirSync5(join23(outDir, ".claude-plugin"), { recursive: true });
|
|
39434
|
+
symlinkSync(resolve2(repoDir), join23(outDir, "caret"));
|
|
39435
|
+
writeFileSync11(join23(outDir, ".claude-plugin", "marketplace.json"), `${JSON.stringify({
|
|
38764
39436
|
name: "caret",
|
|
38765
39437
|
description: "Local caret dev build (generated by caret install --from-local).",
|
|
38766
39438
|
owner: { name: "macintacos" },
|
|
@@ -38771,20 +39443,20 @@ function writeDevMarketplace(repoDir, outDir) {
|
|
|
38771
39443
|
async function prewarmLocalBuild(repoDir, deps = {}) {
|
|
38772
39444
|
const spawn = deps.spawn ?? runCaptured;
|
|
38773
39445
|
try {
|
|
38774
|
-
await spawn([
|
|
39446
|
+
await spawn([join23(repoDir, "bin", "caret-native"), "prewarm"]);
|
|
38775
39447
|
} catch {}
|
|
38776
39448
|
}
|
|
38777
39449
|
function isExecutable(path) {
|
|
38778
39450
|
try {
|
|
38779
|
-
|
|
38780
|
-
return
|
|
39451
|
+
accessSync3(path, constants3.X_OK);
|
|
39452
|
+
return statSync7(path).isFile();
|
|
38781
39453
|
} catch {
|
|
38782
39454
|
return false;
|
|
38783
39455
|
}
|
|
38784
39456
|
}
|
|
38785
39457
|
function isDirectory(path) {
|
|
38786
39458
|
try {
|
|
38787
|
-
return
|
|
39459
|
+
return statSync7(path).isDirectory();
|
|
38788
39460
|
} catch {
|
|
38789
39461
|
return false;
|
|
38790
39462
|
}
|
|
@@ -39050,12 +39722,12 @@ async function runInstallClaudeTarget(opts, deps = {}) {
|
|
|
39050
39722
|
|
|
39051
39723
|
// src/commands/install/opencode.ts
|
|
39052
39724
|
init_config_plugin();
|
|
39053
|
-
import { writeFileSync as
|
|
39054
|
-
import { basename as
|
|
39725
|
+
import { writeFileSync as writeFileSync13 } from "fs";
|
|
39726
|
+
import { basename as basename7, join as join24 } from "path";
|
|
39055
39727
|
|
|
39056
39728
|
// src/adapters/opencode/deploy.ts
|
|
39057
|
-
import { existsSync as existsSync14, mkdirSync as mkdirSync6, rmSync as
|
|
39058
|
-
import { dirname as
|
|
39729
|
+
import { existsSync as existsSync14, mkdirSync as mkdirSync6, rmSync as rmSync7, writeFileSync as writeFileSync12 } from "fs";
|
|
39730
|
+
import { dirname as dirname11 } from "path";
|
|
39059
39731
|
function renderPlugin(source, opts) {
|
|
39060
39732
|
return source.replaceAll("__CARET_VERSION__", () => opts.version).replaceAll("__CARET_BIN__", () => opts.binPath).replaceAll("__CARET_DEMO_TEMPLATE__", () => opts.demoTemplate);
|
|
39061
39733
|
}
|
|
@@ -39063,8 +39735,8 @@ function deployFiles(files, opts) {
|
|
|
39063
39735
|
const paths = [];
|
|
39064
39736
|
for (const f of files) {
|
|
39065
39737
|
if (!opts.dryRun) {
|
|
39066
|
-
mkdirSync6(
|
|
39067
|
-
|
|
39738
|
+
mkdirSync6(dirname11(f.path), { recursive: true });
|
|
39739
|
+
writeFileSync12(f.path, f.contents);
|
|
39068
39740
|
}
|
|
39069
39741
|
paths.push(f.path);
|
|
39070
39742
|
}
|
|
@@ -39076,7 +39748,7 @@ function removeFiles(targets, opts) {
|
|
|
39076
39748
|
if (!existsSync14(p))
|
|
39077
39749
|
continue;
|
|
39078
39750
|
if (!opts.dryRun)
|
|
39079
|
-
|
|
39751
|
+
rmSync7(p, { force: true });
|
|
39080
39752
|
paths.push(p);
|
|
39081
39753
|
}
|
|
39082
39754
|
return { paths, dryRun: opts.dryRun };
|
|
@@ -39084,6 +39756,7 @@ function removeFiles(targets, opts) {
|
|
|
39084
39756
|
|
|
39085
39757
|
// src/commands/install/opencode.ts
|
|
39086
39758
|
init_entries();
|
|
39759
|
+
init_host();
|
|
39087
39760
|
init_paths2();
|
|
39088
39761
|
init_upgrade();
|
|
39089
39762
|
|
|
@@ -39104,7 +39777,7 @@ var INSTALL_TARGETS = [
|
|
|
39104
39777
|
{
|
|
39105
39778
|
id: "opencode",
|
|
39106
39779
|
label: "OpenCode",
|
|
39107
|
-
hint: "add caret to
|
|
39780
|
+
hint: "add caret to OpenCode's plugin list + deploy /caret:* commands",
|
|
39108
39781
|
detect: (probe) => probe.hasCommand("opencode") || probe.hasPath(opencodeConfigDir())
|
|
39109
39782
|
}
|
|
39110
39783
|
];
|
|
@@ -39166,42 +39839,146 @@ function sameEntryForm(entry, specifier) {
|
|
|
39166
39839
|
return false;
|
|
39167
39840
|
return entryIsLocal ? entry === specifier : true;
|
|
39168
39841
|
}
|
|
39169
|
-
function
|
|
39842
|
+
function keptEntry(entries, specifier) {
|
|
39843
|
+
const sameForm = entries.filter((e) => sameEntryForm(e.spec, specifier));
|
|
39844
|
+
return sameForm.find((e) => splitPluginSpecifier(e.spec).version !== null) ?? sameForm[0];
|
|
39845
|
+
}
|
|
39846
|
+
function setCaretPluginEntry(text, target) {
|
|
39847
|
+
const { spec, key, isCheckout } = target;
|
|
39170
39848
|
if (text === null)
|
|
39171
|
-
return addPluginToConfigText(null,
|
|
39172
|
-
const
|
|
39173
|
-
|
|
39849
|
+
return addPluginToConfigText(null, spec, key);
|
|
39850
|
+
const entries = caretEntries(text, isCheckout);
|
|
39851
|
+
const kept = entries.find((e) => e.key === key && e.spec === spec) ?? entries.find((e) => e.key === key);
|
|
39852
|
+
const others = entries.filter((e) => e !== kept);
|
|
39853
|
+
if (kept === undefined)
|
|
39854
|
+
return addPluginToConfigText(dropEntries(text, others), spec, key);
|
|
39855
|
+
const placed = kept.spec === spec ? text : setPluginItemSpec(text, { key, index: kept.index, spec });
|
|
39856
|
+
return dropEntries(placed, others);
|
|
39857
|
+
}
|
|
39858
|
+
function stripCaret(isCheckout) {
|
|
39859
|
+
return (text) => text === null ? null : dropEntries(text, caretEntries(text, isCheckout));
|
|
39860
|
+
}
|
|
39861
|
+
function bumpPin(version) {
|
|
39862
|
+
return (text) => {
|
|
39863
|
+
const entry = caretPackageEntry(text);
|
|
39864
|
+
return text === null || entry === null ? null : setPluginVersionInConfigText(text, { pkg: CARET_PACKAGE, version, key: entry.key });
|
|
39865
|
+
};
|
|
39866
|
+
}
|
|
39867
|
+
function hostLine({
|
|
39868
|
+
hosts,
|
|
39869
|
+
version,
|
|
39870
|
+
key,
|
|
39871
|
+
movedFrom
|
|
39872
|
+
}) {
|
|
39873
|
+
const loadedByAll = version === null ? ", which every OpenCode loads" : "";
|
|
39874
|
+
const found = `${describeHosts(hosts)} \u2014 writing caret to ${key}${loadedByAll}`;
|
|
39875
|
+
return movedFrom === null ? found : `${found} (moved from ${movedFrom})`;
|
|
39174
39876
|
}
|
|
39175
39877
|
async function runInstallOpencodeTarget(opts, deps = {}) {
|
|
39176
39878
|
const dir = deps.configDir ?? opencodeConfigDir();
|
|
39177
|
-
const
|
|
39178
|
-
|
|
39179
|
-
|
|
39180
|
-
|
|
39181
|
-
|
|
39182
|
-
|
|
39183
|
-
|
|
39184
|
-
|
|
39185
|
-
|
|
39186
|
-
|
|
39187
|
-
|
|
39188
|
-
|
|
39189
|
-
|
|
39879
|
+
const setup = {
|
|
39880
|
+
dir,
|
|
39881
|
+
pkg: deps.packaging ?? loadOpencodePackaging(),
|
|
39882
|
+
ui: deps.ui ?? silentUI,
|
|
39883
|
+
isCheckout: deps.isCheckout ?? isCaretCheckout,
|
|
39884
|
+
configFiles: existingConfigFiles(dir),
|
|
39885
|
+
legacy: existingLegacyInstallFiles(dir)
|
|
39886
|
+
};
|
|
39887
|
+
if (opts.uninstall)
|
|
39888
|
+
return opts.dryRun ? previewUninstall(setup) : uninstallOpencode(setup);
|
|
39889
|
+
return opts.dryRun ? previewInstall(setup, opts, deps) : installOpencode(setup, opts, deps);
|
|
39890
|
+
}
|
|
39891
|
+
function readPlacement(setup, local, probe) {
|
|
39892
|
+
const specifier = local ? localPluginSpecifier(local.repoDir) : CARET_PACKAGE;
|
|
39893
|
+
const hosts = probe();
|
|
39894
|
+
const version = sharedHost(hosts);
|
|
39895
|
+
const key = pluginKeyFor(version);
|
|
39896
|
+
const allEntries = readCaretEntries(setup.configFiles, setup.isCheckout);
|
|
39897
|
+
const loadedFiles = loadedConfigFiles(setup.configFiles, version);
|
|
39898
|
+
const loadedEntries = readCaretEntries(loadedFiles, setup.isCheckout);
|
|
39899
|
+
const otherKey = key === "plugin" ? "plugins" : "plugin";
|
|
39900
|
+
const movedFrom = allEntries.some((e) => e.key === otherKey) ? otherKey : null;
|
|
39901
|
+
const writtenSpec = (keptEntry(loadedEntries, specifier) ?? keptEntry(allEntries, specifier))?.spec ?? specifier;
|
|
39902
|
+
const target = resolveConfigFile(setup.dir, hostConfigFilenames(version));
|
|
39903
|
+
return { specifier, hosts, version, key, movedFrom, writtenSpec, target, loadedFiles };
|
|
39904
|
+
}
|
|
39905
|
+
function installEdits({ configFiles, isCheckout }, { target, writtenSpec, key }) {
|
|
39906
|
+
return [
|
|
39907
|
+
...configFiles.filter((path) => path !== target).map((path) => ({ path, transform: stripCaret(isCheckout) })),
|
|
39908
|
+
{
|
|
39909
|
+
path: target,
|
|
39910
|
+
transform: (text) => setCaretPluginEntry(text, { spec: writtenSpec, key, isCheckout })
|
|
39911
|
+
}
|
|
39912
|
+
];
|
|
39913
|
+
}
|
|
39914
|
+
function uninstallEdits({
|
|
39915
|
+
configFiles,
|
|
39916
|
+
isCheckout
|
|
39917
|
+
}) {
|
|
39918
|
+
return configFiles.map((path) => ({ path, transform: stripCaret(isCheckout) }));
|
|
39919
|
+
}
|
|
39920
|
+
function ignoredConfigNote({ configFiles }, { version, target, loadedFiles }, changed) {
|
|
39921
|
+
const ignored = configFiles.filter((p) => !loadedFiles.includes(p));
|
|
39922
|
+
if (ignored.length === 0)
|
|
39923
|
+
return null;
|
|
39924
|
+
if (!ignored.some((p) => changed.includes(p)) && configFiles.includes(target))
|
|
39925
|
+
return null;
|
|
39926
|
+
const host = version === null ? "OpenCode" : `OpenCode v${version[0]}`;
|
|
39927
|
+
const names = ignored.map((p) => basename7(p)).join(", ");
|
|
39928
|
+
return `${host} ignores ${names} \u2014 caret goes in ${basename7(target)}`;
|
|
39929
|
+
}
|
|
39930
|
+
function commandPaths({ dir, pkg }) {
|
|
39931
|
+
return pkg.commands.map((c) => join24(commandDir(dir), namespacedCommandFilename(c.name)));
|
|
39932
|
+
}
|
|
39933
|
+
function previewNote(setup, verb, parts) {
|
|
39934
|
+
const { legacy, ui } = setup;
|
|
39935
|
+
const sweep = legacy.length === 0 ? [] : ["", "pre-array-install files to remove:", ...legacy];
|
|
39936
|
+
ui.note([...parts.configs, ...commandPaths(setup), ...parts.entry, ...sweep, ...parts.found].join(`
|
|
39190
39937
|
`), `OpenCode \u2014 would ${verb}`);
|
|
39191
|
-
|
|
39192
|
-
|
|
39193
|
-
|
|
39194
|
-
|
|
39195
|
-
|
|
39196
|
-
await
|
|
39197
|
-
|
|
39198
|
-
}
|
|
39199
|
-
|
|
39200
|
-
|
|
39201
|
-
|
|
39938
|
+
}
|
|
39939
|
+
async function previewInstall(setup, opts, deps) {
|
|
39940
|
+
const placed = readPlacement(setup, opts.local, deps.opencodeHosts ?? readOpencodeHosts);
|
|
39941
|
+
const found = checksPublished(opts.local) ? [
|
|
39942
|
+
"",
|
|
39943
|
+
previewLine(await readVerdict({ configFiles: setup.configFiles, host: placed.version }, deps))
|
|
39944
|
+
] : [];
|
|
39945
|
+
const entry = ["", `plugin entry: ${placed.specifier} \u2192 ${placed.key}`, hostLine(placed)];
|
|
39946
|
+
const changed = strictPlan(installEdits(setup, placed)).map((e) => e.path);
|
|
39947
|
+
const note = ignoredConfigNote(setup, placed, changed);
|
|
39948
|
+
if (note !== null)
|
|
39949
|
+
entry.push(note);
|
|
39950
|
+
const configs = [placed.target, ...changed.filter((p) => p !== placed.target)];
|
|
39951
|
+
previewNote(setup, "write", { configs, entry, found });
|
|
39952
|
+
}
|
|
39953
|
+
function previewUninstall(setup) {
|
|
39954
|
+
const configs = lenientPlan(uninstallEdits(setup), setup.ui).planned.map((e) => e.path);
|
|
39955
|
+
previewNote(setup, "remove", { configs, entry: [], found: [] });
|
|
39956
|
+
}
|
|
39957
|
+
async function uninstallOpencode(setup) {
|
|
39958
|
+
const { ui, dir, legacy } = setup;
|
|
39959
|
+
const { planned, uneditable } = lenientPlan(uninstallEdits(setup), ui);
|
|
39960
|
+
await ui.step("Removing caret from OpenCode's plugin and plugins keys", async () => writeConfigEdits(planned), (changed) => {
|
|
39961
|
+
if (changed.length > 0)
|
|
39962
|
+
return `Removed caret from ${changed.map((p) => basename7(p)).join(", ")}`;
|
|
39963
|
+
return uneditable.length > 0 ? "caret was not in any editable OpenCode config" : "caret was not in any OpenCode config";
|
|
39964
|
+
});
|
|
39965
|
+
await ui.step("Removing the /caret:* command files", async () => removeFiles(commandPaths(setup), { dryRun: false }), (removed) => `Removed ${removed.paths.length} command file(s) from ${dir}`);
|
|
39966
|
+
await sweepLegacy(legacy, dir, ui);
|
|
39967
|
+
}
|
|
39968
|
+
async function installOpencode(setup, opts, deps) {
|
|
39969
|
+
const { dir, pkg, ui, legacy } = setup;
|
|
39970
|
+
const placed = readPlacement(setup, opts.local, deps.opencodeHosts ?? readOpencodeHosts);
|
|
39971
|
+
const { specifier, key, writtenSpec, target } = placed;
|
|
39972
|
+
ui.info(hostLine(placed));
|
|
39973
|
+
const changed = await ui.step(`Adding ${specifier} to OpenCode's ${key} array`, async () => writeConfigEdits(strictPlan(installEdits(setup, placed))), (written) => installedLine(writtenSpec, target, written));
|
|
39974
|
+
const note = ignoredConfigNote(setup, placed, changed);
|
|
39975
|
+
if (note !== null)
|
|
39976
|
+
ui.info(note);
|
|
39977
|
+
if (checksPublished(opts.local))
|
|
39978
|
+
await upgradeStep(target, opts, deps, ui);
|
|
39202
39979
|
await sweepLegacy(legacy, dir, ui);
|
|
39203
39980
|
const files = pkg.commands.map((c) => ({
|
|
39204
|
-
path:
|
|
39981
|
+
path: join24(commandDir(dir), namespacedCommandFilename(c.name)),
|
|
39205
39982
|
contents: renderPlugin(c.contents, {
|
|
39206
39983
|
version: VERSION,
|
|
39207
39984
|
binPath: pkg.binPath,
|
|
@@ -39215,18 +39992,18 @@ async function sweepLegacy(legacy, dir, ui) {
|
|
|
39215
39992
|
return;
|
|
39216
39993
|
await ui.step("Removing the pre-array-install files", async () => removeFiles(legacy, { dryRun: false }), (removed) => `Removed ${removed.paths.length} pre-array-install file(s) from ${dir}`);
|
|
39217
39994
|
}
|
|
39218
|
-
function
|
|
39219
|
-
return
|
|
39995
|
+
function checksPublished(local) {
|
|
39996
|
+
return local === undefined;
|
|
39220
39997
|
}
|
|
39221
39998
|
function previewLine(verdict) {
|
|
39222
39999
|
const line = upgradeVerdictLine(verdict);
|
|
39223
40000
|
return verdict.kind === "unknown" ? `${line} (${verdict.reason})` : line;
|
|
39224
40001
|
}
|
|
39225
|
-
async function readVerdict(
|
|
39226
|
-
return readUpgradeVerdict({
|
|
40002
|
+
async function readVerdict(at, deps) {
|
|
40003
|
+
return readUpgradeVerdict({ ...at, cacheDir: deps.cacheDir, published: deps.published });
|
|
39227
40004
|
}
|
|
39228
|
-
async function upgradeStep(
|
|
39229
|
-
const verdict = await ui.step("Checking OpenCode's caret version", () => readVerdict(
|
|
40005
|
+
async function upgradeStep(target, opts, deps, ui) {
|
|
40006
|
+
const verdict = await ui.step("Checking OpenCode's caret version", () => readVerdict({ configFiles: [target] }, deps), upgradeVerdictLine);
|
|
39230
40007
|
if (verdict.kind === "unknown") {
|
|
39231
40008
|
ui.warn(`Could not check OpenCode's caret version (${verdict.reason}) \u2014 nothing changed.`);
|
|
39232
40009
|
return;
|
|
@@ -39247,15 +40024,69 @@ async function upgradeStep(configFile, opts, deps, ui) {
|
|
|
39247
40024
|
await ui.step("Clearing OpenCode's cached caret", async () => (deps.clearCache ?? clearCachedCaret)(dirs), (cleared) => `Cleared ${cleared.length} cached ${cleared.length === 1 ? "copy" : "copies"} \u2014 OpenCode re-resolves on next start`);
|
|
39248
40025
|
return;
|
|
39249
40026
|
}
|
|
39250
|
-
await ui.step(`Bumping ${CARET_PACKAGE} to ${verdict.published}`, async () =>
|
|
40027
|
+
await ui.step(`Bumping ${CARET_PACKAGE} to ${verdict.published}`, async () => writeConfigEdits(strictPlan([{ path: target, transform: bumpPin(verdict.published) }])), (changed) => changed.length > 0 ? `Bumped the pin to ${CARET_PACKAGE}@${verdict.published}` : `Left ${basename7(target)} unchanged`);
|
|
39251
40028
|
}
|
|
39252
|
-
function
|
|
39253
|
-
const
|
|
39254
|
-
const
|
|
39255
|
-
|
|
39256
|
-
|
|
39257
|
-
|
|
39258
|
-
|
|
40029
|
+
function installedLine(spec, target, written) {
|
|
40030
|
+
const name = basename7(target);
|
|
40031
|
+
const line = written.includes(target) ? `Added ${spec} to ${name}` : `${spec} was already in ${name}`;
|
|
40032
|
+
const stripped = written.filter((p) => p !== target).map((p) => basename7(p));
|
|
40033
|
+
return stripped.length === 0 ? line : `${line} (removed it from ${stripped.join(", ")})`;
|
|
40034
|
+
}
|
|
40035
|
+
function planConfigEdits(edits) {
|
|
40036
|
+
const plan = { planned: [], uneditable: [] };
|
|
40037
|
+
for (const { path, transform } of edits) {
|
|
40038
|
+
let existing;
|
|
40039
|
+
try {
|
|
40040
|
+
existing = readConfigText(path);
|
|
40041
|
+
} catch (e) {
|
|
40042
|
+
plan.uneditable.push({ path, reason: errorMessage(e) });
|
|
40043
|
+
continue;
|
|
40044
|
+
}
|
|
40045
|
+
const parseError = existing === null ? null : configParseError(existing);
|
|
40046
|
+
if (parseError !== null) {
|
|
40047
|
+
plan.uneditable.push({ path, reason: parseError });
|
|
40048
|
+
continue;
|
|
40049
|
+
}
|
|
40050
|
+
let text;
|
|
40051
|
+
try {
|
|
40052
|
+
text = transform(existing);
|
|
40053
|
+
} catch (e) {
|
|
40054
|
+
plan.uneditable.push({ path, reason: errorMessage(e) });
|
|
40055
|
+
continue;
|
|
40056
|
+
}
|
|
40057
|
+
if (text !== null && text !== existing)
|
|
40058
|
+
plan.planned.push({ path, text });
|
|
40059
|
+
}
|
|
40060
|
+
return plan;
|
|
40061
|
+
}
|
|
40062
|
+
function strictPlan(edits) {
|
|
40063
|
+
const { planned, uneditable } = planConfigEdits(edits);
|
|
40064
|
+
if (uneditable.length > 0) {
|
|
40065
|
+
const files = uneditable.map(({ path, reason }) => `${path} (${reason})`).join(", ");
|
|
40066
|
+
throw new Error(`can't edit ${files} \u2014 caret changed nothing`);
|
|
40067
|
+
}
|
|
40068
|
+
return planned;
|
|
40069
|
+
}
|
|
40070
|
+
function lenientPlan(edits, ui) {
|
|
40071
|
+
const plan = planConfigEdits(edits);
|
|
40072
|
+
for (const { path, reason } of plan.uneditable) {
|
|
40073
|
+
ui.warn(`${path} can't be edited (${reason}) \u2014 caret leaves it alone`);
|
|
40074
|
+
}
|
|
40075
|
+
return plan;
|
|
40076
|
+
}
|
|
40077
|
+
function writeConfigEdits(planned) {
|
|
40078
|
+
const written = [];
|
|
40079
|
+
for (const { path, text } of planned) {
|
|
40080
|
+
try {
|
|
40081
|
+
writeFileSync13(path, text);
|
|
40082
|
+
} catch (e) {
|
|
40083
|
+
if (written.length === 0)
|
|
40084
|
+
throw e;
|
|
40085
|
+
throw new Error(`${errorMessage(e)} \u2014 already changed: ${written.join(", ")}`, { cause: e });
|
|
40086
|
+
}
|
|
40087
|
+
written.push(path);
|
|
40088
|
+
}
|
|
40089
|
+
return written;
|
|
39259
40090
|
}
|
|
39260
40091
|
|
|
39261
40092
|
// src/commands/install/launcher.ts
|
|
@@ -39269,13 +40100,13 @@ import {
|
|
|
39269
40100
|
mkdirSync as mkdirSync7,
|
|
39270
40101
|
mkdtempSync,
|
|
39271
40102
|
readdirSync as readdirSync8,
|
|
39272
|
-
readFileSync as
|
|
40103
|
+
readFileSync as readFileSync15,
|
|
39273
40104
|
renameSync as renameSync5,
|
|
39274
|
-
rmSync as
|
|
39275
|
-
statSync as
|
|
39276
|
-
writeFileSync as
|
|
40105
|
+
rmSync as rmSync8,
|
|
40106
|
+
statSync as statSync8,
|
|
40107
|
+
writeFileSync as writeFileSync14
|
|
39277
40108
|
} from "fs";
|
|
39278
|
-
import {
|
|
40109
|
+
import { dirname as dirname12, join as join25 } from "path";
|
|
39279
40110
|
init_paths2();
|
|
39280
40111
|
init_paths3();
|
|
39281
40112
|
init_lifecycle();
|
|
@@ -39286,34 +40117,34 @@ function publishedRoot(kind = buildKind(), root = resolveCaretRoot) {
|
|
|
39286
40117
|
if (kind !== "bundle")
|
|
39287
40118
|
return;
|
|
39288
40119
|
const dir = root();
|
|
39289
|
-
return existsSync16(
|
|
40120
|
+
return existsSync16(join25(dir, "src", "cli.ts")) ? undefined : { root: dir, version: VERSION };
|
|
39290
40121
|
}
|
|
39291
40122
|
function stageOwnedRoot(from) {
|
|
39292
40123
|
if (!isRunnableRoot(from.root))
|
|
39293
40124
|
return;
|
|
39294
|
-
const dest =
|
|
40125
|
+
const dest = join25(ownedRootsDir(), from.version);
|
|
39295
40126
|
if (isRunnableRoot(dest))
|
|
39296
40127
|
return;
|
|
39297
|
-
const parsed = PackageFiles.safeParse(readJsonFileSync(
|
|
40128
|
+
const parsed = PackageFiles.safeParse(readJsonFileSync(join25(from.root, "package.json")));
|
|
39298
40129
|
if (!parsed.success)
|
|
39299
40130
|
return;
|
|
39300
40131
|
ensureStateDir(ownedRootsDir());
|
|
39301
|
-
const tmp = mkdtempSync(
|
|
40132
|
+
const tmp = mkdtempSync(join25(ownedRootsDir(), `.${from.version}.`));
|
|
39302
40133
|
try {
|
|
39303
40134
|
for (const entry of [...parsed.data.files, "package.json"]) {
|
|
39304
|
-
const src =
|
|
40135
|
+
const src = join25(from.root, entry);
|
|
39305
40136
|
if (!existsSync16(src))
|
|
39306
40137
|
continue;
|
|
39307
|
-
const target =
|
|
39308
|
-
mkdirSync7(
|
|
40138
|
+
const target = join25(tmp, entry);
|
|
40139
|
+
mkdirSync7(dirname12(target), { recursive: true });
|
|
39309
40140
|
cpSync(src, target, { recursive: true });
|
|
39310
40141
|
}
|
|
39311
40142
|
if (!isRunnableRoot(tmp))
|
|
39312
40143
|
return;
|
|
39313
|
-
|
|
40144
|
+
rmSync8(dest, { recursive: true, force: true });
|
|
39314
40145
|
renameSync5(tmp, dest);
|
|
39315
40146
|
} finally {
|
|
39316
|
-
|
|
40147
|
+
rmSync8(tmp, { recursive: true, force: true });
|
|
39317
40148
|
}
|
|
39318
40149
|
}
|
|
39319
40150
|
function pruneOwnedRoots() {
|
|
@@ -39325,22 +40156,22 @@ function pruneOwnedRoots() {
|
|
|
39325
40156
|
}
|
|
39326
40157
|
const keep = pickLauncherRoot(null, listDirs(ownedRootsDir()).map((dir) => ({ dir, owned: true })))?.root;
|
|
39327
40158
|
for (const name of entries) {
|
|
39328
|
-
const path =
|
|
40159
|
+
const path = join25(ownedRootsDir(), name);
|
|
39329
40160
|
if (path === keep)
|
|
39330
40161
|
continue;
|
|
39331
40162
|
try {
|
|
39332
|
-
|
|
40163
|
+
rmSync8(path, { recursive: true, force: true });
|
|
39333
40164
|
} catch {}
|
|
39334
40165
|
}
|
|
39335
40166
|
}
|
|
39336
40167
|
function installLauncher(deps = {}) {
|
|
39337
|
-
const source = (deps.source ?? (() =>
|
|
40168
|
+
const source = (deps.source ?? (() => join25(resolveCaretRoot(), "bin", "caret-launcher")))();
|
|
39338
40169
|
if (deps.pinnedRoot === undefined) {
|
|
39339
40170
|
const owned = (deps.ownedRoot ?? publishedRoot)();
|
|
39340
40171
|
if (owned)
|
|
39341
40172
|
stageOwnedRoot(owned);
|
|
39342
40173
|
}
|
|
39343
|
-
ensureStateDir(
|
|
40174
|
+
ensureStateDir(dirname12(launcherPath()));
|
|
39344
40175
|
const tmp = `${launcherPath()}.${process.pid}.tmp`;
|
|
39345
40176
|
copyFileSync(source, tmp);
|
|
39346
40177
|
chmodSync4(tmp, 493);
|
|
@@ -39348,7 +40179,7 @@ function installLauncher(deps = {}) {
|
|
|
39348
40179
|
const bunPath = deps.bunPath ?? (buildKind() === "binary" ? undefined : process.execPath);
|
|
39349
40180
|
const unpinned = deps.pinnedRoot === undefined && existsSync16(launcherPinnedRootFile());
|
|
39350
40181
|
if (unpinned)
|
|
39351
|
-
|
|
40182
|
+
rmSync8(launcherPinnedRootFile(), { force: true });
|
|
39352
40183
|
if (bunPath === undefined && deps.serviceLabel === undefined && deps.pinnedRoot === undefined) {
|
|
39353
40184
|
return { unpinned };
|
|
39354
40185
|
}
|
|
@@ -39362,13 +40193,13 @@ function installLauncher(deps = {}) {
|
|
|
39362
40193
|
return { unpinned };
|
|
39363
40194
|
}
|
|
39364
40195
|
function writeRecord(path, value) {
|
|
39365
|
-
|
|
40196
|
+
writeFileSync14(path, `${value}
|
|
39366
40197
|
`, { mode: 384 });
|
|
39367
40198
|
}
|
|
39368
40199
|
function uninstallLauncher() {
|
|
39369
|
-
|
|
39370
|
-
|
|
39371
|
-
|
|
40200
|
+
rmSync8(dirname12(launcherPath()), { recursive: true, force: true });
|
|
40201
|
+
rmSync8(launcherRecordDir(), { recursive: true, force: true });
|
|
40202
|
+
rmSync8(ownedRootsDir(), { recursive: true, force: true });
|
|
39372
40203
|
}
|
|
39373
40204
|
function listDirs(dir) {
|
|
39374
40205
|
let names;
|
|
@@ -39377,28 +40208,28 @@ function listDirs(dir) {
|
|
|
39377
40208
|
} catch {
|
|
39378
40209
|
return [];
|
|
39379
40210
|
}
|
|
39380
|
-
return names.filter((n) => !n.startsWith(".")).sort().map((n) =>
|
|
40211
|
+
return names.filter((n) => !n.startsWith(".")).sort().map((n) => join25(dir, n)).filter(isDir);
|
|
39381
40212
|
}
|
|
39382
40213
|
function isDir(path) {
|
|
39383
40214
|
try {
|
|
39384
|
-
return
|
|
40215
|
+
return statSync8(path).isDirectory();
|
|
39385
40216
|
} catch {
|
|
39386
40217
|
return false;
|
|
39387
40218
|
}
|
|
39388
40219
|
}
|
|
39389
40220
|
function launcherCandidateDirs() {
|
|
39390
|
-
const
|
|
40221
|
+
const opencodeRoots = opencodeRootPaths().filter(isDir);
|
|
39391
40222
|
return [
|
|
39392
40223
|
...[
|
|
39393
|
-
...listDirs(
|
|
39394
|
-
...
|
|
40224
|
+
...listDirs(join25(claudeConfigDir(), "plugins", "cache", "caret", "caret")),
|
|
40225
|
+
...opencodeRoots
|
|
39395
40226
|
].map((dir) => ({ dir, owned: false })),
|
|
39396
40227
|
...listDirs(ownedRootsDir()).map((dir) => ({ dir, owned: true }))
|
|
39397
40228
|
];
|
|
39398
40229
|
}
|
|
39399
40230
|
function manifestVersion(root) {
|
|
39400
40231
|
try {
|
|
39401
|
-
const text =
|
|
40232
|
+
const text = readFileSync15(join25(root, "package.json"), "utf8");
|
|
39402
40233
|
return text.match(/^[^\S\n]*"version"[^\S\n]*:[^\S\n]*"([^"\n]*)"/m)?.[1];
|
|
39403
40234
|
} catch {
|
|
39404
40235
|
return;
|
|
@@ -39433,9 +40264,10 @@ function prodLauncherRoot() {
|
|
|
39433
40264
|
}
|
|
39434
40265
|
|
|
39435
40266
|
// src/commands/install/service.ts
|
|
39436
|
-
init_constants();
|
|
39437
40267
|
init_paths3();
|
|
39438
40268
|
init_settings();
|
|
40269
|
+
init_address();
|
|
40270
|
+
init_client();
|
|
39439
40271
|
init_lifecycle();
|
|
39440
40272
|
init_build_id();
|
|
39441
40273
|
function prodServiceWatch() {
|
|
@@ -39445,6 +40277,8 @@ var SETTLE_POLL_MS = 500;
|
|
|
39445
40277
|
var SETTLE_WINDOW_MS = 30000;
|
|
39446
40278
|
var SETTLE_ATTEMPTS = SETTLE_WINDOW_MS / SETTLE_POLL_MS;
|
|
39447
40279
|
function classify(health, installerVersion, replaced) {
|
|
40280
|
+
if (health instanceof DaemonAuthError)
|
|
40281
|
+
return { kind: "unauthorized", message: health.message };
|
|
39448
40282
|
if (health?.service !== "caret")
|
|
39449
40283
|
return { kind: "silent" };
|
|
39450
40284
|
const answeringVersion = health.version;
|
|
@@ -39457,10 +40291,19 @@ function classify(health, installerVersion, replaced) {
|
|
|
39457
40291
|
return { kind: "silent" };
|
|
39458
40292
|
return isNewer(installerVersion, answeringVersion) ? { kind: "stale", version: answeringVersion } : { kind: "ready", version: answeringVersion };
|
|
39459
40293
|
}
|
|
40294
|
+
async function probe(baseUrl, watch) {
|
|
40295
|
+
try {
|
|
40296
|
+
return await watch.health(baseUrl);
|
|
40297
|
+
} catch (e) {
|
|
40298
|
+
if (e instanceof DaemonAuthError)
|
|
40299
|
+
return e;
|
|
40300
|
+
throw e;
|
|
40301
|
+
}
|
|
40302
|
+
}
|
|
39460
40303
|
async function awaitServed(baseUrl, watch, replaced) {
|
|
39461
40304
|
let served = { kind: "silent" };
|
|
39462
40305
|
for (let attempt = 0;attempt < SETTLE_ATTEMPTS; attempt++) {
|
|
39463
|
-
served = classify(await
|
|
40306
|
+
served = classify(await probe(baseUrl, watch), watch.version, replaced);
|
|
39464
40307
|
if (served.kind === "ready")
|
|
39465
40308
|
return served;
|
|
39466
40309
|
await watch.sleep(SETTLE_POLL_MS);
|
|
@@ -39486,6 +40329,8 @@ ${log}`;
|
|
|
39486
40329
|
case "unsupervised":
|
|
39487
40330
|
return `A caret the service did not start${at(served.version)} holds the port.
|
|
39488
40331
|
The service takes over once it exits.`;
|
|
40332
|
+
case "unauthorized":
|
|
40333
|
+
return `${served.message.charAt(0).toUpperCase()}${served.message.slice(1)}.`;
|
|
39489
40334
|
case "silent":
|
|
39490
40335
|
return `No caret daemon answered within ${SETTLE_WINDOW_MS / 1000} seconds.
|
|
39491
40336
|
${log}`;
|
|
@@ -39521,9 +40366,9 @@ async function uninstallService(opts, deps, ui) {
|
|
|
39521
40366
|
}
|
|
39522
40367
|
var SERVE_COMMAND = "bunx --no-cache @macintacos/caret@latest serve";
|
|
39523
40368
|
async function reconcileService(opts, deps, ui) {
|
|
39524
|
-
const
|
|
39525
|
-
const reviewUrl =
|
|
39526
|
-
const baseUrl =
|
|
40369
|
+
const s = loadSettings();
|
|
40370
|
+
const reviewUrl = publicUrl(s.daemon, getPort(s));
|
|
40371
|
+
const baseUrl = daemonBaseUrl(s);
|
|
39527
40372
|
if (opts.choice === "run-yourself")
|
|
39528
40373
|
return runYourself({ ...opts, reviewUrl }, deps, ui);
|
|
39529
40374
|
await withService(deps, ui, async (target) => {
|
|
@@ -39563,7 +40408,7 @@ async function reconcileService(opts, deps, ui) {
|
|
|
39563
40408
|
}
|
|
39564
40409
|
const cycles = opts.refresh || opts.pinnedRoot !== undefined || unpinned;
|
|
39565
40410
|
const { watch } = deps;
|
|
39566
|
-
const replaced = cycles && watch ?
|
|
40411
|
+
const replaced = cycles && watch ? await probe(baseUrl, watch).then((h) => h instanceof DaemonAuthError ? undefined : h?.instanceId) : undefined;
|
|
39567
40412
|
if (cycles)
|
|
39568
40413
|
await manager.restart();
|
|
39569
40414
|
const settled = !watch || await settleService(baseUrl, watch, replaced, cycles, ui);
|
|
@@ -39745,6 +40590,52 @@ async function selectServiceChoice(opts, deps, ui) {
|
|
|
39745
40590
|
return choice;
|
|
39746
40591
|
}
|
|
39747
40592
|
|
|
40593
|
+
// src/commands/login-link.ts
|
|
40594
|
+
init_paths3();
|
|
40595
|
+
init_settings();
|
|
40596
|
+
init_address();
|
|
40597
|
+
init_token();
|
|
40598
|
+
function exposureWarning(daemon, port) {
|
|
40599
|
+
if (!isExposed(daemon.host) || authEnabled(daemon))
|
|
40600
|
+
return null;
|
|
40601
|
+
return `caret: warning: daemon.auth = "none" on an exposed daemon (daemon.host = ${daemon.host}) \u2014 anyone on the network who can reach port ${port} can read plans, read files under a review's working directory, and approve a plan.`;
|
|
40602
|
+
}
|
|
40603
|
+
function loginLinkOutcome(daemon, port, token, tokenFile) {
|
|
40604
|
+
const url = publicUrl(daemon, port);
|
|
40605
|
+
if (!authEnabled(daemon)) {
|
|
40606
|
+
const warning = exposureWarning(daemon, port);
|
|
40607
|
+
return {
|
|
40608
|
+
stderr: [
|
|
40609
|
+
`caret: daemon auth is off, so there is no login link \u2014 open ${url} instead.`,
|
|
40610
|
+
...warning === null ? [] : [warning]
|
|
40611
|
+
],
|
|
40612
|
+
code: 1
|
|
40613
|
+
};
|
|
40614
|
+
}
|
|
40615
|
+
if (token === null) {
|
|
40616
|
+
return {
|
|
40617
|
+
stderr: [
|
|
40618
|
+
`caret: no daemon token at ${tokenFile} yet \u2014 start the daemon (caret serve, or the caret service) to mint one.`
|
|
40619
|
+
],
|
|
40620
|
+
code: 1
|
|
40621
|
+
};
|
|
40622
|
+
}
|
|
40623
|
+
return { stdout: loginLink(url, token), stderr: [], code: 0 };
|
|
40624
|
+
}
|
|
40625
|
+
function runLoginLink() {
|
|
40626
|
+
const s = loadSettings();
|
|
40627
|
+
const tokenFile = daemonTokenFile();
|
|
40628
|
+
const token = authEnabled(s.daemon) ? readToken(tokenFile) : null;
|
|
40629
|
+
const outcome = loginLinkOutcome(s.daemon, getPort(s), token, tokenFile);
|
|
40630
|
+
for (const line of outcome.stderr)
|
|
40631
|
+
process.stderr.write(`${line}
|
|
40632
|
+
`);
|
|
40633
|
+
if (outcome.stdout !== undefined)
|
|
40634
|
+
process.stdout.write(`${outcome.stdout}
|
|
40635
|
+
`);
|
|
40636
|
+
process.exitCode = outcome.code;
|
|
40637
|
+
}
|
|
40638
|
+
|
|
39748
40639
|
// src/commands/prewarm.ts
|
|
39749
40640
|
init_settings();
|
|
39750
40641
|
init_lifecycle();
|
|
@@ -39768,11 +40659,14 @@ async function runPrewarm() {
|
|
|
39768
40659
|
init_adapters();
|
|
39769
40660
|
init_boot();
|
|
39770
40661
|
init_settings();
|
|
40662
|
+
init_address();
|
|
40663
|
+
init_client();
|
|
39771
40664
|
init_log();
|
|
39772
40665
|
|
|
39773
40666
|
// src/review/orchestrate.ts
|
|
39774
|
-
init_constants();
|
|
39775
40667
|
init_paths3();
|
|
40668
|
+
init_address();
|
|
40669
|
+
init_client();
|
|
39776
40670
|
init_log();
|
|
39777
40671
|
|
|
39778
40672
|
// src/plan/format.ts
|
|
@@ -39813,7 +40707,11 @@ var REVIEW_FAILURE_CODES = {
|
|
|
39813
40707
|
longPoll: "unexpected"
|
|
39814
40708
|
};
|
|
39815
40709
|
function reviewFailureCode(step, err) {
|
|
39816
|
-
|
|
40710
|
+
if (err instanceof TimeoutError)
|
|
40711
|
+
return "review-timeout";
|
|
40712
|
+
if (err instanceof DaemonAuthError)
|
|
40713
|
+
return "daemon-unauthorized";
|
|
40714
|
+
return REVIEW_FAILURE_CODES[step];
|
|
39817
40715
|
}
|
|
39818
40716
|
function withTimeout(p, ms, message) {
|
|
39819
40717
|
return new Promise((resolve, reject) => {
|
|
@@ -39834,6 +40732,11 @@ async function expireAbandoned(expire, handle) {
|
|
|
39834
40732
|
await expire(handle.baseUrl, handle.id, handle.version);
|
|
39835
40733
|
} catch {}
|
|
39836
40734
|
}
|
|
40735
|
+
function reviewUrl(baseUrl, hostname, id) {
|
|
40736
|
+
const u = new URL(baseUrl);
|
|
40737
|
+
u.hostname = hostname;
|
|
40738
|
+
return `${u.origin}/?review=${id}`;
|
|
40739
|
+
}
|
|
39837
40740
|
async function runReview(parsed, deps) {
|
|
39838
40741
|
let step = "parse";
|
|
39839
40742
|
setLogContext({});
|
|
@@ -39876,12 +40779,12 @@ async function runReview(parsed, deps) {
|
|
|
39876
40779
|
setLogContext({ ...ids, reviewId });
|
|
39877
40780
|
deps.onPosted?.({ baseUrl, id, version, planFileCurrent });
|
|
39878
40781
|
logDebug("review", `review created: ${shortId(id)}`);
|
|
39879
|
-
|
|
39880
|
-
|
|
39881
|
-
|
|
39882
|
-
|
|
39883
|
-
|
|
39884
|
-
deps.announceUrl(
|
|
40782
|
+
if (!hasLiveClient) {
|
|
40783
|
+
const localUrl = reviewUrl(baseUrl, deps.localHostname, id);
|
|
40784
|
+
const token = deps.loginToken();
|
|
40785
|
+
deps.openBrowser(token === null ? localUrl : loginLink(localUrl, token));
|
|
40786
|
+
}
|
|
40787
|
+
deps.announceUrl(reviewUrl(baseUrl, deps.publicHostname, id));
|
|
39885
40788
|
step = "longPoll";
|
|
39886
40789
|
const deadline = Date.now() + deps.timeoutMs;
|
|
39887
40790
|
let decision;
|
|
@@ -39961,7 +40864,7 @@ async function runReconcileSubcommand() {
|
|
|
39961
40864
|
const loaded = loadSettings();
|
|
39962
40865
|
bootHookLogging(loaded);
|
|
39963
40866
|
const adapter = selectAdapter();
|
|
39964
|
-
const baseUrl =
|
|
40867
|
+
const baseUrl = daemonBaseUrl(loaded);
|
|
39965
40868
|
const stdin = await Bun.stdin.text();
|
|
39966
40869
|
await runReconcile(parseHook(adapter.parseHookInput, stdin), prodReconcileDeps(baseUrl));
|
|
39967
40870
|
} catch (err) {
|
|
@@ -39996,7 +40899,10 @@ init_adapters();
|
|
|
39996
40899
|
init_boot();
|
|
39997
40900
|
init_paths3();
|
|
39998
40901
|
init_settings();
|
|
40902
|
+
init_address();
|
|
40903
|
+
init_client();
|
|
39999
40904
|
init_lifecycle();
|
|
40905
|
+
init_token();
|
|
40000
40906
|
init_log();
|
|
40001
40907
|
function browserOpenCmd(platform, url) {
|
|
40002
40908
|
return platform === "darwin" ? ["open", url] : platform === "win32" ? ["cmd", "/c", "start", "", url] : ["xdg-open", url];
|
|
@@ -40029,6 +40935,9 @@ function prodReviewDeps(settings) {
|
|
|
40029
40935
|
announceUrl: (url) => {
|
|
40030
40936
|
process.stderr.write(reviewUrlLine(url));
|
|
40031
40937
|
},
|
|
40938
|
+
localHostname: localHostname(settings.daemon.host),
|
|
40939
|
+
publicHostname: publicHostname(settings.daemon),
|
|
40940
|
+
loginToken: () => authEnabled(settings.daemon) && process.platform !== "win32" ? readToken(daemonTokenFile()) : null,
|
|
40032
40941
|
readPane: readCmuxPane,
|
|
40033
40942
|
timeoutMs: reviewTimeoutMs(settings),
|
|
40034
40943
|
expire: expireReview
|
|
@@ -40083,18 +40992,21 @@ async function runReviewSubcommand() {
|
|
|
40083
40992
|
}
|
|
40084
40993
|
|
|
40085
40994
|
// src/commands/serve.ts
|
|
40086
|
-
init_constants();
|
|
40087
40995
|
init_paths3();
|
|
40088
40996
|
init_settings();
|
|
40997
|
+
init_address();
|
|
40998
|
+
init_client();
|
|
40089
40999
|
init_lifecycle();
|
|
41000
|
+
init_token();
|
|
40090
41001
|
async function runServe() {
|
|
40091
41002
|
const world = stateDir();
|
|
40092
41003
|
const refusal = await vacatePort({
|
|
40093
|
-
baseUrl:
|
|
41004
|
+
baseUrl: daemonBaseUrl(settings().current()),
|
|
40094
41005
|
currentStateDir: world,
|
|
40095
41006
|
deadlineMs: DRAIN_DEADLINE_MS + 5000,
|
|
40096
41007
|
health: httpHealth,
|
|
40097
41008
|
retire: (baseUrl) => retireDaemon(baseUrl, readDaemonLock(), world),
|
|
41009
|
+
readLock: readDaemonLock,
|
|
40098
41010
|
now: Date.now,
|
|
40099
41011
|
sleep: Bun.sleep
|
|
40100
41012
|
});
|
|
@@ -40104,8 +41016,18 @@ async function runServe() {
|
|
|
40104
41016
|
process.exitCode = 1;
|
|
40105
41017
|
return;
|
|
40106
41018
|
}
|
|
40107
|
-
const port = await runDaemon({ ephemeral: false, resident: true });
|
|
40108
|
-
|
|
41019
|
+
const { port, settings: booted } = await runDaemon({ ephemeral: false, resident: true });
|
|
41020
|
+
const url = publicUrl(booted.daemon, port);
|
|
41021
|
+
process.stdout.write(`caret is serving the review UI at ${url} \u2014 Ctrl+C stops it.
|
|
41022
|
+
`);
|
|
41023
|
+
const token = authEnabled(booted.daemon) ? readToken(daemonTokenFile()) : null;
|
|
41024
|
+
if (token !== null) {
|
|
41025
|
+
process.stdout.write(`Open this link once on each device to log it in: ${loginLink(url, token)}
|
|
41026
|
+
`);
|
|
41027
|
+
}
|
|
41028
|
+
const warning = exposureWarning(booted.daemon, port);
|
|
41029
|
+
if (warning !== null)
|
|
41030
|
+
process.stderr.write(`${warning}
|
|
40109
41031
|
`);
|
|
40110
41032
|
}
|
|
40111
41033
|
|
|
@@ -42199,6 +43121,7 @@ function buildProgram() {
|
|
|
42199
43121
|
await runDaemon({ ephemeral: opts.ephemeral ?? false, resident: isSupervised() });
|
|
42200
43122
|
});
|
|
42201
43123
|
program.command("serve").description("keep the review UI up in this terminal until Ctrl+C").action(() => runServe());
|
|
43124
|
+
program.command("login-link").description("print the link that logs a browser into the review UI").action(() => runLoginLink());
|
|
42202
43125
|
program.command("prewarm").description("warm-start the daemon").action(() => runPrewarm());
|
|
42203
43126
|
program.command("review").description("review a plan from stdin (ExitPlanMode hook)").action(() => runReviewSubcommand());
|
|
42204
43127
|
program.command("mcp").description("serve caret's plan-review tool over MCP stdio (Claude Code plugin)").action(async () => (await Promise.resolve().then(() => (init_mcp2(), exports_mcp))).runMcpSubcommand());
|