@przeprogramowani/10x-cli 0.4.0 → 0.6.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 +20 -0
- package/dist/index.mjs +562 -137
- package/package.json +2 -1
- package/skills/10x-cli-setup/SKILL.md +53 -0
package/README.md
CHANGED
|
@@ -13,6 +13,26 @@ npm install -g @przeprogramowani/10x-cli
|
|
|
13
13
|
# https://github.com/przeprogramowani/10x-cli/releases
|
|
14
14
|
```
|
|
15
15
|
|
|
16
|
+
## Agentic Installation
|
|
17
|
+
|
|
18
|
+
Let your AI coding agent handle the setup. This repo ships a [`10x-cli-setup`](skills/10x-cli-setup/SKILL.md) skill that walks your agent through installing, authenticating, and configuring the CLI — all driven by the latest README.
|
|
19
|
+
|
|
20
|
+
Install the skill with [skills.sh](https://skills.sh):
|
|
21
|
+
|
|
22
|
+
```bash
|
|
23
|
+
# Add the skill to your current project (symlinked)
|
|
24
|
+
npx skills add przeprogramowani/10x-cli
|
|
25
|
+
|
|
26
|
+
# Or install globally so it's available in every project
|
|
27
|
+
npx skills add przeprogramowani/10x-cli -g
|
|
28
|
+
|
|
29
|
+
# Target a specific agent
|
|
30
|
+
npx skills add przeprogramowani/10x-cli -a claude-code
|
|
31
|
+
npx skills add przeprogramowani/10x-cli -a cursor
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
Once installed, just tell your agent to **set up 10x-cli** and it will pick up the skill automatically.
|
|
35
|
+
|
|
16
36
|
## Quick Start
|
|
17
37
|
|
|
18
38
|
```bash
|
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: "0.
|
|
2274
|
+
version: "0.6.0",
|
|
2275
2275
|
description: "Open-source CLI for 10xDevs course content",
|
|
2276
2276
|
repository: {
|
|
2277
2277
|
type: "git",
|
|
@@ -2287,6 +2287,7 @@ var package_default = {
|
|
|
2287
2287
|
},
|
|
2288
2288
|
files: [
|
|
2289
2289
|
"dist",
|
|
2290
|
+
"skills",
|
|
2290
2291
|
"README.md",
|
|
2291
2292
|
"LICENSE"
|
|
2292
2293
|
],
|
|
@@ -2323,6 +2324,9 @@ var package_default = {
|
|
|
2323
2324
|
}
|
|
2324
2325
|
};
|
|
2325
2326
|
|
|
2327
|
+
// node_modules/@clack/prompts/dist/index.mjs
|
|
2328
|
+
import { stripVTControlCharacters as T2 } from "node:util";
|
|
2329
|
+
|
|
2326
2330
|
// node_modules/@clack/core/dist/index.mjs
|
|
2327
2331
|
var import_sisteransi = __toESM(require_src(), 1);
|
|
2328
2332
|
var import_picocolors = __toESM(require_picocolors(), 1);
|
|
@@ -2832,6 +2836,21 @@ ${import_picocolors2.default.cyan(m2)}
|
|
|
2832
2836
|
}
|
|
2833
2837
|
} }).prompt();
|
|
2834
2838
|
};
|
|
2839
|
+
var ye = (s = "", n = "") => {
|
|
2840
|
+
const t = `
|
|
2841
|
+
${s}
|
|
2842
|
+
`.split(`
|
|
2843
|
+
`), i = T2(n).length, r2 = Math.max(t.reduce((o, l2) => {
|
|
2844
|
+
const $2 = T2(l2);
|
|
2845
|
+
return $2.length > o ? $2.length : o;
|
|
2846
|
+
}, 0), i) + 2, c2 = t.map((o) => `${import_picocolors2.default.gray(a)} ${import_picocolors2.default.dim(o)}${" ".repeat(r2 - T2(o).length)}${import_picocolors2.default.gray(a)}`).join(`
|
|
2847
|
+
`);
|
|
2848
|
+
process.stdout.write(`${import_picocolors2.default.gray(a)}
|
|
2849
|
+
${import_picocolors2.default.green(S2)} ${import_picocolors2.default.reset(n)} ${import_picocolors2.default.gray(N2.repeat(Math.max(r2 - i - 1, 1)) + re)}
|
|
2850
|
+
${c2}
|
|
2851
|
+
${import_picocolors2.default.gray(ie + N2.repeat(r2 + 2) + ne)}
|
|
2852
|
+
`);
|
|
2853
|
+
};
|
|
2835
2854
|
var ve = (s = "") => {
|
|
2836
2855
|
process.stdout.write(`${import_picocolors2.default.gray(m2)} ${import_picocolors2.default.red(s)}
|
|
2837
2856
|
|
|
@@ -2926,6 +2945,20 @@ function readAuth() {
|
|
|
2926
2945
|
return null;
|
|
2927
2946
|
}
|
|
2928
2947
|
}
|
|
2948
|
+
function writeJsonAtomic(path, data, opts = {}) {
|
|
2949
|
+
const tmp = `${path}.tmp`;
|
|
2950
|
+
rmSync(tmp, { force: true });
|
|
2951
|
+
const body = `${JSON.stringify(data, null, 2)}
|
|
2952
|
+
`;
|
|
2953
|
+
const writeOpts = opts.mode !== undefined ? { mode: opts.mode } : undefined;
|
|
2954
|
+
writeFileSync(tmp, body, writeOpts);
|
|
2955
|
+
if (opts.mode !== undefined && process.platform !== "win32") {
|
|
2956
|
+
try {
|
|
2957
|
+
chmodSync(tmp, opts.mode);
|
|
2958
|
+
} catch {}
|
|
2959
|
+
}
|
|
2960
|
+
renameSync(tmp, path);
|
|
2961
|
+
}
|
|
2929
2962
|
function saveAuth(data) {
|
|
2930
2963
|
const dir = configDir();
|
|
2931
2964
|
if (!existsSync(dir)) {
|
|
@@ -2936,17 +2969,7 @@ function saveAuth(data) {
|
|
|
2936
2969
|
chmodSync(dir, 448);
|
|
2937
2970
|
} catch {}
|
|
2938
2971
|
}
|
|
2939
|
-
|
|
2940
|
-
const tmp = `${file}.tmp`;
|
|
2941
|
-
rmSync(tmp, { force: true });
|
|
2942
|
-
writeFileSync(tmp, `${JSON.stringify(data, null, 2)}
|
|
2943
|
-
`, { mode: 384 });
|
|
2944
|
-
if (process.platform !== "win32") {
|
|
2945
|
-
try {
|
|
2946
|
-
chmodSync(tmp, 384);
|
|
2947
|
-
} catch {}
|
|
2948
|
-
}
|
|
2949
|
-
renameSync(tmp, file);
|
|
2972
|
+
writeJsonAtomic(authFilePath(), data, { mode: 384 });
|
|
2950
2973
|
}
|
|
2951
2974
|
function deleteAuth() {
|
|
2952
2975
|
const file = authFilePath();
|
|
@@ -2966,6 +2989,9 @@ function readToolConfig() {
|
|
|
2966
2989
|
const parsed = JSON.parse(raw);
|
|
2967
2990
|
if (typeof parsed.tool !== "string")
|
|
2968
2991
|
return null;
|
|
2992
|
+
if (parsed.acknowledgedOrphans !== undefined && (!Array.isArray(parsed.acknowledgedOrphans) || !parsed.acknowledgedOrphans.every((x2) => typeof x2 === "string"))) {
|
|
2993
|
+
delete parsed.acknowledgedOrphans;
|
|
2994
|
+
}
|
|
2969
2995
|
return parsed;
|
|
2970
2996
|
} catch {
|
|
2971
2997
|
return null;
|
|
@@ -2976,9 +3002,7 @@ function saveToolConfig(config) {
|
|
|
2976
3002
|
if (!existsSync(dir)) {
|
|
2977
3003
|
mkdirSync(dir, { recursive: true, mode: 448 });
|
|
2978
3004
|
}
|
|
2979
|
-
|
|
2980
|
-
writeFileSync(file, `${JSON.stringify(config, null, 2)}
|
|
2981
|
-
`);
|
|
3005
|
+
writeJsonAtomic(toolConfigPath(), config);
|
|
2982
3006
|
}
|
|
2983
3007
|
|
|
2984
3008
|
// src/lib/api-client.ts
|
|
@@ -3916,7 +3940,71 @@ function parseModuleRef(raw) {
|
|
|
3916
3940
|
return module;
|
|
3917
3941
|
}
|
|
3918
3942
|
|
|
3943
|
+
// src/lib/tool-detect.ts
|
|
3944
|
+
import { existsSync as existsSync4 } from "node:fs";
|
|
3945
|
+
import { join as join4 } from "node:path";
|
|
3946
|
+
|
|
3947
|
+
// src/lib/manifest.ts
|
|
3948
|
+
import { existsSync as existsSync3, mkdirSync as mkdirSync3, readFileSync as readFileSync2, writeFileSync as writeFileSync2 } from "node:fs";
|
|
3949
|
+
import { dirname as dirname2, join as join3 } from "node:path";
|
|
3950
|
+
var MANIFEST_FILENAME = ".10x-cli-manifest.json";
|
|
3951
|
+
var CLI_PACKAGE_NAME = "@przeprogramowani/10x-cli";
|
|
3952
|
+
function readManifest(dir) {
|
|
3953
|
+
const manifestPath = join3(dir, MANIFEST_FILENAME);
|
|
3954
|
+
if (!existsSync3(manifestPath))
|
|
3955
|
+
return null;
|
|
3956
|
+
let raw;
|
|
3957
|
+
try {
|
|
3958
|
+
raw = readFileSync2(manifestPath, "utf8");
|
|
3959
|
+
} catch {
|
|
3960
|
+
return null;
|
|
3961
|
+
}
|
|
3962
|
+
let parsed;
|
|
3963
|
+
try {
|
|
3964
|
+
parsed = JSON.parse(raw);
|
|
3965
|
+
} catch {
|
|
3966
|
+
return null;
|
|
3967
|
+
}
|
|
3968
|
+
if (!isManifest(parsed))
|
|
3969
|
+
return null;
|
|
3970
|
+
return parsed;
|
|
3971
|
+
}
|
|
3972
|
+
function writeManifest(dir, manifest) {
|
|
3973
|
+
if (!existsSync3(dir)) {
|
|
3974
|
+
mkdirSync3(dir, { recursive: true });
|
|
3975
|
+
}
|
|
3976
|
+
const manifestPath = join3(dir, MANIFEST_FILENAME);
|
|
3977
|
+
mkdirSync3(dirname2(manifestPath), { recursive: true });
|
|
3978
|
+
writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
3979
|
+
`);
|
|
3980
|
+
}
|
|
3981
|
+
function isManifest(value) {
|
|
3982
|
+
if (typeof value !== "object" || value === null)
|
|
3983
|
+
return false;
|
|
3984
|
+
const v2 = value;
|
|
3985
|
+
if (v2["package"] !== CLI_PACKAGE_NAME)
|
|
3986
|
+
return false;
|
|
3987
|
+
if (typeof v2["version"] !== "string")
|
|
3988
|
+
return false;
|
|
3989
|
+
if (typeof v2["lastApplied"] !== "string")
|
|
3990
|
+
return false;
|
|
3991
|
+
if (typeof v2["lessonId"] !== "string")
|
|
3992
|
+
return false;
|
|
3993
|
+
if (typeof v2["course"] !== "string")
|
|
3994
|
+
return false;
|
|
3995
|
+
const files = v2["files"];
|
|
3996
|
+
if (typeof files !== "object" || files === null)
|
|
3997
|
+
return false;
|
|
3998
|
+
const f2 = files;
|
|
3999
|
+
return isStringArray(f2["skills"]) && isStringArray(f2["prompts"]) && isStringArray(f2["configs"]);
|
|
4000
|
+
}
|
|
4001
|
+
function isStringArray(value) {
|
|
4002
|
+
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
4003
|
+
}
|
|
4004
|
+
|
|
3919
4005
|
// src/lib/tool-profile.ts
|
|
4006
|
+
var SENTINEL_BEGIN = "<!-- BEGIN @przeprogramowani/10x-cli -->";
|
|
4007
|
+
var SENTINEL_END = "<!-- END @przeprogramowani/10x-cli -->";
|
|
3920
4008
|
var PROFILES = {
|
|
3921
4009
|
"claude-code": {
|
|
3922
4010
|
toolId: "claude-code",
|
|
@@ -3926,8 +4014,8 @@ var PROFILES = {
|
|
|
3926
4014
|
configPath: (n) => `.claude/config-templates/${n}`,
|
|
3927
4015
|
rulesFile: "CLAUDE.md",
|
|
3928
4016
|
manifestDir: ".claude",
|
|
3929
|
-
sentinelBegin:
|
|
3930
|
-
sentinelEnd:
|
|
4017
|
+
sentinelBegin: SENTINEL_BEGIN,
|
|
4018
|
+
sentinelEnd: SENTINEL_END
|
|
3931
4019
|
},
|
|
3932
4020
|
cursor: {
|
|
3933
4021
|
toolId: "cursor",
|
|
@@ -3937,8 +4025,8 @@ var PROFILES = {
|
|
|
3937
4025
|
configPath: (n) => `.cursor/config-templates/${n}`,
|
|
3938
4026
|
rulesFile: ".cursor/rules/10x-course.mdc",
|
|
3939
4027
|
manifestDir: ".cursor",
|
|
3940
|
-
sentinelBegin:
|
|
3941
|
-
sentinelEnd:
|
|
4028
|
+
sentinelBegin: SENTINEL_BEGIN,
|
|
4029
|
+
sentinelEnd: SENTINEL_END
|
|
3942
4030
|
},
|
|
3943
4031
|
copilot: {
|
|
3944
4032
|
toolId: "copilot",
|
|
@@ -3948,8 +4036,8 @@ var PROFILES = {
|
|
|
3948
4036
|
configPath: (n) => `.github/config-templates/${n}`,
|
|
3949
4037
|
rulesFile: ".github/copilot-instructions.md",
|
|
3950
4038
|
manifestDir: ".github",
|
|
3951
|
-
sentinelBegin:
|
|
3952
|
-
sentinelEnd:
|
|
4039
|
+
sentinelBegin: SENTINEL_BEGIN,
|
|
4040
|
+
sentinelEnd: SENTINEL_END
|
|
3953
4041
|
},
|
|
3954
4042
|
codex: {
|
|
3955
4043
|
toolId: "codex",
|
|
@@ -3959,8 +4047,8 @@ var PROFILES = {
|
|
|
3959
4047
|
configPath: (n) => `.agents/config-templates/${n}`,
|
|
3960
4048
|
rulesFile: "AGENTS.md",
|
|
3961
4049
|
manifestDir: ".agents",
|
|
3962
|
-
sentinelBegin:
|
|
3963
|
-
sentinelEnd:
|
|
4050
|
+
sentinelBegin: SENTINEL_BEGIN,
|
|
4051
|
+
sentinelEnd: SENTINEL_END
|
|
3964
4052
|
},
|
|
3965
4053
|
generic: {
|
|
3966
4054
|
toolId: "generic",
|
|
@@ -3970,116 +4058,143 @@ var PROFILES = {
|
|
|
3970
4058
|
configPath: (n) => `.ai/config-templates/${n}`,
|
|
3971
4059
|
rulesFile: "AGENTS.md",
|
|
3972
4060
|
manifestDir: ".ai",
|
|
3973
|
-
sentinelBegin:
|
|
3974
|
-
sentinelEnd:
|
|
4061
|
+
sentinelBegin: SENTINEL_BEGIN,
|
|
4062
|
+
sentinelEnd: SENTINEL_END
|
|
3975
4063
|
}
|
|
3976
4064
|
};
|
|
3977
4065
|
var DEFAULT_TOOL = "claude-code";
|
|
3978
4066
|
|
|
3979
|
-
// src/lib/tool-
|
|
3980
|
-
|
|
3981
|
-
|
|
3982
|
-
|
|
3983
|
-
|
|
3984
|
-
|
|
3985
|
-
|
|
3986
|
-
|
|
3987
|
-
|
|
3988
|
-
|
|
3989
|
-
if (
|
|
3990
|
-
|
|
4067
|
+
// src/lib/tool-detect.ts
|
|
4068
|
+
function detectTools(projectRoot) {
|
|
4069
|
+
const hit = (rel) => existsSync4(join4(projectRoot, rel));
|
|
4070
|
+
const signals = [];
|
|
4071
|
+
if (hit(".claude/" + MANIFEST_FILENAME)) {
|
|
4072
|
+
signals.push({
|
|
4073
|
+
profileId: "claude-code",
|
|
4074
|
+
confidence: "strong",
|
|
4075
|
+
reason: ".claude/.10x-cli-manifest.json"
|
|
4076
|
+
});
|
|
4077
|
+
} else if (hit(".claude")) {
|
|
4078
|
+
signals.push({ profileId: "claude-code", confidence: "medium", reason: ".claude/ directory" });
|
|
4079
|
+
} else if (hit("CLAUDE.md")) {
|
|
4080
|
+
signals.push({ profileId: "claude-code", confidence: "weak", reason: "CLAUDE.md" });
|
|
4081
|
+
}
|
|
4082
|
+
if (hit(".cursor/" + MANIFEST_FILENAME)) {
|
|
4083
|
+
signals.push({
|
|
4084
|
+
profileId: "cursor",
|
|
4085
|
+
confidence: "strong",
|
|
4086
|
+
reason: ".cursor/.10x-cli-manifest.json"
|
|
4087
|
+
});
|
|
4088
|
+
} else if (hit(".cursor/rules")) {
|
|
4089
|
+
signals.push({ profileId: "cursor", confidence: "strong", reason: ".cursor/rules/" });
|
|
4090
|
+
} else if (hit(".cursor")) {
|
|
4091
|
+
signals.push({ profileId: "cursor", confidence: "medium", reason: ".cursor/ directory" });
|
|
4092
|
+
}
|
|
4093
|
+
if (hit(".github/" + MANIFEST_FILENAME)) {
|
|
4094
|
+
signals.push({
|
|
4095
|
+
profileId: "copilot",
|
|
4096
|
+
confidence: "strong",
|
|
4097
|
+
reason: ".github/.10x-cli-manifest.json"
|
|
4098
|
+
});
|
|
4099
|
+
} else if (hit(".github/copilot-instructions.md")) {
|
|
4100
|
+
signals.push({
|
|
4101
|
+
profileId: "copilot",
|
|
4102
|
+
confidence: "strong",
|
|
4103
|
+
reason: ".github/copilot-instructions.md"
|
|
4104
|
+
});
|
|
4105
|
+
} else if (hit(".github/skills") || hit(".github/prompts")) {
|
|
4106
|
+
signals.push({
|
|
4107
|
+
profileId: "copilot",
|
|
4108
|
+
confidence: "medium",
|
|
4109
|
+
reason: ".github/skills/ or .github/prompts/"
|
|
4110
|
+
});
|
|
3991
4111
|
}
|
|
3992
|
-
if (
|
|
3993
|
-
|
|
3994
|
-
|
|
3995
|
-
|
|
3996
|
-
|
|
3997
|
-
label: profile.displayName,
|
|
3998
|
-
hint: profile.toolId === DEFAULT_TOOL ? "default" : undefined
|
|
3999
|
-
})),
|
|
4000
|
-
initialValue: DEFAULT_TOOL
|
|
4112
|
+
if (hit(".agents/" + MANIFEST_FILENAME)) {
|
|
4113
|
+
signals.push({
|
|
4114
|
+
profileId: "codex",
|
|
4115
|
+
confidence: "strong",
|
|
4116
|
+
reason: ".agents/.10x-cli-manifest.json"
|
|
4001
4117
|
});
|
|
4002
|
-
|
|
4003
|
-
|
|
4004
|
-
|
|
4005
|
-
}
|
|
4006
|
-
|
|
4007
|
-
|
|
4118
|
+
} else if (hit(".agents")) {
|
|
4119
|
+
signals.push({ profileId: "codex", confidence: "strong", reason: ".agents/ directory" });
|
|
4120
|
+
} else if (hit("AGENTS.md") && !hit(".ai")) {
|
|
4121
|
+
signals.push({ profileId: "codex", confidence: "medium", reason: "AGENTS.md" });
|
|
4122
|
+
}
|
|
4123
|
+
if (hit(".ai/" + MANIFEST_FILENAME)) {
|
|
4124
|
+
signals.push({
|
|
4125
|
+
profileId: "generic",
|
|
4126
|
+
confidence: "strong",
|
|
4127
|
+
reason: ".ai/.10x-cli-manifest.json"
|
|
4128
|
+
});
|
|
4129
|
+
} else if (hit(".ai")) {
|
|
4130
|
+
signals.push({ profileId: "generic", confidence: "strong", reason: ".ai/ directory" });
|
|
4131
|
+
} else if (hit("AGENTS.md") && !hit(".agents")) {
|
|
4132
|
+
signals.push({ profileId: "generic", confidence: "weak", reason: "AGENTS.md (generic fallback)" });
|
|
4008
4133
|
}
|
|
4009
|
-
return
|
|
4134
|
+
return rankSignals(signals);
|
|
4135
|
+
}
|
|
4136
|
+
var CONFIDENCE_ORDER = { strong: 3, medium: 2, weak: 1 };
|
|
4137
|
+
var PROFILE_ORDER = ["claude-code", "cursor", "copilot", "codex", "generic"];
|
|
4138
|
+
function rankSignals(signals) {
|
|
4139
|
+
return [...signals].sort((a2, b2) => {
|
|
4140
|
+
const c2 = CONFIDENCE_ORDER[b2.confidence] - CONFIDENCE_ORDER[a2.confidence];
|
|
4141
|
+
if (c2 !== 0)
|
|
4142
|
+
return c2;
|
|
4143
|
+
return PROFILE_ORDER.indexOf(a2.profileId) - PROFILE_ORDER.indexOf(b2.profileId);
|
|
4144
|
+
});
|
|
4145
|
+
}
|
|
4146
|
+
function topDetectedProfile(signals) {
|
|
4147
|
+
const top = signals[0];
|
|
4148
|
+
if (!top)
|
|
4149
|
+
return null;
|
|
4150
|
+
return PROFILES[top.profileId] ?? null;
|
|
4010
4151
|
}
|
|
4011
4152
|
|
|
4012
|
-
// src/lib/
|
|
4153
|
+
// src/lib/tool-switch.ts
|
|
4013
4154
|
import {
|
|
4014
|
-
existsSync as
|
|
4015
|
-
|
|
4016
|
-
|
|
4017
|
-
|
|
4018
|
-
|
|
4155
|
+
existsSync as existsSync6,
|
|
4156
|
+
lstatSync,
|
|
4157
|
+
mkdirSync as mkdirSync5,
|
|
4158
|
+
readFileSync as readFileSync5,
|
|
4159
|
+
renameSync as renameSync2,
|
|
4160
|
+
rmdirSync,
|
|
4161
|
+
rmSync as rmSync3,
|
|
4162
|
+
statSync,
|
|
4163
|
+
writeFileSync as writeFileSync4
|
|
4019
4164
|
} from "node:fs";
|
|
4020
|
-
import { dirname as
|
|
4165
|
+
import { dirname as dirname4, join as join6 } from "node:path";
|
|
4021
4166
|
|
|
4022
|
-
// src/lib/
|
|
4023
|
-
import {
|
|
4024
|
-
|
|
4025
|
-
var MANIFEST_FILENAME = ".10x-cli-manifest.json";
|
|
4026
|
-
var CLI_PACKAGE_NAME = "@przeprogramowani/10x-cli";
|
|
4027
|
-
function readManifest(dir) {
|
|
4028
|
-
const manifestPath = join3(dir, MANIFEST_FILENAME);
|
|
4029
|
-
if (!existsSync3(manifestPath))
|
|
4030
|
-
return null;
|
|
4031
|
-
let raw;
|
|
4167
|
+
// src/lib/fs-utils.ts
|
|
4168
|
+
import { readFileSync as readFileSync3 } from "node:fs";
|
|
4169
|
+
function readFileOrNull(path) {
|
|
4032
4170
|
try {
|
|
4033
|
-
|
|
4171
|
+
return readFileSync3(path, "utf8");
|
|
4034
4172
|
} catch {
|
|
4035
4173
|
return null;
|
|
4036
4174
|
}
|
|
4037
|
-
let parsed;
|
|
4038
|
-
try {
|
|
4039
|
-
parsed = JSON.parse(raw);
|
|
4040
|
-
} catch {
|
|
4041
|
-
return null;
|
|
4042
|
-
}
|
|
4043
|
-
if (!isManifest(parsed))
|
|
4044
|
-
return null;
|
|
4045
|
-
return parsed;
|
|
4046
|
-
}
|
|
4047
|
-
function writeManifest(dir, manifest) {
|
|
4048
|
-
if (!existsSync3(dir)) {
|
|
4049
|
-
mkdirSync3(dir, { recursive: true });
|
|
4050
|
-
}
|
|
4051
|
-
const manifestPath = join3(dir, MANIFEST_FILENAME);
|
|
4052
|
-
mkdirSync3(dirname2(manifestPath), { recursive: true });
|
|
4053
|
-
writeFileSync2(manifestPath, `${JSON.stringify(manifest, null, 2)}
|
|
4054
|
-
`);
|
|
4055
|
-
}
|
|
4056
|
-
function isManifest(value) {
|
|
4057
|
-
if (typeof value !== "object" || value === null)
|
|
4058
|
-
return false;
|
|
4059
|
-
const v2 = value;
|
|
4060
|
-
if (v2["package"] !== CLI_PACKAGE_NAME)
|
|
4061
|
-
return false;
|
|
4062
|
-
if (typeof v2["version"] !== "string")
|
|
4063
|
-
return false;
|
|
4064
|
-
if (typeof v2["lastApplied"] !== "string")
|
|
4065
|
-
return false;
|
|
4066
|
-
if (typeof v2["lessonId"] !== "string")
|
|
4067
|
-
return false;
|
|
4068
|
-
if (typeof v2["course"] !== "string")
|
|
4069
|
-
return false;
|
|
4070
|
-
const files = v2["files"];
|
|
4071
|
-
if (typeof files !== "object" || files === null)
|
|
4072
|
-
return false;
|
|
4073
|
-
const f2 = files;
|
|
4074
|
-
return isStringArray(f2["skills"]) && isStringArray(f2["prompts"]) && isStringArray(f2["configs"]);
|
|
4075
|
-
}
|
|
4076
|
-
function isStringArray(value) {
|
|
4077
|
-
return Array.isArray(value) && value.every((item) => typeof item === "string");
|
|
4078
4175
|
}
|
|
4079
4176
|
|
|
4080
4177
|
// src/lib/sentinel-migration.ts
|
|
4081
4178
|
var OLD_BEGIN = "<!-- BEGIN @przeprogramowani/10x-toolkit -->";
|
|
4082
4179
|
var OLD_END = "<!-- END @przeprogramowani/10x-toolkit -->";
|
|
4180
|
+
function removeRulesBlockWithMarkers(existing, begin, end) {
|
|
4181
|
+
const beginIdx = existing.indexOf(begin);
|
|
4182
|
+
const endIdx = existing.indexOf(end);
|
|
4183
|
+
if (beginIdx === -1 || endIdx === -1 || endIdx < beginIdx) {
|
|
4184
|
+
return { content: existing, removed: false };
|
|
4185
|
+
}
|
|
4186
|
+
const beforeRaw = existing.slice(0, beginIdx);
|
|
4187
|
+
const afterRaw = existing.slice(endIdx + end.length);
|
|
4188
|
+
const before = beforeRaw.replace(/[\r\n]*$/, "");
|
|
4189
|
+
const after = afterRaw.replace(/^[\r\n]*/, "");
|
|
4190
|
+
const joiner = before.length > 0 && after.length > 0 ? `
|
|
4191
|
+
|
|
4192
|
+
` : "";
|
|
4193
|
+
const combined = before + joiner + after;
|
|
4194
|
+
const content = combined.length === 0 ? "" : combined.replace(/[\r\n]*$/, `
|
|
4195
|
+
`);
|
|
4196
|
+
return { content, removed: true };
|
|
4197
|
+
}
|
|
4083
4198
|
function applyRulesBlockWithMarkers(existingContent, rulesBody, beginMarker, endMarker) {
|
|
4084
4199
|
for (const marker of [OLD_BEGIN, OLD_END, beginMarker, endMarker]) {
|
|
4085
4200
|
if (rulesBody.includes(marker)) {
|
|
@@ -4127,6 +4242,14 @@ function stripBlock(content, begin, end, label, warnings) {
|
|
|
4127
4242
|
}
|
|
4128
4243
|
|
|
4129
4244
|
// src/lib/writer.ts
|
|
4245
|
+
import {
|
|
4246
|
+
existsSync as existsSync5,
|
|
4247
|
+
mkdirSync as mkdirSync4,
|
|
4248
|
+
readFileSync as readFileSync4,
|
|
4249
|
+
rmSync as rmSync2,
|
|
4250
|
+
writeFileSync as writeFileSync3
|
|
4251
|
+
} from "node:fs";
|
|
4252
|
+
import { dirname as dirname3, join as join5 } from "node:path";
|
|
4130
4253
|
var CLI_VERSION = package_default.version;
|
|
4131
4254
|
var DEFAULT_COURSE = "10xdevs3";
|
|
4132
4255
|
function applyBundle(bundle, projectRoot, options = {}) {
|
|
@@ -4134,7 +4257,7 @@ function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4134
4257
|
const partial = options.partial === true;
|
|
4135
4258
|
const course = options.course ?? DEFAULT_COURSE;
|
|
4136
4259
|
const profile = options.profile ?? PROFILES[DEFAULT_TOOL];
|
|
4137
|
-
const manifestDir =
|
|
4260
|
+
const manifestDir = join5(projectRoot, profile.manifestDir);
|
|
4138
4261
|
const prevManifest = readManifest(manifestDir);
|
|
4139
4262
|
for (const skill of bundle.skills)
|
|
4140
4263
|
assertSafeName(skill.name, "skill");
|
|
@@ -4143,7 +4266,7 @@ function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4143
4266
|
for (const config of bundle.configs)
|
|
4144
4267
|
assertSafeName(config.name, "config");
|
|
4145
4268
|
const skills = bundle.skills.map((skill) => {
|
|
4146
|
-
const target =
|
|
4269
|
+
const target = join5(projectRoot, profile.skillPath(skill.name));
|
|
4147
4270
|
const action = computeFileAction(target, skill.content);
|
|
4148
4271
|
if (!dryRun && action !== "unchanged") {
|
|
4149
4272
|
writeFileAt(target, skill.content);
|
|
@@ -4151,14 +4274,14 @@ function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4151
4274
|
return { name: skill.name, path: target, action };
|
|
4152
4275
|
});
|
|
4153
4276
|
const prompts = bundle.prompts.map((prompt) => {
|
|
4154
|
-
const target =
|
|
4277
|
+
const target = join5(projectRoot, profile.promptPath(prompt.name));
|
|
4155
4278
|
const action = computeFileAction(target, prompt.content);
|
|
4156
4279
|
if (!dryRun && action !== "unchanged") {
|
|
4157
4280
|
writeFileAt(target, prompt.content);
|
|
4158
4281
|
}
|
|
4159
4282
|
return { name: prompt.name, path: target, action };
|
|
4160
4283
|
});
|
|
4161
|
-
const rulesFilePath =
|
|
4284
|
+
const rulesFilePath = join5(projectRoot, profile.rulesFile);
|
|
4162
4285
|
const existingRules = readFileOrEmpty(rulesFilePath);
|
|
4163
4286
|
let rulesAction;
|
|
4164
4287
|
if (bundle.rules.length === 0) {
|
|
@@ -4180,8 +4303,8 @@ function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4180
4303
|
}
|
|
4181
4304
|
}
|
|
4182
4305
|
const configs = bundle.configs.map((config) => {
|
|
4183
|
-
const target =
|
|
4184
|
-
const action =
|
|
4306
|
+
const target = join5(projectRoot, profile.configPath(config.name));
|
|
4307
|
+
const action = existsSync5(target) ? "skipped" : "created";
|
|
4185
4308
|
if (!dryRun && action === "created") {
|
|
4186
4309
|
writeFileAt(target, config.content);
|
|
4187
4310
|
}
|
|
@@ -4220,21 +4343,21 @@ function applyBundle(bundle, projectRoot, options = {}) {
|
|
|
4220
4343
|
};
|
|
4221
4344
|
}
|
|
4222
4345
|
function computeFileAction(filePath, newContent) {
|
|
4223
|
-
if (!
|
|
4346
|
+
if (!existsSync5(filePath))
|
|
4224
4347
|
return "created";
|
|
4225
4348
|
let current;
|
|
4226
4349
|
try {
|
|
4227
|
-
current =
|
|
4350
|
+
current = readFileSync4(filePath, "utf8");
|
|
4228
4351
|
} catch {
|
|
4229
4352
|
return "updated";
|
|
4230
4353
|
}
|
|
4231
4354
|
return current === newContent ? "unchanged" : "updated";
|
|
4232
4355
|
}
|
|
4233
4356
|
function readFileOrEmpty(filePath) {
|
|
4234
|
-
if (!
|
|
4357
|
+
if (!existsSync5(filePath))
|
|
4235
4358
|
return "";
|
|
4236
4359
|
try {
|
|
4237
|
-
return
|
|
4360
|
+
return readFileSync4(filePath, "utf8");
|
|
4238
4361
|
} catch {
|
|
4239
4362
|
return "";
|
|
4240
4363
|
}
|
|
@@ -4256,7 +4379,7 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
4256
4379
|
continue;
|
|
4257
4380
|
if (!isSafeName(skillName))
|
|
4258
4381
|
continue;
|
|
4259
|
-
const skillTarget =
|
|
4382
|
+
const skillTarget = join5(projectRoot, profile.skillPath(skillName));
|
|
4260
4383
|
removed.skills.push({ name: skillName, path: dirname3(skillTarget) });
|
|
4261
4384
|
}
|
|
4262
4385
|
for (const promptFile of prevManifest.files.prompts) {
|
|
@@ -4267,7 +4390,7 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
4267
4390
|
const promptName = promptFile.replace(/\.md$/, "");
|
|
4268
4391
|
removed.prompts.push({
|
|
4269
4392
|
name: promptFile,
|
|
4270
|
-
path:
|
|
4393
|
+
path: join5(projectRoot, profile.promptPath(promptName))
|
|
4271
4394
|
});
|
|
4272
4395
|
}
|
|
4273
4396
|
for (const configFile of prevManifest.files.configs) {
|
|
@@ -4277,23 +4400,33 @@ function computeRemovals(prevManifest, bundle, profile, projectRoot) {
|
|
|
4277
4400
|
continue;
|
|
4278
4401
|
removed.configs.push({
|
|
4279
4402
|
name: configFile,
|
|
4280
|
-
path:
|
|
4403
|
+
path: join5(projectRoot, profile.configPath(configFile))
|
|
4281
4404
|
});
|
|
4282
4405
|
}
|
|
4283
4406
|
return removed;
|
|
4284
4407
|
}
|
|
4285
|
-
function
|
|
4408
|
+
function findOrphanedManifests(projectRoot, currentProfile) {
|
|
4409
|
+
const out = [];
|
|
4286
4410
|
for (const profile of Object.values(PROFILES)) {
|
|
4287
4411
|
if (profile.toolId === currentProfile.toolId)
|
|
4288
4412
|
continue;
|
|
4289
|
-
const
|
|
4290
|
-
if (
|
|
4291
|
-
|
|
4292
|
-
|
|
4293
|
-
|
|
4294
|
-
|
|
4413
|
+
const manifestPath = join5(projectRoot, profile.manifestDir, MANIFEST_FILENAME);
|
|
4414
|
+
if (!existsSync5(manifestPath))
|
|
4415
|
+
continue;
|
|
4416
|
+
const manifest = readManifest(join5(projectRoot, profile.manifestDir));
|
|
4417
|
+
if (!manifest)
|
|
4418
|
+
continue;
|
|
4419
|
+
out.push({ profile, manifestPath, manifest });
|
|
4295
4420
|
}
|
|
4296
|
-
return
|
|
4421
|
+
return out;
|
|
4422
|
+
}
|
|
4423
|
+
function detectOrphanedArtifacts(projectRoot, currentProfile) {
|
|
4424
|
+
const first = findOrphanedManifests(projectRoot, currentProfile)[0];
|
|
4425
|
+
if (!first)
|
|
4426
|
+
return null;
|
|
4427
|
+
return `Found existing 10x artifacts in ${first.profile.manifestDir}/ from ${first.profile.displayName}.
|
|
4428
|
+
Manually remove ${first.profile.manifestDir}/ if you no longer need it.
|
|
4429
|
+
Your new artifacts will be written to ${currentProfile.manifestDir}/`;
|
|
4297
4430
|
}
|
|
4298
4431
|
function assertSafeName(name, kind) {
|
|
4299
4432
|
if (!isSafeName(name)) {
|
|
@@ -4321,6 +4454,296 @@ function isSafeName(name) {
|
|
|
4321
4454
|
return true;
|
|
4322
4455
|
}
|
|
4323
4456
|
|
|
4457
|
+
// src/lib/tool-switch.ts
|
|
4458
|
+
function migrateArtifacts(projectRoot, orphan, newProfile) {
|
|
4459
|
+
const summary = {
|
|
4460
|
+
action: "migrated",
|
|
4461
|
+
oldToolId: orphan.profile.toolId,
|
|
4462
|
+
movedOrRemoved: { skills: [], prompts: [], configs: [] },
|
|
4463
|
+
sentinelStripped: false,
|
|
4464
|
+
skipped: []
|
|
4465
|
+
};
|
|
4466
|
+
const oldProfile = orphan.profile;
|
|
4467
|
+
for (const skillName of orphan.manifest.files.skills) {
|
|
4468
|
+
if (!isSafeName(skillName)) {
|
|
4469
|
+
summary.skipped.push({ path: skillName, reason: "unsafe name in manifest" });
|
|
4470
|
+
continue;
|
|
4471
|
+
}
|
|
4472
|
+
const from = join6(projectRoot, oldProfile.skillPath(skillName));
|
|
4473
|
+
const to = join6(projectRoot, newProfile.skillPath(skillName));
|
|
4474
|
+
if (moveIfSafe(from, to, summary.skipped)) {
|
|
4475
|
+
summary.movedOrRemoved.skills.push(skillName);
|
|
4476
|
+
}
|
|
4477
|
+
}
|
|
4478
|
+
for (const promptFile of orphan.manifest.files.prompts) {
|
|
4479
|
+
if (!isSafeName(promptFile)) {
|
|
4480
|
+
summary.skipped.push({ path: promptFile, reason: "unsafe name in manifest" });
|
|
4481
|
+
continue;
|
|
4482
|
+
}
|
|
4483
|
+
const promptName = promptFile.replace(/\.md$/, "");
|
|
4484
|
+
const from = join6(projectRoot, oldProfile.promptPath(promptName));
|
|
4485
|
+
const to = join6(projectRoot, newProfile.promptPath(promptName));
|
|
4486
|
+
if (moveIfSafe(from, to, summary.skipped)) {
|
|
4487
|
+
summary.movedOrRemoved.prompts.push(promptFile);
|
|
4488
|
+
}
|
|
4489
|
+
}
|
|
4490
|
+
for (const configFile of orphan.manifest.files.configs) {
|
|
4491
|
+
if (!isSafeName(configFile)) {
|
|
4492
|
+
summary.skipped.push({ path: configFile, reason: "unsafe name in manifest" });
|
|
4493
|
+
continue;
|
|
4494
|
+
}
|
|
4495
|
+
const from = join6(projectRoot, oldProfile.configPath(configFile));
|
|
4496
|
+
const to = join6(projectRoot, newProfile.configPath(configFile));
|
|
4497
|
+
if (moveIfSafe(from, to, summary.skipped)) {
|
|
4498
|
+
summary.movedOrRemoved.configs.push(configFile);
|
|
4499
|
+
}
|
|
4500
|
+
}
|
|
4501
|
+
summary.sentinelStripped = stripSentinelFromRulesFile(projectRoot, oldProfile);
|
|
4502
|
+
rmSync3(orphan.manifestPath, { force: true });
|
|
4503
|
+
tryRemoveEmptyDir(join6(projectRoot, oldProfile.manifestDir));
|
|
4504
|
+
return summary;
|
|
4505
|
+
}
|
|
4506
|
+
function deleteArtifacts(projectRoot, orphan) {
|
|
4507
|
+
const summary = {
|
|
4508
|
+
action: "deleted",
|
|
4509
|
+
oldToolId: orphan.profile.toolId,
|
|
4510
|
+
movedOrRemoved: { skills: [], prompts: [], configs: [] },
|
|
4511
|
+
sentinelStripped: false,
|
|
4512
|
+
skipped: []
|
|
4513
|
+
};
|
|
4514
|
+
const oldProfile = orphan.profile;
|
|
4515
|
+
for (const skillName of orphan.manifest.files.skills) {
|
|
4516
|
+
if (!isSafeName(skillName)) {
|
|
4517
|
+
summary.skipped.push({ path: skillName, reason: "unsafe name in manifest" });
|
|
4518
|
+
continue;
|
|
4519
|
+
}
|
|
4520
|
+
const skillFile = join6(projectRoot, oldProfile.skillPath(skillName));
|
|
4521
|
+
const skillDir = dirname4(skillFile);
|
|
4522
|
+
rmSync3(skillFile, { force: true });
|
|
4523
|
+
tryRemoveEmptyDir(skillDir);
|
|
4524
|
+
summary.movedOrRemoved.skills.push(skillName);
|
|
4525
|
+
}
|
|
4526
|
+
for (const promptFile of orphan.manifest.files.prompts) {
|
|
4527
|
+
if (!isSafeName(promptFile)) {
|
|
4528
|
+
summary.skipped.push({ path: promptFile, reason: "unsafe name in manifest" });
|
|
4529
|
+
continue;
|
|
4530
|
+
}
|
|
4531
|
+
const promptName = promptFile.replace(/\.md$/, "");
|
|
4532
|
+
rmSync3(join6(projectRoot, oldProfile.promptPath(promptName)), { force: true });
|
|
4533
|
+
summary.movedOrRemoved.prompts.push(promptFile);
|
|
4534
|
+
}
|
|
4535
|
+
for (const configFile of orphan.manifest.files.configs) {
|
|
4536
|
+
if (!isSafeName(configFile)) {
|
|
4537
|
+
summary.skipped.push({ path: configFile, reason: "unsafe name in manifest" });
|
|
4538
|
+
continue;
|
|
4539
|
+
}
|
|
4540
|
+
rmSync3(join6(projectRoot, oldProfile.configPath(configFile)), { force: true });
|
|
4541
|
+
summary.movedOrRemoved.configs.push(configFile);
|
|
4542
|
+
}
|
|
4543
|
+
summary.sentinelStripped = stripSentinelFromRulesFile(projectRoot, oldProfile);
|
|
4544
|
+
rmSync3(orphan.manifestPath, { force: true });
|
|
4545
|
+
const manifestDirAbs = join6(projectRoot, oldProfile.manifestDir);
|
|
4546
|
+
tryRemoveEmptyDir(join6(manifestDirAbs, "skills"));
|
|
4547
|
+
tryRemoveEmptyDir(join6(manifestDirAbs, "prompts"));
|
|
4548
|
+
tryRemoveEmptyDir(join6(manifestDirAbs, "config-templates"));
|
|
4549
|
+
tryRemoveEmptyDir(manifestDirAbs);
|
|
4550
|
+
return summary;
|
|
4551
|
+
}
|
|
4552
|
+
function moveIfSafe(from, to, skipped) {
|
|
4553
|
+
if (!existsSync6(from)) {
|
|
4554
|
+
return false;
|
|
4555
|
+
}
|
|
4556
|
+
if (lstatSync(from).isSymbolicLink()) {
|
|
4557
|
+
skipped.push({ path: from, reason: "source is a symlink" });
|
|
4558
|
+
return false;
|
|
4559
|
+
}
|
|
4560
|
+
if (existsSync6(to)) {
|
|
4561
|
+
if (!filesAreIdentical(from, to)) {
|
|
4562
|
+
skipped.push({
|
|
4563
|
+
path: to,
|
|
4564
|
+
reason: "destination already exists with different content"
|
|
4565
|
+
});
|
|
4566
|
+
return false;
|
|
4567
|
+
}
|
|
4568
|
+
rmSync3(from, { force: true });
|
|
4569
|
+
return true;
|
|
4570
|
+
}
|
|
4571
|
+
mkdirSync5(dirname4(to), { recursive: true });
|
|
4572
|
+
try {
|
|
4573
|
+
renameSync2(from, to);
|
|
4574
|
+
} catch (err) {
|
|
4575
|
+
const code = err?.code;
|
|
4576
|
+
if (code !== "EXDEV")
|
|
4577
|
+
throw err;
|
|
4578
|
+
const bytes = readFileSync5(from);
|
|
4579
|
+
const tmp = `${to}.tmp`;
|
|
4580
|
+
writeFileSync4(tmp, bytes);
|
|
4581
|
+
renameSync2(tmp, to);
|
|
4582
|
+
try {
|
|
4583
|
+
rmSync3(from, { force: true });
|
|
4584
|
+
} catch {
|
|
4585
|
+
skipped.push({
|
|
4586
|
+
path: from,
|
|
4587
|
+
reason: "copied to destination but could not remove source"
|
|
4588
|
+
});
|
|
4589
|
+
return true;
|
|
4590
|
+
}
|
|
4591
|
+
}
|
|
4592
|
+
return true;
|
|
4593
|
+
}
|
|
4594
|
+
function stripSentinelFromRulesFile(projectRoot, oldProfile) {
|
|
4595
|
+
const rulesPath = join6(projectRoot, oldProfile.rulesFile);
|
|
4596
|
+
if (!existsSync6(rulesPath))
|
|
4597
|
+
return false;
|
|
4598
|
+
const existing = readFileOrNull(rulesPath);
|
|
4599
|
+
if (existing === null)
|
|
4600
|
+
return false;
|
|
4601
|
+
const { content, removed } = removeRulesBlockWithMarkers(existing, oldProfile.sentinelBegin, oldProfile.sentinelEnd);
|
|
4602
|
+
if (!removed)
|
|
4603
|
+
return false;
|
|
4604
|
+
if (content.trim().length === 0) {
|
|
4605
|
+
rmSync3(rulesPath, { force: true });
|
|
4606
|
+
} else {
|
|
4607
|
+
writeFileSync4(rulesPath, content);
|
|
4608
|
+
}
|
|
4609
|
+
return true;
|
|
4610
|
+
}
|
|
4611
|
+
function filesAreIdentical(a2, b2) {
|
|
4612
|
+
try {
|
|
4613
|
+
const aStat = statSync(a2);
|
|
4614
|
+
const bStat = statSync(b2);
|
|
4615
|
+
if (aStat.size !== bStat.size)
|
|
4616
|
+
return false;
|
|
4617
|
+
return readFileSync5(a2).equals(readFileSync5(b2));
|
|
4618
|
+
} catch {
|
|
4619
|
+
return false;
|
|
4620
|
+
}
|
|
4621
|
+
}
|
|
4622
|
+
function tryRemoveEmptyDir(dir) {
|
|
4623
|
+
if (!existsSync6(dir))
|
|
4624
|
+
return;
|
|
4625
|
+
try {
|
|
4626
|
+
rmdirSync(dir);
|
|
4627
|
+
} catch {}
|
|
4628
|
+
}
|
|
4629
|
+
|
|
4630
|
+
// src/lib/tool-prompt.ts
|
|
4631
|
+
async function resolveToolProfile(flagOverride, projectRoot = process.cwd()) {
|
|
4632
|
+
const profile = await resolveProfileOnly(flagOverride, projectRoot);
|
|
4633
|
+
await handleToolSwitch(projectRoot, profile);
|
|
4634
|
+
return profile;
|
|
4635
|
+
}
|
|
4636
|
+
async function resolveProfileOnly(flagOverride, projectRoot) {
|
|
4637
|
+
if (flagOverride) {
|
|
4638
|
+
const profile = PROFILES[flagOverride];
|
|
4639
|
+
if (!profile) {
|
|
4640
|
+
throw new Error(`Unknown tool '${flagOverride}'. Supported: ${Object.keys(PROFILES).join(", ")}`);
|
|
4641
|
+
}
|
|
4642
|
+
return profile;
|
|
4643
|
+
}
|
|
4644
|
+
const config = readToolConfig();
|
|
4645
|
+
if (config?.tool && PROFILES[config.tool]) {
|
|
4646
|
+
return PROFILES[config.tool];
|
|
4647
|
+
}
|
|
4648
|
+
if (process.stdout.isTTY) {
|
|
4649
|
+
const signals = detectTools(projectRoot);
|
|
4650
|
+
const detected = topDetectedProfile(signals);
|
|
4651
|
+
const top = signals[0];
|
|
4652
|
+
if (top && detected) {
|
|
4653
|
+
ye(`Detected: ${detected.displayName} (${top.reason})`);
|
|
4654
|
+
}
|
|
4655
|
+
const initial = detected?.toolId ?? DEFAULT_TOOL;
|
|
4656
|
+
const choice = await de({
|
|
4657
|
+
message: "Which AI coding tool do you use?",
|
|
4658
|
+
options: Object.values(PROFILES).map((profile) => ({
|
|
4659
|
+
value: profile.toolId,
|
|
4660
|
+
label: profile.displayName,
|
|
4661
|
+
hint: profile.toolId === initial ? "default" : undefined
|
|
4662
|
+
})),
|
|
4663
|
+
initialValue: initial
|
|
4664
|
+
});
|
|
4665
|
+
if (BD(choice)) {
|
|
4666
|
+
ve("Using default (Claude Code).");
|
|
4667
|
+
return PROFILES[DEFAULT_TOOL];
|
|
4668
|
+
}
|
|
4669
|
+
saveToolConfig({ ...config ?? {}, tool: choice });
|
|
4670
|
+
return PROFILES[choice];
|
|
4671
|
+
}
|
|
4672
|
+
return PROFILES[DEFAULT_TOOL];
|
|
4673
|
+
}
|
|
4674
|
+
async function handleToolSwitch(projectRoot, newProfile) {
|
|
4675
|
+
if (!process.stdout.isTTY)
|
|
4676
|
+
return;
|
|
4677
|
+
const cfg = readToolConfig();
|
|
4678
|
+
const acknowledged = new Set(cfg?.acknowledgedOrphans ?? []);
|
|
4679
|
+
const orphans = findOrphanedManifests(projectRoot, newProfile).filter((o) => !acknowledged.has(o.profile.toolId));
|
|
4680
|
+
if (orphans.length === 0)
|
|
4681
|
+
return;
|
|
4682
|
+
for (const orphan of orphans) {
|
|
4683
|
+
const action = await de({
|
|
4684
|
+
message: `Found 10x artifacts from ${orphan.profile.displayName} in ${orphan.profile.manifestDir}/. What should we do?`,
|
|
4685
|
+
options: [
|
|
4686
|
+
{
|
|
4687
|
+
value: "migrate",
|
|
4688
|
+
label: `Migrate to ${newProfile.manifestDir}/`,
|
|
4689
|
+
hint: "recommended"
|
|
4690
|
+
},
|
|
4691
|
+
{
|
|
4692
|
+
value: "delete",
|
|
4693
|
+
label: `Remove 10x artifacts from ${orphan.profile.manifestDir}/`
|
|
4694
|
+
},
|
|
4695
|
+
{ value: "keep", label: "Keep both (don't ask again)" }
|
|
4696
|
+
],
|
|
4697
|
+
initialValue: "migrate"
|
|
4698
|
+
});
|
|
4699
|
+
if (BD(action))
|
|
4700
|
+
continue;
|
|
4701
|
+
if (action === "migrate") {
|
|
4702
|
+
const summary = migrateArtifacts(projectRoot, orphan, newProfile);
|
|
4703
|
+
printMigrationSummary(summary, newProfile);
|
|
4704
|
+
} else if (action === "delete") {
|
|
4705
|
+
const summary = deleteArtifacts(projectRoot, orphan);
|
|
4706
|
+
printMigrationSummary(summary, newProfile);
|
|
4707
|
+
} else {
|
|
4708
|
+
const existing = readToolConfig();
|
|
4709
|
+
const nextAcks = [
|
|
4710
|
+
...existing?.acknowledgedOrphans ?? [],
|
|
4711
|
+
orphan.profile.toolId
|
|
4712
|
+
];
|
|
4713
|
+
saveToolConfig({
|
|
4714
|
+
...existing ?? {},
|
|
4715
|
+
tool: existing?.tool ?? newProfile.toolId,
|
|
4716
|
+
acknowledgedOrphans: nextAcks
|
|
4717
|
+
});
|
|
4718
|
+
}
|
|
4719
|
+
}
|
|
4720
|
+
}
|
|
4721
|
+
function printMigrationSummary(summary, newProfile) {
|
|
4722
|
+
const { skills, prompts, configs } = summary.movedOrRemoved;
|
|
4723
|
+
const verb = summary.action === "migrated" ? "Moved" : "Removed";
|
|
4724
|
+
const target = summary.action === "migrated" ? `to ${newProfile.manifestDir}/` : "";
|
|
4725
|
+
const parts = [];
|
|
4726
|
+
if (skills.length)
|
|
4727
|
+
parts.push(`${skills.length} skill${skills.length === 1 ? "" : "s"}`);
|
|
4728
|
+
if (prompts.length)
|
|
4729
|
+
parts.push(`${prompts.length} prompt${prompts.length === 1 ? "" : "s"}`);
|
|
4730
|
+
if (configs.length)
|
|
4731
|
+
parts.push(`${configs.length} config${configs.length === 1 ? "" : "s"}`);
|
|
4732
|
+
const lines = [];
|
|
4733
|
+
if (parts.length > 0) {
|
|
4734
|
+
lines.push(`${verb} ${parts.join(", ")}${target ? " " + target : ""}.`);
|
|
4735
|
+
}
|
|
4736
|
+
if (summary.sentinelStripped) {
|
|
4737
|
+
lines.push(`Removed 10x block from the old rules file.`);
|
|
4738
|
+
}
|
|
4739
|
+
for (const s of summary.skipped) {
|
|
4740
|
+
lines.push(`Skipped ${s.path} (${s.reason}).`);
|
|
4741
|
+
}
|
|
4742
|
+
if (lines.length > 0)
|
|
4743
|
+
ye(lines.join(`
|
|
4744
|
+
`));
|
|
4745
|
+
}
|
|
4746
|
+
|
|
4324
4747
|
// src/commands/get.ts
|
|
4325
4748
|
var ARTIFACT_TYPES = ["skills", "prompts", "rules", "configs"];
|
|
4326
4749
|
function isArtifactType(s) {
|
|
@@ -4350,7 +4773,7 @@ async function runGet(ctx, ref, options) {
|
|
|
4350
4773
|
}
|
|
4351
4774
|
const auth = await requireAuth(ctx);
|
|
4352
4775
|
const course = options.course ?? DEFAULT_COURSE2;
|
|
4353
|
-
const profile = await resolveToolProfile(options.tool);
|
|
4776
|
+
const profile = await resolveToolProfile(options.tool, process.cwd());
|
|
4354
4777
|
const lang = options.lang ?? readToolConfig()?.lang ?? "en";
|
|
4355
4778
|
if (options.lang) {
|
|
4356
4779
|
const existing = readToolConfig();
|
|
@@ -4370,9 +4793,11 @@ async function runGet(ctx, ref, options) {
|
|
|
4370
4793
|
if (isFallback && contentLang) {
|
|
4371
4794
|
verbose(ctx, `${lang.toUpperCase()} not available for ${parsed.lessonId}, showing ${contentLang.toUpperCase()}.`);
|
|
4372
4795
|
}
|
|
4373
|
-
|
|
4374
|
-
|
|
4375
|
-
|
|
4796
|
+
if (!process.stdout.isTTY) {
|
|
4797
|
+
const orphanWarning = detectOrphanedArtifacts(process.cwd(), profile);
|
|
4798
|
+
if (orphanWarning)
|
|
4799
|
+
verbose(ctx, orphanWarning);
|
|
4800
|
+
}
|
|
4376
4801
|
const isFiltered = options.type !== undefined;
|
|
4377
4802
|
const bundle = filterBundle(ctx, result.data, options);
|
|
4378
4803
|
const writeResult = applyBundle(bundle, process.cwd(), {
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@przeprogramowani/10x-cli",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.6.0",
|
|
4
4
|
"description": "Open-source CLI for 10xDevs course content",
|
|
5
5
|
"repository": {
|
|
6
6
|
"type": "git",
|
|
@@ -16,6 +16,7 @@
|
|
|
16
16
|
},
|
|
17
17
|
"files": [
|
|
18
18
|
"dist",
|
|
19
|
+
"skills",
|
|
19
20
|
"README.md",
|
|
20
21
|
"LICENSE"
|
|
21
22
|
],
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
---
|
|
2
|
+
name: 10x-cli-setup
|
|
3
|
+
description: ALWAYS invoke this skill when the user mentions 10x-cli, @przeprogramowani/10x-cli, the 10xDevs CLI, or the 10xDevs course environment in a setup context. This skill fetches the live README — Claude does not know 10x-cli's current install steps without it. Applies to: installing, updating, reconfiguring for different AI tools (Cursor, Copilot, Claude Code), permission/npm errors, authentication, and onboarding after 10xDevs enrollment. Excludes: developing 10x-cli source code, contributing to the repo, building similar CLIs, or general project setup.
|
|
4
|
+
---
|
|
5
|
+
|
|
6
|
+
# 10x-cli Setup
|
|
7
|
+
|
|
8
|
+
This skill sets up the `@przeprogramowani/10x-cli` on the user's machine. The core principle is simple: **the README is the single source of truth**. The CLI evolves — version requirements change, new install methods appear, commands get updated. Rather than hardcoding any of that here, this skill tells you *how to work*, and the README tells you *what to do*.
|
|
9
|
+
|
|
10
|
+
## Step 1: Check if the CLI is already installed
|
|
11
|
+
|
|
12
|
+
Before anything else, check the current state:
|
|
13
|
+
|
|
14
|
+
```bash
|
|
15
|
+
10x --version 2>/dev/null || echo "NOT_INSTALLED"
|
|
16
|
+
```
|
|
17
|
+
|
|
18
|
+
- If a version is printed, the CLI is already installed. Tell the user and ask if they want to update, reconfigure, or troubleshoot.
|
|
19
|
+
- If not installed, proceed to Step 2.
|
|
20
|
+
|
|
21
|
+
This avoids wasting time on prerequisites when the user might just need a config change or re-auth.
|
|
22
|
+
|
|
23
|
+
## Step 2: Fetch the latest README
|
|
24
|
+
|
|
25
|
+
Retrieve the current README from GitHub — this is the authoritative source for all install steps, prerequisites, commands, and tool configurations:
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
URL: https://raw.githubusercontent.com/przeprogramowani/10x-cli/refs/heads/master/README.md
|
|
29
|
+
```
|
|
30
|
+
|
|
31
|
+
Use WebFetch or `curl -sL` to get it. If the fetch fails, tell the user and stop — don't guess at install steps from memory, because they may be outdated.
|
|
32
|
+
|
|
33
|
+
## Step 3: Build a plan from the README and execute it
|
|
34
|
+
|
|
35
|
+
Read the fetched README and construct a step-by-step setup plan from it. The README contains everything needed: prerequisites, install commands, auth flow, available commands, and tool-specific configuration. Your job is to translate the README into actionable steps for the user's specific situation.
|
|
36
|
+
|
|
37
|
+
The general flow from the README is:
|
|
38
|
+
1. **Prerequisites** — whatever the README says is required (runtime version, package manager, etc.). Check each one and stop if something is missing.
|
|
39
|
+
2. **Install** — use the install method described in the README. Verify it worked.
|
|
40
|
+
3. **Authenticate** — the README describes the auth command and flow. Note: auth is interactive (magic-link email), so the user may need to run it themselves via `! 10x auth` if the shell doesn't support input.
|
|
41
|
+
4. **Verify** — the README lists a diagnostic command. Run it and review the output with the user.
|
|
42
|
+
5. **Explore** — show the user how to browse and fetch content using the commands from the README.
|
|
43
|
+
6. **Tool configuration** — if the user mentioned a specific AI tool (Claude Code, Cursor, etc.), use the README's multi-tool support section to configure it. If not, explain the options and let them choose.
|
|
44
|
+
|
|
45
|
+
Do not hardcode specific version numbers, command flags, or directory paths — read them from the README. This way the skill stays correct even when the CLI changes.
|
|
46
|
+
|
|
47
|
+
## Important principles
|
|
48
|
+
|
|
49
|
+
- **README over memory.** If you think you know a command or requirement, but the fetched README says something different, follow the README. Always.
|
|
50
|
+
- **Check before installing.** Step 1 exists for a reason — don't reinstall what's already there.
|
|
51
|
+
- **Be interactive.** Confirm with the user before installing global packages or modifying their system. Ask before running `sudo`.
|
|
52
|
+
- **Diagnose before fixing.** If something fails, read the error and the README's guidance before suggesting a fix. Don't just retry blindly.
|
|
53
|
+
- **Stay focused on end-user setup.** This skill is about installing and configuring the published CLI package, not about development/contributing workflows.
|