@openlor/dotai 0.1.1 → 0.2.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +6 -6
- package/dist/cli.js +61 -21
- package/dist/profiles/profiles/claude.json +1 -1
- package/dist/profiles/profiles/codex.json +1 -1
- package/package.json +3 -2
package/README.md
CHANGED
|
@@ -14,13 +14,13 @@ AI CLI config manager — sync Claude Code, Codex, and more between machines.
|
|
|
14
14
|
|
|
15
15
|
```bash
|
|
16
16
|
# In your config repo directory:
|
|
17
|
-
npx dotai init # Auto-discover CLIs, generate manifest
|
|
18
|
-
npx dotai export # Copy configs from machine to repo
|
|
17
|
+
npx @openlor/dotai init # Auto-discover CLIs, generate manifest
|
|
18
|
+
npx @openlor/dotai export # Copy configs from machine to repo
|
|
19
19
|
git add -A && git commit -m "my ai config"
|
|
20
20
|
git push
|
|
21
21
|
|
|
22
22
|
# On another machine:
|
|
23
|
-
npx dotai use <your-github-user>/dotai-config
|
|
23
|
+
npx @openlor/dotai use <your-github-user>/dotai-config
|
|
24
24
|
```
|
|
25
25
|
|
|
26
26
|
## Commands
|
|
@@ -113,11 +113,11 @@ Every managed file is in one of four states:
|
|
|
113
113
|
|
|
114
114
|
```bash
|
|
115
115
|
# Use without installing (recommended)
|
|
116
|
-
npx dotai <command>
|
|
116
|
+
npx @openlor/dotai <command>
|
|
117
117
|
|
|
118
118
|
# Or install globally
|
|
119
|
-
npm install -g dotai
|
|
120
|
-
bun install -g dotai
|
|
119
|
+
npm install -g @openlor/dotai
|
|
120
|
+
bun install -g @openlor/dotai
|
|
121
121
|
```
|
|
122
122
|
|
|
123
123
|
Requires Node.js >= 18 or Bun.
|
package/dist/cli.js
CHANGED
|
@@ -2637,6 +2637,7 @@ var source_default = chalk;
|
|
|
2637
2637
|
|
|
2638
2638
|
// src/lib/manifest.ts
|
|
2639
2639
|
import { readFileSync } from "node:fs";
|
|
2640
|
+
import { resolve } from "node:path";
|
|
2640
2641
|
function parseManifest(jsonString) {
|
|
2641
2642
|
return JSON.parse(jsonString);
|
|
2642
2643
|
}
|
|
@@ -2676,7 +2677,16 @@ function validateManifest(data) {
|
|
|
2676
2677
|
}
|
|
2677
2678
|
var KNOWN_CONFIG_DIRS = ["~/.claude", "~/.codex", "~/.cursor", "~/.windsurf", "~/.aider"];
|
|
2678
2679
|
function isKnownConfigDir(source) {
|
|
2679
|
-
|
|
2680
|
+
const home = process.env.HOME || "/nonexistent";
|
|
2681
|
+
const normalize = (p) => {
|
|
2682
|
+
const expanded = p.startsWith("~/") ? resolve(home, p.slice(2)) : resolve(p);
|
|
2683
|
+
return expanded;
|
|
2684
|
+
};
|
|
2685
|
+
const resolved = normalize(source);
|
|
2686
|
+
return KNOWN_CONFIG_DIRS.some((dir) => {
|
|
2687
|
+
const resolvedDir = normalize(dir);
|
|
2688
|
+
return resolved === resolvedDir || resolved.startsWith(resolvedDir + "/");
|
|
2689
|
+
});
|
|
2680
2690
|
}
|
|
2681
2691
|
|
|
2682
2692
|
// src/commands/init.ts
|
|
@@ -2684,15 +2694,15 @@ import { existsSync as existsSync3, mkdirSync as mkdirSync2, writeFileSync } fro
|
|
|
2684
2694
|
import { join as join4 } from "node:path";
|
|
2685
2695
|
|
|
2686
2696
|
// src/lib/profiles.ts
|
|
2687
|
-
import { readFileSync as readFileSync2 } from "node:fs";
|
|
2697
|
+
import { readFileSync as readFileSync2, readdirSync } from "node:fs";
|
|
2688
2698
|
import { join, dirname } from "node:path";
|
|
2689
2699
|
import { fileURLToPath } from "node:url";
|
|
2690
2700
|
var __dirname2 = dirname(fileURLToPath(import.meta.url));
|
|
2691
2701
|
var PROFILES_DIR = join(__dirname2, "..", "profiles");
|
|
2692
2702
|
function loadBuiltinProfiles() {
|
|
2693
2703
|
const profiles = {};
|
|
2694
|
-
const
|
|
2695
|
-
for (const file of
|
|
2704
|
+
const entries = readdirSync(PROFILES_DIR).filter((f) => f.endsWith(".json"));
|
|
2705
|
+
for (const file of entries) {
|
|
2696
2706
|
const content = readFileSync2(join(PROFILES_DIR, file), "utf-8");
|
|
2697
2707
|
const profile = JSON.parse(content);
|
|
2698
2708
|
profiles[profile.name] = profile;
|
|
@@ -2734,7 +2744,7 @@ function generateGitignore(profiles) {
|
|
|
2734
2744
|
}
|
|
2735
2745
|
|
|
2736
2746
|
// src/lib/resolve.ts
|
|
2737
|
-
import { existsSync as existsSync2, readdirSync as
|
|
2747
|
+
import { existsSync as existsSync2, readdirSync as readdirSync3, statSync as statSync2 } from "node:fs";
|
|
2738
2748
|
import { join as join3, relative } from "node:path";
|
|
2739
2749
|
|
|
2740
2750
|
// src/lib/fileops.ts
|
|
@@ -2743,7 +2753,7 @@ import {
|
|
|
2743
2753
|
cpSync,
|
|
2744
2754
|
mkdirSync,
|
|
2745
2755
|
readFileSync as readFileSync3,
|
|
2746
|
-
readdirSync,
|
|
2756
|
+
readdirSync as readdirSync2,
|
|
2747
2757
|
readlinkSync,
|
|
2748
2758
|
rmSync,
|
|
2749
2759
|
symlinkSync
|
|
@@ -2758,7 +2768,7 @@ function copyDir(src, dst) {
|
|
|
2758
2768
|
_copyDirRecursive(src, dst);
|
|
2759
2769
|
}
|
|
2760
2770
|
function _copyDirRecursive(src, dst) {
|
|
2761
|
-
const entries =
|
|
2771
|
+
const entries = readdirSync2(src, { withFileTypes: true });
|
|
2762
2772
|
for (const entry of entries) {
|
|
2763
2773
|
if (entry.name === ".git" && entry.isDirectory())
|
|
2764
2774
|
continue;
|
|
@@ -2799,7 +2809,11 @@ function filesAreEqual(pathA, pathB) {
|
|
|
2799
2809
|
// src/lib/resolve.ts
|
|
2800
2810
|
function expandHome(p) {
|
|
2801
2811
|
if (p.startsWith("~/")) {
|
|
2802
|
-
|
|
2812
|
+
const home = process.env.HOME;
|
|
2813
|
+
if (!home) {
|
|
2814
|
+
throw new Error("HOME environment variable is not set. Cannot resolve path: " + p);
|
|
2815
|
+
}
|
|
2816
|
+
return join3(home, p.slice(2));
|
|
2803
2817
|
}
|
|
2804
2818
|
return p;
|
|
2805
2819
|
}
|
|
@@ -2857,7 +2871,7 @@ function resolveFiles(manifest, repoDir, toolName) {
|
|
|
2857
2871
|
function walkDir(dir, base, out) {
|
|
2858
2872
|
if (!existsSync2(dir))
|
|
2859
2873
|
return;
|
|
2860
|
-
const entries =
|
|
2874
|
+
const entries = readdirSync3(dir, { withFileTypes: true });
|
|
2861
2875
|
for (const entry of entries) {
|
|
2862
2876
|
if (entry.name === ".git" && entry.isDirectory())
|
|
2863
2877
|
continue;
|
|
@@ -2889,6 +2903,14 @@ function isExcluded(relPath, patterns) {
|
|
|
2889
2903
|
// src/commands/init.ts
|
|
2890
2904
|
function runInit(options) {
|
|
2891
2905
|
const { repoDir, homeDir } = options;
|
|
2906
|
+
const manifestPath = join4(repoDir, "dotai.json");
|
|
2907
|
+
if (existsSync3(manifestPath)) {
|
|
2908
|
+
return {
|
|
2909
|
+
manifestPath: null,
|
|
2910
|
+
warnings: ["dotai.json already exists. Delete it first if you want to re-initialize."],
|
|
2911
|
+
toolsFound: []
|
|
2912
|
+
};
|
|
2913
|
+
}
|
|
2892
2914
|
const prevHome = process.env.HOME;
|
|
2893
2915
|
if (homeDir)
|
|
2894
2916
|
process.env.HOME = homeDir;
|
|
@@ -2912,7 +2934,6 @@ function runInit(options) {
|
|
|
2912
2934
|
warnings.push("No AI CLI config directories found. Creating empty manifest — add tools manually.");
|
|
2913
2935
|
}
|
|
2914
2936
|
mkdirSync2(repoDir, { recursive: true });
|
|
2915
|
-
const manifestPath = join4(repoDir, "dotai.json");
|
|
2916
2937
|
writeFileSync(manifestPath, JSON.stringify(manifest, null, 2) + `
|
|
2917
2938
|
`, "utf-8");
|
|
2918
2939
|
const profileList = toolsFound.map((name) => profiles[name]);
|
|
@@ -2939,7 +2960,7 @@ function generateReadme(manifest) {
|
|
|
2939
2960
|
"## Quick Start",
|
|
2940
2961
|
"",
|
|
2941
2962
|
"```bash",
|
|
2942
|
-
"npx dotai use <this-repo>",
|
|
2963
|
+
"npx @openlor/dotai use <this-repo>",
|
|
2943
2964
|
"```",
|
|
2944
2965
|
"",
|
|
2945
2966
|
"## Managed Tools",
|
|
@@ -3115,9 +3136,21 @@ function runUse(options) {
|
|
|
3115
3136
|
try {
|
|
3116
3137
|
if (verbose)
|
|
3117
3138
|
console.log(`Cloning ${ref.url}...`);
|
|
3118
|
-
|
|
3119
|
-
|
|
3120
|
-
|
|
3139
|
+
try {
|
|
3140
|
+
execFileSync("git", ["clone", "--depth", "1", ref.url, tempDir], {
|
|
3141
|
+
stdio: verbose ? "inherit" : "pipe"
|
|
3142
|
+
});
|
|
3143
|
+
} catch (err) {
|
|
3144
|
+
rmSync3(tempDir, { recursive: true, force: true });
|
|
3145
|
+
if (err.code === "ENOENT") {
|
|
3146
|
+
throw new Error("git is not installed. Install git and try again.");
|
|
3147
|
+
}
|
|
3148
|
+
const stderr = err.stderr?.toString() || "";
|
|
3149
|
+
if (stderr.includes("not found") || stderr.includes("does not exist") || err.status === 128) {
|
|
3150
|
+
throw new Error(`Repository not found: ${ref.owner}/${ref.repo}. ` + `Check the URL and ensure you have access.`);
|
|
3151
|
+
}
|
|
3152
|
+
throw new Error(`Failed to clone ${ref.owner}/${ref.repo}: ${stderr || err.message}`);
|
|
3153
|
+
}
|
|
3121
3154
|
const manifestPath = join6(tempDir, "dotai.json");
|
|
3122
3155
|
if (!existsSync5(manifestPath)) {
|
|
3123
3156
|
throw new Error(`No dotai.json found in ${ref.owner}/${ref.repo}`);
|
|
@@ -3136,7 +3169,7 @@ ${errors.map((e) => ` - ${e}`).join(`
|
|
|
3136
3169
|
}
|
|
3137
3170
|
|
|
3138
3171
|
// src/commands/doctor.ts
|
|
3139
|
-
import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as
|
|
3172
|
+
import { existsSync as existsSync6, readFileSync as readFileSync4, readdirSync as readdirSync4 } from "node:fs";
|
|
3140
3173
|
import { join as join7 } from "node:path";
|
|
3141
3174
|
var CREDENTIAL_PATTERNS = ["auth.json", ".env", "credentials.json", "*.key", "*.pem", "token.json"];
|
|
3142
3175
|
function runDoctor(options) {
|
|
@@ -3175,7 +3208,7 @@ function runDoctor(options) {
|
|
|
3175
3208
|
}
|
|
3176
3209
|
function scanForCredentials(dir, prefix, warnings) {
|
|
3177
3210
|
try {
|
|
3178
|
-
const entries =
|
|
3211
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
3179
3212
|
for (const entry of entries) {
|
|
3180
3213
|
if (entry.isFile()) {
|
|
3181
3214
|
for (const pattern of CREDENTIAL_PATTERNS) {
|
|
@@ -3195,7 +3228,7 @@ function scanForCredentials(dir, prefix, warnings) {
|
|
|
3195
3228
|
}
|
|
3196
3229
|
function scanForStalePaths(dir, prefix, homeDir, warnings) {
|
|
3197
3230
|
try {
|
|
3198
|
-
const entries =
|
|
3231
|
+
const entries = readdirSync4(dir, { withFileTypes: true });
|
|
3199
3232
|
for (const entry of entries) {
|
|
3200
3233
|
if (entry.isFile() && (entry.name.endsWith(".json") || entry.name.endsWith(".toml"))) {
|
|
3201
3234
|
const content = readFileSync4(join7(dir, entry.name), "utf-8");
|
|
@@ -3210,7 +3243,14 @@ function scanForStalePaths(dir, prefix, homeDir, warnings) {
|
|
|
3210
3243
|
}
|
|
3211
3244
|
|
|
3212
3245
|
// src/cli.ts
|
|
3213
|
-
|
|
3246
|
+
function getBackupBase() {
|
|
3247
|
+
const home = process.env.HOME;
|
|
3248
|
+
if (!home) {
|
|
3249
|
+
console.error("HOME environment variable is not set.");
|
|
3250
|
+
process.exit(1);
|
|
3251
|
+
}
|
|
3252
|
+
return join8(home, ".dotai-backup");
|
|
3253
|
+
}
|
|
3214
3254
|
function getManifest(repoDir) {
|
|
3215
3255
|
const manifestPath = join8(repoDir, "dotai.json");
|
|
3216
3256
|
if (!existsSync7(manifestPath)) {
|
|
@@ -3220,7 +3260,7 @@ function getManifest(repoDir) {
|
|
|
3220
3260
|
return readManifest(manifestPath);
|
|
3221
3261
|
}
|
|
3222
3262
|
var program2 = new Command;
|
|
3223
|
-
program2.name("dotai").description("AI CLI config manager").version("0.
|
|
3263
|
+
program2.name("dotai").description("AI CLI config manager").version("0.2.0");
|
|
3224
3264
|
program2.command("init").description("Auto-discover AI CLIs and generate manifest").action(() => {
|
|
3225
3265
|
const repoDir = process.cwd();
|
|
3226
3266
|
const result = runInit({ repoDir });
|
|
@@ -3246,7 +3286,7 @@ program2.command("import").description("Copy configs from repo to machine").opti
|
|
|
3246
3286
|
dryRun: opts.dryRun,
|
|
3247
3287
|
sync: opts.sync,
|
|
3248
3288
|
only: opts.only,
|
|
3249
|
-
backupBase:
|
|
3289
|
+
backupBase: getBackupBase()
|
|
3250
3290
|
});
|
|
3251
3291
|
if (opts.dryRun) {
|
|
3252
3292
|
console.log(source_default.yellow("Dry run — no changes made."));
|
|
@@ -3293,7 +3333,7 @@ program2.command("status").description("Sync summary dashboard").option("--only
|
|
|
3293
3333
|
⚠ Out of sync`));
|
|
3294
3334
|
});
|
|
3295
3335
|
program2.command("use <repo>").description("Import config from a GitHub repo (owner/repo)").option("--dry-run", "Show what would change without writing", false).option("--verbose", "Show details", false).action((repo, opts) => {
|
|
3296
|
-
runUse({ repoArg: repo, dryRun: opts.dryRun, verbose: opts.verbose, backupBase:
|
|
3336
|
+
runUse({ repoArg: repo, dryRun: opts.dryRun, verbose: opts.verbose, backupBase: getBackupBase() });
|
|
3297
3337
|
if (!opts.dryRun)
|
|
3298
3338
|
console.log(source_default.green("✅ Config imported successfully"));
|
|
3299
3339
|
});
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "claude",
|
|
3
3
|
"description": "Claude Code by Anthropic",
|
|
4
4
|
"configDir": "~/.claude",
|
|
5
|
-
"portable": ["settings.json", "commands/", "skills/", "plugins/config.json", "plugins/installed_plugins.json", "statusline-command.sh"],
|
|
5
|
+
"portable": ["settings.json", "commands/", "skills/", "plugins/config.json", "plugins/installed_plugins.json", "statusline-command.sh", "hooks/", "agents/", "rules/", "CLAUDE.md", "keybindings.json"],
|
|
6
6
|
"ephemeral": ["sessions/", "session-env/", "cache/", "shell-snapshots/", "debug/", "paste-cache/", "file-history/", "plans/", "ide/", "backups/", "history.jsonl", ".session-stats.json", "policy-limits.json", "mcp-needs-auth-cache.json", "statsig/", "stats-cache.json", "plugins/cache/"],
|
|
7
7
|
"credentials": ["auth.json"]
|
|
8
8
|
}
|
|
@@ -2,7 +2,7 @@
|
|
|
2
2
|
"name": "codex",
|
|
3
3
|
"description": "Codex CLI by OpenAI",
|
|
4
4
|
"configDir": "~/.codex",
|
|
5
|
-
"portable": ["config.toml", "rules/", "skills/", "prompts/"],
|
|
5
|
+
"portable": ["config.toml", "rules/", "skills/", "prompts/", "agents/", "AGENTS.md"],
|
|
6
6
|
"ephemeral": ["history.jsonl", "sessions/", "log/", "*.sqlite", "models_cache.json", "shell_snapshots/", "tmp/", ".codex-global-state.json", "version.json"],
|
|
7
7
|
"credentials": ["auth.json"]
|
|
8
8
|
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@openlor/dotai",
|
|
3
|
-
"version": "0.
|
|
3
|
+
"version": "0.2.0",
|
|
4
4
|
"description": "AI CLI config manager — sync Claude Code, Codex, and more between machines",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"bin": {
|
|
@@ -10,7 +10,8 @@
|
|
|
10
10
|
"build": "bun build src/cli.ts --outdir dist --target node && cp -r src/profiles dist/profiles",
|
|
11
11
|
"test": "vitest run",
|
|
12
12
|
"test:watch": "vitest",
|
|
13
|
-
"dev": "bun run src/cli.ts"
|
|
13
|
+
"dev": "bun run src/cli.ts",
|
|
14
|
+
"prepublishOnly": "bun run test && bun run build"
|
|
14
15
|
},
|
|
15
16
|
"keywords": [
|
|
16
17
|
"dotfiles",
|