@przeprogramowani/10x-cli 1.11.0 → 1.12.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +17 -0
- package/dist/index.mjs +307 -102
- package/package.json +1 -1
- package/skills/10x-cli-guide/SKILL.md +17 -1
package/README.md
CHANGED
|
@@ -49,6 +49,7 @@ Once installed, just tell your agent to **set up 10x-cli** and it will pick up t
|
|
|
49
49
|
10x get m1l1 # Fetch and apply lesson artifacts
|
|
50
50
|
10x sync # Update everything you've downloaded; show what changed
|
|
51
51
|
10x doctor # Check everything is working
|
|
52
|
+
10x bench # Show the 10xBench top-10 model leaderboard
|
|
52
53
|
```
|
|
53
54
|
|
|
54
55
|
## Commands
|
|
@@ -60,6 +61,7 @@ Once installed, just tell your agent to **set up 10x-cli** and it will pick up t
|
|
|
60
61
|
| `10x get <ref>` | Fetch a lesson and apply artifacts to your workspace |
|
|
61
62
|
| `10x sync` | Bulk-download / refresh lessons and report what changed upstream |
|
|
62
63
|
| `10x doctor` | Diagnose auth, API connectivity, and local config |
|
|
64
|
+
| `10x bench` | Live top-10 AI model leaderboard from [10xbench.ai](https://10xbench.ai) — no login needed |
|
|
63
65
|
|
|
64
66
|
### `10x get` Flags
|
|
65
67
|
|
|
@@ -163,6 +165,21 @@ all. **Change visibility covers skills and prompts** — configs are create-only
|
|
|
163
165
|
conflict is reported, not a failure), `1` if any lesson failed to fetch. The full
|
|
164
166
|
report is still emitted on a partial failure.
|
|
165
167
|
|
|
168
|
+
### `10x bench`
|
|
169
|
+
|
|
170
|
+
Shows the [10xBench](https://10xbench.ai) leaderboard — the top 10 AI models
|
|
171
|
+
benchmarked on vibe-coding the Przeprogramowani.pl website — rendered as
|
|
172
|
+
color-coded score bars in your terminal. Public data, works without logging in.
|
|
173
|
+
|
|
174
|
+
```bash
|
|
175
|
+
10x bench # Top 10 models with scores, runs, and cost per run
|
|
176
|
+
10x bench --limit 5 # Just the top 5
|
|
177
|
+
10x bench --json # Machine-readable (also auto-selected when piped)
|
|
178
|
+
```
|
|
179
|
+
|
|
180
|
+
The data refreshes whenever new benchmark results are published on 10xbench.ai.
|
|
181
|
+
Colors honor `NO_COLOR` and are disabled automatically when output is piped.
|
|
182
|
+
|
|
166
183
|
### Global Flags
|
|
167
184
|
|
|
168
185
|
- `--json` — Machine-readable JSON output (auto-detected when piped)
|
package/dist/index.mjs
CHANGED
|
@@ -2271,7 +2271,7 @@ var cac = (name = "") => new CAC(name);
|
|
|
2271
2271
|
// package.json
|
|
2272
2272
|
var package_default = {
|
|
2273
2273
|
name: "@przeprogramowani/10x-cli",
|
|
2274
|
-
version: "1.
|
|
2274
|
+
version: "1.12.0",
|
|
2275
2275
|
description: "Open-source CLI for 10xDevs course content",
|
|
2276
2276
|
repository: {
|
|
2277
2277
|
type: "git",
|
|
@@ -3262,6 +3262,10 @@ function verbose(ctx, message) {
|
|
|
3262
3262
|
process.stderr.write(`[verbose] ${message}
|
|
3263
3263
|
`);
|
|
3264
3264
|
}
|
|
3265
|
+
function exitNotImplemented(command, phase, flags = {}) {
|
|
3266
|
+
const ctx = resolveContext(flags);
|
|
3267
|
+
outputError(ctx, "not_implemented", `'10x ${command}' lands in ${phase}.`, ExitCodes.ERROR, "See thoughts/shared/plans/2026-04-07-10x-cli-design.md for the full roadmap.");
|
|
3268
|
+
}
|
|
3265
3269
|
|
|
3266
3270
|
// src/lib/auth-guard.ts
|
|
3267
3271
|
var DEFAULT_REFRESH_WINDOW_MS = 5 * 60 * 1000;
|
|
@@ -3763,9 +3767,209 @@ function formatRow(entry, rank, name, detail, rankWidth, nameWidth, detailWidth)
|
|
|
3763
3767
|
].join(" ");
|
|
3764
3768
|
}
|
|
3765
3769
|
|
|
3770
|
+
// src/commands/bench-kit.ts
|
|
3771
|
+
import { spawn } from "node:child_process";
|
|
3772
|
+
import {
|
|
3773
|
+
cpSync,
|
|
3774
|
+
existsSync as existsSync2,
|
|
3775
|
+
mkdirSync as mkdirSync3,
|
|
3776
|
+
mkdtempSync,
|
|
3777
|
+
readFileSync as readFileSync2,
|
|
3778
|
+
readdirSync,
|
|
3779
|
+
rmSync as rmSync2,
|
|
3780
|
+
writeFileSync as writeFileSync2
|
|
3781
|
+
} from "node:fs";
|
|
3782
|
+
import { tmpdir } from "node:os";
|
|
3783
|
+
import { join as join2, resolve } from "node:path";
|
|
3784
|
+
|
|
3785
|
+
// src/lib/experimental.ts
|
|
3786
|
+
var EXPERIMENTAL_ENV = "TENX_CLI_EXPERIMENTAL";
|
|
3787
|
+
function experimentalEnabled(env = process.env) {
|
|
3788
|
+
const value = env[EXPERIMENTAL_ENV];
|
|
3789
|
+
return value === "1" || value === "true";
|
|
3790
|
+
}
|
|
3791
|
+
function requireExperimental(command, flags, env = process.env) {
|
|
3792
|
+
if (experimentalEnabled(env))
|
|
3793
|
+
return;
|
|
3794
|
+
const ctx = resolveContext(flags);
|
|
3795
|
+
outputError(ctx, "experimental_locked", `'10x ${command}' is experimental and currently locked.`, ExitCodes.FORBIDDEN, `Set ${EXPERIMENTAL_ENV}=1 and run '10x ${command}' again.`);
|
|
3796
|
+
}
|
|
3797
|
+
|
|
3798
|
+
// src/commands/bench-kit.ts
|
|
3799
|
+
var TEMPLATE_REPO_URL = "https://github.com/przeprogramowani/10x-bench-kit";
|
|
3800
|
+
function registerBenchKitCommand(cli) {
|
|
3801
|
+
if (!experimentalEnabled())
|
|
3802
|
+
return;
|
|
3803
|
+
cli.command("bench-kit <action> [dir]", "Manage a benchmark instance (actions: init, update; experimental)").option("--template-version <tag>", "Template tag to install (default: latest)").option("--yes", "Run non-interactively, accepting defaults").example("10x bench-kit init my-benchmark").example("10x bench-kit init my-benchmark --template-version v0.1.0").action(async (action, dir, options) => {
|
|
3804
|
+
requireExperimental(`bench-kit ${action}`, options);
|
|
3805
|
+
const ctx = resolveContext(options);
|
|
3806
|
+
if (action === "init") {
|
|
3807
|
+
await runBenchKitInit(ctx, dir, options);
|
|
3808
|
+
return;
|
|
3809
|
+
}
|
|
3810
|
+
if (action === "update") {
|
|
3811
|
+
exitNotImplemented("bench-kit update", "a later bench-kit phase", options);
|
|
3812
|
+
}
|
|
3813
|
+
outputError(ctx, "unknown_action", `'${action}' is not a bench-kit action.`, ExitCodes.USAGE, "Run '10x bench-kit init [dir]' or '10x bench-kit update'.");
|
|
3814
|
+
});
|
|
3815
|
+
}
|
|
3816
|
+
async function runBenchKitInit(ctx, dirArg, options, deps = defaultDeps) {
|
|
3817
|
+
const targetDir = resolve(dirArg ?? ".");
|
|
3818
|
+
const requestedRef = normalizeRef(ctx, options.templateVersion);
|
|
3819
|
+
await preflight(ctx, deps);
|
|
3820
|
+
const existingVersion = readInstanceVersion(targetDir);
|
|
3821
|
+
const repair = existingVersion !== null;
|
|
3822
|
+
if (repair && requestedRef !== null) {
|
|
3823
|
+
outputError(ctx, "version_conflict", `This directory already holds a benchmark instance on template version ${existingVersion}.`, ExitCodes.USAGE, "Run '10x bench-kit update' to change the template version of an existing instance.");
|
|
3824
|
+
}
|
|
3825
|
+
if (!repair && existsSync2(targetDir) && readdirSync(targetDir).length > 0) {
|
|
3826
|
+
outputError(ctx, "target_not_empty", `Directory '${targetDir}' is not empty and is not a benchmark instance.`, ExitCodes.ERROR, "Run '10x bench-kit init <dir>' with an empty or new directory.");
|
|
3827
|
+
}
|
|
3828
|
+
const scratch = mkdtempSync(join2(tmpdir(), "bench-kit-"));
|
|
3829
|
+
try {
|
|
3830
|
+
verbose(ctx, `cloning ${TEMPLATE_REPO_URL} (${requestedRef ?? "latest"}) into ${scratch}`);
|
|
3831
|
+
const clone = await deps.cloneTemplate(requestedRef, scratch);
|
|
3832
|
+
if (!clone.ok) {
|
|
3833
|
+
outputError(ctx, "clone_failed", `Could not download the template from ${TEMPLATE_REPO_URL}.`, ExitCodes.ERROR, clone.error ? `Git said: ${clone.error.trim()}` : "Check your internet connection and run '10x bench-kit init' again.");
|
|
3834
|
+
}
|
|
3835
|
+
const templateVersion = readTemplateVersion(ctx, scratch);
|
|
3836
|
+
mkdirSync3(targetDir, { recursive: true });
|
|
3837
|
+
const copied = materialize(scratch, targetDir, { skipExisting: repair });
|
|
3838
|
+
const manifest = {
|
|
3839
|
+
templateVersion,
|
|
3840
|
+
templateRef: requestedRef ?? "latest",
|
|
3841
|
+
templateSource: TEMPLATE_REPO_URL,
|
|
3842
|
+
initializedAt: deps.now().toISOString()
|
|
3843
|
+
};
|
|
3844
|
+
writeFileSync2(join2(targetDir, ".bench-kit", "instance.json"), `${JSON.stringify(manifest, null, 2)}
|
|
3845
|
+
`);
|
|
3846
|
+
let committed = false;
|
|
3847
|
+
if (!repair) {
|
|
3848
|
+
committed = await freshGitInit(ctx, deps, targetDir, templateVersion);
|
|
3849
|
+
}
|
|
3850
|
+
const humanLines = repair ? [
|
|
3851
|
+
`Repaired the benchmark instance in '${targetDir}' (template ${templateVersion}).`,
|
|
3852
|
+
`Restored ${copied} missing file${copied === 1 ? "" : "s"}; your tasks, evaluation pool and config were not touched.`
|
|
3853
|
+
] : [
|
|
3854
|
+
`Created a benchmark instance in '${targetDir}' from template ${templateVersion}.`,
|
|
3855
|
+
committed ? "Initialized a fresh git repository with an initial commit." : "Initialized a fresh git repository (initial commit skipped — commit the files yourself).",
|
|
3856
|
+
"Next: wire up secrets, then run 'bench validate' before the first run."
|
|
3857
|
+
];
|
|
3858
|
+
output(ctx, humanLines.join(`
|
|
3859
|
+
`), {
|
|
3860
|
+
dir: targetDir,
|
|
3861
|
+
mode: repair ? "repair" : "init",
|
|
3862
|
+
templateVersion,
|
|
3863
|
+
templateRef: manifest.templateRef,
|
|
3864
|
+
filesCopied: copied,
|
|
3865
|
+
gitInitialized: !repair,
|
|
3866
|
+
committed
|
|
3867
|
+
});
|
|
3868
|
+
} finally {
|
|
3869
|
+
rmSync2(scratch, { recursive: true, force: true });
|
|
3870
|
+
}
|
|
3871
|
+
}
|
|
3872
|
+
function normalizeRef(ctx, raw) {
|
|
3873
|
+
if (raw === undefined)
|
|
3874
|
+
return null;
|
|
3875
|
+
const ref = raw.trim();
|
|
3876
|
+
if (ref === "" || ref === "latest")
|
|
3877
|
+
return null;
|
|
3878
|
+
if (!/^[A-Za-z0-9._/-]+$/.test(ref)) {
|
|
3879
|
+
outputError(ctx, "invalid_template_version", `'${raw}' is not a valid template tag.`, ExitCodes.USAGE, "Pass a tag name, for example '10x bench-kit init --template-version v0.1.0'.");
|
|
3880
|
+
}
|
|
3881
|
+
return ref;
|
|
3882
|
+
}
|
|
3883
|
+
async function preflight(ctx, deps) {
|
|
3884
|
+
if (!await deps.toolAvailable("git")) {
|
|
3885
|
+
outputError(ctx, "preflight_failed", "Git is required to download the template and initialize the instance.", ExitCodes.ERROR, "Install git (https://git-scm.com) and run '10x bench-kit init' again.");
|
|
3886
|
+
}
|
|
3887
|
+
const hasEngine = await deps.toolAvailable("docker") || await deps.toolAvailable("podman");
|
|
3888
|
+
if (!hasEngine) {
|
|
3889
|
+
verbose(ctx, "neither docker nor podman found — benchmark runs will need one later");
|
|
3890
|
+
}
|
|
3891
|
+
}
|
|
3892
|
+
function readInstanceVersion(dir) {
|
|
3893
|
+
const versionFile = join2(dir, ".bench-kit", "VERSION");
|
|
3894
|
+
if (!existsSync2(versionFile))
|
|
3895
|
+
return null;
|
|
3896
|
+
return readFileSync2(versionFile, "utf8").trim();
|
|
3897
|
+
}
|
|
3898
|
+
function readTemplateVersion(ctx, cloneDir) {
|
|
3899
|
+
const versionFile = join2(cloneDir, ".bench-kit", "VERSION");
|
|
3900
|
+
if (!existsSync2(versionFile)) {
|
|
3901
|
+
outputError(ctx, "invalid_template", "The downloaded template has no .bench-kit/VERSION file.", ExitCodes.ERROR, "Pass a valid tag via '10x bench-kit init --template-version <tag>'.");
|
|
3902
|
+
}
|
|
3903
|
+
return readFileSync2(versionFile, "utf8").trim();
|
|
3904
|
+
}
|
|
3905
|
+
function materialize(srcDir, destDir, opts) {
|
|
3906
|
+
let copied = 0;
|
|
3907
|
+
const walk = (rel) => {
|
|
3908
|
+
for (const entry of readdirSync(join2(srcDir, rel), { withFileTypes: true })) {
|
|
3909
|
+
if (rel === "" && entry.name === ".git")
|
|
3910
|
+
continue;
|
|
3911
|
+
const relPath = join2(rel, entry.name);
|
|
3912
|
+
const from = join2(srcDir, relPath);
|
|
3913
|
+
const to = join2(destDir, relPath);
|
|
3914
|
+
if (entry.isDirectory()) {
|
|
3915
|
+
mkdirSync3(to, { recursive: true });
|
|
3916
|
+
walk(relPath);
|
|
3917
|
+
continue;
|
|
3918
|
+
}
|
|
3919
|
+
if (opts.skipExisting && existsSync2(to))
|
|
3920
|
+
continue;
|
|
3921
|
+
cpSync(from, to);
|
|
3922
|
+
copied++;
|
|
3923
|
+
}
|
|
3924
|
+
};
|
|
3925
|
+
walk("");
|
|
3926
|
+
return copied;
|
|
3927
|
+
}
|
|
3928
|
+
async function freshGitInit(ctx, deps, dir, templateVersion) {
|
|
3929
|
+
const init = await deps.runGit(["init"], dir);
|
|
3930
|
+
if (!init.ok) {
|
|
3931
|
+
outputError(ctx, "git_init_failed", "Could not initialize a git repository in the instance directory.", ExitCodes.ERROR, init.error ? `Git said: ${init.error.trim()}` : undefined);
|
|
3932
|
+
}
|
|
3933
|
+
const add = await deps.runGit(["add", "-A"], dir);
|
|
3934
|
+
const commit = add.ok ? await deps.runGit(["commit", "-m", `chore: bench-kit init (template ${templateVersion})`], dir) : add;
|
|
3935
|
+
if (!commit.ok) {
|
|
3936
|
+
verbose(ctx, `initial commit failed (${commit.error.trim()}) — files are staged, commit manually`);
|
|
3937
|
+
return false;
|
|
3938
|
+
}
|
|
3939
|
+
return true;
|
|
3940
|
+
}
|
|
3941
|
+
function run(cmd, args, cwd) {
|
|
3942
|
+
return new Promise((resolvePromise) => {
|
|
3943
|
+
const child = spawn(cmd, args, { cwd, stdio: ["ignore", "ignore", "pipe"] });
|
|
3944
|
+
let stderr = "";
|
|
3945
|
+
child.stderr?.on("data", (chunk) => {
|
|
3946
|
+
stderr += chunk.toString();
|
|
3947
|
+
});
|
|
3948
|
+
child.on("error", (err) => resolvePromise({ ok: false, error: err.message }));
|
|
3949
|
+
child.on("close", (code) => resolvePromise({ ok: code === 0, error: stderr }));
|
|
3950
|
+
});
|
|
3951
|
+
}
|
|
3952
|
+
var defaultDeps = {
|
|
3953
|
+
async toolAvailable(cmd) {
|
|
3954
|
+
const result = await run(cmd, ["--version"]);
|
|
3955
|
+
return result.ok;
|
|
3956
|
+
},
|
|
3957
|
+
cloneTemplate(ref, destDir) {
|
|
3958
|
+
const args = ["clone", "--depth", "1"];
|
|
3959
|
+
if (ref !== null)
|
|
3960
|
+
args.push("--branch", ref);
|
|
3961
|
+
args.push(TEMPLATE_REPO_URL, destDir);
|
|
3962
|
+
return run("git", args);
|
|
3963
|
+
},
|
|
3964
|
+
runGit(args, cwd) {
|
|
3965
|
+
return run("git", args, cwd);
|
|
3966
|
+
},
|
|
3967
|
+
now: () => new Date
|
|
3968
|
+
};
|
|
3969
|
+
|
|
3766
3970
|
// src/commands/doctor.ts
|
|
3767
|
-
import { existsSync as
|
|
3768
|
-
import { join as
|
|
3971
|
+
import { existsSync as existsSync3, accessSync, constants } from "node:fs";
|
|
3972
|
+
import { join as join3 } from "node:path";
|
|
3769
3973
|
|
|
3770
3974
|
// src/lib/signing.ts
|
|
3771
3975
|
import { createHash, createPublicKey, verify } from "node:crypto";
|
|
@@ -4261,7 +4465,7 @@ async function checkApiConnectivity() {
|
|
|
4261
4465
|
}
|
|
4262
4466
|
function checkConfigDirectory() {
|
|
4263
4467
|
const dir = configDir();
|
|
4264
|
-
if (!
|
|
4468
|
+
if (!existsSync3(dir)) {
|
|
4265
4469
|
return {
|
|
4266
4470
|
name: "config",
|
|
4267
4471
|
label: "Config",
|
|
@@ -4326,8 +4530,8 @@ function checkToolDirectory() {
|
|
|
4326
4530
|
const toolId = readToolConfig()?.tool ?? DEFAULT_TOOL;
|
|
4327
4531
|
const profile = PROFILES[toolId] ?? PROFILES[DEFAULT_TOOL];
|
|
4328
4532
|
const dirName = profile.manifestDir;
|
|
4329
|
-
const toolDir =
|
|
4330
|
-
if (!
|
|
4533
|
+
const toolDir = join3(cwd, dirName);
|
|
4534
|
+
if (!existsSync3(toolDir)) {
|
|
4331
4535
|
return {
|
|
4332
4536
|
name: "tool-dir",
|
|
4333
4537
|
label: profile.displayName,
|
|
@@ -4359,7 +4563,7 @@ function checkToolDirectory() {
|
|
|
4359
4563
|
}
|
|
4360
4564
|
|
|
4361
4565
|
// src/commands/get.ts
|
|
4362
|
-
import { join as
|
|
4566
|
+
import { join as join8 } from "node:path";
|
|
4363
4567
|
|
|
4364
4568
|
// src/lib/conflict-prompt.ts
|
|
4365
4569
|
function createConflictResolver(tty) {
|
|
@@ -4444,8 +4648,8 @@ function parseModuleRef(raw) {
|
|
|
4444
4648
|
|
|
4445
4649
|
// src/lib/manifest.ts
|
|
4446
4650
|
import { createHash as createHash2 } from "node:crypto";
|
|
4447
|
-
import { existsSync as
|
|
4448
|
-
import { dirname as dirname2, join as
|
|
4651
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync3, renameSync as renameSync2, rmSync as rmSync3, writeFileSync as writeFileSync3 } from "node:fs";
|
|
4652
|
+
import { dirname as dirname2, join as join4 } from "node:path";
|
|
4449
4653
|
var MANIFEST_FILENAME = ".10x-cli-manifest.json";
|
|
4450
4654
|
var CLI_PACKAGE_NAME = "@przeprogramowani/10x-cli";
|
|
4451
4655
|
var MANIFEST_VERSION = 3;
|
|
@@ -4453,12 +4657,12 @@ function contentHash(content) {
|
|
|
4453
4657
|
return createHash2("sha256").update(content, "utf8").digest("hex");
|
|
4454
4658
|
}
|
|
4455
4659
|
function readManifest(dir) {
|
|
4456
|
-
const manifestPath =
|
|
4457
|
-
if (!
|
|
4660
|
+
const manifestPath = join4(dir, MANIFEST_FILENAME);
|
|
4661
|
+
if (!existsSync4(manifestPath))
|
|
4458
4662
|
return null;
|
|
4459
4663
|
let raw;
|
|
4460
4664
|
try {
|
|
4461
|
-
raw =
|
|
4665
|
+
raw = readFileSync3(manifestPath, "utf8");
|
|
4462
4666
|
} catch {
|
|
4463
4667
|
return null;
|
|
4464
4668
|
}
|
|
@@ -4473,14 +4677,14 @@ function readManifest(dir) {
|
|
|
4473
4677
|
return parsed;
|
|
4474
4678
|
}
|
|
4475
4679
|
function writeManifest(dir, manifest) {
|
|
4476
|
-
if (!
|
|
4477
|
-
|
|
4680
|
+
if (!existsSync4(dir)) {
|
|
4681
|
+
mkdirSync4(dir, { recursive: true });
|
|
4478
4682
|
}
|
|
4479
|
-
const manifestPath =
|
|
4480
|
-
|
|
4683
|
+
const manifestPath = join4(dir, MANIFEST_FILENAME);
|
|
4684
|
+
mkdirSync4(dirname2(manifestPath), { recursive: true });
|
|
4481
4685
|
const tmp = `${manifestPath}.tmp`;
|
|
4482
|
-
|
|
4483
|
-
|
|
4686
|
+
rmSync3(tmp, { force: true });
|
|
4687
|
+
writeFileSync3(tmp, `${JSON.stringify(manifest, null, 2)}
|
|
4484
4688
|
`);
|
|
4485
4689
|
renameSync2(tmp, manifestPath);
|
|
4486
4690
|
}
|
|
@@ -4580,10 +4784,10 @@ function buildUnionFiles(lessons) {
|
|
|
4580
4784
|
}
|
|
4581
4785
|
|
|
4582
4786
|
// src/lib/tool-detect.ts
|
|
4583
|
-
import { existsSync as
|
|
4584
|
-
import { join as
|
|
4787
|
+
import { existsSync as existsSync5 } from "node:fs";
|
|
4788
|
+
import { join as join5 } from "node:path";
|
|
4585
4789
|
function detectTools(projectRoot) {
|
|
4586
|
-
const hit = (rel) =>
|
|
4790
|
+
const hit = (rel) => existsSync5(join5(projectRoot, rel));
|
|
4587
4791
|
const signals = [];
|
|
4588
4792
|
if (hit(".claude/" + MANIFEST_FILENAME)) {
|
|
4589
4793
|
signals.push({
|
|
@@ -4691,23 +4895,23 @@ function topDetectedProfile(signals) {
|
|
|
4691
4895
|
|
|
4692
4896
|
// src/lib/tool-switch.ts
|
|
4693
4897
|
import {
|
|
4694
|
-
existsSync as
|
|
4898
|
+
existsSync as existsSync7,
|
|
4695
4899
|
lstatSync,
|
|
4696
|
-
mkdirSync as
|
|
4697
|
-
readFileSync as
|
|
4900
|
+
mkdirSync as mkdirSync6,
|
|
4901
|
+
readFileSync as readFileSync6,
|
|
4698
4902
|
renameSync as renameSync3,
|
|
4699
4903
|
rmdirSync as rmdirSync2,
|
|
4700
|
-
rmSync as
|
|
4904
|
+
rmSync as rmSync5,
|
|
4701
4905
|
statSync,
|
|
4702
|
-
writeFileSync as
|
|
4906
|
+
writeFileSync as writeFileSync5
|
|
4703
4907
|
} from "node:fs";
|
|
4704
|
-
import { dirname as dirname4, join as
|
|
4908
|
+
import { dirname as dirname4, join as join7 } from "node:path";
|
|
4705
4909
|
|
|
4706
4910
|
// src/lib/fs-utils.ts
|
|
4707
|
-
import { readFileSync as
|
|
4911
|
+
import { readFileSync as readFileSync4 } from "node:fs";
|
|
4708
4912
|
function readFileOrNull(path) {
|
|
4709
4913
|
try {
|
|
4710
|
-
return
|
|
4914
|
+
return readFileSync4(path, "utf8");
|
|
4711
4915
|
} catch {
|
|
4712
4916
|
return null;
|
|
4713
4917
|
}
|
|
@@ -4783,15 +4987,15 @@ function stripBlock(content, begin, end, label, warnings) {
|
|
|
4783
4987
|
// src/lib/writer.ts
|
|
4784
4988
|
import {
|
|
4785
4989
|
chmodSync as chmodSync2,
|
|
4786
|
-
existsSync as
|
|
4787
|
-
mkdirSync as
|
|
4788
|
-
readFileSync as
|
|
4789
|
-
readdirSync,
|
|
4990
|
+
existsSync as existsSync6,
|
|
4991
|
+
mkdirSync as mkdirSync5,
|
|
4992
|
+
readFileSync as readFileSync5,
|
|
4993
|
+
readdirSync as readdirSync2,
|
|
4790
4994
|
rmdirSync,
|
|
4791
|
-
rmSync as
|
|
4792
|
-
writeFileSync as
|
|
4995
|
+
rmSync as rmSync4,
|
|
4996
|
+
writeFileSync as writeFileSync4
|
|
4793
4997
|
} from "node:fs";
|
|
4794
|
-
import { dirname as dirname3, join as
|
|
4998
|
+
import { dirname as dirname3, join as join6 } from "node:path";
|
|
4795
4999
|
var CLI_VERSION = package_default.version;
|
|
4796
5000
|
var DEFAULT_COURSE = "10xdevs3";
|
|
4797
5001
|
async function applyBundle(bundle, projectRoot, options = {}) {
|
|
@@ -4801,7 +5005,7 @@ async function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4801
5005
|
const profile = options.profile ?? PROFILES[DEFAULT_TOOL];
|
|
4802
5006
|
const onConflict = options.onConflict;
|
|
4803
5007
|
const applyCourseRules = options.applyCourseRules !== false;
|
|
4804
|
-
const manifestDir =
|
|
5008
|
+
const manifestDir = join6(projectRoot, profile.manifestDir);
|
|
4805
5009
|
const prevManifest = readManifest(manifestDir);
|
|
4806
5010
|
const plan = planBundle(bundle, projectRoot, { profile, applyCourseRules });
|
|
4807
5011
|
const nextSkillHashes = {};
|
|
@@ -4903,7 +5107,7 @@ async function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4903
5107
|
}
|
|
4904
5108
|
prompts.push({ name: prompt.name, path: target, action: finalAction, userBackupPath });
|
|
4905
5109
|
}
|
|
4906
|
-
const rulesFilePath =
|
|
5110
|
+
const rulesFilePath = join6(projectRoot, profile.rulesFile);
|
|
4907
5111
|
const existingRules = readFileOrEmpty(rulesFilePath);
|
|
4908
5112
|
const { action: rulesAction, content: newRulesContent } = planRules(existingRules, bundle, profile, applyCourseRules);
|
|
4909
5113
|
if (!dryRun && rulesAction !== "unchanged") {
|
|
@@ -4925,24 +5129,24 @@ async function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4925
5129
|
for (const entry of removed.skillDirs) {
|
|
4926
5130
|
removalResult.skills.push({ name: entry.name, path: entry.path, action: "removed" });
|
|
4927
5131
|
if (!dryRun && !partial)
|
|
4928
|
-
|
|
5132
|
+
rmSync4(entry.path, { recursive: true, force: true });
|
|
4929
5133
|
}
|
|
4930
5134
|
for (const entry of removed.skillFiles) {
|
|
4931
5135
|
removalResult.skills.push({ name: entry.name, path: entry.path, action: "removed" });
|
|
4932
5136
|
if (!dryRun && !partial) {
|
|
4933
|
-
|
|
5137
|
+
rmSync4(entry.path, { force: true });
|
|
4934
5138
|
removeEmptyParentDirs(entry.path, entry.skillDirAbs);
|
|
4935
5139
|
}
|
|
4936
5140
|
}
|
|
4937
5141
|
for (const entry of removed.prompts) {
|
|
4938
5142
|
removalResult.prompts.push({ name: entry.name, path: entry.path, action: "removed" });
|
|
4939
5143
|
if (!dryRun && !partial)
|
|
4940
|
-
|
|
5144
|
+
rmSync4(entry.path, { force: true });
|
|
4941
5145
|
}
|
|
4942
5146
|
for (const entry of removed.configs) {
|
|
4943
5147
|
removalResult.configs.push({ name: entry.name, path: entry.path, action: "removed" });
|
|
4944
5148
|
if (!dryRun && !partial)
|
|
4945
|
-
|
|
5149
|
+
rmSync4(entry.path, { force: true });
|
|
4946
5150
|
}
|
|
4947
5151
|
if (!dryRun && !partial) {
|
|
4948
5152
|
const catalogContentHash = options.catalogContentHash ?? prevManifest?.lessons?.[bundle.lessonId]?.catalogContentHash;
|
|
@@ -5023,13 +5227,13 @@ function planBundle(bundle, projectRoot, options = {}) {
|
|
|
5023
5227
|
assertSafeName(prompt.name, "prompt");
|
|
5024
5228
|
for (const config of bundle.configs)
|
|
5025
5229
|
assertSafeName(config.name, "config");
|
|
5026
|
-
const manifestDir =
|
|
5230
|
+
const manifestDir = join6(projectRoot, profile.manifestDir);
|
|
5027
5231
|
const prevManifest = readManifest(manifestDir);
|
|
5028
5232
|
const skills = bundle.skills.map((skill) => {
|
|
5029
|
-
const skillDir =
|
|
5233
|
+
const skillDir = join6(projectRoot, profile.skillDir(skill.name));
|
|
5030
5234
|
const prevEntry = prevManifest?.files.skills[skill.name];
|
|
5031
5235
|
const files = skill.files.map((file) => {
|
|
5032
|
-
const target =
|
|
5236
|
+
const target = join6(skillDir, file.path);
|
|
5033
5237
|
const storedHash = prevEntry?.contentHashes?.[file.path];
|
|
5034
5238
|
const { action, isConflict } = computeFileAction(target, file.content, storedHash);
|
|
5035
5239
|
return {
|
|
@@ -5043,7 +5247,7 @@ function planBundle(bundle, projectRoot, options = {}) {
|
|
|
5043
5247
|
return { name: skill.name, files };
|
|
5044
5248
|
});
|
|
5045
5249
|
const prompts = bundle.prompts.map((prompt) => {
|
|
5046
|
-
const target =
|
|
5250
|
+
const target = join6(projectRoot, profile.promptPath(prompt.name));
|
|
5047
5251
|
const storedHash = prevManifest?.files.promptHashes?.[`${prompt.name}.md`];
|
|
5048
5252
|
const { action, isConflict } = computeFileAction(target, prompt.content, storedHash);
|
|
5049
5253
|
return {
|
|
@@ -5054,12 +5258,12 @@ function planBundle(bundle, projectRoot, options = {}) {
|
|
|
5054
5258
|
upstreamChanged: computeUpstreamChanged(prompt.content, storedHash, action)
|
|
5055
5259
|
};
|
|
5056
5260
|
});
|
|
5057
|
-
const rulesFilePath =
|
|
5261
|
+
const rulesFilePath = join6(projectRoot, profile.rulesFile);
|
|
5058
5262
|
const existingRules = readFileOrEmpty(rulesFilePath);
|
|
5059
5263
|
const { action: rulesAction } = planRules(existingRules, bundle, profile, applyCourseRules);
|
|
5060
5264
|
const configs = bundle.configs.map((config) => {
|
|
5061
|
-
const target =
|
|
5062
|
-
const action =
|
|
5265
|
+
const target = join6(projectRoot, profile.configPath(config.name));
|
|
5266
|
+
const action = existsSync6(target) ? "skipped" : "created";
|
|
5063
5267
|
return {
|
|
5064
5268
|
name: config.name,
|
|
5065
5269
|
path: target,
|
|
@@ -5113,11 +5317,11 @@ function planRules(existingRules, bundle, profile, applyCourseRules) {
|
|
|
5113
5317
|
return { action, content: newRules };
|
|
5114
5318
|
}
|
|
5115
5319
|
function computeFileAction(filePath, newContent, storedHash) {
|
|
5116
|
-
if (!
|
|
5320
|
+
if (!existsSync6(filePath))
|
|
5117
5321
|
return { action: "created", isConflict: false };
|
|
5118
5322
|
let current;
|
|
5119
5323
|
try {
|
|
5120
|
-
current =
|
|
5324
|
+
current = readFileSync5(filePath, "utf8");
|
|
5121
5325
|
} catch {
|
|
5122
5326
|
return { action: "updated", isConflict: false };
|
|
5123
5327
|
}
|
|
@@ -5138,21 +5342,21 @@ function buildUserBackupPath(filePath) {
|
|
|
5138
5342
|
return `${filePath.slice(0, lastDot)}.user${filePath.slice(lastDot)}`;
|
|
5139
5343
|
}
|
|
5140
5344
|
function copyFileSync(src, dest) {
|
|
5141
|
-
|
|
5142
|
-
|
|
5345
|
+
mkdirSync5(dirname3(dest), { recursive: true });
|
|
5346
|
+
writeFileSync4(dest, readFileSync5(src));
|
|
5143
5347
|
}
|
|
5144
5348
|
function readFileOrEmpty(filePath) {
|
|
5145
|
-
if (!
|
|
5349
|
+
if (!existsSync6(filePath))
|
|
5146
5350
|
return "";
|
|
5147
5351
|
try {
|
|
5148
|
-
return
|
|
5352
|
+
return readFileSync5(filePath, "utf8");
|
|
5149
5353
|
} catch {
|
|
5150
5354
|
return "";
|
|
5151
5355
|
}
|
|
5152
5356
|
}
|
|
5153
5357
|
function writeFileAt(filePath, content) {
|
|
5154
|
-
|
|
5155
|
-
|
|
5358
|
+
mkdirSync5(dirname3(filePath), { recursive: true });
|
|
5359
|
+
writeFileSync4(filePath, content);
|
|
5156
5360
|
}
|
|
5157
5361
|
function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
5158
5362
|
const empty = {
|
|
@@ -5195,7 +5399,7 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
5195
5399
|
for (const [skillName, skill] of Object.entries(prevLessonEntry.skills)) {
|
|
5196
5400
|
if (!isSafeName(skillName))
|
|
5197
5401
|
continue;
|
|
5198
|
-
const skillDirAbs =
|
|
5402
|
+
const skillDirAbs = join6(projectRoot, profile.skillDir(skillName));
|
|
5199
5403
|
if (!currentSkills.has(skillName)) {
|
|
5200
5404
|
if (protectedSkills.has(skillName)) {
|
|
5201
5405
|
const prot = protectedSkills.get(skillName);
|
|
@@ -5206,7 +5410,7 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
5206
5410
|
continue;
|
|
5207
5411
|
removed.skillFiles.push({
|
|
5208
5412
|
name: `${skillName}/${relPath}`,
|
|
5209
|
-
path:
|
|
5413
|
+
path: join6(skillDirAbs, relPath),
|
|
5210
5414
|
skillDirAbs
|
|
5211
5415
|
});
|
|
5212
5416
|
}
|
|
@@ -5225,7 +5429,7 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
5225
5429
|
continue;
|
|
5226
5430
|
removed.skillFiles.push({
|
|
5227
5431
|
name: `${skillName}/${relPath}`,
|
|
5228
|
-
path:
|
|
5432
|
+
path: join6(skillDirAbs, relPath),
|
|
5229
5433
|
skillDirAbs
|
|
5230
5434
|
});
|
|
5231
5435
|
}
|
|
@@ -5240,7 +5444,7 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
5240
5444
|
const promptName = promptFile.replace(/\.md$/, "");
|
|
5241
5445
|
removed.prompts.push({
|
|
5242
5446
|
name: promptFile,
|
|
5243
|
-
path:
|
|
5447
|
+
path: join6(projectRoot, profile.promptPath(promptName))
|
|
5244
5448
|
});
|
|
5245
5449
|
}
|
|
5246
5450
|
for (const configFile of prevLessonEntry.configs) {
|
|
@@ -5252,7 +5456,7 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
5252
5456
|
continue;
|
|
5253
5457
|
removed.configs.push({
|
|
5254
5458
|
name: configFile,
|
|
5255
|
-
path:
|
|
5459
|
+
path: join6(projectRoot, profile.configPath(configFile))
|
|
5256
5460
|
});
|
|
5257
5461
|
}
|
|
5258
5462
|
return removed;
|
|
@@ -5261,7 +5465,7 @@ function removeEmptyParentDirs(filePath, skillDirAbs) {
|
|
|
5261
5465
|
let dir = dirname3(filePath);
|
|
5262
5466
|
while (dir.startsWith(skillDirAbs) && dir !== skillDirAbs) {
|
|
5263
5467
|
try {
|
|
5264
|
-
if (
|
|
5468
|
+
if (readdirSync2(dir).length > 0)
|
|
5265
5469
|
return;
|
|
5266
5470
|
rmdirSync(dir);
|
|
5267
5471
|
} catch {
|
|
@@ -5275,10 +5479,10 @@ function findOrphanedManifests(projectRoot, currentProfile) {
|
|
|
5275
5479
|
for (const profile of Object.values(PROFILES)) {
|
|
5276
5480
|
if (profile.toolId === currentProfile.toolId)
|
|
5277
5481
|
continue;
|
|
5278
|
-
const manifestPath =
|
|
5279
|
-
if (!
|
|
5482
|
+
const manifestPath = join6(projectRoot, profile.manifestDir, MANIFEST_FILENAME);
|
|
5483
|
+
if (!existsSync6(manifestPath))
|
|
5280
5484
|
continue;
|
|
5281
|
-
const manifest = readManifest(
|
|
5485
|
+
const manifest = readManifest(join6(projectRoot, profile.manifestDir));
|
|
5282
5486
|
if (!manifest)
|
|
5283
5487
|
continue;
|
|
5284
5488
|
out.push({ profile, manifestPath, manifest });
|
|
@@ -5357,8 +5561,8 @@ function migrateArtifacts(projectRoot, orphan, newProfile) {
|
|
|
5357
5561
|
summary.skipped.push({ path: skillName, reason: "unsafe name in manifest" });
|
|
5358
5562
|
continue;
|
|
5359
5563
|
}
|
|
5360
|
-
const fromSkillDir =
|
|
5361
|
-
const toSkillDir =
|
|
5564
|
+
const fromSkillDir = join7(projectRoot, oldProfile.skillDir(skillName));
|
|
5565
|
+
const toSkillDir = join7(projectRoot, newProfile.skillDir(skillName));
|
|
5362
5566
|
let anyMoved = false;
|
|
5363
5567
|
for (const relPath of entry.files) {
|
|
5364
5568
|
if (!isSafeSkillFilePath(relPath)) {
|
|
@@ -5368,8 +5572,8 @@ function migrateArtifacts(projectRoot, orphan, newProfile) {
|
|
|
5368
5572
|
});
|
|
5369
5573
|
continue;
|
|
5370
5574
|
}
|
|
5371
|
-
const from =
|
|
5372
|
-
const to =
|
|
5575
|
+
const from = join7(fromSkillDir, relPath);
|
|
5576
|
+
const to = join7(toSkillDir, relPath);
|
|
5373
5577
|
if (moveIfSafe(from, to, summary.skipped))
|
|
5374
5578
|
anyMoved = true;
|
|
5375
5579
|
}
|
|
@@ -5383,8 +5587,8 @@ function migrateArtifacts(projectRoot, orphan, newProfile) {
|
|
|
5383
5587
|
continue;
|
|
5384
5588
|
}
|
|
5385
5589
|
const promptName = promptFile.replace(/\.md$/, "");
|
|
5386
|
-
const from =
|
|
5387
|
-
const to =
|
|
5590
|
+
const from = join7(projectRoot, oldProfile.promptPath(promptName));
|
|
5591
|
+
const to = join7(projectRoot, newProfile.promptPath(promptName));
|
|
5388
5592
|
if (moveIfSafe(from, to, summary.skipped)) {
|
|
5389
5593
|
summary.movedOrRemoved.prompts.push(promptFile);
|
|
5390
5594
|
}
|
|
@@ -5394,15 +5598,15 @@ function migrateArtifacts(projectRoot, orphan, newProfile) {
|
|
|
5394
5598
|
summary.skipped.push({ path: configFile, reason: "unsafe name in manifest" });
|
|
5395
5599
|
continue;
|
|
5396
5600
|
}
|
|
5397
|
-
const from =
|
|
5398
|
-
const to =
|
|
5601
|
+
const from = join7(projectRoot, oldProfile.configPath(configFile));
|
|
5602
|
+
const to = join7(projectRoot, newProfile.configPath(configFile));
|
|
5399
5603
|
if (moveIfSafe(from, to, summary.skipped)) {
|
|
5400
5604
|
summary.movedOrRemoved.configs.push(configFile);
|
|
5401
5605
|
}
|
|
5402
5606
|
}
|
|
5403
5607
|
summary.sentinelStripped = stripSentinelFromRulesFile(projectRoot, oldProfile);
|
|
5404
|
-
|
|
5405
|
-
tryRemoveEmptyDir(
|
|
5608
|
+
rmSync5(orphan.manifestPath, { force: true });
|
|
5609
|
+
tryRemoveEmptyDir(join7(projectRoot, oldProfile.manifestDir));
|
|
5406
5610
|
return summary;
|
|
5407
5611
|
}
|
|
5408
5612
|
function deleteArtifacts(projectRoot, orphan) {
|
|
@@ -5419,7 +5623,7 @@ function deleteArtifacts(projectRoot, orphan) {
|
|
|
5419
5623
|
summary.skipped.push({ path: skillName, reason: "unsafe name in manifest" });
|
|
5420
5624
|
continue;
|
|
5421
5625
|
}
|
|
5422
|
-
const skillDir =
|
|
5626
|
+
const skillDir = join7(projectRoot, oldProfile.skillDir(skillName));
|
|
5423
5627
|
for (const relPath of entry.files) {
|
|
5424
5628
|
if (!isSafeSkillFilePath(relPath)) {
|
|
5425
5629
|
summary.skipped.push({
|
|
@@ -5428,9 +5632,9 @@ function deleteArtifacts(projectRoot, orphan) {
|
|
|
5428
5632
|
});
|
|
5429
5633
|
continue;
|
|
5430
5634
|
}
|
|
5431
|
-
|
|
5635
|
+
rmSync5(join7(skillDir, relPath), { force: true });
|
|
5432
5636
|
}
|
|
5433
|
-
|
|
5637
|
+
rmSync5(skillDir, { recursive: true, force: true });
|
|
5434
5638
|
summary.movedOrRemoved.skills.push(skillName);
|
|
5435
5639
|
}
|
|
5436
5640
|
for (const promptFile of orphan.manifest.files.prompts) {
|
|
@@ -5439,7 +5643,7 @@ function deleteArtifacts(projectRoot, orphan) {
|
|
|
5439
5643
|
continue;
|
|
5440
5644
|
}
|
|
5441
5645
|
const promptName = promptFile.replace(/\.md$/, "");
|
|
5442
|
-
|
|
5646
|
+
rmSync5(join7(projectRoot, oldProfile.promptPath(promptName)), { force: true });
|
|
5443
5647
|
summary.movedOrRemoved.prompts.push(promptFile);
|
|
5444
5648
|
}
|
|
5445
5649
|
for (const configFile of orphan.manifest.files.configs) {
|
|
@@ -5447,27 +5651,27 @@ function deleteArtifacts(projectRoot, orphan) {
|
|
|
5447
5651
|
summary.skipped.push({ path: configFile, reason: "unsafe name in manifest" });
|
|
5448
5652
|
continue;
|
|
5449
5653
|
}
|
|
5450
|
-
|
|
5654
|
+
rmSync5(join7(projectRoot, oldProfile.configPath(configFile)), { force: true });
|
|
5451
5655
|
summary.movedOrRemoved.configs.push(configFile);
|
|
5452
5656
|
}
|
|
5453
5657
|
summary.sentinelStripped = stripSentinelFromRulesFile(projectRoot, oldProfile);
|
|
5454
|
-
|
|
5455
|
-
const manifestDirAbs =
|
|
5456
|
-
tryRemoveEmptyDir(
|
|
5457
|
-
tryRemoveEmptyDir(
|
|
5458
|
-
tryRemoveEmptyDir(
|
|
5658
|
+
rmSync5(orphan.manifestPath, { force: true });
|
|
5659
|
+
const manifestDirAbs = join7(projectRoot, oldProfile.manifestDir);
|
|
5660
|
+
tryRemoveEmptyDir(join7(manifestDirAbs, "skills"));
|
|
5661
|
+
tryRemoveEmptyDir(join7(manifestDirAbs, "prompts"));
|
|
5662
|
+
tryRemoveEmptyDir(join7(manifestDirAbs, "config-templates"));
|
|
5459
5663
|
tryRemoveEmptyDir(manifestDirAbs);
|
|
5460
5664
|
return summary;
|
|
5461
5665
|
}
|
|
5462
5666
|
function moveIfSafe(from, to, skipped) {
|
|
5463
|
-
if (!
|
|
5667
|
+
if (!existsSync7(from)) {
|
|
5464
5668
|
return false;
|
|
5465
5669
|
}
|
|
5466
5670
|
if (lstatSync(from).isSymbolicLink()) {
|
|
5467
5671
|
skipped.push({ path: from, reason: "source is a symlink" });
|
|
5468
5672
|
return false;
|
|
5469
5673
|
}
|
|
5470
|
-
if (
|
|
5674
|
+
if (existsSync7(to)) {
|
|
5471
5675
|
if (!filesAreIdentical(from, to)) {
|
|
5472
5676
|
skipped.push({
|
|
5473
5677
|
path: to,
|
|
@@ -5475,22 +5679,22 @@ function moveIfSafe(from, to, skipped) {
|
|
|
5475
5679
|
});
|
|
5476
5680
|
return false;
|
|
5477
5681
|
}
|
|
5478
|
-
|
|
5682
|
+
rmSync5(from, { force: true });
|
|
5479
5683
|
return true;
|
|
5480
5684
|
}
|
|
5481
|
-
|
|
5685
|
+
mkdirSync6(dirname4(to), { recursive: true });
|
|
5482
5686
|
try {
|
|
5483
5687
|
renameSync3(from, to);
|
|
5484
5688
|
} catch (err) {
|
|
5485
5689
|
const code = err?.code;
|
|
5486
5690
|
if (code !== "EXDEV")
|
|
5487
5691
|
throw err;
|
|
5488
|
-
const bytes =
|
|
5692
|
+
const bytes = readFileSync6(from);
|
|
5489
5693
|
const tmp = `${to}.tmp`;
|
|
5490
|
-
|
|
5694
|
+
writeFileSync5(tmp, bytes);
|
|
5491
5695
|
renameSync3(tmp, to);
|
|
5492
5696
|
try {
|
|
5493
|
-
|
|
5697
|
+
rmSync5(from, { force: true });
|
|
5494
5698
|
} catch {
|
|
5495
5699
|
skipped.push({
|
|
5496
5700
|
path: from,
|
|
@@ -5502,8 +5706,8 @@ function moveIfSafe(from, to, skipped) {
|
|
|
5502
5706
|
return true;
|
|
5503
5707
|
}
|
|
5504
5708
|
function stripSentinelFromRulesFile(projectRoot, oldProfile) {
|
|
5505
|
-
const rulesPath =
|
|
5506
|
-
if (!
|
|
5709
|
+
const rulesPath = join7(projectRoot, oldProfile.rulesFile);
|
|
5710
|
+
if (!existsSync7(rulesPath))
|
|
5507
5711
|
return false;
|
|
5508
5712
|
const existing = readFileOrNull(rulesPath);
|
|
5509
5713
|
if (existing === null)
|
|
@@ -5512,9 +5716,9 @@ function stripSentinelFromRulesFile(projectRoot, oldProfile) {
|
|
|
5512
5716
|
if (!removed)
|
|
5513
5717
|
return false;
|
|
5514
5718
|
if (content.trim().length === 0) {
|
|
5515
|
-
|
|
5719
|
+
rmSync5(rulesPath, { force: true });
|
|
5516
5720
|
} else {
|
|
5517
|
-
|
|
5721
|
+
writeFileSync5(rulesPath, content);
|
|
5518
5722
|
}
|
|
5519
5723
|
return true;
|
|
5520
5724
|
}
|
|
@@ -5524,13 +5728,13 @@ function filesAreIdentical(a2, b2) {
|
|
|
5524
5728
|
const bStat = statSync(b2);
|
|
5525
5729
|
if (aStat.size !== bStat.size)
|
|
5526
5730
|
return false;
|
|
5527
|
-
return
|
|
5731
|
+
return readFileSync6(a2).equals(readFileSync6(b2));
|
|
5528
5732
|
} catch {
|
|
5529
5733
|
return false;
|
|
5530
5734
|
}
|
|
5531
5735
|
}
|
|
5532
5736
|
function tryRemoveEmptyDir(dir) {
|
|
5533
|
-
if (!
|
|
5737
|
+
if (!existsSync7(dir))
|
|
5534
5738
|
return;
|
|
5535
5739
|
try {
|
|
5536
5740
|
rmdirSync2(dir);
|
|
@@ -5737,7 +5941,7 @@ async function runGet(ctx, ref, options) {
|
|
|
5737
5941
|
const isFiltered = options.type !== undefined;
|
|
5738
5942
|
const bundle = filterBundle(ctx, result.data, options);
|
|
5739
5943
|
const isTTY = process.stdout.isTTY === true;
|
|
5740
|
-
const prevManifest = readManifest(
|
|
5944
|
+
const prevManifest = readManifest(join8(process.cwd(), profile.manifestDir));
|
|
5741
5945
|
if (prevManifest && prevManifest.manifestVersion === 2) {
|
|
5742
5946
|
showUpgradeNotice(isTTY);
|
|
5743
5947
|
}
|
|
@@ -6107,7 +6311,7 @@ function renderModuleDetail(ctx, module) {
|
|
|
6107
6311
|
}
|
|
6108
6312
|
|
|
6109
6313
|
// src/commands/sync.ts
|
|
6110
|
-
import { join as
|
|
6314
|
+
import { join as join9 } from "node:path";
|
|
6111
6315
|
var DEFAULT_COURSE4 = "10xdevs3";
|
|
6112
6316
|
var SUPPORTED_LANGS = ["en", "pl"];
|
|
6113
6317
|
function registerSyncCommand(cli) {
|
|
@@ -6153,7 +6357,7 @@ async function runSync(ctx, options) {
|
|
|
6153
6357
|
}
|
|
6154
6358
|
return true;
|
|
6155
6359
|
});
|
|
6156
|
-
const manifest = readManifest(
|
|
6360
|
+
const manifest = readManifest(join9(process.cwd(), profile.manifestDir));
|
|
6157
6361
|
const manifestLessonIds = new Set(manifest?.lessons ? Object.keys(manifest.lessons) : []);
|
|
6158
6362
|
let targets = unlocked;
|
|
6159
6363
|
if (moduleFilter !== undefined)
|
|
@@ -6518,6 +6722,7 @@ registerListCommand(cli);
|
|
|
6518
6722
|
registerSyncCommand(cli);
|
|
6519
6723
|
registerDoctorCommand(cli);
|
|
6520
6724
|
registerBenchCommand(cli);
|
|
6725
|
+
registerBenchKitCommand(cli);
|
|
6521
6726
|
cli.help();
|
|
6522
6727
|
cli.version(package_default.version);
|
|
6523
6728
|
try {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
---
|
|
2
2
|
name: 10x-cli-guide
|
|
3
|
-
description: "Invoke this skill when the user asks how to USE the 10x-cli day-to-day — fetching lessons, listing modules, switching AI tools, troubleshooting errors, understanding where artifacts land, or working on a specific OS (Windows, Linux, macOS). Covers commands (get, list, doctor, auth --status/--logout), tool profiles, artifact locations, common errors, and platform-specific tips. Excludes: first-time installation and onboarding (use 10x-cli-setup instead), developing or contributing to 10x-cli source code, and general programming help."
|
|
3
|
+
description: "Invoke this skill when the user asks how to USE the 10x-cli day-to-day — fetching lessons, listing modules, switching AI tools, troubleshooting errors, understanding where artifacts land, checking the 10xBench model leaderboard, or working on a specific OS (Windows, Linux, macOS). Covers commands (get, list, doctor, auth --status/--logout, bench), tool profiles, artifact locations, common errors, and platform-specific tips. Excludes: first-time installation and onboarding (use 10x-cli-setup instead), developing or contributing to 10x-cli source code, and general programming help."
|
|
4
4
|
---
|
|
5
5
|
|
|
6
6
|
# 10x-cli Daily Usage Guide
|
|
@@ -117,6 +117,22 @@ Exit code 78 means at least one check failed.
|
|
|
117
117
|
|
|
118
118
|
Sessions refresh transparently — if a token is near expiry, the next command refreshes it automatically. You only need to re-auth manually if the session has fully expired.
|
|
119
119
|
|
|
120
|
+
### `10x bench` — 10xBench model leaderboard
|
|
121
|
+
|
|
122
|
+
```bash
|
|
123
|
+
10x bench # Top 10 AI models with color-coded score bars
|
|
124
|
+
10x bench --limit 5 # Just the top 5
|
|
125
|
+
10x bench --json # Machine-readable envelope (also when piped)
|
|
126
|
+
```
|
|
127
|
+
|
|
128
|
+
Shows the live leaderboard from [10xbench.ai](https://10xbench.ai) — AI models
|
|
129
|
+
benchmarked on vibe-coding the Przeprogramowani.pl website. Each row has the
|
|
130
|
+
average score, number of runs, cost per run where measured, and the agent
|
|
131
|
+
harness used. **No authentication needed** — this works before `10x auth`.
|
|
132
|
+
Data updates when new benchmark results are published; superseded model
|
|
133
|
+
versions are already filtered out upstream. If it reports the leaderboard as
|
|
134
|
+
unavailable, the site may be mid-deploy — retry in a few minutes.
|
|
135
|
+
|
|
120
136
|
---
|
|
121
137
|
|
|
122
138
|
## Switching Tools
|