├── .editorconfig
├── .gitignore
├── .prettierrc
├── EvernoteSync.js
├── README.md
├── RoamPrivateApi.js
├── Sync.js
├── examples
├── cmd.js
├── download.js
├── import-data.js
├── quick_capture.js
└── sync_evernote.js
├── package-lock.json
├── package.json
└── secrets-template.json
/.editorconfig:
--------------------------------------------------------------------------------
1 | # http://editorconfig.org
2 | root = true
3 |
4 | [*]
5 | # yes, we use tabs
6 | indent_style = tab
7 |
8 | # To control tab size in github diffs
9 | # See https://github.com/isaacs/github/issues/170#issuecomment-150489692
10 | indent_size = 4
11 |
12 | end_of_line = lf
13 | charset = utf-8
14 | trim_trailing_whitespace = true
15 | insert_final_newline = true
16 |
17 | [*.md]
18 | trim_trailing_whitespace = false
19 |
20 | [*.yml]
21 | indent_size = 2
22 | indent_style = space
23 |
--------------------------------------------------------------------------------
/.gitignore:
--------------------------------------------------------------------------------
1 | tmp
2 | secrets.json
3 | .DS_Store
4 | node_modules
5 |
--------------------------------------------------------------------------------
/.prettierrc:
--------------------------------------------------------------------------------
1 | useTabs: true
2 | tabWidth: 2
3 | printWidth: 100
4 | singleQuote: true
5 | bracketSpacing: true
6 | parenSpacing: true
7 | jsxBracketSameLine: false
8 | semi: true
9 |
--------------------------------------------------------------------------------
/EvernoteSync.js:
--------------------------------------------------------------------------------
1 | const Evernote = require( 'evernote' );
2 | const ENML = require( 'enml-js' );
3 | const RoamSyncAdapter = require( './Sync' );
4 | const moment = require( 'moment' );
5 |
6 | class EvernoteSyncAdapter extends RoamSyncAdapter {
7 | EvernoteClient = null;
8 | NoteStore = null;
9 | notebookGuid = '';
10 | defaultNotebook = '';
11 | timeOut = 500;
12 | mapping;
13 | backlinks = {};
14 | notesBeingImported = [];
15 |
16 | wrapItem( string, title ) {
17 | return `
${ string }`;
18 | }
19 | wrapText( string, title ) {
20 | const backlinks = [];
21 | string = this.htmlEntities( string );
22 | string = string.replace( '{{[[TODO]]}}', '[[TODO]]' ); // will not achieve the same goal.
23 | string = string.replace( '{{{[[DONE]]}}}}', '' );
24 | string = string.replace( /\!\[([^\]]*?)\]\(([^\)]+)\)/g, '
' );
25 | string = string.replace( /\[([^\]]+)\]\((http|evernote)([^\)]+)\)/g, '$1' );
26 | string = string.replace( /(^|[^"?/])((evernote|http|https|mailto):[a-zA-Z0-9\/.\?\&=;\-_]+)/g, '$1$2' );
27 | string = string.replace( /\*\*([^*]+)\*\*/g, '$1' );
28 | string = string.replace( /__([^_]+)__/g, '$1' );
29 | string = string.replace( /#?\[\[([^\]]+)\]\]/g, ( match, contents ) => {
30 | const targetPage = this.titleMapping.get( contents );
31 | if (
32 | targetPage &&
33 | targetPage.uid &&
34 | this.mapping.get( targetPage.uid )
35 | ) {
36 | const guid = this.mapping.get( targetPage.uid ).guid;
37 | const url = this.getNoteUrl( guid );
38 | backlinks.push( contents );
39 | return `${ contents }`;
40 | }
41 | return match;
42 | } );
43 | this.addBacklink( backlinks, title, string );
44 | return string;
45 | }
46 |
47 | wrapChildren( childrenString ) {
48 | childrenString = childrenString.join( '' );
49 | return ``;
50 | }
51 | htmlEntities( str ) {
52 | return String( str )
53 | .replace( /&/g, '&' )
54 | .replace( //g, '>' )
56 | .replace( /"/g, '"' );
57 | }
58 | htmlEntitiesDecode( str ) {
59 | return String( str )
60 | .replace( '&', '&' )
61 | .replace( '<', '<' )
62 | .replace( '>', '>' )
63 | .replace( '"', '"' );
64 | }
65 | wrapNote( noteBody ) {
66 | noteBody = noteBody.replace( '&Amp;', '&' ); // Readwise artifact
67 | var nBody = '';
68 | nBody += '';
69 | nBody += '' + noteBody;
70 | nBody += '';
71 | return nBody;
72 | }
73 |
74 | makeNote( noteTitle, noteBody, url, uid ) {
75 | // Create note object
76 | var nBody = this.wrapNote( noteBody );
77 | let foundNote;
78 |
79 | if ( this.mapping.get( uid ) ) {
80 | foundNote = Promise.resolve( {
81 | totalNotes: 1,
82 | notes: [ this.mapping.get( uid ) ]
83 | } );
84 | } else {
85 | foundNote = this.findPreviousNote( url );
86 | }
87 |
88 | const foundNotes = ( notes ) => {
89 | if ( ! notes.totalNotes ) {
90 | return Promise.resolve( new Evernote.Types.Note() );
91 | }
92 | if ( notes.notes[0].contentLength === nBody.length ) {
93 | //console.log( '[[' + noteTitle + ']]: content length has not changed, skipping' );
94 | return Promise.resolve( false );
95 | }
96 | // These request we want to rate limit.
97 | return new Promise( ( resolve, reject ) => setTimeout( () => {
98 | this.NoteStore.getNote( notes.notes[0].guid, true, false, false, false )
99 | .catch( ( err ) => {
100 | console.warn( '[[' + noteTitle + ']] :Took too long to pull note ' );
101 | if ( err.code === 'ETIMEDOUT' ) {
102 | this.timeOut = this.timeOut * 2;
103 | console.log( 'Exponential Backoff increased: ', this.timeOut );
104 | }
105 | if ( this.timeOut > 60000 * 5 ) {
106 | console.error( 'Timeot reached 5 minutes. Exiting' );
107 | require('process').exit();
108 | }
109 | resolve( foundNotes( notes ) );
110 | } )
111 | .then( result => resolve( result ) );
112 | }, this.timeOut ) );
113 |
114 | }
115 |
116 | foundNote.then( foundNotes )
117 | .then( ( ourNote ) => {
118 | if ( ! ourNote ) {
119 | return Promise.resolve( false );
120 | }
121 | // Build body of note
122 | if ( ourNote.content && ourNote.content === nBody ) {
123 | console.log( '[[' + noteTitle + ']]: has not changed, skipping' );
124 | return Promise.resolve( ourNote );
125 | }
126 | ourNote.content = nBody;
127 |
128 | const attributes = new Evernote.Types.NoteAttributes();
129 | attributes.contentClass = 'piszek.roam';
130 | attributes.sourceURL = url;
131 | attributes.sourceApplication = 'piszek.roam';
132 | ourNote.attributes = attributes;
133 | ourNote.title = this.htmlEntities( noteTitle.trim() );
134 |
135 | if ( ourNote.guid ) {
136 | console.log( '[[' + noteTitle + ']]: updating' );
137 | ourNote.updated = Date.now();
138 | return this.NoteStore.updateNote( ourNote ).catch( err => {
139 | console.log( 'Update note problem', err, nBody );
140 | return Promise.resolve( false );
141 | } );
142 | } else {
143 | // parentNotebook is optional; if omitted, default notebook is used
144 | if ( this.notebookGuid ) {
145 | ourNote.notebookGuid = this.notebookGuid;
146 | }
147 | console.log( '[[' + noteTitle + ']] Creating new note ' );
148 | return this.NoteStore.createNote( ourNote ).then( ( note ) => {
149 | this.mapping.set( uid, {
150 | guid: note.guid,
151 | title: note.title,
152 | contentLength: note.contentLength
153 | } );
154 | return Promise.resolve( note );
155 | } ).catch( err => {
156 | console.warn( 'Error creating note:', err );
157 | Promise.resolve( false );
158 | });
159 | }
160 | } );
161 | }
162 |
163 | findNotebook() {
164 | return new Promise( ( resolve, reject ) => {
165 | this.NoteStore.listNotebooks().then( ( notebooks ) => {
166 | const filtered = notebooks.filter( ( nb ) => nb.name === 'Roam' );
167 | const def = notebooks.filter( ( nb ) => nb.defaultNotebook );
168 | this.defaultNotebook = def[ 0 ].guid
169 | if ( filtered ) {
170 | this.notebookGuid = filtered[ 0 ].guid;
171 | resolve( this.notebookGuid );
172 | } else {
173 | console.warn( 'You have to have a notebook named "Roam"' );
174 | reject( 'You have to have a notebook named "Roam"' );
175 | }
176 | } );
177 | } );
178 | }
179 | getNotesToImport() {
180 | const filter = new Evernote.NoteStore.NoteFilter();
181 | const spec = new Evernote.NoteStore.NotesMetadataResultSpec();
182 | spec.includeTitle = false;
183 | filter.words = '-tag:RoamImported';
184 | filter.notebookGuid = this.defaultNotebook;
185 | const batchCount = 100;
186 | const loadMoreNotes = ( result ) => {
187 | if ( result.notes ) {
188 | this.notesBeingImported = this.notesBeingImported.concat(
189 | result.notes.map( ( note ) =>
190 | this.NoteStore.getNote( note.guid, true, false, false, false )
191 | )
192 | );
193 | }
194 | if ( result.startIndex < result.totalNotes ) {
195 | return this.NoteStore.findNotesMetadata(
196 | filter,
197 | result.startIndex + result.notes.length,
198 | batchCount,
199 | spec
200 | ).then( loadMoreNotes );
201 | } else {
202 | return Promise.resolve( this.mapping );
203 | }
204 | };
205 | return this.NoteStore.findNotesMetadata( filter, 0, batchCount, spec )
206 | .then( loadMoreNotes )
207 | .then( () =>
208 | Promise.all( this.notesBeingImported ).then( ( notes ) => {
209 | this.notesBeingImported = notes;
210 | return Promise.resolve( notes );
211 | } )
212 | );
213 | }
214 | adjustTitle( title, force ) {
215 | if ( force || title === 'Bez tytułu' || title === 'Untitled Note' ) {
216 | return moment( new Date() ).format( 'MMMM Do, YYYY' );
217 | } else {
218 | return title;
219 | }
220 | }
221 | getRoamPayload() {
222 | return this.notesBeingImported.map( ( note ) => {
223 | const md = ENML.PlainTextOfENML( note.content );
224 | return {
225 | title: this.adjustTitle( note.title, true ),
226 | children: [ { string: 'Imported from Evernote: [' + note.title + '](' + this.getNoteUrl( note.guid ) + ')', children: [ { string: md } ] } ],
227 | };
228 | } );
229 | }
230 | cleanupImportNotes() {
231 | return Promise.all(
232 | this.notesBeingImported.map( ( note ) => {
233 | note.tagGuids = [];
234 | note.tagNames = [ 'RoamImported' ];
235 | return this.NoteStore.updateNote( note );
236 | } )
237 | );
238 | }
239 |
240 | findPreviousNote( url ) {
241 | const filter = new Evernote.NoteStore.NoteFilter();
242 | const spec = new Evernote.NoteStore.NotesMetadataResultSpec();
243 | spec.includeContentLength = true;
244 | spec.includeTitle = true;
245 | filter.words = `sourceUrl:"${url}" contentClass:piszek.roam`;
246 | return this.NoteStore.findNotesMetadata( filter, 0, 1, spec );
247 | }
248 |
249 | loadPreviousNotes() {
250 | let duplicates = 0;
251 | const filter = new Evernote.NoteStore.NoteFilter();
252 | const spec = new Evernote.NoteStore.NotesMetadataResultSpec();
253 | spec.includeTitle = true;
254 | spec.includeContentLength = true;
255 | spec.includeDeleted = false;
256 | spec.includeAttributes = true;
257 | filter.words = 'contentClass:piszek.roam';
258 | const batchCount = 100;
259 |
260 | const loadMoreNotes = ( result ) => {
261 | if ( result.notes ) {
262 | result.notes.forEach( ( note ) => {
263 | if ( ! note.attributes.sourceURL ) {
264 | console.log( note.title , 'no src url' );
265 | return;
266 | }
267 | const match = note.attributes.sourceURL.match( /https:\/\/roamresearch\.com\/\#\/app\/[a-z]+\/page\/([a-zA-Z0-9_-]+)/);
268 | if ( ! match ) {
269 | console.log( note.title ,'no match', note.attributes.sourceURL );
270 | return;
271 | }
272 |
273 | const uid = match[1];
274 |
275 | if ( this.mapping.get( uid ) && this.mapping.get( uid ).guid !== note.guid ) {
276 | console.log(
277 | '[[' + this.mapping.get( uid ).title + ']]',
278 | 'Note is a duplicate ',
279 | this.getNoteUrl( this.mapping.get( uid ).guid ),
280 | this.getNoteUrl( note.guid )
281 | );
282 | // this.NoteStore.deleteNote( note.guid );
283 | } else {
284 | this.mapping.set( uid, {
285 | guid: note.guid,
286 | title: note.title,
287 | contentLength: note.contentLength
288 | } );
289 | }
290 | } );
291 | }
292 | if ( result.startIndex < result.totalNotes ) {
293 | return this.NoteStore.findNotesMetadata(
294 | filter,
295 | result.startIndex + result.notes.length,
296 | batchCount,
297 | spec
298 | ).then( loadMoreNotes );
299 | } else {
300 | // console.log( batchCount );
301 | return Promise.resolve( this.mapping );
302 | }
303 | };
304 | return this.NoteStore.findNotesMetadata( filter, 0, batchCount, spec ).then( loadMoreNotes );
305 | }
306 |
307 | addBacklink( titles, target, text ) {
308 | titles.forEach( ( title ) => {
309 | if ( ! this.backlinks[ title ] ) {
310 | this.backlinks[ title ] = [];
311 | }
312 | this.backlinks[ title ].push( {
313 | target: target,
314 | text: text,
315 | } );
316 | } );
317 | }
318 | getNoteUrl( guid ) {
319 | return `evernote:///view/${ this.user.id }/${ this.user.shardId }/${ guid }/${ guid }/`;
320 | }
321 | init( prevData = {} ) {
322 | this.mapping = new Map( prevData );
323 | this.EvernoteClient = new Evernote.Client( this.credentials );
324 | this.NoteStore = this.EvernoteClient.getNoteStore();
325 | const loadNotesPromise = this.loadPreviousNotes();
326 | loadNotesPromise.then( () => console.log( 'Loaded previous notes: ', this.mapping.size ) );
327 |
328 | return Promise.all( [
329 | new Promise( ( resolve, reject ) => {
330 | this.EvernoteClient.getUserStore()
331 | .getUser()
332 | .then( ( user ) => {
333 | this.user = user;
334 | resolve();
335 | } );
336 | } ),
337 | this.findNotebook().catch( ( err ) => console.log( 'Cannot find notebook Roam:', err ) ),
338 | loadNotesPromise,
339 | ] );
340 | }
341 |
342 | sync( pages ) {
343 | // This can potentially introduce a race condition, but it's unlikely. Famous last words.
344 | var p = Promise.resolve();
345 | pages.forEach( ( page ) => {
346 | p = p
347 | .then( () => this.syncPage( page ) )
348 | .catch( ( err ) => console.warn( 'Problem with syncing page ' + page.title, err, page.content ) );
349 | } );
350 | return p.then( () => Promise.resolve( this.mapping ) );
351 | }
352 |
353 | syncPage( page ) {
354 | let url;
355 | if ( page.uid ) {
356 | url = `https://roamresearch.com/#/app/${this.graphName}/page/` + page.uid;
357 | } else {
358 | console.warn( "Page must have UIDs and this one does not. Using title as a backup." );
359 | url = page.title;
360 | }
361 | let newContent = page.content;
362 | if ( this.backlinks[ page.title ] ) {
363 | const list = this.backlinks[ page.title ]
364 | .map( ( target ) => {
365 | let reference = '[[' + target.target + ']]';
366 | const targetPage = this.titleMapping.get( target.target );
367 | if (
368 | targetPage &&
369 | targetPage.uid &&
370 | this.mapping.get( targetPage.uid )
371 | ) {
372 | reference =
373 | '' +
376 | target.target +
377 | '';
378 | }
379 | return '' + reference + ': ' + target.text + '';
380 | } )
381 | .join( '' );
382 |
383 | const backlinks = 'Linked References
';
384 | newContent = page.content + backlinks;
385 | }
386 |
387 | return this.makeNote( page.title, newContent, url, page.uid );
388 | }
389 | }
390 |
391 | module.exports = EvernoteSyncAdapter;
392 |
--------------------------------------------------------------------------------
/README.md:
--------------------------------------------------------------------------------
1 | ## Roam Private API
2 |
3 | This project exposes command line tool (`roam-api`) and a `node` library to connect Roam Research to your other software. You can use it in bash scripts, Github actions, or as a dependency of your project.
4 | ## How does it work?
5 |
6 | It looks like Roam is not providing a REST API any time soon. If you want to bridge Roam with your other software, you can do so from within Roam (with JavaScript), but that has limited number of use cases.
7 | Without a REST API, this project launches an invisible browser and performs automated actions, just as you would manually. No need to install chrome, this library comes with one. It uses your login and password, so **this won't work if you are using Google login**.
8 | It wraps around import/export functionality and actions exposed via `roamAlphaApi`.
9 | ## Command line tool `roam-api`
10 |
11 | This package exposes a `roam-api` tool in your system. You can use it to automate Roam and bridge other systems with your Roam graph.
12 |
13 | ### Installation:
14 | This entire library is build on node, so you need `node v12` and `npm v6` in your system. You can install the package the following way:
15 | ```
16 | npm i -g roam-research-private-api
17 | ```
18 |
19 | Now you can use a variety of commands. All command take the following arguments, which you can also set as environmental variables:
20 | - `-g`, `--graph` or env variable `ROAM_API_GRAPH` - this is your graph name
21 | - `-e`, `--email` or env variable `ROAM_API_EMAIL` - email to log into your Roam
22 | - `-p`, `--password` or env variable `ROAM_API_PASSWORD` - password to your Roam.
23 |
24 | #### `roam-api export` will export your Roam graph to a directory of your choice.
25 |
26 | This example will export the graph to your desktop. It will appear as "db.json".
27 | ```
28 | roam-api export ~/Desktop
29 | ```
30 |
31 | It can also push the new version of the graph to an URL of your choosing. That way, you can upload the graph to some other system or use it with Zapier and similar tools.
32 | ```
33 | roam-api export ~/Desktop http://secret.url?token=secret_token.
34 | ```
35 |
36 | #### `roam-api search` will search your Roam graph for a phrase:
37 |
38 | ```
39 | roam-api search "potatoes"
40 | ```
41 |
42 | Result will be JSON array of objects `{ blockUid, pageTitle, string }`
43 |
44 | #### `roam-api-query` will let you do a full Datalog query.
45 |
46 | This will find all block uids in your database which have the content "Import".
47 | ```
48 | roam-api query '[:find ?uid :where [?b :block/string "Import"] [?b :block/uid ?uid]]'
49 | ```
50 |
51 | Check out [this fantastic article](https://www.zsolt.blog/2021/01/Roam-Data-Structure-Query.html) to know more about the Roam data structure.
52 |
53 | #### `roam-api create` create a block under specified uid. If no uid is provided, it will be inserted into your daily page:
54 |
55 | ```
56 | roam-api create "This will be prepended to my daily page"
57 | ```
58 |
59 | ## Library to use in your project.
60 |
61 | As mentioned, this is also a library that you can use within your project. Here are examples on how to do so:
62 |
63 | - [All the functionality in the roam-api tool](https://github.com/artpi/roam-research-private-api/blob/master/examples/cmd.js)
64 | - [Sync your Roam graph to Evernote](https://github.com/artpi/roam-research-private-api/blob/master/examples/sync_evernote.js) - you can also use command-line utility `roam-evernote-sync`.
65 | - [Import arbitrary data into any note](https://github.com/artpi/roam-research-private-api/blob/master/examples/import-data.js)
66 | - [Send note to Roam using the Quick Capture feature](https://github.com/artpi/roam-research-private-api/blob/master/examples/quick_capture.js)
67 |
68 | ###
69 |
70 | Pull requests welcome and I take no responsibility in case this messes up your Roam Graph :).
71 |
--------------------------------------------------------------------------------
/RoamPrivateApi.js:
--------------------------------------------------------------------------------
1 | const puppeteer = require( 'puppeteer' );
2 | const fs = require( 'fs' );
3 | const path = require('path');
4 | const os = require( 'os' );
5 | const unzip = require( 'node-unzip-2' );
6 | const { isString } = require( 'util' );
7 | const moment = require( 'moment' );
8 |
9 | /**
10 | * This class represents wraps Puppeteer and exposes a few methods useful in manipulating Roam Research.
11 | */
12 | class RoamPrivateApi {
13 | options;
14 | browser;
15 | page;
16 | db;
17 | login;
18 | pass;
19 |
20 | constructor( db, login, pass, options = { headless: true, folder: null, nodownload: false } ) {
21 | // If you dont pass folder option, we will use the system tmp directory.
22 | if ( ! options.folder ) {
23 | options.folder = os.tmpdir();
24 | }
25 | options.folder = fs.realpathSync( options.folder );
26 | this.db = db;
27 | this.login = login;
28 | this.pass = pass;
29 | this.options = options;
30 | }
31 |
32 | /**
33 | * Run a query on the new Roam Alpha API object.
34 | * More about the query syntax: https://www.zsolt.blog/2021/01/Roam-Data-Structure-Query.html
35 | * @param {string} query - datalog query.
36 | */
37 | async runQuery( query ) {
38 | return await this.page.evaluate( ( query ) => {
39 | if ( ! window.roamAlphaAPI ) {
40 | return Promise.reject( 'No Roam API detected' );
41 | }
42 | const result = window.roamAlphaAPI.q( query );
43 | console.log( result );
44 | return Promise.resolve( result );
45 | }, query );
46 | }
47 |
48 | /**
49 | * Create a block as a child of block.
50 | * @param {string} text
51 | * @param {uid} uid - parent UID where block has to be inserted.
52 | */
53 | async createBlock( text, uid ) {
54 | const result = await this.page.evaluate( ( text, uid ) => {
55 | if ( ! window.roamAlphaAPI ) {
56 | return Promise.reject( 'No Roam API detected' );
57 | }
58 | const result = window.roamAlphaAPI.createBlock(
59 | {"location":
60 | {"parent-uid": uid,
61 | "order": 0},
62 | "block":
63 | {"string": text}})
64 | console.log( result );
65 | return Promise.resolve( result );
66 | }, text, uid );
67 | // Let's give time to sync.
68 | await this.page.waitForTimeout( 1000 );
69 | return result;
70 | }
71 |
72 | /**
73 | * Delete blocks matching the query. Hass some protections, but
74 | * THIS IS VERY UNSAFE. DO NOT USE THIS IF YOU ARE NOT 100% SURE WHAT YOU ARE DOING
75 | * @param {string} query - datalog query to find blocks to delete. Has to return block uid.
76 | * @param {int} limit - limit deleting to this many blocks. Default is 1.
77 | */
78 | async deleteBlocksMatchingQuery( query, limit ) {
79 | if ( ! limit ) {
80 | limit = 1;
81 | }
82 | return await this.page.evaluate( ( query, limit ) => {
83 | if ( ! window.roamAlphaAPI ) {
84 | return Promise.reject( 'No Roam API detected' );
85 | }
86 | const result = window.roamAlphaAPI.q( query );
87 | console.log( result );
88 | if ( result.length > 100 ) {
89 | return Promise.reject( 'Too many results. Is your query ok?' );
90 |
91 | }
92 | const limited = result.slice( 0, limit );
93 | limited.forEach( ( block ) => {
94 | const id = block[0];
95 | console.log( 'DELETING', id );
96 | window.roamAlphaAPI.deleteBlock( { block: { uid: id } } );
97 | } );
98 | return Promise.resolve( limited );
99 | }, query, limit );
100 | }
101 |
102 | /**
103 | * Returns a query to find blocks with exact text on the page with title.
104 | * Useful with conjuction with deleteBlocksMatchingQuery,
105 | * @param {string} text - Exact text in the block.
106 | * @param {*} pageTitle - page title to find the blocks in.
107 | */
108 | getQueryToFindBlocksOnPage( text, pageTitle ) {
109 | text = text.replace( '"', '\"' );
110 | pageTitle = pageTitle.replace( '"', '\"' );
111 |
112 | return `[:find ?uid
113 | :where [?b :block/string "${text}"]
114 | [?b :block/uid ?uid]
115 | [?b :block/page ?p]
116 | [?p :node/title "${pageTitle}"]]`;
117 | }
118 |
119 | /**
120 | * Returns datalog query to find all blocks containing the text.
121 | * Returns results in format [[ blockUid, text, pageTitle ]].
122 | * @param {string} text - text to search.
123 | */
124 | getQueryToFindBlocks( text ) {
125 | text = text.replace( '"', '\"' );
126 | return `[:find ?uid ?string ?title :where
127 | [?b :block/string ?string]
128 | [(clojure.string/includes? ?string "${text}")]
129 | [?b :block/uid ?uid]
130 | [?b :block/page ?p]
131 | [?p :node/title ?title]
132 | ]`;
133 | }
134 |
135 | /**
136 | * When importing in Roam, import leaves an "Import" block.
137 | * This removes that from your daily page.
138 | * THIS IS UNSAFE since it deletes blocks.
139 | */
140 | async removeImportBlockFromDailyNote() {
141 | await this.deleteBlocksMatchingQuery(
142 | this.getQueryToFindBlocksOnPage(
143 | 'Import',
144 | this.dailyNoteTitle()
145 | ),
146 | 1
147 | );
148 | //Lets give time to sync
149 | await this.page.waitForTimeout( 1000 );
150 | return;
151 | }
152 |
153 | /**
154 | * Return page title for the current daily note.
155 | */
156 | dailyNoteTitle() {
157 | return moment( new Date() ).format( 'MMMM Do, YYYY' );
158 | }
159 | /**
160 | * Return page uid for the current daily note.
161 | */
162 | dailyNoteUid() {
163 | return moment( new Date() ).format( 'MM-DD-YYYY' );
164 | }
165 |
166 | /**
167 | * Export your Roam database and return the JSON data.
168 | * @param {boolean} autoremove - should the zip file be removed after extracting?
169 | */
170 | async getExportData( autoremove ) {
171 | // Mostly for testing purposes when we want to use a preexisting download.
172 | if ( ! this.options.nodownload ) {
173 | await this.logIn();
174 | await this.downloadExport( this.options.folder );
175 | }
176 | const latestExport = this.getLatestFile( this.options.folder );
177 | const content = await this.getContentsOfRepo( this.options.folder, latestExport );
178 | if ( autoremove ) {
179 | fs.unlinkSync( latestExport );
180 | }
181 | await this.close();
182 | return content;
183 | }
184 | /**
185 | * Logs in to Roam interface.
186 | */
187 | async logIn() {
188 | if ( this.browser ) {
189 | return this.browser;
190 | }
191 | this.browser = await puppeteer.launch( this.options );
192 | try {
193 | this.page = await this.browser.newPage();
194 | this.page.setDefaultTimeout( 60000 );
195 | await this.page.goto( 'https://roamresearch.com/#/app/' + this.db );
196 | await this.page.waitForNavigation();
197 | await this.page.waitForSelector( 'input[name=email]' );
198 | } catch ( e ) {
199 | console.error( 'Cannot load the login screen!' );
200 | throw e;
201 | }
202 | // Login
203 | await this.page.type( 'input[name=email]', this.login );
204 | await this.page.type( 'input[name=password]', this.pass );
205 | await this.page.click( '.bp3-button' );
206 | await this.page.waitForSelector( '.bp3-icon-more' );
207 | return;
208 | }
209 |
210 | /**
211 | * Import blocks to your Roam graph
212 | * @see examples/import.js.
213 | * @param {array} items
214 | */
215 | async import( items = [] ) {
216 | const fileName = path.resolve( this.options.folder, 'roam-research-private-api-sync.json' );
217 | fs.writeFileSync( fileName, JSON.stringify( items ) );
218 | await this.logIn();
219 | await this.page.waitForSelector( '.bp3-icon-more' );
220 | await this.clickMenuItem( 'Import Files' );
221 | // await this.page.click( '.bp3-icon-more' );
222 | // // This should contain "Export All"
223 | // await this.page.waitFor( 2000 );
224 | // await this.page.click( '.bp3-menu :nth-child(5) a' );
225 | await this.page.waitForSelector( 'input[type=file]' );
226 | await this.page.waitForTimeout( 1000 );
227 | // get the ElementHandle of the selector above
228 | const inputUploadHandle = await this.page.$( 'input[type=file]' );
229 |
230 | // Sets the value of the file input to fileToUpload
231 | inputUploadHandle.uploadFile( fileName );
232 | await this.page.waitForSelector( '.bp3-dialog .bp3-intent-primary' );
233 | await this.page.click( '.bp3-dialog .bp3-intent-primary' );
234 | await this.page.waitForTimeout( 3000 );
235 | await this.removeImportBlockFromDailyNote();
236 | return;
237 | }
238 |
239 | /**
240 | * Inserts text to your quickcapture.
241 | * @param {string} text
242 | */
243 | async quickCapture( text = [] ) {
244 | await this.logIn();
245 | const page = await this.browser.newPage();
246 | await page.emulate( puppeteer.devices[ 'iPhone X' ] );
247 | // set user agent (override the default headless User Agent)
248 | await page.goto( 'https://roamresearch.com/#/app/' + this.db );
249 |
250 | await page.waitForSelector( '#block-input-quick-capture-window-qcapture' );
251 | if ( isString( text ) ) {
252 | text = [ text ];
253 | }
254 |
255 | text.forEach( async function ( t ) {
256 | await page.type( '#block-input-quick-capture-window-qcapture', t );
257 | await page.click( 'button.bp3-intent-primary' );
258 | } );
259 | await page.waitForTimeout( 500 );
260 | // page.close();
261 | await this.close();
262 | return;
263 | }
264 |
265 | /**
266 | * Click item in the side-menu. This is mostly internal.
267 | * @param {string} title
268 | */
269 | async clickMenuItem( title ) {
270 | await this.page.click( '.bp3-icon-more' );
271 | // This should contain "Export All"
272 | await this.page.waitForTimeout( 1000 );
273 | await this.page.evaluate( ( title ) => {
274 | const items = [ ...document.querySelectorAll( '.bp3-menu li a' ) ];
275 | items.forEach( ( item ) => {
276 | console.log( item.innerText, title );
277 | if ( item.innerText === title ) {
278 | item.click();
279 | return;
280 | }
281 | } );
282 | }, title );
283 | }
284 |
285 | /**
286 | * Download Roam export to a selected folder.
287 | * @param {string} folder
288 | */
289 | async downloadExport( folder ) {
290 | await this.page._client.send( 'Page.setDownloadBehavior', {
291 | behavior: 'allow',
292 | downloadPath: folder,
293 | } );
294 | // Try to download
295 | // await this.page.goto( 'https://roamresearch.com/#/app/' + this.db );
296 | // await this.page.waitForNavigation();
297 | await this.page.waitForSelector( '.bp3-icon-more' );
298 | await this.clickMenuItem( 'Export All' );
299 | // await this.page.click( '.bp3-icon-more' );
300 | // // This should contain "Export All"
301 | // await this.page.waitFor( 2000 );
302 | // await this.page.click( '.bp3-menu :nth-child(4) a' );
303 | //Change markdown to JSON:
304 | // This should contain markdown
305 | await this.page.waitForTimeout( 2000 );
306 | await this.page.click( '.bp3-dialog-container .bp3-popover-wrapper button' );
307 | // This should contain JSON
308 | await this.page.waitForTimeout( 2000 );
309 | await this.page.click( '.bp3-dialog-container .bp3-popover-wrapper .bp3-popover-dismiss' );
310 | // This should contain "Export All"
311 | await this.page.waitForTimeout( 2000 );
312 | await this.page.click( '.bp3-dialog-container .bp3-intent-primary' );
313 |
314 | await this.page.waitForTimeout( 60000 ); // This can take quite some time on slower systems
315 | // Network idle is a hack to wait until we donwloaded stuff. I don't think it works though.
316 | await this.page.goto( 'https://news.ycombinator.com/', { waitUntil: 'networkidle2' } );
317 | return;
318 | }
319 |
320 | /**
321 | * Close the fake browser session.
322 | */
323 | async close() {
324 | if ( this.browser ) {
325 | await this.page.waitForTimeout( 1000 );
326 | await this.browser.close();
327 | this.browser = null;
328 | }
329 | return;
330 | }
331 |
332 | /**
333 | * Get the freshest file in the directory, for finding the newest export.
334 | * @param {string} dir
335 | */
336 | getLatestFile( dir ) {
337 | const orderReccentFiles = ( dir ) =>
338 | fs
339 | .readdirSync( dir )
340 | .filter( ( f ) => fs.lstatSync( path.resolve( dir, f ) ) && fs.lstatSync( path.resolve( dir, f ) ).isFile() )
341 | .filter( ( f ) => f.indexOf( 'Roam-Export' ) !== -1 )
342 | .map( ( file ) => ( { file, mtime: fs.lstatSync( path.resolve( dir, file ) ).mtime } ) )
343 | .sort( ( a, b ) => b.mtime.getTime() - a.mtime.getTime() );
344 |
345 | const getMostRecentFile = ( dir ) => {
346 | const files = orderReccentFiles( dir );
347 | return files.length ? files[ 0 ] : undefined;
348 | };
349 | return path.resolve( dir, getMostRecentFile( dir ).file );
350 | }
351 |
352 | /**
353 | * Unzip the export and get the content.
354 | * @param {string} dir
355 | * @param {string} file
356 | */
357 | getContentsOfRepo( dir, file ) {
358 | return new Promise( ( resolve, reject ) => {
359 | const stream = fs.createReadStream( file ).pipe( unzip.Parse() );
360 | stream.on( 'entry', function ( entry ) {
361 | var fileName = entry.path;
362 | var type = entry.type; // 'Directory' or 'File'
363 | var size = entry.size;
364 | if ( fileName.indexOf( '.json' ) != -1 ) {
365 | entry.pipe( fs.createWriteStream( path.resolve( dir, 'db.json' ) ) );
366 | } else {
367 | entry.autodrain();
368 | }
369 | } );
370 | // Timeouts are here so that the system locks can be removed - takes time on some systems.
371 | stream.on( 'close', function () {
372 | setTimeout( function() {
373 | fs.readFile( path.resolve( dir, 'db.json' ), 'utf8', function ( err, data ) {
374 | if ( err ) {
375 | reject( err );
376 | } else {
377 | resolve( JSON.parse( data ) );
378 | }
379 | } );
380 | }, 1000 );
381 | } );
382 | } );
383 | }
384 | }
385 |
386 | module.exports = RoamPrivateApi;
387 |
--------------------------------------------------------------------------------
/Sync.js:
--------------------------------------------------------------------------------
1 | class RoamSyncAdapter {
2 | credentials;
3 | graphName = '';
4 | pages = [];
5 | titleMapping;
6 |
7 | constructor( data, graphName ) {
8 | this.titleMapping = new Map();
9 | this.credentials = data;
10 | this.graphName = graphName;
11 | }
12 |
13 | sync( pages ) {
14 | return new Promise( ( resolve, reject ) => {
15 | console.log( pages );
16 | resolve( this.titleMapping );
17 | } );
18 | }
19 |
20 | wrapItem( string, title ) {
21 | const intend = ''; // this has to grow
22 | return (
23 | intend +
24 | ' - ' +
25 | string +
26 | `
27 | `
28 | );
29 | }
30 |
31 | wrapChildren( childrenString, title ) {
32 | return childrenString.join( '' );
33 | }
34 |
35 | wrapText( string, title ) {
36 | return string;
37 | }
38 | flattenRoamDB( roamData, level, title ) {
39 | let ret = '';
40 | if ( roamData.string ) {
41 | ret += this.wrapText( roamData.string, title );
42 | }
43 | if ( roamData.children ) {
44 | ret += this.wrapChildren(
45 | roamData.children.map( ( child ) => this.flattenRoamDB( child, level + 1, title ) )
46 | );
47 | }
48 | return this.wrapItem( ret, title );
49 | }
50 |
51 | processJSON( newData ) {
52 | this.pages = newData.map( ( page ) => {
53 | const newPage = {
54 | uid: page.uid,
55 | title: page.title,
56 | updateTime: page[ 'edit-time' ],
57 | content: '',
58 | };
59 | if ( page.string ) {
60 | newPage.content = page.string;
61 | }
62 | if ( page.children && page.children[ 0 ] ) {
63 | newPage.content += this.flattenRoamDB( page, 0, page.title );
64 | }
65 | this.titleMapping.set( page.title, newPage );
66 | return newPage;
67 | } );
68 | return this.sync( this.pages );
69 | }
70 | }
71 |
72 | module.exports = RoamSyncAdapter;
73 |
--------------------------------------------------------------------------------
/examples/cmd.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const yargs = require( 'yargs' );
3 | const fs = require( 'fs' );
4 | const fetch = require( 'node-fetch' );
5 |
6 | const argv = yargs
7 | .option( 'graph', {
8 | alias: 'g',
9 | description: 'Your graph name',
10 | type: 'string',
11 | } )
12 | .option( 'email', {
13 | alias: 'e',
14 | description: 'Your Roam Email',
15 | type: 'string',
16 | } )
17 | .option( 'password', {
18 | alias: 'p',
19 | description: 'Your Roam Password',
20 | type: 'string',
21 | } )
22 | .option( 'debug', {
23 | description: 'enable debug mode',
24 | type: 'boolean',
25 | default: false,
26 | } )
27 | .option( 'stdin', {
28 | alias: 'i',
29 | description: 'Read from STDIN',
30 | type: 'boolean',
31 | default: false,
32 | } )
33 | .option( 'removezip', {
34 | description: 'If downloading the Roam Graph, should the timestamp zip file be removed after downloading?',
35 | type: 'boolean',
36 | default: true,
37 | } )
38 | .command(
39 | 'query [query]',
40 | 'Query your Roam Graph using datalog syntax',
41 | () => {},
42 | ( argv ) => {
43 | let input = '';
44 | if ( argv.stdin ) {
45 | input = fs.readFileSync( 0, 'utf-8' );
46 | } else {
47 | input = argv['query'];
48 | }
49 |
50 | if ( ! input || input.length < 3 ) {
51 | console.warn( 'You have to provide a query at least 3 chars long' );
52 | return;
53 | }
54 | console.log( "Logging in to your Roam and running query:" );
55 | console.log( input );
56 | const RoamPrivateApi = require( '../' );
57 | const api = new RoamPrivateApi( argv.graph, argv.email, argv.password, {
58 | headless: ! argv.debug,
59 | } );
60 |
61 | api.logIn()
62 | .then( () => api.runQuery( input ) )
63 | .then( result => {
64 | console.log( JSON.stringify( result, null, 4 ) );
65 | api.close();
66 | } );
67 | }
68 | )
69 | .command(
70 | 'search ',
71 | 'Query your Roam Graph blocks using simple text search.',
72 | () => {},
73 | ( argv ) => {
74 | const RoamPrivateApi = require( '../' );
75 | const api = new RoamPrivateApi( argv.graph, argv.email, argv.password, {
76 | headless: ! argv.debug,
77 | } );
78 |
79 | api.logIn()
80 | .then( () => api.runQuery( api.getQueryToFindBlocks( argv['query'] ) ) )
81 | .then( result => {
82 | result = result.map( result => ( {
83 | blockUid: result[0],
84 | pageTitle: result[2],
85 | string: result[1]
86 | } ) );
87 | console.log( JSON.stringify( result, null, 4 ) );
88 | api.close();
89 | } );
90 | }
91 | )
92 | .command(
93 | 'create [text] [parentuid]',
94 | 'Append a block to a block with a selected uid. If no uid is provided, block will be appended to the daily page. You can also pass data from stdin.',
95 | () => {},
96 | ( argv ) => {
97 | let input = '';
98 | if ( argv.stdin ) {
99 | input = fs.readFileSync( 0, 'utf-8' );
100 | } else {
101 | input = argv['text'];
102 | }
103 |
104 | if ( ! input || input.length < 3 ) {
105 | console.warn( 'You have to provide content at least 3 chars long' );
106 | return;
107 | }
108 |
109 | const RoamPrivateApi = require( '../' );
110 | const api = new RoamPrivateApi( argv.graph, argv.email, argv.password, {
111 | headless: ! argv.debug,
112 | } );
113 |
114 | if ( ! argv['parentuid'] ) {
115 | argv['parentuid'] = api.dailyNoteUid();
116 | }
117 |
118 | api.logIn()
119 | .then( () => api.createBlock( input, argv['parentuid'] ) )
120 | .then( result => api.close() );
121 | }
122 | )
123 | .command(
124 | 'export [exporturl]',
125 | 'Export your Roam database to a selected directory. If URL is provided, then the concent will be sent by POST request to the specified URL.',
126 | () => {},
127 | ( argv ) => {
128 | const RoamPrivateApi = require( '../' );
129 | const api = new RoamPrivateApi( argv.graph, argv.email, argv.password, {
130 | headless: ! argv.debug,
131 | folder: argv['dir']
132 | } );
133 | let promises = api.getExportData( argv['removezip'] );
134 | promises.then( data => console.log( 'Downloaded' ) );
135 | if ( argv['exporturl'] ) {
136 | promises.then( data => fetch( argv['exporturl'], {
137 | method: 'post',
138 | body: JSON.stringify( {
139 | graphContent: data,
140 | graphName: api.db
141 | } ),
142 | headers: {'Content-Type': 'application/json'}
143 | } ) )
144 | .catch( err => console.log( err ) )
145 | .then( () => console.log( "Uploaded to export url." ) )
146 | }
147 | }
148 | )
149 | .help()
150 | .alias( 'help', 'h' )
151 | .env( 'ROAM_API' )
152 | .demandOption(
153 | [ 'graph', 'email', 'password' ],
154 | 'You need to provide graph name, email and password'
155 | ).argv;
156 |
--------------------------------------------------------------------------------
/examples/download.js:
--------------------------------------------------------------------------------
1 | const RoamPrivateApi = require( '../' );
2 | const secrets = require( '../secrets.json' );
3 |
4 | const api = new RoamPrivateApi( secrets.graph, secrets.email, secrets.password, {
5 | headless: false,
6 | folder: './tmp/',
7 | nodownload: false,
8 | } );
9 | api.getExportData().then( data => console.log( 'success', data ) );
10 |
--------------------------------------------------------------------------------
/examples/import-data.js:
--------------------------------------------------------------------------------
1 | const RoamPrivateApi = require( '../' );
2 | const secrets = require( '../secrets.json' );
3 | var fs = require( 'fs' );
4 |
5 | const api = new RoamPrivateApi( secrets.graph, secrets.email, secrets.password, {
6 | headless: false,
7 | folder: './tmp/',
8 | } );
9 | api.import( [
10 | { title: 'test', children: [ { string: 'Test child' }, { string: 'Another test child' } ] },
11 | ] );
12 |
--------------------------------------------------------------------------------
/examples/quick_capture.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const yargs = require( 'yargs' );
3 | const fs = require( 'fs' );
4 |
5 | const argv = yargs
6 | .option( 'graph', {
7 | alias: 'g',
8 | description: 'Your graph name',
9 | type: 'string',
10 | } )
11 | .option( 'email', {
12 | alias: 'e',
13 | description: 'Your Roam Email',
14 | type: 'string',
15 | } )
16 | .option( 'password', {
17 | alias: 'p',
18 | description: 'Your Roam Password',
19 | type: 'string',
20 | } )
21 | .option( 'debug', {
22 | description: 'enable debug mode',
23 | type: 'boolean',
24 | } )
25 | .option( 'stdin', {
26 | alias: 'i',
27 | description: 'Read from STDIN',
28 | type: 'boolean',
29 | } )
30 | .command(
31 | '$0',
32 | 'Save Quick capture',
33 | () => {},
34 | ( argv ) => {
35 | let input = '';
36 | if ( argv.stdin ) {
37 | input = fs.readFileSync( 0, 'utf-8' );
38 | } else {
39 | input = argv[ '_' ].join( ' ' );
40 | }
41 |
42 | if ( ! input || input.length < 3 ) {
43 | console.warn( 'You have to provide a note at least 3 chars long' );
44 | return;
45 | }
46 | const RoamPrivateApi = require( '../' );
47 | const api = new RoamPrivateApi( argv.graph, argv.email, argv.password, {
48 | headless: ! argv.debug,
49 | } );
50 |
51 | api.quickCapture( [ input ] );
52 | }
53 | )
54 | .help()
55 | .alias( 'help', 'h' )
56 | .demandOption(
57 | [ 'graph', 'email', 'password' ],
58 | 'You need to provide graph name, email and password'
59 | ).argv;
60 |
--------------------------------------------------------------------------------
/examples/sync_evernote.js:
--------------------------------------------------------------------------------
1 | #!/usr/bin/env node
2 | const helpText=`
3 | The command exposed here (roam-evernote-sync sync) will sync your Roam Graph to your Evernote database.
4 | Depending on a few configuration options, it will:
5 | 1. Download payload from external URL to import INTO roam (useful for connecting with other services)
6 | 2. Take all notes in your default Evernote notebook and import them into your daily note. They will be marked with 'RoamImported' tag to prevent doing so multiple times
7 | 3. Export all notes from your Roam and import them to "Roam" notebook in your Evernote account
8 | 4. The backlinks will be kept intact, notes will be updated when possible
9 | 5. After all that is done, DB will be pushed to external URL ('exporturl') if provided to provide connection for https://deliber.at/roam/wp-roam-block or similar projects
10 |
11 | - 'dir' is a directory where database will be downloaded.
12 | - 'mappingcachefile' is a JSON file that provides a cache for Roam UID <-> Evernote GUID mapping. This is used to relieve Evernote API a bit
13 | `;
14 |
15 | const yargs = require( 'yargs' );
16 | const fetch = require( 'node-fetch' );
17 | var fs = require( 'fs' ).promises;
18 |
19 | const argv = yargs
20 | .option( 'graph', {
21 | alias: 'g',
22 | description: 'Your graph name',
23 | type: 'string',
24 | } )
25 | .option( 'email', {
26 | alias: 'e',
27 | description: 'Your Roam Email',
28 | type: 'string',
29 | } )
30 | .option( 'password', {
31 | alias: 'p',
32 | description: 'Your Roam Password',
33 | type: 'string',
34 | } )
35 | .option( 'evernote_token', {
36 | alias: 't',
37 | description: 'Your Evernote Token',
38 | type: 'string',
39 | } )
40 | .option( 'debug', {
41 | description: 'enable debug mode',
42 | type: 'boolean',
43 | default: false,
44 | } )
45 | .option( 'nodownload', {
46 | description: 'Skip the download of the roam graph. Default no - do download.',
47 | type: 'boolean',
48 | default: false,
49 | } )
50 | .option( 'nosandbox', {
51 | description: 'Skip the Chrome Sandbox.',
52 | type: 'boolean',
53 | default: false,
54 | } )
55 | .option( 'executable', {
56 | description: 'Executable path to Chromium.',
57 | type: 'string',
58 | default: '',
59 | } )
60 | .option( 'verbose', {
61 | alias: 'v',
62 | description: 'You know, verbose.',
63 | type: 'boolean',
64 | default: false,
65 | } )
66 | .option( 'privateapiurl', {
67 | description: 'Additional endpoint that provides data to sync INTO Roam. Has nothing to do with Evernote, its just convenient.',
68 | type: 'string',
69 | default: '',
70 | } )
71 | .option( 'removezip', {
72 | description: 'If downloading the Roam Graph, should the timestamp zip file be removed after downloading?',
73 | type: 'boolean',
74 | default: true,
75 | } )
76 | .command(
77 | 'sync [exporturl]',
78 | helpText,
79 | () => {},
80 | ( argv ) => {
81 |
82 | const RoamPrivateApi = require( '../' );
83 | const EvernoteSyncAdapter = require( '../EvernoteSync' );
84 | const options = {
85 | headless: ! argv.debug,
86 | nodownload: argv.nodownload,
87 | folder: argv['dir']
88 | };
89 | if ( argv[ 'executable' ] ) {
90 | options['executablePath'] = argv[ 'executable' ];
91 | }
92 | if ( argv[ 'nosandbox' ] ) {
93 | options['args'] = ['--no-sandbox', '--disable-setuid-sandbox'];
94 | }
95 |
96 | const e = new EvernoteSyncAdapter( { token: argv.evernoteToken, sandbox: false }, argv.graph );
97 | const api = new RoamPrivateApi( argv.graph, argv.email, argv.password, options );
98 |
99 | // This downloads the private additional content for my Roam graph, served by other means.
100 | const importIntoRoam = [];
101 | if ( argv.privateapiurl ) {
102 | const private_api = fetch( argv.privateapiurl ).then( response => response.json() );
103 | private_api.then( data => console.log( 'Private API payload', JSON.stringify( data, null, 2 ) ) );
104 | importIntoRoam.push( private_api );
105 | }
106 |
107 | let evernote_to_roam;
108 | if ( argv.mappingcachefile ) {
109 | // There is a mapping file.
110 | evernote_to_roam = fs.readFile( argv.mappingcachefile )
111 | .then( ( data ) => e.init( JSON.parse( data ) ) )
112 | .catch( ( err ) => e.init( null ) )
113 | } else {
114 | evernote_to_roam = e.init( null );
115 | }
116 |
117 | // This finds notes IN Evernote to import into Roam:
118 | evernote_to_roam = evernote_to_roam
119 | .then( () => e.getNotesToImport() )
120 | .then( payload => Promise.resolve( e.getRoamPayload( payload ) ) );
121 | importIntoRoam.push( evernote_to_roam );
122 |
123 | // Let's start the flow with Roam:
124 | const roamdata = Promise.all( importIntoRoam )
125 | .then( results => {
126 | const payload = results[0].concat( results[1] );
127 | console.log( 'Importing into Roam', JSON.stringify( payload, null, 2 ) );
128 | if( payload.length > 0 ) {
129 | return api.import( payload );
130 | } else {
131 | return Promise.resolve();
132 | }
133 | } )
134 | .then( () => e.cleanupImportNotes() )
135 | .then( () => api.getExportData( ! argv.nodownload && argv['removezip'] ) ); // Removing zip is only possible if we downloaded it.
136 |
137 | // We are saving the intermediate step of mapping just in case.
138 | if ( argv.mappingcachefile ) {
139 | roamdata.then( data => fs.writeFile( argv.mappingcachefile, JSON.stringify( [ ...e.mapping ], null, 2 ), 'utf8' ) );
140 | }
141 |
142 | // This will push Roam graph to the URL of your choice - can be WordPress
143 | if ( argv.exporturl ) {
144 | roamdata.then( data => fetch( argv.exporturl, {
145 | method: 'post',
146 | body: JSON.stringify( {
147 | graphContent: data,
148 | graphName: api.db
149 | } ),
150 | headers: {'Content-Type': 'application/json'}
151 | } ) )
152 | .then( response => response.text() )
153 | .then( ( data ) => console.log( 'Updated in your remote URL', data ) );
154 | }
155 |
156 | // This is the actual moment where we sync to Evernote:
157 | let finish = roamdata.then( ( data ) => e.processJSON( data ) );
158 | // We are saving the final step of mapping just in case.
159 | if ( argv.mappingcachefile ) {
160 | finish = finish.then( data => fs.writeFile( argv.mappingcachefile, JSON.stringify( [ ...e.mapping ], null, 2 ), 'utf8' ) );
161 | }
162 | finish.then( () => console.log( 'success' ) );
163 | }
164 | )
165 | .help()
166 | .alias( 'help', 'h' )
167 | .env( 'ROAM_API' )
168 | .demandOption(
169 | [ 'graph', 'email', 'password' ],
170 | 'You need to provide graph name, email and password'
171 | ).argv;
172 |
--------------------------------------------------------------------------------
/package-lock.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "roam-research-private-api",
3 | "version": "0.9.1",
4 | "lockfileVersion": 1,
5 | "requires": true,
6 | "dependencies": {
7 | "@types/color-name": {
8 | "version": "1.1.1",
9 | "resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
10 | "integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ=="
11 | },
12 | "@types/node": {
13 | "version": "14.14.20",
14 | "resolved": "https://registry.npmjs.org/@types/node/-/node-14.14.20.tgz",
15 | "integrity": "sha512-Y93R97Ouif9JEOWPIUyU+eyIdyRqQR0I8Ez1dzku4hDx34NWh4HbtIc3WNzwB1Y9ULvNGeu5B8h8bVL5cAk4/A==",
16 | "optional": true
17 | },
18 | "@types/yauzl": {
19 | "version": "2.9.1",
20 | "resolved": "https://registry.npmjs.org/@types/yauzl/-/yauzl-2.9.1.tgz",
21 | "integrity": "sha512-A1b8SU4D10uoPjwb0lnHmmu8wZhR9d+9o2PKBQT2jU5YPTKsxac6M2qGAdY7VcL+dHHhARVUDmeg0rOrcd9EjA==",
22 | "optional": true,
23 | "requires": {
24 | "@types/node": "*"
25 | }
26 | },
27 | "agent-base": {
28 | "version": "5.1.1",
29 | "resolved": "https://registry.npmjs.org/agent-base/-/agent-base-5.1.1.tgz",
30 | "integrity": "sha512-TMeqbNl2fMW0nMjTEPOwe3J/PRFP4vqeoNuQMG0HlMrtm5QxKqdvAkZ1pRBQ/ulIyDD5Yq0nJ7YbdD8ey0TO3g=="
31 | },
32 | "ansi-regex": {
33 | "version": "5.0.0",
34 | "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.0.tgz",
35 | "integrity": "sha512-bY6fj56OUQ0hU1KjFNDQuJFezqKdrAyFdIevADiqrWHwSlbmBNMHp5ak2f40Pm8JTFyM2mqxkG6ngkHO11f/lg=="
36 | },
37 | "ansi-styles": {
38 | "version": "4.2.1",
39 | "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz",
40 | "integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==",
41 | "requires": {
42 | "@types/color-name": "^1.1.1",
43 | "color-convert": "^2.0.1"
44 | }
45 | },
46 | "balanced-match": {
47 | "version": "1.0.0",
48 | "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.0.tgz",
49 | "integrity": "sha1-ibTRmasr7kneFk6gK4nORi1xt2c="
50 | },
51 | "base64-js": {
52 | "version": "1.5.1",
53 | "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
54 | "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA=="
55 | },
56 | "binary": {
57 | "version": "0.3.0",
58 | "resolved": "https://registry.npmjs.org/binary/-/binary-0.3.0.tgz",
59 | "integrity": "sha1-n2BVO8XOjDOG87VTz/R0Yq3sqnk=",
60 | "requires": {
61 | "buffers": "~0.1.1",
62 | "chainsaw": "~0.1.0"
63 | }
64 | },
65 | "bl": {
66 | "version": "4.0.3",
67 | "resolved": "https://registry.npmjs.org/bl/-/bl-4.0.3.tgz",
68 | "integrity": "sha512-fs4G6/Hu4/EE+F75J8DuN/0IpQqNjAdC7aEQv7Qt8MHGUH7Ckv2MwTEEeN9QehD0pfIDkMI1bkHYkKy7xHyKIg==",
69 | "requires": {
70 | "buffer": "^5.5.0",
71 | "inherits": "^2.0.4",
72 | "readable-stream": "^3.4.0"
73 | }
74 | },
75 | "brace-expansion": {
76 | "version": "1.1.11",
77 | "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
78 | "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
79 | "requires": {
80 | "balanced-match": "^1.0.0",
81 | "concat-map": "0.0.1"
82 | }
83 | },
84 | "buffer": {
85 | "version": "5.7.1",
86 | "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz",
87 | "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==",
88 | "requires": {
89 | "base64-js": "^1.3.1",
90 | "ieee754": "^1.1.13"
91 | }
92 | },
93 | "buffer-crc32": {
94 | "version": "0.2.13",
95 | "resolved": "https://registry.npmjs.org/buffer-crc32/-/buffer-crc32-0.2.13.tgz",
96 | "integrity": "sha1-DTM+PwDqxQqhRUq9MO+MKl2ackI="
97 | },
98 | "buffers": {
99 | "version": "0.1.1",
100 | "resolved": "https://registry.npmjs.org/buffers/-/buffers-0.1.1.tgz",
101 | "integrity": "sha1-skV5w77U1tOWru5tmorn9Ugqt7s="
102 | },
103 | "camelcase": {
104 | "version": "5.3.1",
105 | "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-5.3.1.tgz",
106 | "integrity": "sha512-L28STB170nwWS63UjtlEOE3dldQApaJXZkOI1uMFfzf3rRuPegHaHesyee+YxQ+W6SvRDQV6UrdOdRiR153wJg=="
107 | },
108 | "chainsaw": {
109 | "version": "0.1.0",
110 | "resolved": "https://registry.npmjs.org/chainsaw/-/chainsaw-0.1.0.tgz",
111 | "integrity": "sha1-XqtQsor+WAdNDVgpE4iCi15fvJg=",
112 | "requires": {
113 | "traverse": ">=0.3.0 <0.4"
114 | }
115 | },
116 | "chownr": {
117 | "version": "1.1.4",
118 | "resolved": "https://registry.npmjs.org/chownr/-/chownr-1.1.4.tgz",
119 | "integrity": "sha512-jJ0bqzaylmJtVnNgzTeSOs8DPavpbYgEr/b0YL8/2GO3xJEhInFmhKMUnEJQjZumK7KXGFhUy89PrsJWlakBVg=="
120 | },
121 | "cliui": {
122 | "version": "6.0.0",
123 | "resolved": "https://registry.npmjs.org/cliui/-/cliui-6.0.0.tgz",
124 | "integrity": "sha512-t6wbgtoCXvAzst7QgXxJYqPt0usEfbgQdftEPbLL/cvv6HPE5VgvqCuAIDR0NgU52ds6rFwqrgakNLrHEjCbrQ==",
125 | "requires": {
126 | "string-width": "^4.2.0",
127 | "strip-ansi": "^6.0.0",
128 | "wrap-ansi": "^6.2.0"
129 | }
130 | },
131 | "color-convert": {
132 | "version": "2.0.1",
133 | "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
134 | "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
135 | "requires": {
136 | "color-name": "~1.1.4"
137 | }
138 | },
139 | "color-name": {
140 | "version": "1.1.4",
141 | "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
142 | "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
143 | },
144 | "concat-map": {
145 | "version": "0.0.1",
146 | "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
147 | "integrity": "sha1-2Klr13/Wjfd5OnMDajug1UBdR3s="
148 | },
149 | "core-util-is": {
150 | "version": "1.0.2",
151 | "resolved": "https://registry.npmjs.org/core-util-is/-/core-util-is-1.0.2.tgz",
152 | "integrity": "sha1-tf1UIgqivFq1eqtxQMlAdUUDwac="
153 | },
154 | "debug": {
155 | "version": "4.3.1",
156 | "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.1.tgz",
157 | "integrity": "sha512-doEwdvm4PCeK4K3RQN2ZC2BYUBaxwLARCqZmMjtF8a51J2Rb0xpVloFRnCODwqjpwnAoao4pelN8l3RJdv3gRQ==",
158 | "requires": {
159 | "ms": "2.1.2"
160 | }
161 | },
162 | "decamelize": {
163 | "version": "1.2.0",
164 | "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-1.2.0.tgz",
165 | "integrity": "sha1-9lNNFRSCabIDUue+4m9QH5oZEpA="
166 | },
167 | "devtools-protocol": {
168 | "version": "0.0.818844",
169 | "resolved": "https://registry.npmjs.org/devtools-protocol/-/devtools-protocol-0.0.818844.tgz",
170 | "integrity": "sha512-AD1hi7iVJ8OD0aMLQU5VK0XH9LDlA1+BcPIgrAxPfaibx2DbWucuyOhc4oyQCbnvDDO68nN6/LcKfqTP343Jjg=="
171 | },
172 | "emoji-regex": {
173 | "version": "8.0.0",
174 | "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
175 | "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
176 | },
177 | "end-of-stream": {
178 | "version": "1.4.4",
179 | "resolved": "https://registry.npmjs.org/end-of-stream/-/end-of-stream-1.4.4.tgz",
180 | "integrity": "sha512-+uw1inIHVPQoaVuHzRyXd21icM+cnt4CzD5rW+NC1wjOUSTOs+Te7FOv7AhN7vS9x/oIyhLP5PR1H+phQAHu5Q==",
181 | "requires": {
182 | "once": "^1.4.0"
183 | }
184 | },
185 | "enml-js": {
186 | "version": "0.1.3",
187 | "resolved": "https://registry.npmjs.org/enml-js/-/enml-js-0.1.3.tgz",
188 | "integrity": "sha1-I+cHAuHPA/vN7w+/tY2Z0QZ5oww="
189 | },
190 | "evernote": {
191 | "version": "2.0.5",
192 | "resolved": "https://registry.npmjs.org/evernote/-/evernote-2.0.5.tgz",
193 | "integrity": "sha512-DuOk3t9HKwkxZBaZU1Mz8vmZSEM2LVI3eLdML90Is3+8WlPwpYcLeo4z+eklT8NUGD3GA6oOnlP+6e0z0rDX1Q==",
194 | "requires": {
195 | "oauth": "^0.9.14"
196 | }
197 | },
198 | "extract-zip": {
199 | "version": "2.0.1",
200 | "resolved": "https://registry.npmjs.org/extract-zip/-/extract-zip-2.0.1.tgz",
201 | "integrity": "sha512-GDhU9ntwuKyGXdZBUgTIe+vXnWj0fppUEtMDL0+idd5Sta8TGpHssn/eusA9mrPr9qNDym6SxAYZjNvCn/9RBg==",
202 | "requires": {
203 | "@types/yauzl": "^2.9.1",
204 | "debug": "^4.1.1",
205 | "get-stream": "^5.1.0",
206 | "yauzl": "^2.10.0"
207 | }
208 | },
209 | "fd-slicer": {
210 | "version": "1.1.0",
211 | "resolved": "https://registry.npmjs.org/fd-slicer/-/fd-slicer-1.1.0.tgz",
212 | "integrity": "sha1-JcfInLH5B3+IkbvmHY85Dq4lbx4=",
213 | "requires": {
214 | "pend": "~1.2.0"
215 | }
216 | },
217 | "find-up": {
218 | "version": "4.1.0",
219 | "resolved": "https://registry.npmjs.org/find-up/-/find-up-4.1.0.tgz",
220 | "integrity": "sha512-PpOwAdQ/YlXQ2vj8a3h8IipDuYRi3wceVQQGYWxNINccq40Anw7BlsEXCMbt1Zt+OLA6Fq9suIpIWD0OsnISlw==",
221 | "requires": {
222 | "locate-path": "^5.0.0",
223 | "path-exists": "^4.0.0"
224 | }
225 | },
226 | "fs-constants": {
227 | "version": "1.0.0",
228 | "resolved": "https://registry.npmjs.org/fs-constants/-/fs-constants-1.0.0.tgz",
229 | "integrity": "sha512-y6OAwoSIf7FyjMIv94u+b5rdheZEjzR63GTyZJm5qh4Bi+2YgwLCcI/fPFZkL5PSixOt6ZNKm+w+Hfp/Bciwow=="
230 | },
231 | "fs.realpath": {
232 | "version": "1.0.0",
233 | "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
234 | "integrity": "sha1-FQStJSMVjKpA20onh8sBQRmU6k8="
235 | },
236 | "fstream": {
237 | "version": "1.0.12",
238 | "resolved": "https://registry.npmjs.org/fstream/-/fstream-1.0.12.tgz",
239 | "integrity": "sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg==",
240 | "requires": {
241 | "graceful-fs": "^4.1.2",
242 | "inherits": "~2.0.0",
243 | "mkdirp": ">=0.5 0",
244 | "rimraf": "2"
245 | },
246 | "dependencies": {
247 | "rimraf": {
248 | "version": "2.7.1",
249 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-2.7.1.tgz",
250 | "integrity": "sha512-uWjbaKIK3T1OSVptzX7Nl6PvQ3qAGtKEtVRjRuazjfL3Bx5eI409VZSqgND+4UNnmzLVdPj9FqFJNPqBZFve4w==",
251 | "requires": {
252 | "glob": "^7.1.3"
253 | }
254 | }
255 | }
256 | },
257 | "get-caller-file": {
258 | "version": "2.0.5",
259 | "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
260 | "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg=="
261 | },
262 | "get-stream": {
263 | "version": "5.2.0",
264 | "resolved": "https://registry.npmjs.org/get-stream/-/get-stream-5.2.0.tgz",
265 | "integrity": "sha512-nBF+F1rAZVCu/p7rjzgA+Yb4lfYXrpl7a6VmJrU8wF9I1CKvP/QwPNZHnOlwbTkY6dvtFIzFMSyQXbLoTQPRpA==",
266 | "requires": {
267 | "pump": "^3.0.0"
268 | }
269 | },
270 | "glob": {
271 | "version": "7.1.6",
272 | "resolved": "https://registry.npmjs.org/glob/-/glob-7.1.6.tgz",
273 | "integrity": "sha512-LwaxwyZ72Lk7vZINtNNrywX0ZuLyStrdDtabefZKAY5ZGJhVtgdznluResxNmPitE0SAO+O26sWTHeKSI2wMBA==",
274 | "requires": {
275 | "fs.realpath": "^1.0.0",
276 | "inflight": "^1.0.4",
277 | "inherits": "2",
278 | "minimatch": "^3.0.4",
279 | "once": "^1.3.0",
280 | "path-is-absolute": "^1.0.0"
281 | }
282 | },
283 | "graceful-fs": {
284 | "version": "4.2.4",
285 | "resolved": "https://registry.npmjs.org/graceful-fs/-/graceful-fs-4.2.4.tgz",
286 | "integrity": "sha512-WjKPNJF79dtJAVniUlGGWHYGz2jWxT6VhN/4m1NdkbZ2nOsEF+cI1Edgql5zCRhs/VsQYRvrXctxktVXZUkixw=="
287 | },
288 | "https-proxy-agent": {
289 | "version": "4.0.0",
290 | "resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-4.0.0.tgz",
291 | "integrity": "sha512-zoDhWrkR3of1l9QAL8/scJZyLu8j/gBkcwcaQOZh7Gyh/+uJQzGVETdgT30akuwkpL8HTRfssqI3BZuV18teDg==",
292 | "requires": {
293 | "agent-base": "5",
294 | "debug": "4"
295 | }
296 | },
297 | "ieee754": {
298 | "version": "1.2.1",
299 | "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
300 | "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA=="
301 | },
302 | "inflight": {
303 | "version": "1.0.6",
304 | "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
305 | "integrity": "sha1-Sb1jMdfQLQwJvJEKEHW6gWW1bfk=",
306 | "requires": {
307 | "once": "^1.3.0",
308 | "wrappy": "1"
309 | }
310 | },
311 | "inherits": {
312 | "version": "2.0.4",
313 | "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
314 | "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
315 | },
316 | "is-fullwidth-code-point": {
317 | "version": "3.0.0",
318 | "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
319 | "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
320 | },
321 | "isarray": {
322 | "version": "0.0.1",
323 | "resolved": "https://registry.npmjs.org/isarray/-/isarray-0.0.1.tgz",
324 | "integrity": "sha1-ihis/Kmo9Bd+Cav8YDiTmwXR7t8="
325 | },
326 | "locate-path": {
327 | "version": "5.0.0",
328 | "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-5.0.0.tgz",
329 | "integrity": "sha512-t7hw9pI+WvuwNJXwk5zVHpyhIqzg2qTlklJOf0mVxGSbe3Fp2VieZcduNYjaLDoy6p9uGpQEGWG87WpMKlNq8g==",
330 | "requires": {
331 | "p-locate": "^4.1.0"
332 | }
333 | },
334 | "match-stream": {
335 | "version": "0.0.2",
336 | "resolved": "https://registry.npmjs.org/match-stream/-/match-stream-0.0.2.tgz",
337 | "integrity": "sha1-mesFAJOzTf+t5CG5rAtBCpz6F88=",
338 | "requires": {
339 | "buffers": "~0.1.1",
340 | "readable-stream": "~1.0.0"
341 | },
342 | "dependencies": {
343 | "readable-stream": {
344 | "version": "1.0.34",
345 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
346 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
347 | "requires": {
348 | "core-util-is": "~1.0.0",
349 | "inherits": "~2.0.1",
350 | "isarray": "0.0.1",
351 | "string_decoder": "~0.10.x"
352 | }
353 | },
354 | "string_decoder": {
355 | "version": "0.10.31",
356 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
357 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
358 | }
359 | }
360 | },
361 | "minimatch": {
362 | "version": "3.0.4",
363 | "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.0.4.tgz",
364 | "integrity": "sha512-yJHVQEhyqPLUTgt9B83PXu6W3rx4MvvHvSUvToogpwoGDOUQ+yDrR0HRot+yOCdCO7u4hX3pWft6kWBBcqh0UA==",
365 | "requires": {
366 | "brace-expansion": "^1.1.7"
367 | }
368 | },
369 | "minimist": {
370 | "version": "1.2.5",
371 | "resolved": "https://registry.npmjs.org/minimist/-/minimist-1.2.5.tgz",
372 | "integrity": "sha512-FM9nNUYrRBAELZQT3xeZQ7fmMOBg6nWNmJKTcgsJeaLstP/UODVpGsr5OhXhhXg6f+qtJ8uiZ+PUxkDWcgIXLw=="
373 | },
374 | "mkdirp": {
375 | "version": "0.5.5",
376 | "resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-0.5.5.tgz",
377 | "integrity": "sha512-NKmAlESf6jMGym1++R0Ra7wvhV+wFW63FaSOFPwRahvea0gMUcGUhVeAg/0BC0wiv9ih5NYPB1Wn1UEI1/L+xQ==",
378 | "requires": {
379 | "minimist": "^1.2.5"
380 | }
381 | },
382 | "mkdirp-classic": {
383 | "version": "0.5.3",
384 | "resolved": "https://registry.npmjs.org/mkdirp-classic/-/mkdirp-classic-0.5.3.tgz",
385 | "integrity": "sha512-gKLcREMhtuZRwRAfqP3RFW+TK4JqApVBtOIftVgjuABpAtpxhPGaDcfvbhNvD0B8iD1oUr/txX35NjcaY6Ns/A=="
386 | },
387 | "moment": {
388 | "version": "2.27.0",
389 | "resolved": "https://registry.npmjs.org/moment/-/moment-2.27.0.tgz",
390 | "integrity": "sha512-al0MUK7cpIcglMv3YF13qSgdAIqxHTO7brRtaz3DlSULbqfazqkc5kEjNrLDOM7fsjshoFIihnU8snrP7zUvhQ=="
391 | },
392 | "ms": {
393 | "version": "2.1.2",
394 | "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
395 | "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
396 | },
397 | "node-fetch": {
398 | "version": "2.6.1",
399 | "resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.6.1.tgz",
400 | "integrity": "sha512-V4aYg89jEoVRxRb2fJdAg8FHvI7cEyYdVAh94HH0UIK8oJxUfkjlDQN9RbMx+bEjP7+ggMiFRprSti032Oipxw=="
401 | },
402 | "node-unzip-2": {
403 | "version": "0.2.8",
404 | "resolved": "https://registry.npmjs.org/node-unzip-2/-/node-unzip-2-0.2.8.tgz",
405 | "integrity": "sha512-fmJi73zTRW7RSo/1wyrKc2srKMwb3L6Ppke/7elzQ0QRt6sUjfiIcVsWdrqO5uEHAdvRKXjoySuo4HYe5BB0rw==",
406 | "requires": {
407 | "binary": "~0.3.0",
408 | "fstream": "~1.0.12",
409 | "match-stream": "~0.0.2",
410 | "pullstream": "~0.4.0",
411 | "readable-stream": "~1.0.0",
412 | "setimmediate": "~1.0.1"
413 | },
414 | "dependencies": {
415 | "readable-stream": {
416 | "version": "1.0.34",
417 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
418 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
419 | "requires": {
420 | "core-util-is": "~1.0.0",
421 | "inherits": "~2.0.1",
422 | "isarray": "0.0.1",
423 | "string_decoder": "~0.10.x"
424 | }
425 | },
426 | "string_decoder": {
427 | "version": "0.10.31",
428 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
429 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
430 | }
431 | }
432 | },
433 | "oauth": {
434 | "version": "0.9.15",
435 | "resolved": "https://registry.npmjs.org/oauth/-/oauth-0.9.15.tgz",
436 | "integrity": "sha1-vR/vr2hslrdUda7VGWQS/2DPucE="
437 | },
438 | "once": {
439 | "version": "1.4.0",
440 | "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
441 | "integrity": "sha1-WDsap3WWHUsROsF9nFC6753Xa9E=",
442 | "requires": {
443 | "wrappy": "1"
444 | }
445 | },
446 | "over": {
447 | "version": "0.0.5",
448 | "resolved": "https://registry.npmjs.org/over/-/over-0.0.5.tgz",
449 | "integrity": "sha1-8phS5w/X4l82DgE6jsRMgq7bVwg="
450 | },
451 | "p-limit": {
452 | "version": "2.3.0",
453 | "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-2.3.0.tgz",
454 | "integrity": "sha512-//88mFWSJx8lxCzwdAABTJL2MyWB12+eIY7MDL2SqLmAkeKU9qxRvWuSyTjm3FUmpBEMuFfckAIqEaVGUDxb6w==",
455 | "requires": {
456 | "p-try": "^2.0.0"
457 | }
458 | },
459 | "p-locate": {
460 | "version": "4.1.0",
461 | "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-4.1.0.tgz",
462 | "integrity": "sha512-R79ZZ/0wAxKGu3oYMlz8jy/kbhsNrS7SKZ7PxEHBgJ5+F2mtFW2fK2cOtBh1cHYkQsbzFV7I+EoRKe6Yt0oK7A==",
463 | "requires": {
464 | "p-limit": "^2.2.0"
465 | }
466 | },
467 | "p-try": {
468 | "version": "2.2.0",
469 | "resolved": "https://registry.npmjs.org/p-try/-/p-try-2.2.0.tgz",
470 | "integrity": "sha512-R4nPAVTAU0B9D35/Gk3uJf/7XYbQcyohSKdvAxIRSNghFl4e71hVoGnBNQz9cWaXxO2I10KTC+3jMdvvoKw6dQ=="
471 | },
472 | "path-exists": {
473 | "version": "4.0.0",
474 | "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
475 | "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
476 | },
477 | "path-is-absolute": {
478 | "version": "1.0.1",
479 | "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
480 | "integrity": "sha1-F0uSaHNVNP+8es5r9TpanhtcX18="
481 | },
482 | "pend": {
483 | "version": "1.2.0",
484 | "resolved": "https://registry.npmjs.org/pend/-/pend-1.2.0.tgz",
485 | "integrity": "sha1-elfrVQpng/kRUzH89GY9XI4AelA="
486 | },
487 | "pkg-dir": {
488 | "version": "4.2.0",
489 | "resolved": "https://registry.npmjs.org/pkg-dir/-/pkg-dir-4.2.0.tgz",
490 | "integrity": "sha512-HRDzbaKjC+AOWVXxAU/x54COGeIv9eb+6CkDSQoNTt4XyWoIJvuPsXizxu/Fr23EiekbtZwmh1IcIG/l/a10GQ==",
491 | "requires": {
492 | "find-up": "^4.0.0"
493 | }
494 | },
495 | "prettier": {
496 | "version": "npm:wp-prettier@2.0.5",
497 | "resolved": "https://registry.npmjs.org/wp-prettier/-/wp-prettier-2.0.5.tgz",
498 | "integrity": "sha512-5GCgdeevIXwR3cW4Qj5XWC5MO1iSCz8+IPn0mMw6awAt/PBiey8yyO7MhePRsaMqghJAhg6Q3QLYWSnUHWkG6A==",
499 | "dev": true
500 | },
501 | "progress": {
502 | "version": "2.0.3",
503 | "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
504 | "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA=="
505 | },
506 | "proxy-from-env": {
507 | "version": "1.1.0",
508 | "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-1.1.0.tgz",
509 | "integrity": "sha512-D+zkORCbA9f1tdWRK0RaCR3GPv50cMxcrz4X8k5LTSUD1Dkw47mKJEZQNunItRTkWwgtaUSo1RVFRIG9ZXiFYg=="
510 | },
511 | "pullstream": {
512 | "version": "0.4.1",
513 | "resolved": "https://registry.npmjs.org/pullstream/-/pullstream-0.4.1.tgz",
514 | "integrity": "sha1-1vs79a7Wl+gxFQ6xACwlo/iuExQ=",
515 | "requires": {
516 | "over": ">= 0.0.5 < 1",
517 | "readable-stream": "~1.0.31",
518 | "setimmediate": ">= 1.0.2 < 2",
519 | "slice-stream": ">= 1.0.0 < 2"
520 | },
521 | "dependencies": {
522 | "readable-stream": {
523 | "version": "1.0.34",
524 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
525 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
526 | "requires": {
527 | "core-util-is": "~1.0.0",
528 | "inherits": "~2.0.1",
529 | "isarray": "0.0.1",
530 | "string_decoder": "~0.10.x"
531 | }
532 | },
533 | "string_decoder": {
534 | "version": "0.10.31",
535 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
536 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
537 | }
538 | }
539 | },
540 | "pump": {
541 | "version": "3.0.0",
542 | "resolved": "https://registry.npmjs.org/pump/-/pump-3.0.0.tgz",
543 | "integrity": "sha512-LwZy+p3SFs1Pytd/jYct4wpv49HiYCqd9Rlc5ZVdk0V+8Yzv6jR5Blk3TRmPL1ft69TxP0IMZGJ+WPFU2BFhww==",
544 | "requires": {
545 | "end-of-stream": "^1.1.0",
546 | "once": "^1.3.1"
547 | }
548 | },
549 | "puppeteer": {
550 | "version": "5.5.0",
551 | "resolved": "https://registry.npmjs.org/puppeteer/-/puppeteer-5.5.0.tgz",
552 | "integrity": "sha512-OM8ZvTXAhfgFA7wBIIGlPQzvyEETzDjeRa4mZRCRHxYL+GNH5WAuYUQdja3rpWZvkX/JKqmuVgbsxDNsDFjMEg==",
553 | "requires": {
554 | "debug": "^4.1.0",
555 | "devtools-protocol": "0.0.818844",
556 | "extract-zip": "^2.0.0",
557 | "https-proxy-agent": "^4.0.0",
558 | "node-fetch": "^2.6.1",
559 | "pkg-dir": "^4.2.0",
560 | "progress": "^2.0.1",
561 | "proxy-from-env": "^1.0.0",
562 | "rimraf": "^3.0.2",
563 | "tar-fs": "^2.0.0",
564 | "unbzip2-stream": "^1.3.3",
565 | "ws": "^7.2.3"
566 | }
567 | },
568 | "readable-stream": {
569 | "version": "3.6.0",
570 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.0.tgz",
571 | "integrity": "sha512-BViHy7LKeTz4oNnkcLJ+lVSL6vpiFeX6/d3oSH8zCW7UxP2onchk+vTGB143xuFjHS3deTgkKoXXymXqymiIdA==",
572 | "requires": {
573 | "inherits": "^2.0.3",
574 | "string_decoder": "^1.1.1",
575 | "util-deprecate": "^1.0.1"
576 | }
577 | },
578 | "require-directory": {
579 | "version": "2.1.1",
580 | "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
581 | "integrity": "sha1-jGStX9MNqxyXbiNE/+f3kqam30I="
582 | },
583 | "require-main-filename": {
584 | "version": "2.0.0",
585 | "resolved": "https://registry.npmjs.org/require-main-filename/-/require-main-filename-2.0.0.tgz",
586 | "integrity": "sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg=="
587 | },
588 | "rimraf": {
589 | "version": "3.0.2",
590 | "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
591 | "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
592 | "requires": {
593 | "glob": "^7.1.3"
594 | }
595 | },
596 | "safe-buffer": {
597 | "version": "5.2.1",
598 | "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
599 | "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ=="
600 | },
601 | "set-blocking": {
602 | "version": "2.0.0",
603 | "resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
604 | "integrity": "sha1-BF+XgtARrppoA93TgrJDkrPYkPc="
605 | },
606 | "setimmediate": {
607 | "version": "1.0.5",
608 | "resolved": "https://registry.npmjs.org/setimmediate/-/setimmediate-1.0.5.tgz",
609 | "integrity": "sha1-KQy7Iy4waULX1+qbg3Mqt4VvgoU="
610 | },
611 | "slice-stream": {
612 | "version": "1.0.0",
613 | "resolved": "https://registry.npmjs.org/slice-stream/-/slice-stream-1.0.0.tgz",
614 | "integrity": "sha1-WzO9ZvATsaf4ZGCwPUY97DmtPqA=",
615 | "requires": {
616 | "readable-stream": "~1.0.31"
617 | },
618 | "dependencies": {
619 | "readable-stream": {
620 | "version": "1.0.34",
621 | "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-1.0.34.tgz",
622 | "integrity": "sha1-Elgg40vIQtLyqq+v5MKRbuMsFXw=",
623 | "requires": {
624 | "core-util-is": "~1.0.0",
625 | "inherits": "~2.0.1",
626 | "isarray": "0.0.1",
627 | "string_decoder": "~0.10.x"
628 | }
629 | },
630 | "string_decoder": {
631 | "version": "0.10.31",
632 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-0.10.31.tgz",
633 | "integrity": "sha1-YuIDvEF2bGwoyfyEMB2rHFMQ+pQ="
634 | }
635 | }
636 | },
637 | "string-width": {
638 | "version": "4.2.0",
639 | "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.0.tgz",
640 | "integrity": "sha512-zUz5JD+tgqtuDjMhwIg5uFVV3dtqZ9yQJlZVfq4I01/K5Paj5UHj7VyrQOJvzawSVlKpObApbfD0Ed6yJc+1eg==",
641 | "requires": {
642 | "emoji-regex": "^8.0.0",
643 | "is-fullwidth-code-point": "^3.0.0",
644 | "strip-ansi": "^6.0.0"
645 | }
646 | },
647 | "string_decoder": {
648 | "version": "1.3.0",
649 | "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
650 | "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
651 | "requires": {
652 | "safe-buffer": "~5.2.0"
653 | }
654 | },
655 | "strip-ansi": {
656 | "version": "6.0.0",
657 | "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.0.tgz",
658 | "integrity": "sha512-AuvKTrTfQNYNIctbR1K/YGTR1756GycPsg7b9bdV9Duqur4gv6aKqHXah67Z8ImS7WEz5QVcOtlfW2rZEugt6w==",
659 | "requires": {
660 | "ansi-regex": "^5.0.0"
661 | }
662 | },
663 | "tar-fs": {
664 | "version": "2.1.1",
665 | "resolved": "https://registry.npmjs.org/tar-fs/-/tar-fs-2.1.1.tgz",
666 | "integrity": "sha512-V0r2Y9scmbDRLCNex/+hYzvp/zyYjvFbHPNgVTKfQvVrb6guiE/fxP+XblDNR011utopbkex2nM4dHNV6GDsng==",
667 | "requires": {
668 | "chownr": "^1.1.1",
669 | "mkdirp-classic": "^0.5.2",
670 | "pump": "^3.0.0",
671 | "tar-stream": "^2.1.4"
672 | }
673 | },
674 | "tar-stream": {
675 | "version": "2.2.0",
676 | "resolved": "https://registry.npmjs.org/tar-stream/-/tar-stream-2.2.0.tgz",
677 | "integrity": "sha512-ujeqbceABgwMZxEJnk2HDY2DlnUZ+9oEcb1KzTVfYHio0UE6dG71n60d8D2I4qNvleWrrXpmjpt7vZeF1LnMZQ==",
678 | "requires": {
679 | "bl": "^4.0.3",
680 | "end-of-stream": "^1.4.1",
681 | "fs-constants": "^1.0.0",
682 | "inherits": "^2.0.3",
683 | "readable-stream": "^3.1.1"
684 | }
685 | },
686 | "through": {
687 | "version": "2.3.8",
688 | "resolved": "https://registry.npmjs.org/through/-/through-2.3.8.tgz",
689 | "integrity": "sha1-DdTJ/6q8NXlgsbckEV1+Doai4fU="
690 | },
691 | "traverse": {
692 | "version": "0.3.9",
693 | "resolved": "https://registry.npmjs.org/traverse/-/traverse-0.3.9.tgz",
694 | "integrity": "sha1-cXuPIgzAu3tE5AUUwisui7xw2Lk="
695 | },
696 | "unbzip2-stream": {
697 | "version": "1.4.3",
698 | "resolved": "https://registry.npmjs.org/unbzip2-stream/-/unbzip2-stream-1.4.3.tgz",
699 | "integrity": "sha512-mlExGW4w71ebDJviH16lQLtZS32VKqsSfk80GCfUlwT/4/hNRFsoscrF/c++9xinkMzECL1uL9DDwXqFWkruPg==",
700 | "requires": {
701 | "buffer": "^5.2.1",
702 | "through": "^2.3.8"
703 | }
704 | },
705 | "util-deprecate": {
706 | "version": "1.0.2",
707 | "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
708 | "integrity": "sha1-RQ1Nyfpw3nMnYvvS1KKJgUGaDM8="
709 | },
710 | "which-module": {
711 | "version": "2.0.0",
712 | "resolved": "https://registry.npmjs.org/which-module/-/which-module-2.0.0.tgz",
713 | "integrity": "sha1-2e8H3Od7mQK4o6j6SzHD4/fm6Ho="
714 | },
715 | "wrap-ansi": {
716 | "version": "6.2.0",
717 | "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz",
718 | "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==",
719 | "requires": {
720 | "ansi-styles": "^4.0.0",
721 | "string-width": "^4.1.0",
722 | "strip-ansi": "^6.0.0"
723 | }
724 | },
725 | "wrappy": {
726 | "version": "1.0.2",
727 | "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
728 | "integrity": "sha1-tSQ9jz7BqjXxNkYFvA0QNuMKtp8="
729 | },
730 | "ws": {
731 | "version": "7.4.2",
732 | "resolved": "https://registry.npmjs.org/ws/-/ws-7.4.2.tgz",
733 | "integrity": "sha512-T4tewALS3+qsrpGI/8dqNMLIVdq/g/85U98HPMa6F0m6xTbvhXU6RCQLqPH3+SlomNV/LdY6RXEbBpMH6EOJnA=="
734 | },
735 | "y18n": {
736 | "version": "4.0.0",
737 | "resolved": "https://registry.npmjs.org/y18n/-/y18n-4.0.0.tgz",
738 | "integrity": "sha512-r9S/ZyXu/Xu9q1tYlpsLIsa3EeLXXk0VwlxqTcFRfg9EhMW+17kbt9G0NrgCmhGb5vT2hyhJZLfDGx+7+5Uj/w=="
739 | },
740 | "yargs": {
741 | "version": "15.4.1",
742 | "resolved": "https://registry.npmjs.org/yargs/-/yargs-15.4.1.tgz",
743 | "integrity": "sha512-aePbxDmcYW++PaqBsJ+HYUFwCdv4LVvdnhBy78E57PIor8/OVvhMrADFFEDh8DHDFRv/O9i3lPhsENjO7QX0+A==",
744 | "requires": {
745 | "cliui": "^6.0.0",
746 | "decamelize": "^1.2.0",
747 | "find-up": "^4.1.0",
748 | "get-caller-file": "^2.0.1",
749 | "require-directory": "^2.1.1",
750 | "require-main-filename": "^2.0.0",
751 | "set-blocking": "^2.0.0",
752 | "string-width": "^4.2.0",
753 | "which-module": "^2.0.0",
754 | "y18n": "^4.0.0",
755 | "yargs-parser": "^18.1.2"
756 | }
757 | },
758 | "yargs-parser": {
759 | "version": "18.1.3",
760 | "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-18.1.3.tgz",
761 | "integrity": "sha512-o50j0JeToy/4K6OZcaQmW6lyXXKhq7csREXcDwk2omFPJEwUNOVtJKvmDr9EI1fAJZUyZcRF7kxGBWmRXudrCQ==",
762 | "requires": {
763 | "camelcase": "^5.0.0",
764 | "decamelize": "^1.2.0"
765 | }
766 | },
767 | "yauzl": {
768 | "version": "2.10.0",
769 | "resolved": "https://registry.npmjs.org/yauzl/-/yauzl-2.10.0.tgz",
770 | "integrity": "sha1-x+sXyT4RLLEIb6bY5R+wZnt5pfk=",
771 | "requires": {
772 | "buffer-crc32": "~0.2.3",
773 | "fd-slicer": "~1.1.0"
774 | }
775 | }
776 | }
777 | }
778 |
--------------------------------------------------------------------------------
/package.json:
--------------------------------------------------------------------------------
1 | {
2 | "name": "roam-research-private-api",
3 | "version": "0.9.3",
4 | "description": "Library that loads your Roam Research graph as a browser and performs tasks as you.",
5 | "homepage": "https://deliber.at/roam/roam-api/",
6 | "keywords": [
7 | "roam",
8 | "roam-research",
9 | "evernote",
10 | "puppeteer"
11 | ],
12 | "main": "RoamPrivateApi.js",
13 | "repository": {
14 | "type": "git",
15 | "url": "https://github.com/artpi/roam-research-private-api"
16 | },
17 | "scripts": {
18 | "test": "echo \"Error: no test specified\" && exit 1",
19 | "evernote_sync": "node examples/sync_evernote.js",
20 | "reformat-files": "./node_modules/.bin/prettier --ignore-path .eslintignore --write \"**/*.{js,jsx,json,ts,tsx}\""
21 | },
22 | "bin": {
23 | "roam-api": "examples/cmd.js",
24 | "roam-evernote-sync": "examples/sync_evernote.js"
25 | },
26 | "author": "Artur Piszek ( piszek.com )",
27 | "license": "MIT",
28 | "dependencies": {
29 | "enml-js": "^0.1.3",
30 | "evernote": "^2.0.5",
31 | "moment": "^2.27.0",
32 | "node-fetch": "^2.6.1",
33 | "node-unzip-2": "^0.2.8",
34 | "puppeteer": "^5.5.0",
35 | "yargs": "^15.4.1"
36 | },
37 | "devDependencies": {
38 | "prettier": "npm:wp-prettier@2.0.5"
39 | },
40 | "engines": {
41 | "node": ">=12.0.0",
42 | "npm": ">=6.0.0"
43 | }
44 | }
45 |
--------------------------------------------------------------------------------
/secrets-template.json:
--------------------------------------------------------------------------------
1 | {
2 | "email": "your-roam-account@email.com",
3 | "password": "roam-account-password",
4 | "graph": "your-roam-graph-name",
5 | "evernote_token": "Evernote developer token from https://www.evernote.com/api/DeveloperToken.action"
6 | }
7 |
--------------------------------------------------------------------------------