@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 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
- return KNOWN_CONFIG_DIRS.some((dir) => source === dir || source.startsWith(dir + "/"));
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 profileFiles = ["claude.json", "codex.json"];
2695
- for (const file of profileFiles) {
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 readdirSync2, statSync as statSync2 } from "node:fs";
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 = readdirSync(src, { withFileTypes: true });
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
- return join3(process.env.HOME || "", p.slice(2));
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 = readdirSync2(dir, { withFileTypes: true });
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
- execFileSync("git", ["clone", "--depth", "1", ref.url, tempDir], {
3119
- stdio: verbose ? "inherit" : "pipe"
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 readdirSync3 } from "node:fs";
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 = readdirSync3(dir, { withFileTypes: true });
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 = readdirSync3(dir, { withFileTypes: true });
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
- var BACKUP_BASE = join8(process.env.HOME || "", ".dotai-backup");
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.1.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: BACKUP_BASE
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: BACKUP_BASE });
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.1.1",
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",