<NME> path-inventory.cocoascript
<BEF> function PathInventory () {}



PathInventory.returnProjectNameFromPath = function(projectPath) {
  var currentOpenedProjectCleanPath = [[projectPath componentsSeparatedByString:".framer"] firstObject]
  var currentOpenedProjectName = [[currentOpenedProjectCleanPath componentsSeparatedByString:"/"] lastObject]
  return currentOpenedProjectName
}

PathInventory.returnProjectPathFromURLPath = function(projectPath) {
    var cleanPath = projectPath
    if ([[projectPath componentsSeparatedByString:"file://"] count] > 1) { cleanPath = [[projectPath componentsSeparatedByString:"file://"] lastObject] }
    return [cleanPath stringByRemovingPercentEncoding]
}


PathInventory.returnOpenedFramerProjects = function() {
    var openedFramerProjects = NSMutableDictionary.new()
    var relativePlistPath = NSHomeDirectory() + "/Library/Saved Application State/com.motif.framer.savedState/windows.plist"

    var plistData = [NSData dataWithContentsOfFile:relativePlistPath];
    if (!plistData) { return openedFramerProjects }

    var plistRoot = nil
    var plistSerializationError = nil;

    id plist = [NSPropertyListSerialization propertyListWithData:plistData options:NSPropertyListMutableContainersAndLeaves format:nil error:plistSerializationError];


    if (!plistSerializationError) { plistRoot = plist; }
    else { return openedFramerProjects }

    for (var i = 0; i < plistRoot.count(); i++) {
        var currenPlistBlock = plistRoot[i]

        if ([currenPlistBlock objectForKey:"NSRepresentedURL"]) {
            var currentFramerProjectPath = currenPlistBlock["NSRepresentedURL"]
            var cleanFramerProjectPath = PathInventory.returnProjectPathFromURLPath(currentFramerProjectPath)
            if (cleanFramerProjectPath != "") { openedFramerProjects[currentFramerProjectPath] = cleanFramerProjectPath }
        }
    }

    return openedFramerProjects
}


PathInventory.createPathModal = function() {
  var openedFramerProjects = PathInventory.returnOpenedFramerProjects()

  if (openedFramerProjects.count() == 0) { selectProjectWithNoOpened() }

  ToolbarInventory.setContentView()
}
    //configuration.foldedCodeRanges = newRange

    [data writeToFile:configFile atomically:true];

    return newRange

}

<MSG> Added some methods to work with Folds

<DFF> @@ -1,3 +1,5 @@
+@import "Framer-Inventory-for-Sketch/Framer Inventory.sketchplugin/Contents/Sketch/inventory/libs/testing/logger.js"
+
 function PathInventory () {}
 
 
@@ -52,3 +54,80 @@ PathInventory.createPathModal = function() {
 
   ToolbarInventory.setContentView()
 }
+
+
+
+PathInventory.readProjectCode = function(prototypePath) {
+  if (prototypePath == "") { return "" }
+  var codeFilePath = prototypePath + "/app.coffee"
+
+  if ([[NSFileManager defaultManager] fileExistsAtPath:codeFilePath]) {
+    var currentString = [NSString stringWithContentsOfFile:codeFilePath encoding:NSUTF8StringEncoding error:nil]
+    return currentString
+  }
+  return ""
+}
+
+PathInventory.replaceProjectCode = function(prototypePath, newCode) {
+  if (prototypePath == "") { return }
+
+  var codeFilePath = prototypePath + "/app.coffee"
+  writeTextToFile(newCode, codeFilePath)
+  return
+}
+
+
+PathInventory.updateFoldRanges = function(prototypePath, shiftBy, callback) {
+    const extractFirstNumber = /\d+/
+    const configFile = prototypePath + '/framer/config.json'
+    // const codeFilePath = prototypePath + "/app.coffee"
+
+    var data = [NSData dataWithContentsOfFile:configFile];
+    var configuration = [NSJSONSerialization JSONObjectWithData:data options:nil error:nil];
+    log([configuration className])
+
+    var ranges = [configuration objectForKey:"foldedCodeRanges"]
+    log(ranges)
+
+
+    const newRange = []
+
+    for (var i = 0; i < ranges.count(); i++) {
+        var rangeString = [ranges objectAtIndex:i]
+
+        // var firstNumber = parseInt( extractFirstNumber.exec(range)[0] )
+        var matches = rangeString.match(/\d+/g)
+        var firstNumber = Number(matches[0])
+        var firstNumberShift = firstNumber + shiftBy
+        newRange.push(rangeString.replace(matches[0], firstNumberShift) )
+    }
+
+
+    /*
+    Configuration for code folds is an array of ranges (strings):
+    '{ startingCharacterNumber, numberOfCharactersInFold }'
+    e.g.:
+    "foldedCodeRanges": [
+        "{68, 1039}",
+        "{1108, 2066}"
+    ]
+    With every new require() command un/written to app.coffee, we need to shift
+    code fold ranges to maintain the folds in Framer Studio.
+    e.g. when writing require string that is 68 characters long,
+         '{1, 1039}' becomes '{69, 1039}'
+    */
+
+    // configuration.foldedCodeRanges.forEach(range => {
+       // const firstNumber = parseInt( extractFirstNumber.exec(range)[0] )
+       // const firstNumberShift = firstNumber + shiftBy
+
+        // newRange.push( range.replace(extractFirstNumber, firstNumberShift) )
+    //})
+
+    //configuration.foldedCodeRanges = newRange
+
+    [data writeToFile:configFile atomically:true];
+
+    return newRange
+
+}

Added some methods to work with Folds