Here's a utility function I wrote that I ended up using. Note that I'm filtering out some read only, static, and utility columns used by SharePoint (because I also use this for copying objects between lists), but that "filter" can easily be removed. Posting here in case others find it helpful.
/**
* Gets all fields' static names from list
* @param {string} listName
* @param {string} [webURL]
* @returns {{}}
*/
function GetAllListFieldStaticNames(listName, webURL){
if(webURL == undefined || webURL.trim() == "") webURL = ""; // default to blank
var fields = {};
$().SPServices({
operation: "GetList",
listName: listName,
webURL: webURL,
async: false,
completefunc: function(xData, Status) {
$(xData.responseXML).SPFilterNode("Field").each(function() {
var node = $(this);
if((node.attr("ReadOnly") != "TRUE" && node.attr("Hidden") != "TRUE"
&& node.attr("StaticName") != "Attachments" && node.attr("StaticName") != undefined)
|| node.attr("Name") == "ID"){
fields[node.attr("StaticName")] = node.attr("DisplayName");
}
})
}
});
return fields;
}