@iannuttall/dotagents 0.1.0 → 0.1.1
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 +2 -0
- package/dist/cli.js +168 -10
- package/package.json +1 -1
package/README.md
CHANGED
package/dist/cli.js
CHANGED
|
@@ -3498,6 +3498,7 @@ import { render } from "ink";
|
|
|
3498
3498
|
// src/tui/App.tsx
|
|
3499
3499
|
import { useEffect as useEffect2, useMemo, useState } from "react";
|
|
3500
3500
|
import fs10 from "fs";
|
|
3501
|
+
import path15 from "path";
|
|
3501
3502
|
import { Box as Box4, Text as Text2, useApp, useInput as useInput2, useStdout as useStdout3 } from "ink";
|
|
3502
3503
|
import SelectInput from "ink-select-input";
|
|
3503
3504
|
import TextInput from "ink-text-input";
|
|
@@ -3571,7 +3572,8 @@ function getMappings(opts) {
|
|
|
3571
3572
|
source: path2.join(canonical, "skills"),
|
|
3572
3573
|
targets: [
|
|
3573
3574
|
path2.join(roots.claudeRoot, "skills"),
|
|
3574
|
-
path2.join(roots.factoryRoot, "skills")
|
|
3575
|
+
path2.join(roots.factoryRoot, "skills"),
|
|
3576
|
+
path2.join(roots.codexRoot, "skills")
|
|
3575
3577
|
],
|
|
3576
3578
|
kind: "dir"
|
|
3577
3579
|
}
|
|
@@ -3719,26 +3721,91 @@ async function createSource(task) {
|
|
|
3719
3721
|
}
|
|
3720
3722
|
await ensureFile(task.path, DEFAULT_AGENTS);
|
|
3721
3723
|
}
|
|
3722
|
-
|
|
3724
|
+
function commonPath(paths) {
|
|
3725
|
+
if (paths.length === 0)
|
|
3726
|
+
return null;
|
|
3727
|
+
const splitPaths = paths.map((p) => path5.resolve(p).split(path5.sep));
|
|
3728
|
+
const minLen = Math.min(...splitPaths.map((parts) => parts.length));
|
|
3729
|
+
const shared = [];
|
|
3730
|
+
for (let i = 0;i < minLen; i += 1) {
|
|
3731
|
+
const segment = splitPaths[0]?.[i];
|
|
3732
|
+
if (!segment)
|
|
3733
|
+
break;
|
|
3734
|
+
if (splitPaths.every((parts) => parts[i] === segment)) {
|
|
3735
|
+
shared.push(segment);
|
|
3736
|
+
} else {
|
|
3737
|
+
break;
|
|
3738
|
+
}
|
|
3739
|
+
}
|
|
3740
|
+
if (shared.length === 0)
|
|
3741
|
+
return null;
|
|
3742
|
+
if (shared[0] === "")
|
|
3743
|
+
return path5.sep + shared.slice(1).join(path5.sep);
|
|
3744
|
+
return shared.join(path5.sep);
|
|
3745
|
+
}
|
|
3746
|
+
function inferBackupDir(plan) {
|
|
3747
|
+
const sourceDirs = plan.tasks.map((task) => task.type === "ensure-source" ? task.path : task.source).filter(Boolean).map((p) => path5.dirname(p));
|
|
3748
|
+
const root = commonPath(sourceDirs);
|
|
3749
|
+
if (!root)
|
|
3750
|
+
return null;
|
|
3751
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
3752
|
+
return path5.join(root, "backup", timestamp);
|
|
3753
|
+
}
|
|
3754
|
+
async function backupTarget(target, backupDir) {
|
|
3755
|
+
if (!await pathExists(target))
|
|
3756
|
+
return false;
|
|
3757
|
+
const stat = await fs3.promises.lstat(target);
|
|
3758
|
+
if (stat.isSymbolicLink())
|
|
3759
|
+
return false;
|
|
3760
|
+
const root = path5.parse(target).root || path5.sep;
|
|
3761
|
+
const rel = path5.relative(root, target);
|
|
3762
|
+
const dest = path5.join(backupDir, rel);
|
|
3763
|
+
await ensureDir(path5.dirname(dest));
|
|
3764
|
+
try {
|
|
3765
|
+
await fs3.promises.rename(target, dest);
|
|
3766
|
+
return true;
|
|
3767
|
+
} catch (err) {
|
|
3768
|
+
if (err?.code !== "EXDEV")
|
|
3769
|
+
throw err;
|
|
3770
|
+
}
|
|
3771
|
+
if (stat.isDirectory()) {
|
|
3772
|
+
await copyDir(target, dest, true);
|
|
3773
|
+
} else {
|
|
3774
|
+
await copyFile(target, dest, true);
|
|
3775
|
+
}
|
|
3776
|
+
await removePath(target);
|
|
3777
|
+
return true;
|
|
3778
|
+
}
|
|
3779
|
+
async function createLink(source, target, kind, force, backupDir) {
|
|
3723
3780
|
if (await pathExists(target)) {
|
|
3724
3781
|
if (!force)
|
|
3725
|
-
return;
|
|
3782
|
+
return { created: false, backedUp: false };
|
|
3783
|
+
const backedUp = backupDir ? await backupTarget(target, backupDir) : false;
|
|
3726
3784
|
await removePath(target);
|
|
3785
|
+
await ensureDir(path5.dirname(target));
|
|
3786
|
+
const type2 = kind === "dir" ? "junction" : "file";
|
|
3787
|
+
await fs3.promises.symlink(source, target, type2);
|
|
3788
|
+
return { created: true, backedUp };
|
|
3727
3789
|
}
|
|
3728
3790
|
await ensureDir(path5.dirname(target));
|
|
3729
3791
|
const type = kind === "dir" ? "junction" : "file";
|
|
3730
3792
|
await fs3.promises.symlink(source, target, type);
|
|
3793
|
+
return { created: true, backedUp: false };
|
|
3731
3794
|
}
|
|
3732
3795
|
async function applyLinkPlan(plan, opts) {
|
|
3733
3796
|
const force = !!opts?.force;
|
|
3797
|
+
const backupDir = force ? opts?.backupDir || inferBackupDir(plan) || undefined : undefined;
|
|
3734
3798
|
let applied = 0;
|
|
3735
3799
|
let skipped = 0;
|
|
3736
3800
|
let conflicts = 0;
|
|
3801
|
+
let backedUp = 0;
|
|
3737
3802
|
for (const task of plan.tasks) {
|
|
3738
3803
|
if (task.type === "conflict") {
|
|
3739
3804
|
conflicts += 1;
|
|
3740
3805
|
if (force && task.target !== task.source && task.kind) {
|
|
3741
|
-
await createLink(task.source, task.target, task.kind, true);
|
|
3806
|
+
const result = await createLink(task.source, task.target, task.kind, true, backupDir);
|
|
3807
|
+
if (result.backedUp)
|
|
3808
|
+
backedUp += 1;
|
|
3742
3809
|
applied += 1;
|
|
3743
3810
|
}
|
|
3744
3811
|
continue;
|
|
@@ -3754,14 +3821,16 @@ async function applyLinkPlan(plan, opts) {
|
|
|
3754
3821
|
}
|
|
3755
3822
|
if (task.type === "link") {
|
|
3756
3823
|
const before = await pathExists(task.target);
|
|
3757
|
-
await createLink(task.source, task.target, task.kind, force);
|
|
3824
|
+
const result = await createLink(task.source, task.target, task.kind, force, backupDir);
|
|
3825
|
+
if (result.backedUp)
|
|
3826
|
+
backedUp += 1;
|
|
3758
3827
|
if (before && !force)
|
|
3759
3828
|
skipped += 1;
|
|
3760
3829
|
else
|
|
3761
3830
|
applied += 1;
|
|
3762
3831
|
}
|
|
3763
3832
|
}
|
|
3764
|
-
return { applied, skipped, conflicts };
|
|
3833
|
+
return { applied, skipped, conflicts, backupDir, backedUp };
|
|
3765
3834
|
}
|
|
3766
3835
|
|
|
3767
3836
|
// src/core/status.ts
|
|
@@ -3923,7 +3992,8 @@ async function scanMigration(opts) {
|
|
|
3923
3992
|
],
|
|
3924
3993
|
skills: [
|
|
3925
3994
|
{ label: "Claude skills", dir: path8.join(roots.claudeRoot, "skills") },
|
|
3926
|
-
{ label: "Factory skills", dir: path8.join(roots.factoryRoot, "skills") }
|
|
3995
|
+
{ label: "Factory skills", dir: path8.join(roots.factoryRoot, "skills") },
|
|
3996
|
+
{ label: "Codex skills", dir: path8.join(roots.codexRoot, "skills") }
|
|
3927
3997
|
],
|
|
3928
3998
|
agents: [
|
|
3929
3999
|
{ label: "Claude CLAUDE.md", file: path8.join(roots.claudeRoot, "CLAUDE.md") }
|
|
@@ -4006,6 +4076,7 @@ async function scanMigration(opts) {
|
|
|
4006
4076
|
{ label: "factory/hooks", path: path8.join(roots.factoryRoot, "hooks"), kind: "dir" },
|
|
4007
4077
|
{ label: "claude/skills", path: path8.join(roots.claudeRoot, "skills"), kind: "dir" },
|
|
4008
4078
|
{ label: "factory/skills", path: path8.join(roots.factoryRoot, "skills"), kind: "dir" },
|
|
4079
|
+
{ label: "codex/skills", path: path8.join(roots.codexRoot, "skills"), kind: "dir" },
|
|
4009
4080
|
{ label: "claude/CLAUDE.md", path: path8.join(roots.claudeRoot, "CLAUDE.md"), kind: "file" }
|
|
4010
4081
|
];
|
|
4011
4082
|
return { auto, conflicts, backupPaths, canonicalRoot };
|
|
@@ -4605,6 +4676,7 @@ var App = () => {
|
|
|
4605
4676
|
const [skillInput, setSkillInput] = useState("");
|
|
4606
4677
|
const [marketplaceInput, setMarketplaceInput] = useState("");
|
|
4607
4678
|
const [marketplacePlugins, setMarketplacePlugins] = useState([]);
|
|
4679
|
+
const [forceBackupDir, setForceBackupDir] = useState(null);
|
|
4608
4680
|
const [showDetails, setShowDetails] = useState(false);
|
|
4609
4681
|
const [conflictsOnly, setConflictsOnly] = useState(false);
|
|
4610
4682
|
const { stdout } = useStdout3();
|
|
@@ -4622,6 +4694,8 @@ var App = () => {
|
|
|
4622
4694
|
return setStep("scope");
|
|
4623
4695
|
if (step === "status")
|
|
4624
4696
|
return setStep("action");
|
|
4697
|
+
if (step === "force-confirm")
|
|
4698
|
+
return setStep("action");
|
|
4625
4699
|
if (step === "migrate-choice") {
|
|
4626
4700
|
setMigratePlan(null);
|
|
4627
4701
|
setMigrateSelections(new Map);
|
|
@@ -4659,7 +4733,7 @@ var App = () => {
|
|
|
4659
4733
|
{ label: "Apply/repair links", value: "apply" }
|
|
4660
4734
|
];
|
|
4661
4735
|
if (conflicts > 0)
|
|
4662
|
-
items.push({ label: "Force apply (overwrite conflicts)", value: "force-apply" });
|
|
4736
|
+
items.push({ label: "Force apply (backup + overwrite conflicts)", value: "force-apply" });
|
|
4663
4737
|
items.push({ label: "View status", value: "view-status" });
|
|
4664
4738
|
items.push({ label: "Migrate existing content", value: "migrate" });
|
|
4665
4739
|
items.push({ label: "Add skill", value: "add-skill" });
|
|
@@ -4892,13 +4966,33 @@ var App = () => {
|
|
|
4892
4966
|
return;
|
|
4893
4967
|
}
|
|
4894
4968
|
if (item.value === "apply" || item.value === "force-apply") {
|
|
4895
|
-
|
|
4969
|
+
if (item.value === "force-apply") {
|
|
4970
|
+
setBusy("Preparing force apply...");
|
|
4971
|
+
setStep("applying");
|
|
4972
|
+
(async () => {
|
|
4973
|
+
try {
|
|
4974
|
+
if (!scope)
|
|
4975
|
+
return;
|
|
4976
|
+
const roots = resolveRoots({ scope });
|
|
4977
|
+
const timestamp = new Date().toISOString().replace(/[:.]/g, "-");
|
|
4978
|
+
setForceBackupDir(path15.join(roots.canonicalRoot, "backup", timestamp));
|
|
4979
|
+
setStep("force-confirm");
|
|
4980
|
+
} catch (err) {
|
|
4981
|
+
setMessage(err?.message || String(err));
|
|
4982
|
+
setStep("done");
|
|
4983
|
+
} finally {
|
|
4984
|
+
setBusy(null);
|
|
4985
|
+
}
|
|
4986
|
+
})();
|
|
4987
|
+
return;
|
|
4988
|
+
}
|
|
4989
|
+
setBusy("Applying...");
|
|
4896
4990
|
setStep("applying");
|
|
4897
4991
|
(async () => {
|
|
4898
4992
|
try {
|
|
4899
4993
|
if (!plan || !scope)
|
|
4900
4994
|
return;
|
|
4901
|
-
const result = await applyLinkPlan(plan
|
|
4995
|
+
const result = await applyLinkPlan(plan);
|
|
4902
4996
|
setMessage(`Applied: ${result.applied}, Skipped: ${result.skipped}, Conflicts: ${result.conflicts}`);
|
|
4903
4997
|
await refreshStatus(scope);
|
|
4904
4998
|
} catch (err) {
|
|
@@ -4981,6 +5075,70 @@ var App = () => {
|
|
|
4981
5075
|
]
|
|
4982
5076
|
}, undefined, true, undefined, this);
|
|
4983
5077
|
}
|
|
5078
|
+
if (step === "force-confirm") {
|
|
5079
|
+
return /* @__PURE__ */ jsxDEV4(Screen, {
|
|
5080
|
+
children: [
|
|
5081
|
+
/* @__PURE__ */ jsxDEV4(Text2, {
|
|
5082
|
+
color: "yellow",
|
|
5083
|
+
children: "Force apply will overwrite existing real files/directories."
|
|
5084
|
+
}, undefined, false, undefined, this),
|
|
5085
|
+
/* @__PURE__ */ jsxDEV4(Box4, {
|
|
5086
|
+
flexDirection: "column",
|
|
5087
|
+
marginTop: 1,
|
|
5088
|
+
children: [
|
|
5089
|
+
/* @__PURE__ */ jsxDEV4(Text2, {
|
|
5090
|
+
children: "Backup will be created at:"
|
|
5091
|
+
}, undefined, false, undefined, this),
|
|
5092
|
+
/* @__PURE__ */ jsxDEV4(Text2, {
|
|
5093
|
+
dimColor: true,
|
|
5094
|
+
children: forceBackupDir || "(pending)"
|
|
5095
|
+
}, undefined, false, undefined, this)
|
|
5096
|
+
]
|
|
5097
|
+
}, undefined, true, undefined, this),
|
|
5098
|
+
/* @__PURE__ */ jsxDEV4(Box4, {
|
|
5099
|
+
marginTop: 1,
|
|
5100
|
+
flexDirection: "column",
|
|
5101
|
+
children: [
|
|
5102
|
+
/* @__PURE__ */ jsxDEV4(SelectInput, {
|
|
5103
|
+
items: [
|
|
5104
|
+
{ label: "Proceed (create backup + overwrite)", value: "proceed" },
|
|
5105
|
+
{ label: "Cancel", value: "cancel" }
|
|
5106
|
+
],
|
|
5107
|
+
onSelect: (item) => {
|
|
5108
|
+
if (item.value === "cancel") {
|
|
5109
|
+
setForceBackupDir(null);
|
|
5110
|
+
setStep("action");
|
|
5111
|
+
return;
|
|
5112
|
+
}
|
|
5113
|
+
setBusy("Applying (force)...");
|
|
5114
|
+
setStep("applying");
|
|
5115
|
+
(async () => {
|
|
5116
|
+
try {
|
|
5117
|
+
if (!plan || !scope)
|
|
5118
|
+
return;
|
|
5119
|
+
const backupDir = forceBackupDir || undefined;
|
|
5120
|
+
const result = await applyLinkPlan(plan, { force: true, backupDir });
|
|
5121
|
+
const backupNote = result.backedUp > 0 && result.backupDir ? `, Backed up: ${result.backedUp} (${result.backupDir})` : "";
|
|
5122
|
+
setMessage(`Applied: ${result.applied}, Skipped: ${result.skipped}, Conflicts: ${result.conflicts}${backupNote}`);
|
|
5123
|
+
await refreshStatus(scope);
|
|
5124
|
+
} catch (err) {
|
|
5125
|
+
setMessage(err?.message || String(err));
|
|
5126
|
+
} finally {
|
|
5127
|
+
setForceBackupDir(null);
|
|
5128
|
+
setBusy(null);
|
|
5129
|
+
setStep("done");
|
|
5130
|
+
}
|
|
5131
|
+
})();
|
|
5132
|
+
}
|
|
5133
|
+
}, undefined, false, undefined, this),
|
|
5134
|
+
/* @__PURE__ */ jsxDEV4(HelpBar, {
|
|
5135
|
+
text: "Enter to confirm · Esc to cancel · q to quit"
|
|
5136
|
+
}, undefined, false, undefined, this)
|
|
5137
|
+
]
|
|
5138
|
+
}, undefined, true, undefined, this)
|
|
5139
|
+
]
|
|
5140
|
+
}, undefined, true, undefined, this);
|
|
5141
|
+
}
|
|
4984
5142
|
if (step === "migrate-choice" && migratePlan) {
|
|
4985
5143
|
const conflict = migratePlan.conflicts[migrateIndex];
|
|
4986
5144
|
if (!conflict) {
|