@singbox-iac/cli 0.1.6 → 0.1.7
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/dist/cli/commands/apply.js +21 -9
- package/dist/cli/commands/apply.js.map +1 -1
- package/dist/cli/commands/history.d.ts +2 -0
- package/dist/cli/commands/history.js +21 -0
- package/dist/cli/commands/history.js.map +1 -0
- package/dist/cli/commands/rollback.d.ts +2 -0
- package/dist/cli/commands/rollback.js +38 -0
- package/dist/cli/commands/rollback.js.map +1 -0
- package/dist/cli/commands/verify.js +72 -20
- package/dist/cli/commands/verify.js.map +1 -1
- package/dist/cli/index.js +6 -0
- package/dist/cli/index.js.map +1 -1
- package/dist/domain/dns-plan.d.ts +27 -0
- package/dist/domain/dns-plan.js +2 -0
- package/dist/domain/dns-plan.js.map +1 -0
- package/dist/domain/intent.d.ts +61 -0
- package/dist/domain/intent.js +2 -0
- package/dist/domain/intent.js.map +1 -0
- package/dist/domain/transaction.d.ts +12 -0
- package/dist/domain/transaction.js +2 -0
- package/dist/domain/transaction.js.map +1 -0
- package/dist/domain/verification-plan.d.ts +34 -0
- package/dist/domain/verification-plan.js +2 -0
- package/dist/domain/verification-plan.js.map +1 -0
- package/dist/modules/authoring/index.d.ts +2 -0
- package/dist/modules/authoring/index.js +32 -17
- package/dist/modules/authoring/index.js.map +1 -1
- package/dist/modules/build/index.d.ts +3 -0
- package/dist/modules/build/index.js +8 -1
- package/dist/modules/build/index.js.map +1 -1
- package/dist/modules/compiler/index.d.ts +2 -2
- package/dist/modules/compiler/index.js +38 -53
- package/dist/modules/compiler/index.js.map +1 -1
- package/dist/modules/dns-plan/index.d.ts +10 -0
- package/dist/modules/dns-plan/index.js +104 -0
- package/dist/modules/dns-plan/index.js.map +1 -0
- package/dist/modules/intent/index.d.ts +7 -0
- package/dist/modules/intent/index.js +101 -0
- package/dist/modules/intent/index.js.map +1 -0
- package/dist/modules/transactions/index.d.ts +20 -0
- package/dist/modules/transactions/index.js +150 -0
- package/dist/modules/transactions/index.js.map +1 -0
- package/dist/modules/update/index.d.ts +1 -0
- package/dist/modules/update/index.js +22 -6
- package/dist/modules/update/index.js.map +1 -1
- package/dist/modules/verification/index.d.ts +42 -0
- package/dist/modules/verification/index.js +274 -0
- package/dist/modules/verification/index.js.map +1 -1
- package/dist/modules/verification-plan/index.d.ts +9 -0
- package/dist/modules/verification-plan/index.js +117 -0
- package/dist/modules/verification-plan/index.js.map +1 -0
- package/package.json +1 -1
|
@@ -1,4 +1,5 @@
|
|
|
1
1
|
import { applyConfig } from "../../modules/manager/index.js";
|
|
2
|
+
import { applyWithTransaction } from "../../modules/transactions/index.js";
|
|
2
3
|
import { resolveBuilderConfig } from "../command-helpers.js";
|
|
3
4
|
export function registerApplyCommand(program) {
|
|
4
5
|
program
|
|
@@ -18,15 +19,26 @@ export function registerApplyCommand(program) {
|
|
|
18
19
|
if (!stagingPath || !livePath) {
|
|
19
20
|
throw new Error("Unable to resolve staging/live paths. Pass --input/--live-path or provide a builder config.");
|
|
20
21
|
}
|
|
21
|
-
|
|
22
|
-
|
|
23
|
-
|
|
24
|
-
|
|
25
|
-
|
|
26
|
-
|
|
27
|
-
|
|
28
|
-
|
|
29
|
-
|
|
22
|
+
const transaction = builderConfig
|
|
23
|
+
? await applyWithTransaction({
|
|
24
|
+
config: builderConfig,
|
|
25
|
+
generatedPath: stagingPath,
|
|
26
|
+
livePath,
|
|
27
|
+
backupPath: backupPath ?? builderConfig.output.backupPath,
|
|
28
|
+
verificationSummary: {},
|
|
29
|
+
apply: async () => {
|
|
30
|
+
await applyConfig({
|
|
31
|
+
stagingPath,
|
|
32
|
+
livePath,
|
|
33
|
+
...(backupPath ? { backupPath } : {}),
|
|
34
|
+
...(options.singBoxBin ? { singBoxBinary: options.singBoxBin } : {}),
|
|
35
|
+
...(options.reload !== undefined ? { reload: options.reload } : {}),
|
|
36
|
+
...(builderConfig?.runtime.reload ? { runtime: builderConfig.runtime.reload } : {}),
|
|
37
|
+
});
|
|
38
|
+
},
|
|
39
|
+
})
|
|
40
|
+
: undefined;
|
|
41
|
+
process.stdout.write(`Applied config.\nSource: ${stagingPath}\nLive: ${livePath}\n${backupPath ? `Backup: ${backupPath}\n` : ""}${transaction ? `Transaction: ${transaction.txId}\n` : ""}`);
|
|
30
42
|
});
|
|
31
43
|
}
|
|
32
44
|
//# sourceMappingURL=apply.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../../src/cli/commands/apply.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,mDAAmD,CAAC;SAChE,MAAM,CAAC,qBAAqB,EAAE,6BAA6B,CAAC;SAC5D,MAAM,CAAC,oBAAoB,EAAE,6BAA6B,CAAC;SAC3D,MAAM,CAAC,oBAAoB,EAAE,2BAA2B,CAAC;SACzD,MAAM,CAAC,sBAAsB,EAAE,6BAA6B,CAAC;SAC7D,MAAM,CAAC,uBAAuB,EAAE,yBAAyB,CAAC;SAC1D,MAAM,CAAC,UAAU,EAAE,+BAA+B,CAAC;SACnD,MAAM,CAAC,KAAK,EAAE,OAA4B,EAAE,EAAE;QAC7C,MAAM,aAAa,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,IAAI,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC;QACvE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC;QACpE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC;QAE1E,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,6FAA6F,CAC9F,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"apply.js","sourceRoot":"","sources":["../../../src/cli/commands/apply.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,WAAW,EAAE,MAAM,gCAAgC,CAAC;AAC7D,OAAO,EAAE,oBAAoB,EAAE,MAAM,qCAAqC,CAAC;AAC3E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,UAAU,oBAAoB,CAAC,OAAgB;IACnD,OAAO;SACJ,OAAO,CAAC,OAAO,CAAC;SAChB,WAAW,CAAC,mDAAmD,CAAC;SAChE,MAAM,CAAC,qBAAqB,EAAE,6BAA6B,CAAC;SAC5D,MAAM,CAAC,oBAAoB,EAAE,6BAA6B,CAAC;SAC3D,MAAM,CAAC,oBAAoB,EAAE,2BAA2B,CAAC;SACzD,MAAM,CAAC,sBAAsB,EAAE,6BAA6B,CAAC;SAC7D,MAAM,CAAC,uBAAuB,EAAE,yBAAyB,CAAC;SAC1D,MAAM,CAAC,UAAU,EAAE,+BAA+B,CAAC;SACnD,MAAM,CAAC,KAAK,EAAE,OAA4B,EAAE,EAAE;QAC7C,MAAM,aAAa,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC1D,MAAM,WAAW,GAAG,OAAO,CAAC,KAAK,IAAI,aAAa,EAAE,MAAM,CAAC,WAAW,CAAC;QACvE,MAAM,QAAQ,GAAG,OAAO,CAAC,QAAQ,IAAI,aAAa,EAAE,MAAM,CAAC,QAAQ,CAAC;QACpE,MAAM,UAAU,GAAG,OAAO,CAAC,UAAU,IAAI,aAAa,EAAE,MAAM,CAAC,UAAU,CAAC;QAE1E,IAAI,CAAC,WAAW,IAAI,CAAC,QAAQ,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CACb,6FAA6F,CAC9F,CAAC;QACJ,CAAC;QAED,MAAM,WAAW,GAAG,aAAa;YAC/B,CAAC,CAAC,MAAM,oBAAoB,CAAC;gBACzB,MAAM,EAAE,aAAa;gBACrB,aAAa,EAAE,WAAW;gBAC1B,QAAQ;gBACR,UAAU,EAAE,UAAU,IAAI,aAAa,CAAC,MAAM,CAAC,UAAU;gBACzD,mBAAmB,EAAE,EAAE;gBACvB,KAAK,EAAE,KAAK,IAAI,EAAE;oBAChB,MAAM,WAAW,CAAC;wBAChB,WAAW;wBACX,QAAQ;wBACR,GAAG,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACrC,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACpE,GAAG,CAAC,OAAO,CAAC,MAAM,KAAK,SAAS,CAAC,CAAC,CAAC,EAAE,MAAM,EAAE,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACnE,GAAG,CAAC,aAAa,EAAE,OAAO,CAAC,MAAM,CAAC,CAAC,CAAC,EAAE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;qBACpF,CAAC,CAAC;gBACL,CAAC;aACF,CAAC;YACJ,CAAC,CAAC,SAAS,CAAC;QAEd,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,4BAA4B,WAAW,WAAW,QAAQ,KACxD,UAAU,CAAC,CAAC,CAAC,WAAW,UAAU,IAAI,CAAC,CAAC,CAAC,EAC3C,GAAG,WAAW,CAAC,CAAC,CAAC,gBAAgB,WAAW,CAAC,IAAI,IAAI,CAAC,CAAC,CAAC,EAAE,EAAE,CAC7D,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { listTransactionHistory } from "../../modules/transactions/index.js";
|
|
2
|
+
import { resolveBuilderConfig } from "../command-helpers.js";
|
|
3
|
+
export function registerHistoryCommand(program) {
|
|
4
|
+
program
|
|
5
|
+
.command("history")
|
|
6
|
+
.description("Show recent publish transactions.")
|
|
7
|
+
.option("-c, --config <path>", "path to builder config YAML")
|
|
8
|
+
.action(async (options) => {
|
|
9
|
+
const builderConfig = await resolveBuilderConfig(options);
|
|
10
|
+
if (!builderConfig) {
|
|
11
|
+
throw new Error("history requires a builder config.");
|
|
12
|
+
}
|
|
13
|
+
const entries = await listTransactionHistory(builderConfig);
|
|
14
|
+
const lines = [
|
|
15
|
+
`Transactions: ${entries.length}`,
|
|
16
|
+
...entries.map((entry) => `- ${entry.txId} ${entry.status} ${entry.startedAt} ${entry.generatedPath} -> ${entry.livePath}`),
|
|
17
|
+
];
|
|
18
|
+
process.stdout.write(`${lines.join("\n")}\n`);
|
|
19
|
+
});
|
|
20
|
+
}
|
|
21
|
+
//# sourceMappingURL=history.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"history.js","sourceRoot":"","sources":["../../../src/cli/commands/history.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,sBAAsB,EAAE,MAAM,qCAAqC,CAAC;AAC7E,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,UAAU,sBAAsB,CAAC,OAAgB;IACrD,OAAO;SACJ,OAAO,CAAC,SAAS,CAAC;SAClB,WAAW,CAAC,mCAAmC,CAAC;SAChD,MAAM,CAAC,qBAAqB,EAAE,6BAA6B,CAAC;SAC5D,MAAM,CAAC,KAAK,EAAE,OAAqC,EAAE,EAAE;QACtD,MAAM,aAAa,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC1D,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,oCAAoC,CAAC,CAAC;QACxD,CAAC;QAED,MAAM,OAAO,GAAG,MAAM,sBAAsB,CAAC,aAAa,CAAC,CAAC;QAC5D,MAAM,KAAK,GAAG;YACZ,iBAAiB,OAAO,CAAC,MAAM,EAAE;YACjC,GAAG,OAAO,CAAC,GAAG,CACZ,CAAC,KAAK,EAAE,EAAE,CACR,KAAK,KAAK,CAAC,IAAI,IAAI,KAAK,CAAC,MAAM,IAAI,KAAK,CAAC,SAAS,IAAI,KAAK,CAAC,aAAa,OAAO,KAAK,CAAC,QAAQ,EAAE,CACnG;SACF,CAAC;QACF,OAAO,CAAC,MAAM,CAAC,KAAK,CAAC,GAAG,KAAK,CAAC,IAAI,CAAC,IAAI,CAAC,IAAI,CAAC,CAAC;IAChD,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { reloadRuntime, resolveSingBoxBinary } from "../../modules/manager/index.js";
|
|
2
|
+
import { rollbackToPrevious } from "../../modules/transactions/index.js";
|
|
3
|
+
import { resolveBuilderConfig } from "../command-helpers.js";
|
|
4
|
+
export function registerRollbackCommand(program) {
|
|
5
|
+
program
|
|
6
|
+
.command("rollback")
|
|
7
|
+
.description("Rollback the live config to the previous published snapshot.")
|
|
8
|
+
.option("-c, --config <path>", "path to builder config YAML")
|
|
9
|
+
.option("--to <target>", 'rollback target, currently only "previous"', "previous")
|
|
10
|
+
.option("--sing-box-bin <path>", "path to sing-box binary")
|
|
11
|
+
.option("--reload", "reload sing-box after restoring the previous snapshot")
|
|
12
|
+
.action(async (options) => {
|
|
13
|
+
if (options.to !== "previous") {
|
|
14
|
+
throw new Error('Only "--to previous" is currently supported.');
|
|
15
|
+
}
|
|
16
|
+
const builderConfig = await resolveBuilderConfig(options);
|
|
17
|
+
if (!builderConfig) {
|
|
18
|
+
throw new Error("rollback requires a builder config.");
|
|
19
|
+
}
|
|
20
|
+
const afterRestore = options.reload || options.singBoxBin
|
|
21
|
+
? async () => {
|
|
22
|
+
await resolveSingBoxBinary(options.singBoxBin);
|
|
23
|
+
if (options.reload) {
|
|
24
|
+
await reloadRuntime({
|
|
25
|
+
...(options.singBoxBin ? { singBoxBinary: options.singBoxBin } : {}),
|
|
26
|
+
runtime: builderConfig.runtime.reload,
|
|
27
|
+
});
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
: undefined;
|
|
31
|
+
const transaction = await rollbackToPrevious({
|
|
32
|
+
config: builderConfig,
|
|
33
|
+
...(afterRestore ? { afterRestore } : {}),
|
|
34
|
+
});
|
|
35
|
+
process.stdout.write(`Rollback complete.\nTransaction: ${transaction.txId}\nLive: ${transaction.livePath}\nSnapshot: ${transaction.snapshotPath}\n`);
|
|
36
|
+
});
|
|
37
|
+
}
|
|
38
|
+
//# sourceMappingURL=rollback.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"rollback.js","sourceRoot":"","sources":["../../../src/cli/commands/rollback.ts"],"names":[],"mappings":"AAEA,OAAO,EAAE,aAAa,EAAE,oBAAoB,EAAE,MAAM,gCAAgC,CAAC;AACrF,OAAO,EAAE,kBAAkB,EAAE,MAAM,qCAAqC,CAAC;AACzE,OAAO,EAAE,oBAAoB,EAAE,MAAM,uBAAuB,CAAC;AAE7D,MAAM,UAAU,uBAAuB,CAAC,OAAgB;IACtD,OAAO;SACJ,OAAO,CAAC,UAAU,CAAC;SACnB,WAAW,CAAC,8DAA8D,CAAC;SAC3E,MAAM,CAAC,qBAAqB,EAAE,6BAA6B,CAAC;SAC5D,MAAM,CAAC,eAAe,EAAE,4CAA4C,EAAE,UAAU,CAAC;SACjF,MAAM,CAAC,uBAAuB,EAAE,yBAAyB,CAAC;SAC1D,MAAM,CAAC,UAAU,EAAE,uDAAuD,CAAC;SAC3E,MAAM,CAAC,KAAK,EAAE,OAA+B,EAAE,EAAE;QAChD,IAAI,OAAO,CAAC,EAAE,KAAK,UAAU,EAAE,CAAC;YAC9B,MAAM,IAAI,KAAK,CAAC,8CAA8C,CAAC,CAAC;QAClE,CAAC;QAED,MAAM,aAAa,GAAG,MAAM,oBAAoB,CAAC,OAAO,CAAC,CAAC;QAC1D,IAAI,CAAC,aAAa,EAAE,CAAC;YACnB,MAAM,IAAI,KAAK,CAAC,qCAAqC,CAAC,CAAC;QACzD,CAAC;QAED,MAAM,YAAY,GAChB,OAAO,CAAC,MAAM,IAAI,OAAO,CAAC,UAAU;YAClC,CAAC,CAAC,KAAK,IAAI,EAAE;gBACT,MAAM,oBAAoB,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;gBAC/C,IAAI,OAAO,CAAC,MAAM,EAAE,CAAC;oBACnB,MAAM,aAAa,CAAC;wBAClB,GAAG,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC,CAAC,EAAE,aAAa,EAAE,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;wBACpE,OAAO,EAAE,aAAa,CAAC,OAAO,CAAC,MAAM;qBACtC,CAAC,CAAC;gBACL,CAAC;YACH,CAAC;YACH,CAAC,CAAC,SAAS,CAAC;QAEhB,MAAM,WAAW,GAAG,MAAM,kBAAkB,CAAC;YAC3C,MAAM,EAAE,aAAa;YACrB,GAAG,CAAC,YAAY,CAAC,CAAC,CAAC,EAAE,YAAY,EAAE,CAAC,CAAC,CAAC,EAAE,CAAC;SAC1C,CAAC,CAAC;QAEH,OAAO,CAAC,MAAM,CAAC,KAAK,CAClB,oCAAoC,WAAW,CAAC,IAAI,WAAW,WAAW,CAAC,QAAQ,eAAe,WAAW,CAAC,YAAY,IAAI,CAC/H,CAAC;IACJ,CAAC,CAAC,CAAC;AACP,CAAC"}
|
|
@@ -1,19 +1,25 @@
|
|
|
1
|
-
import {
|
|
2
|
-
import {
|
|
1
|
+
import { existsSync } from "node:fs";
|
|
2
|
+
import { readFile } from "node:fs/promises";
|
|
3
|
+
import { buildConfigArtifact, resolveEffectiveIntent } from "../../modules/build/index.js";
|
|
4
|
+
import { buildDnsPlan } from "../../modules/dns-plan/index.js";
|
|
5
|
+
import { buildVerificationPlan } from "../../modules/verification-plan/index.js";
|
|
6
|
+
import { assertVerificationReportPassed, verifyAppPlan, verifyConfigRoutes, verifyDnsPlan, verifyEgressPlan, verifyProtocolPlan, } from "../../modules/verification/index.js";
|
|
3
7
|
import { resolveBuilderConfig } from "../command-helpers.js";
|
|
4
8
|
export function registerVerifyCommand(program) {
|
|
5
9
|
program
|
|
6
10
|
.command("verify")
|
|
7
11
|
.description("Build and run closed-loop route verification with sing-box and Chrome.")
|
|
12
|
+
.argument("[mode]", "verification scope: all, route, dns, egress, app, protocol", "all")
|
|
8
13
|
.option("-c, --config <path>", "path to builder config YAML")
|
|
9
14
|
.option("-i, --input <path>", "path to an existing config JSON to verify")
|
|
10
15
|
.option("--subscription-url <url>", "override subscription URL when rebuilding")
|
|
11
16
|
.option("--subscription-file <path>", "use a local subscription file instead of fetching")
|
|
12
17
|
.option("--sing-box-bin <path>", "path to sing-box binary")
|
|
13
18
|
.option("--chrome-bin <path>", "path to Chrome binary")
|
|
14
|
-
.action(async (options) => {
|
|
19
|
+
.action(async (mode, options) => {
|
|
15
20
|
let configPath = options.input;
|
|
16
21
|
const builderConfig = await resolveBuilderConfig(options);
|
|
22
|
+
let effectiveIntent = builderConfig ? await resolveEffectiveIntent(builderConfig) : undefined;
|
|
17
23
|
if (!configPath) {
|
|
18
24
|
if (!builderConfig) {
|
|
19
25
|
throw new Error("Verification requires --input or a builder config.");
|
|
@@ -24,26 +30,72 @@ export function registerVerifyCommand(program) {
|
|
|
24
30
|
...(options.subscriptionUrl ? { subscriptionUrl: options.subscriptionUrl } : {}),
|
|
25
31
|
});
|
|
26
32
|
configPath = result.outputPath;
|
|
33
|
+
effectiveIntent = result.intent;
|
|
27
34
|
}
|
|
28
|
-
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
35
|
+
if (!builderConfig || !effectiveIntent) {
|
|
36
|
+
throw new Error("Verification requires a builder config so the verification plan can be built.");
|
|
37
|
+
}
|
|
38
|
+
const dnsPlan = buildDnsPlan({
|
|
39
|
+
config: builderConfig,
|
|
40
|
+
intent: effectiveIntent,
|
|
41
|
+
activeRuleSetTags: builderConfig.ruleSets
|
|
42
|
+
.filter((ruleSet) => existsSync(ruleSet.path))
|
|
43
|
+
.map((ruleSet) => ruleSet.tag),
|
|
44
|
+
});
|
|
45
|
+
const verificationPlan = buildVerificationPlan({
|
|
46
|
+
config: builderConfig,
|
|
47
|
+
intent: effectiveIntent,
|
|
48
|
+
dnsPlan,
|
|
33
49
|
});
|
|
34
|
-
const
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
|
|
39
|
-
|
|
40
|
-
|
|
41
|
-
|
|
42
|
-
|
|
43
|
-
|
|
44
|
-
|
|
50
|
+
const rawConfig = JSON.parse(await readFile(configPath, "utf8"));
|
|
51
|
+
const lines = [`Verified config: ${configPath}`, `Mode: ${mode}`];
|
|
52
|
+
if (mode === "all" || mode === "route") {
|
|
53
|
+
const report = await verifyConfigRoutes({
|
|
54
|
+
configPath,
|
|
55
|
+
...(options.singBoxBin ? { singBoxBinary: options.singBoxBin } : {}),
|
|
56
|
+
...(options.chromeBin ? { chromeBinary: options.chromeBin } : {}),
|
|
57
|
+
configuredScenarios: builderConfig.verification.scenarios,
|
|
58
|
+
});
|
|
59
|
+
lines.push("");
|
|
60
|
+
lines.push("Route checks:");
|
|
61
|
+
lines.push(...report.scenarios.map((scenario) => `- ${scenario.passed ? "PASS" : "FAIL"} ${scenario.name}: ${scenario.expectedOutboundTag} via ${scenario.inboundTag}`));
|
|
62
|
+
assertVerificationReportPassed(report);
|
|
63
|
+
}
|
|
64
|
+
if (mode === "all" || mode === "dns") {
|
|
65
|
+
lines.push("");
|
|
66
|
+
lines.push("DNS checks:");
|
|
67
|
+
lines.push(...formatDnsChecks(verifyDnsPlan(verificationPlan, dnsPlan)));
|
|
68
|
+
}
|
|
69
|
+
if (mode === "all" || mode === "app") {
|
|
70
|
+
lines.push("");
|
|
71
|
+
lines.push("App checks:");
|
|
72
|
+
lines.push(...verifyAppPlan(verificationPlan, rawConfig).map((check) => `- ${check.passed ? "PASS" : "FAIL"} ${check.app}: ${check.expectedInbound} -> ${check.expectedOutboundGroup}`));
|
|
73
|
+
}
|
|
74
|
+
if (mode === "all" || mode === "protocol") {
|
|
75
|
+
lines.push("");
|
|
76
|
+
lines.push("Protocol checks:");
|
|
77
|
+
lines.push(...formatProtocolChecks(verifyProtocolPlan(verificationPlan, rawConfig)));
|
|
78
|
+
}
|
|
79
|
+
if (mode === "all" || mode === "egress") {
|
|
80
|
+
const egressResults = await verifyEgressPlan({
|
|
81
|
+
configPath,
|
|
82
|
+
checks: verificationPlan.egressChecks,
|
|
83
|
+
...(options.singBoxBin ? { singBoxBinary: options.singBoxBin } : {}),
|
|
84
|
+
});
|
|
85
|
+
lines.push("");
|
|
86
|
+
lines.push("Egress checks:");
|
|
87
|
+
lines.push(...formatEgressChecks(egressResults));
|
|
88
|
+
}
|
|
45
89
|
process.stdout.write(`${lines.join("\n")}\n`);
|
|
46
|
-
assertVerificationReportPassed(report);
|
|
47
90
|
});
|
|
48
91
|
}
|
|
92
|
+
function formatDnsChecks(checks) {
|
|
93
|
+
return checks.map((check) => `- ${check.passed ? "PASS" : "FAIL"} ${check.domain}: ${check.actualMode} via ${check.resolver}`);
|
|
94
|
+
}
|
|
95
|
+
function formatProtocolChecks(checks) {
|
|
96
|
+
return checks.map((check) => `- ${check.passed ? "PASS" : "FAIL"} ${new URL(check.target).hostname}: ${check.details}`);
|
|
97
|
+
}
|
|
98
|
+
function formatEgressChecks(checks) {
|
|
99
|
+
return checks.map((check) => `- ${check.passed ? "PASS" : "FAIL"} ${check.expectedOutboundGroup}: ${check.ip ?? "unknown-ip"} ${check.country ?? ""} ${check.asn ?? ""}`.trim());
|
|
100
|
+
}
|
|
49
101
|
//# sourceMappingURL=verify.js.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/cli/commands/verify.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"verify.js","sourceRoot":"","sources":["../../../src/cli/commands/verify.ts"],"names":[],"mappings":"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"}
|
package/dist/cli/index.js
CHANGED
|
@@ -8,10 +8,12 @@ import { registerBuildCommand } from "./commands/build.js";
|
|
|
8
8
|
import { registerCheckCommand } from "./commands/check.js";
|
|
9
9
|
import { registerDoctorCommand } from "./commands/doctor.js";
|
|
10
10
|
import { registerGoCommand } from "./commands/go.js";
|
|
11
|
+
import { registerHistoryCommand } from "./commands/history.js";
|
|
11
12
|
import { registerInitCommand } from "./commands/init.js";
|
|
12
13
|
import { registerProxifierCommand } from "./commands/proxifier.js";
|
|
13
14
|
import { registerQuickstartCommand } from "./commands/quickstart.js";
|
|
14
15
|
import { registerReloadCommand } from "./commands/reload.js";
|
|
16
|
+
import { registerRollbackCommand } from "./commands/rollback.js";
|
|
15
17
|
import { registerRunCommand } from "./commands/run.js";
|
|
16
18
|
import { registerScheduleCommand } from "./commands/schedule.js";
|
|
17
19
|
import { registerSetupCommand } from "./commands/setup.js";
|
|
@@ -44,7 +46,9 @@ export function createProgram() {
|
|
|
44
46
|
registerBuildCommand(program);
|
|
45
47
|
registerCheckCommand(program);
|
|
46
48
|
registerApplyCommand(program);
|
|
49
|
+
registerHistoryCommand(program);
|
|
47
50
|
registerRunCommand(program);
|
|
51
|
+
registerRollbackCommand(program);
|
|
48
52
|
registerVerifyCommand(program);
|
|
49
53
|
registerDoctorCommand(program);
|
|
50
54
|
registerProxifierCommand(program);
|
|
@@ -63,7 +67,9 @@ function hideAdvancedCommands(program) {
|
|
|
63
67
|
"build",
|
|
64
68
|
"check",
|
|
65
69
|
"apply",
|
|
70
|
+
"history",
|
|
66
71
|
"run",
|
|
72
|
+
"rollback",
|
|
67
73
|
"verify",
|
|
68
74
|
"doctor",
|
|
69
75
|
"proxifier",
|
package/dist/cli/index.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/cli/index.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
export interface DnsPlanServer {
|
|
2
|
+
readonly type: "local" | "tcp";
|
|
3
|
+
readonly tag: string;
|
|
4
|
+
readonly server?: string;
|
|
5
|
+
readonly serverPort?: number;
|
|
6
|
+
readonly preferGo?: boolean;
|
|
7
|
+
}
|
|
8
|
+
export interface DnsPlanRule {
|
|
9
|
+
readonly match: {
|
|
10
|
+
readonly domain?: readonly string[];
|
|
11
|
+
readonly domainSuffix?: readonly string[];
|
|
12
|
+
readonly ruleSet?: readonly string[];
|
|
13
|
+
};
|
|
14
|
+
readonly resolvers: readonly string[];
|
|
15
|
+
}
|
|
16
|
+
export interface DNSPlan {
|
|
17
|
+
readonly mode: "real-ip" | "fake-ip";
|
|
18
|
+
readonly defaultResolvers: readonly string[];
|
|
19
|
+
readonly directResolvers: readonly string[];
|
|
20
|
+
readonly proxyResolvers: readonly string[];
|
|
21
|
+
readonly fallbackResolvers: readonly string[];
|
|
22
|
+
readonly servers: readonly DnsPlanServer[];
|
|
23
|
+
readonly nameserverPolicy: readonly DnsPlanRule[];
|
|
24
|
+
readonly fakeIpFilter: readonly string[];
|
|
25
|
+
readonly localHosts?: Record<string, string>;
|
|
26
|
+
readonly strategy: "prefer_ipv4" | "prefer_ipv6";
|
|
27
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"dns-plan.js","sourceRoot":"","sources":["../../src/domain/dns-plan.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,61 @@
|
|
|
1
|
+
export type IntentVersion = "v1";
|
|
2
|
+
export type IntentPlacement = "beforeBuiltins" | "afterBuiltins";
|
|
3
|
+
export interface IntentVerifyHint {
|
|
4
|
+
readonly expectedCountry?: readonly string[];
|
|
5
|
+
readonly expectedASN?: readonly string[];
|
|
6
|
+
readonly probeUrls?: readonly string[];
|
|
7
|
+
}
|
|
8
|
+
export interface IntentSiteMatch {
|
|
9
|
+
readonly inbound?: readonly string[];
|
|
10
|
+
readonly protocol?: string;
|
|
11
|
+
readonly network?: "tcp" | "udp";
|
|
12
|
+
readonly port?: number;
|
|
13
|
+
readonly domain?: readonly string[];
|
|
14
|
+
readonly domainSuffix?: readonly string[];
|
|
15
|
+
readonly ruleSet?: readonly string[];
|
|
16
|
+
readonly category?: readonly string[];
|
|
17
|
+
}
|
|
18
|
+
export type IntentSiteAction = {
|
|
19
|
+
readonly type: "route";
|
|
20
|
+
readonly outboundGroup: string;
|
|
21
|
+
} | {
|
|
22
|
+
readonly type: "reject";
|
|
23
|
+
};
|
|
24
|
+
export interface IntentSitePolicy {
|
|
25
|
+
readonly placement: IntentPlacement;
|
|
26
|
+
readonly name?: string;
|
|
27
|
+
readonly match: IntentSiteMatch;
|
|
28
|
+
readonly action: IntentSiteAction;
|
|
29
|
+
readonly verify?: IntentVerifyHint;
|
|
30
|
+
}
|
|
31
|
+
export interface IntentProcessMatch {
|
|
32
|
+
readonly bundleId?: readonly string[];
|
|
33
|
+
readonly processName?: readonly string[];
|
|
34
|
+
}
|
|
35
|
+
export interface IntentProcessPolicy {
|
|
36
|
+
readonly name?: string;
|
|
37
|
+
readonly match: IntentProcessMatch;
|
|
38
|
+
readonly inbound: "in-proxifier" | "in-default";
|
|
39
|
+
readonly outboundGroup: string;
|
|
40
|
+
readonly verify?: {
|
|
41
|
+
readonly expectProxyHit?: boolean;
|
|
42
|
+
};
|
|
43
|
+
}
|
|
44
|
+
export interface IntentLocalOverride {
|
|
45
|
+
readonly hosts?: Record<string, string>;
|
|
46
|
+
readonly dnsPolicy?: readonly {
|
|
47
|
+
readonly domainSuffix: readonly string[];
|
|
48
|
+
readonly server: readonly string[];
|
|
49
|
+
}[];
|
|
50
|
+
}
|
|
51
|
+
export interface IntentIR {
|
|
52
|
+
readonly version: IntentVersion;
|
|
53
|
+
readonly globals: {
|
|
54
|
+
readonly updateIntervalMinutes?: number;
|
|
55
|
+
readonly preferIPv6?: boolean;
|
|
56
|
+
readonly enableTUN?: boolean;
|
|
57
|
+
};
|
|
58
|
+
readonly sitePolicies: readonly IntentSitePolicy[];
|
|
59
|
+
readonly processPolicies: readonly IntentProcessPolicy[];
|
|
60
|
+
readonly localOverrides: IntentLocalOverride;
|
|
61
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"intent.js","sourceRoot":"","sources":["../../src/domain/intent.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
export interface ApplyTransaction {
|
|
2
|
+
readonly txId: string;
|
|
3
|
+
readonly generatedPath: string;
|
|
4
|
+
readonly livePath: string;
|
|
5
|
+
readonly backupPath: string;
|
|
6
|
+
readonly snapshotPath?: string;
|
|
7
|
+
readonly startedAt: string;
|
|
8
|
+
readonly completedAt?: string;
|
|
9
|
+
readonly status: "pending" | "applied" | "rolled-back" | "failed";
|
|
10
|
+
readonly verificationSummary: Record<string, unknown>;
|
|
11
|
+
readonly error?: string;
|
|
12
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"transaction.js","sourceRoot":"","sources":["../../src/domain/transaction.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,34 @@
|
|
|
1
|
+
export interface VerificationPlan {
|
|
2
|
+
readonly dnsChecks: readonly {
|
|
3
|
+
readonly domain: string;
|
|
4
|
+
readonly expectedMode: "fake-ip" | "real-ip";
|
|
5
|
+
readonly expectedResolver?: string;
|
|
6
|
+
}[];
|
|
7
|
+
readonly routeChecks: readonly {
|
|
8
|
+
readonly id: string;
|
|
9
|
+
readonly name: string;
|
|
10
|
+
readonly target: string;
|
|
11
|
+
readonly inbound: "in-mixed" | "in-proxifier";
|
|
12
|
+
readonly expectedOutboundGroup: string;
|
|
13
|
+
readonly expectedRuleHint?: string;
|
|
14
|
+
}[];
|
|
15
|
+
readonly egressChecks: readonly {
|
|
16
|
+
readonly id: string;
|
|
17
|
+
readonly target: string;
|
|
18
|
+
readonly inbound: "in-mixed" | "in-proxifier";
|
|
19
|
+
readonly expectedOutboundGroup: string;
|
|
20
|
+
readonly expectedCountry?: readonly string[];
|
|
21
|
+
readonly expectedASN?: readonly string[];
|
|
22
|
+
}[];
|
|
23
|
+
readonly appChecks: readonly {
|
|
24
|
+
readonly id: string;
|
|
25
|
+
readonly app: string;
|
|
26
|
+
readonly expectedInbound: "in-proxifier" | "in-default";
|
|
27
|
+
readonly expectedOutboundGroup: string;
|
|
28
|
+
}[];
|
|
29
|
+
readonly protocolChecks: readonly {
|
|
30
|
+
readonly id: string;
|
|
31
|
+
readonly target: string;
|
|
32
|
+
readonly expectTCPOnly?: boolean;
|
|
33
|
+
}[];
|
|
34
|
+
}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"verification-plan.js","sourceRoot":"","sources":["../../src/domain/verification-plan.ts"],"names":[],"mappings":""}
|
|
@@ -1,5 +1,6 @@
|
|
|
1
1
|
import { z } from "zod";
|
|
2
2
|
import type { BuilderConfig } from "../../config/schema.js";
|
|
3
|
+
import type { IntentIR } from "../../domain/intent.js";
|
|
3
4
|
import { type NaturalLanguagePlan } from "../natural-language/index.js";
|
|
4
5
|
declare const providerSchema: z.ZodEnum<["deterministic", "auto", "claude", "exec"]>;
|
|
5
6
|
export type AuthoringProvider = z.infer<typeof providerSchema>;
|
|
@@ -27,6 +28,7 @@ export interface GenerateAuthoringPlanInput {
|
|
|
27
28
|
}
|
|
28
29
|
export interface GenerateAuthoringPlanResult {
|
|
29
30
|
readonly plan: NaturalLanguagePlan;
|
|
31
|
+
readonly intent: IntentIR;
|
|
30
32
|
readonly providerRequested: AuthoringProvider;
|
|
31
33
|
readonly providerUsed: Exclude<AuthoringProvider, "auto">;
|
|
32
34
|
}
|
|
@@ -4,6 +4,7 @@ import { access, mkdtemp, readFile, rm, writeFile } from "node:fs/promises";
|
|
|
4
4
|
import { tmpdir } from "node:os";
|
|
5
5
|
import path from "node:path";
|
|
6
6
|
import { z } from "zod";
|
|
7
|
+
import { intentFromNaturalLanguagePlan } from "../intent/index.js";
|
|
7
8
|
import { generateRulesFromPrompt, } from "../natural-language/index.js";
|
|
8
9
|
import { mergeRuleTemplates } from "../rule-templates/index.js";
|
|
9
10
|
const providerSchema = z.enum(["deterministic", "auto", "claude", "exec"]);
|
|
@@ -90,27 +91,33 @@ export async function generateAuthoringPlan(input) {
|
|
|
90
91
|
const timeoutMs = input.timeoutMs ?? input.config?.authoring.timeoutMs ?? defaultTimeoutMs;
|
|
91
92
|
const runner = input.runner ?? runAuthoringCommand;
|
|
92
93
|
if (providerRequested === "deterministic") {
|
|
94
|
+
const plan = generateRulesFromPrompt(input.prompt);
|
|
93
95
|
return {
|
|
94
96
|
providerRequested,
|
|
95
97
|
providerUsed: "deterministic",
|
|
96
|
-
plan
|
|
98
|
+
plan,
|
|
99
|
+
intent: intentFromNaturalLanguagePlan(plan),
|
|
97
100
|
};
|
|
98
101
|
}
|
|
99
102
|
if (providerRequested === "claude") {
|
|
103
|
+
const plan = await generateWithClaude(input.prompt, input.config, timeoutMs, runner);
|
|
100
104
|
return {
|
|
101
105
|
providerRequested,
|
|
102
106
|
providerUsed: "claude",
|
|
103
|
-
plan
|
|
107
|
+
plan,
|
|
108
|
+
intent: intentFromNaturalLanguagePlan(plan),
|
|
104
109
|
};
|
|
105
110
|
}
|
|
106
111
|
if (providerRequested === "exec") {
|
|
112
|
+
const plan = await generateWithExec(input.prompt, input.config, {
|
|
113
|
+
command: input.execCommand ?? input.config?.authoring.exec?.command,
|
|
114
|
+
args: input.execArgs ?? input.config?.authoring.exec?.args,
|
|
115
|
+
}, timeoutMs, runner);
|
|
107
116
|
return {
|
|
108
117
|
providerRequested,
|
|
109
118
|
providerUsed: "exec",
|
|
110
|
-
plan
|
|
111
|
-
|
|
112
|
-
args: input.execArgs ?? input.config?.authoring.exec?.args,
|
|
113
|
-
}, timeoutMs, runner),
|
|
119
|
+
plan,
|
|
120
|
+
intent: intentFromNaturalLanguagePlan(plan),
|
|
114
121
|
};
|
|
115
122
|
}
|
|
116
123
|
return generateWithAuto(input.prompt, input.config, input.execCommand, input.execArgs, timeoutMs, runner);
|
|
@@ -156,13 +163,15 @@ export async function detectLocalAiClis() {
|
|
|
156
163
|
async function generateWithAuto(prompt, config, execCommand, execArgs, timeoutMs, runner) {
|
|
157
164
|
if (execCommand || config?.authoring.exec?.command) {
|
|
158
165
|
try {
|
|
166
|
+
const plan = await generateWithExec(prompt, config, {
|
|
167
|
+
command: execCommand ?? config?.authoring.exec?.command,
|
|
168
|
+
args: execArgs ?? config?.authoring.exec?.args,
|
|
169
|
+
}, timeoutMs, runner);
|
|
159
170
|
return {
|
|
160
171
|
providerRequested: "auto",
|
|
161
172
|
providerUsed: "exec",
|
|
162
|
-
plan
|
|
163
|
-
|
|
164
|
-
args: execArgs ?? config?.authoring.exec?.args,
|
|
165
|
-
}, timeoutMs, runner),
|
|
173
|
+
plan,
|
|
174
|
+
intent: intentFromNaturalLanguagePlan(plan),
|
|
166
175
|
};
|
|
167
176
|
}
|
|
168
177
|
catch (error) {
|
|
@@ -172,22 +181,26 @@ async function generateWithAuto(prompt, config, execCommand, execArgs, timeoutMs
|
|
|
172
181
|
const claudePath = await resolveExecutable("claude");
|
|
173
182
|
if (claudePath) {
|
|
174
183
|
try {
|
|
184
|
+
const plan = await generateWithClaude(prompt, config, timeoutMs, runner, claudePath);
|
|
175
185
|
return {
|
|
176
186
|
providerRequested: "auto",
|
|
177
187
|
providerUsed: "claude",
|
|
178
|
-
plan
|
|
188
|
+
plan,
|
|
189
|
+
intent: intentFromNaturalLanguagePlan(plan),
|
|
179
190
|
};
|
|
180
191
|
}
|
|
181
192
|
catch (error) {
|
|
182
193
|
return fallbackDeterministic(prompt, "claude", error instanceof Error ? error.message : String(error));
|
|
183
194
|
}
|
|
184
195
|
}
|
|
196
|
+
const plan = appendNotes(generateRulesFromPrompt(prompt), [
|
|
197
|
+
"Auto authoring fell back to the built-in deterministic parser because no supported local AI CLI was available.",
|
|
198
|
+
]);
|
|
185
199
|
return {
|
|
186
200
|
providerRequested: "auto",
|
|
187
201
|
providerUsed: "deterministic",
|
|
188
|
-
plan
|
|
189
|
-
|
|
190
|
-
]),
|
|
202
|
+
plan,
|
|
203
|
+
intent: intentFromNaturalLanguagePlan(plan),
|
|
191
204
|
};
|
|
192
205
|
}
|
|
193
206
|
async function generateWithClaude(prompt, config, timeoutMs, runner, explicitCommand) {
|
|
@@ -414,12 +427,14 @@ function buildPortableExecPrompt(config, prompt) {
|
|
|
414
427
|
].join("\n");
|
|
415
428
|
}
|
|
416
429
|
function fallbackDeterministic(prompt, providerUsed, reason) {
|
|
430
|
+
const plan = appendNotes(generateRulesFromPrompt(prompt), [
|
|
431
|
+
`Auto authoring fell back from ${providerUsed} to the deterministic parser: ${reason}`,
|
|
432
|
+
]);
|
|
417
433
|
return {
|
|
418
434
|
providerRequested: "auto",
|
|
419
435
|
providerUsed: "deterministic",
|
|
420
|
-
plan
|
|
421
|
-
|
|
422
|
-
]),
|
|
436
|
+
plan,
|
|
437
|
+
intent: intentFromNaturalLanguagePlan(plan),
|
|
423
438
|
};
|
|
424
439
|
}
|
|
425
440
|
function appendNotes(plan, extraNotes) {
|