Is there a way to copy a project + tasks from the Mac clipboard to the top of the TaskPaper document?

Hello all!

I have a project + tasks on the Mac clipboard (alternatively, it could be placed into a Keyboard Maestro variable).

Rather than manually pasting it into my TaskPaper document, I want to automate it with JavaScript.

I’d like to place the clipboard contents at the top of the TaskPaper document, if possible.


Just to check, you don’t mean the currently open TaskPaper document ?

( you are working mainly with just one TP document, which may not be open at the time of the prepend from clipboard ?)

If that’s the case, you could, I think use the

Place project items in TaskPaper 3 file

KM plugin which we worked on once before.

Something like this ?

Untitled 2

Otherwise, if you just want to prepend from clipboard text to a text file, you might be able to get quite far with a shell command line combining pbpaste with some permutation of the approaches talked through here:

[Unix command to prepend text to a file - Stack Overflow](Unix command to prepend text to a file - Stack Overflow)

In something like [CodeRunner](, you could perhaps experiment with things like this:


touch text.txt
printf '%s\n%s\n' "$(pbpaste)" "$(cat text.txt)" > text.txt
cat text.txt

Hi @complexpoint !

Well, let me be frank. My ulterior goal here is to learn how to repurpose or rewrite some code that Jesse wrote. I should have been upfront about that, as Jesse probably knows the answer off the top of his head. Jesse never fails to inspire and impress me with his coding. That is not flattery, that is honesty.

I like to tinker, and when I look at your code, I am in awe, and simultaneously, at a loss. Your code is beyond me—which hinders my (admittedly) feeble ability to tinker.

Jessie’s code is clearer to me—I can sometimes alter it to suit my purposes (bonus!). Along the way, I learn a bit more about JavaScript (double bonus!!). And I get to make a new TaskPaper macro (triple bonus!!!). As a tinkerer, this type of stuff is important to me.

So, following is the code that inspired my current tinkering project. If you, or Jesse, have any suggestions, my tinkering fingers are listening.

This code works with two opened TaskPaper documents. It grabs the project that the text cursor is in (the cursor could be on a project, task or sub-task) and it moves it to the top of the second TaskPaper document. It works like a charm, as long as both TaskPaper documents have text in them (which is fine for me, as I never use a completely empty TaskPaper document).

function TaskPaperExportContext(editor, options) {
	let outline = editor.outline
	let selection = editor.selection
	let currentItem = selection.startItem
	let owningProjects = outline.evaluateItemPath('ancestor-or-self::project', currentItem)
	let owningProject = owningProjects[owningProjects.length - 1]
	if (owningProject) {
		return ItemSerializer.serializeItems(owningProject.branchItems)

function TaskPaperImportContext(editor, options) {
	let outline = editor.outline
	let imported = ItemSerializer.deserializeItems(options, outline)
	outline.insertItemsBefore(imported, null)
	let root = editor.outline.root
	root.insertChildrenBefore(imported, root.firstChild)

var exportedText = Application('TaskPaper').documents[0].evaluate({
  script: TaskPaperExportContext.toString()

if (exportedText) {
	  script: TaskPaperImportContext.toString(),
	  withOptions: exportedText,

So the following is my tinkering goal:

Take the above code, and alter it to take a project with tasks from the Mac clipboard (or a Keyboard Maestro variable) and do what it already does (place that project and tasks at the top of the opened TaskPaper document).

I want to remove the code that selects the project (which I think is entirely within the first function). And I want to use this with one TaskPaper document instead of two. I think that I want to work solely with the code in the second function, but all of my attempts thus far have met with failure.

Once I have the above figured out, I have four new macros that will benefit from my tinkering.


1 Like

Are you capturing the text from the clipboard and then bringing it in as a property of options ?

( and the goal, then, is to bring the text into the active document, rather than into a file that might not be open in TaskPaper ? )

1 Like

The text can come in from the clipboard or a Keyboard Maestro variable. I think it is to be a property of options, but now we are getting into territory that I am lost in.

Yes, the goal is to bring the text into the active TaskPaper document.

Well, a rough draft here, for you to pick through and probe us about, and then improve : - )

(() => {
    'use strict';

    ObjC.import('AppKit');  // for the clipboard.

    // main :: IO ()
    const main = () => {

        // clip :: IO String -- Could be empty. Source out of view.
        const clip = clipboardText().trim();

        return 0 < clip.length ? (() => {
                taskPaper = Application('TaskPaper'),
                documents = taskPaper.documents;

            return 0 < documents.length ? (
                    script: taskPaperImportClip.toString(),

                    // Defining the 'options' dictionary of
                    // key:value pairs seen in the TP Context.
                    withOptions: {
                        clipText: clip
            ) : 'No documents open in TaskPaper';
        })() : 'No text found in the clipboard.';

    // ---------------- TASKPAPER CONTEXT ----------------

    // taskPaperImportClip :: (TP Editor, Dict) -> IO String
    const taskPaperImportClip = (editor, options) => {
            outline = editor.outline,
            imports = ItemSerializer.deserializeItems(
                options.clipText + '\n', 
        return (
            // A (⌘Z reversible) change in the 
            // TaskPaper editing buffer,
            outline.groupUndoAndChanges(() =>
            // and the imported string passed back to JXA,
            // if all has gone well.

    // ------------------- JXA CONTEXT -------------------

    // clipboardText :: () -> IO String
    const clipboardText = () =>
        // Any plain text in the clipboard.

    // MAIN ---
    return main();

That is great @complexpoint ! Thank you!

I noticed there was an extra line after the imported text, so i changed:

            options.clipText + '\n', 



Added to wiki

1 Like