{"action":"created","comment":{"url":"https://api.github.com/repos/resin-io/resin-cli/pulls/comments/196018609","pull_request_review_id":129490457,"id":196018609,"node_id":"MDI0OlB1bGxSZXF1ZXN0UmV2aWV3Q29tbWVudDE5NjAxODYwOQ==","diff_hunk":"@@ -0,0 +1,371 @@\n+import { spawn } from 'child_process';\n+\n+import { stripIndent } from 'common-tags';\n+\n+import * as Bluebird from 'bluebird';\n+import * as resin from 'resin-sdk';\n+import TypedError = require('typed-error');\n+\n+import Logger = require('./logger');\n+\n+import { getSubShellCommand, runCommand } from './helpers';\n+\n+export async function join(\n+\tlogger: Logger,\n+\tsdk: resin.ResinSDK,\n+\tdeviceHostnameOrIp?: string,\n+\tappName?: string,\n+): Promise<void> {\n+\tlogger.logDebug('Checking login...');\n+\tconst isLoggedIn = await sdk.auth.isLoggedIn();\n+\tif (!isLoggedIn) {\n+\t\tlogger.logInfo(\"Looks like you're not logged in yet!\");\n+\t\tlogger.logInfo('Lets go through a quick wizard to get you started.\\n');\n+\t\tawait runCommand('login');\n+\t}\n+\n+\tlogger.logDebug('Determining device...');\n+\tconst deviceIp = await getOrSelectLocalDevice(deviceHostnameOrIp);\n+\tawait assertDeviceIsCompatible(deviceIp);\n+\tlogger.logDebug(`Using device: ${deviceIp}`);\n+\n+\tlogger.logDebug('Determining device type...');\n+\tconst deviceType = await getDeviceType(deviceIp);\n+\tlogger.logDebug(`Device type: ${deviceType}`);\n+\n+\tlogger.logDebug('Determining application...');\n+\tconst app = await getOrSelectApplication(sdk, deviceType, appName);\n+\tlogger.logDebug(`Using application: ${app.app_name}`);\n+\n+\tlogger.logDebug('Generating application config...');\n+\tconst config = await generateApplicationConfig(sdk, app);\n+\tlogger.logDebug(`Using config: ${JSON.stringify(config, null, 2)}`);\n+\n+\tlogger.logDebug('Configuring...');\n+\tawait configure(deviceIp, config);\n+\tlogger.logDebug('All done.');\n+\n+\tconst platformUrl = await sdk.settings.get('resinUrl');\n+\tlogger.logSuccess(`Device successfully joined ${platformUrl}!`);\n+}\n+\n+export async function leave(\n+\tlogger: Logger,\n+\tsdk: resin.ResinSDK,\n+\tdeviceHostnameOrIp?: string,\n+): Promise<void> {\n+\tlogger.logDebug('Determining device...');\n+\tconst deviceIp = await getOrSelectLocalDevice(deviceHostnameOrIp);\n+\tawait assertDeviceIsCompatible(deviceIp);\n+\tlogger.logDebug(`Using device: ${deviceIp}`);\n+\n+\tlogger.logDebug('Deconfiguring...');\n+\tawait deconfigure(deviceIp);\n+\tlogger.logDebug('All done.');\n+\n+\tconst platformUrl = await sdk.settings.get('resinUrl');\n+\tlogger.logSuccess(`Device successfully left ${platformUrl}.`);\n+}\n+\n+export class ExecError extends TypedError {\n+\tpublic cmd: string;\n+\tpublic exitCode: number;\n+\n+\tconstructor(cmd: string, exitCode: number) {\n+\t\tsuper(`Command '${cmd}' failed with error: ${exitCode}`);\n+\t\tthis.cmd = cmd;\n+\t\tthis.exitCode = exitCode;\n+\t}\n+}\n+\n+async function exec(\n+\tdeviceIp: string,\n+\tcmd: string,\n+\tstdout?: NodeJS.WritableStream,\n+): Promise<void> {\n+\tconst command = `ssh \\\n+\t\t-t \\\n+\t\t-p 22222 \\\n+\t\t-o LogLevel=ERROR \\\n+\t\t-o StrictHostKeyChecking=no \\\n+\t\t-o UserKnownHostsFile=/dev/null \\\n+\t\troot@${deviceIp} \\\n+\t\t${cmd}`;\n+\n+\tconst stdio = ['ignore', stdout ? 'pipe' : 'inherit', 'ignore'];\n+\tconst { program, args } = getSubShellCommand(command);\n+\n+\tconst exitCode = await new Bluebird<number>((resolve, reject) => {\n+\t\tconst ps = spawn(program, args, { stdio })\n+\t\t\t.on('error', reject)\n+\t\t\t.on('close', resolve);\n+\n+\t\tif (stdout) {\n+\t\t\tps.stdout.pipe(stdout);\n+\t\t}\n+\t});\n+\tif (exitCode != 0) {\n+\t\tthrow new ExecError(cmd, exitCode);\n+\t}\n+}\n+\n+async function execBuffered(\n+\tdeviceIp: string,\n+\tcmd: string,\n+\tenc?: string,\n+): Promise<string> {\n+\tconst through = await import('through2');\n+\tconst buffer: string[] = [];\n+\tawait exec(\n+\t\tdeviceIp,\n+\t\tcmd,\n+\t\tthrough(function(data, _enc, cb) {\n+\t\t\tbuffer.push(data.toString(enc));\n+\t\t\tcb();\n+\t\t}),\n+\t);\n+\treturn buffer.join('');\n+}\n+\n+async function execCommand(\n+\tdeviceIp: string,\n+\tcmd: string,\n+\tmsg: string,\n+): Promise<void> {\n+\tconst through = await import('through2');\n+\tconst visuals = await import('resin-cli-visuals');\n+\n+\tconst spinner = new visuals.Spinner(`[${deviceIp}] Connecting...`);\n+\tconst innerSpinner = spinner.spinner;\n+\n+\tconst stream = through(function(data, _enc, cb) {\n+\t\tinnerSpinner.setSpinnerTitle(`%s [${deviceIp}] ${msg}`);\n+\t\tcb(null, data);\n+\t});\n+\n+\tspinner.start();\n+\tawait exec(deviceIp, cmd, stream);\n+\tspinner.stop();\n+}\n+\n+async function configure(deviceIp: string, config: any): Promise<void> {\n+\tconst json = JSON.stringify(config);\n+\tconst b64 = Buffer.from(json).toString('base64');\n+\tconst str = `\"$(base64 -d <<< ${b64})\"`;\n+\tawait execCommand(deviceIp, `os-config join '${str}'`, 'Configuring...');\n+}\n+\n+async function deconfigure(deviceIp: string): Promise<void> {\n+\tawait execCommand(deviceIp, 'os-config leave', 'Configuring...');\n+}\n+\n+async function assertDeviceIsCompatible(deviceIp: string): Promise<void> {\n+\tconst { exitWithExpectedError } = await import('../utils/patterns');\n+\ttry {\n+\t\tawait execBuffered(deviceIp, 'os-config --version');\n+\t} catch (err) {\n+\t\texitWithExpectedError(stripIndent`\n+\t\t\tDevice \"${deviceIp}\" is incompatible and cannot join or leave an application.\n+\t\t\tPlease select or provision a newer device.`);\n+\t}\n+}\n+\n+async function getDeviceType(deviceIp: string): Promise<string> {\n+\tconst output = await execBuffered(deviceIp, 'cat /etc/os-release');\n+\tconst match = /^SLUG=\"([^\"]+)\"$/m.exec(output);\n+\tif (!match) {\n+\t\tthrow new Error('Failed to determine device type');\n+\t}\n+\treturn match[1];\n+}\n+\n+async function getOrSelectLocalDevice(deviceIp?: string): Promise<string> {\n+\tif (deviceIp) {\n+\t\treturn deviceIp;\n+\t}\n+\n+\tconst through = await import('through2');\n+\n+\tlet ip: string | null = null;\n+\tconst stream = through(function(data, _enc, cb) {\n+\t\tconst match = /^==> Selected device: (.*)$/m.exec(data.toString());\n+\t\tif (match) {\n+\t\t\tip = match[1];\n+\t\t\tcb();\n+\t\t} else {\n+\t\t\tcb(null, data);\n+\t\t}\n+\t});\n+\n+\tstream.pipe(process.stderr);\n+\n+\tconst { sudo } = await import('../utils/helpers');\n+\tconst command = process.argv.slice(0, 2).concat(['internal', 'scandevices']);\n+\tawait sudo(command, {\n+\t\tstderr: stream,\n+\t\tmsg:\n+\t\t\t'Scanning for local devices. If asked, please type your computer password.',\n+\t});\n+\n+\tif (!ip) {\n+\t\tthrow new Error('No device selected');\n+\t}\n+\n+\treturn ip;\n+}\n+\n+async function getOrSelectApplication(\n+\tsdk: resin.ResinSDK,\n+\tdeviceType: string,\n+\tappName?: string,\n+): Promise<resin.Application> {\n+\tconst _ = await import('lodash');\n+\tconst form = await import('resin-cli-form');\n+\tconst { selectFromList } = await import('../utils/patterns');\n+\n+\tconst options: any = {\n+\t\t$expand: { user: { $select: ['username'] } },\n+\t\t$filter: { device_type: deviceType },","path":"lib/utils/promote.ts","position":228,"original_position":228,"commit_id":"225dca7df5ff5e76c5e8aa366af96f778ab41d31","original_commit_id":"225dca7df5ff5e76c5e8aa366af96f778ab41d31","user":{"login":"dfunckt","id":130359,"node_id":"MDQ6VXNlcjEzMDM1OQ==","avatar_url":"https://avatars3.githubusercontent.com/u/130359?v=4","gravatar_id":"","url":"https://api.github.com/users/dfunckt","html_url":"https://github.com/dfunckt","followers_url":"https://api.github.com/users/dfunckt/followers","following_url":"https://api.github.com/users/dfunckt/following{/other_user}","gists_url":"https://api.github.com/users/dfunckt/gists{/gist_id}","starred_url":"https://api.github.com/users/dfunckt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dfunckt/subscriptions","organizations_url":"https://api.github.com/users/dfunckt/orgs","repos_url":"https://api.github.com/users/dfunckt/repos","events_url":"https://api.github.com/users/dfunckt/events{/privacy}","received_events_url":"https://api.github.com/users/dfunckt/received_events","type":"User","site_admin":false},"body":"Hmm, good catch. This is tricky though, and `os-config` on resinOS is also doing this sort of validation (I only do validation here in order to fail early and give feedback to user). This needs further discussion, though unless you feel strongly about it, I'd rather we didn't block on this.","created_at":"2018-06-18T09:40:06Z","updated_at":"2018-06-18T09:40:07Z","html_url":"https://github.com/resin-io/resin-cli/pull/895#discussion_r196018609","pull_request_url":"https://api.github.com/repos/resin-io/resin-cli/pulls/895","author_association":"MEMBER","_links":{"self":{"href":"https://api.github.com/repos/resin-io/resin-cli/pulls/comments/196018609"},"html":{"href":"https://github.com/resin-io/resin-cli/pull/895#discussion_r196018609"},"pull_request":{"href":"https://api.github.com/repos/resin-io/resin-cli/pulls/895"}},"in_reply_to_id":195777686},"pull_request":{"url":"https://api.github.com/repos/resin-io/resin-cli/pulls/895","id":194509669,"node_id":"MDExOlB1bGxSZXF1ZXN0MTk0NTA5NjY5","html_url":"https://github.com/resin-io/resin-cli/pull/895","diff_url":"https://github.com/resin-io/resin-cli/pull/895.diff","patch_url":"https://github.com/resin-io/resin-cli/pull/895.patch","issue_url":"https://api.github.com/repos/resin-io/resin-cli/issues/895","number":895,"state":"open","locked":false,"title":" Add join/leave commands to promote and move devices between platforms","user":{"login":"dfunckt","id":130359,"node_id":"MDQ6VXNlcjEzMDM1OQ==","avatar_url":"https://avatars3.githubusercontent.com/u/130359?v=4","gravatar_id":"","url":"https://api.github.com/users/dfunckt","html_url":"https://github.com/dfunckt","followers_url":"https://api.github.com/users/dfunckt/followers","following_url":"https://api.github.com/users/dfunckt/following{/other_user}","gists_url":"https://api.github.com/users/dfunckt/gists{/gist_id}","starred_url":"https://api.github.com/users/dfunckt/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/dfunckt/subscriptions","organizations_url":"https://api.github.com/users/dfunckt/orgs","repos_url":"https://api.github.com/users/dfunckt/repos","events_url":"https://api.github.com/users/dfunckt/events{/privacy}","received_events_url":"https://api.github.com/users/dfunckt/received_events","type":"User","site_admin":false},"body":"Both commands work with local devices by remotely invoking the `os-config` executable via SSH. This requires an as of yet unreleased resinOS (that will most likely be v2.14) and the commands ascertain compatibility merely by looking for the `os-config` executable in the device, and bail out if it’s not present.\r\n\r\n`join` and `leave` accept a couple of optional arguments and implement a wizard-style interface if these are not given. They allow to interactively select the device and the application to promote to. If the user has no apps, `join` will offer the user to create one. `join` will also offer the user to login or create an account if they’re not logged in already without exiting the wizard.\r\n\r\n`resin-sync` (that's used internally to discover local devices) requires admin privileges. If no device has been specified as an argument, the commands will launch the device scanning process in a privileged subprocess via two new internal commands: `internal sudo` and `internal scanDevices`. This avoids having the user to invoke the commands with sudo and only request escalation if truly needed. This commit also removes the dependency to “president”, implementing “sudo” functionality within the CLI.\r\n\r\nChange-Type: minor","created_at":"2018-06-13T08:38:13Z","updated_at":"2018-06-18T09:40:07Z","closed_at":null,"merged_at":null,"merge_commit_sha":"4ad35db958a3f4ca9b26fe2441eb8c7044fdbacf","assignee":null,"assignees":[],"requested_reviewers":[{"login":"thgreasi","id":1295829,"node_id":"MDQ6VXNlcjEyOTU4Mjk=","avatar_url":"https://avatars0.githubusercontent.com/u/1295829?v=4","gravatar_id":"","url":"https://api.github.com/users/thgreasi","html_url":"https://github.com/thgreasi","followers_url":"https://api.github.com/users/thgreasi/followers","following_url":"https://api.github.com/users/thgreasi/following{/other_user}","gists_url":"https://api.github.com/users/thgreasi/gists{/gist_id}","starred_url":"https://api.github.com/users/thgreasi/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/thgreasi/subscriptions","organizations_url":"https://api.github.com/users/thgreasi/orgs","repos_url":"https://api.github.com/users/thgreasi/repos","events_url":"https://api.github.com/users/thgreasi/events{/privacy}","received_events_url":"https://api.github.com/users/thgreasi/received_events","type":"User","site_admin":false},{"login":"shaunmulligan","id":3915043,"node_id":"MDQ6VXNlcjM5MTUwNDM=","avatar_url":"https://avatars1.githubusercontent.com/u/3915043?v=4","gravatar_id":"","url":"https://api.github.com/users/shaunmulligan","html_url":"https://github.com/shaunmulligan","followers_url":"https://api.github.com/users/shaunmulligan/followers","following_url":"https://api.github.com/users/shaunmulligan/following{/other_user}","gists_url":"https://api.github.com/users/shaunmulligan/gists{/gist_id}","starred_url":"https://api.github.com/users/shaunmulligan/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/shaunmulligan/subscriptions","organizations_url":"https://api.github.com/users/shaunmulligan/orgs","repos_url":"https://api.github.com/users/shaunmulligan/repos","events_url":"https://api.github.com/users/shaunmulligan/events{/privacy}","received_events_url":"https://api.github.com/users/shaunmulligan/received_events","type":"User","site_admin":false},{"login":"CameronDiver","id":7694128,"node_id":"MDQ6VXNlcjc2OTQxMjg=","avatar_url":"https://avatars0.githubusercontent.com/u/7694128?v=4","gravatar_id":"","url":"https://api.github.com/users/CameronDiver","html_url":"https://github.com/CameronDiver","followers_url":"https://api.github.com/users/CameronDiver/followers","following_url":"https://api.github.com/users/CameronDiver/following{/other_user}","gists_url":"https://api.github.com/users/CameronDiver/gists{/gist_id}","starred_url":"https://api.github.com/users/CameronDiver/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/CameronDiver/subscriptions","organizations_url":"https://api.github.com/users/CameronDiver/orgs","repos_url":"https://api.github.com/users/CameronDiver/repos","events_url":"https://api.github.com/users/CameronDiver/events{/privacy}","received_events_url":"https://api.github.com/users/CameronDiver/received_events","type":"User","site_admin":false}],"requested_teams":[],"labels":[],"milestone":null,"commits_url":"https://api.github.com/repos/resin-io/resin-cli/pulls/895/commits","review_comments_url":"https://api.github.com/repos/resin-io/resin-cli/pulls/895/comments","review_comment_url":"https://api.github.com/repos/resin-io/resin-cli/pulls/comments{/number}","comments_url":"https://api.github.com/repos/resin-io/resin-cli/issues/895/comments","statuses_url":"https://api.github.com/repos/resin-io/resin-cli/statuses/225dca7df5ff5e76c5e8aa366af96f778ab41d31","head":{"label":"resin-io:promote-command","ref":"promote-command","sha":"225dca7df5ff5e76c5e8aa366af96f778ab41d31","user":{"login":"resin-io","id":6157842,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTc4NDI=","avatar_url":"https://avatars1.githubusercontent.com/u/6157842?v=4","gravatar_id":"","url":"https://api.github.com/users/resin-io","html_url":"https://github.com/resin-io","followers_url":"https://api.github.com/users/resin-io/followers","following_url":"https://api.github.com/users/resin-io/following{/other_user}","gists_url":"https://api.github.com/users/resin-io/gists{/gist_id}","starred_url":"https://api.github.com/users/resin-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/resin-io/subscriptions","organizations_url":"https://api.github.com/users/resin-io/orgs","repos_url":"https://api.github.com/users/resin-io/repos","events_url":"https://api.github.com/users/resin-io/events{/privacy}","received_events_url":"https://api.github.com/users/resin-io/received_events","type":"Organization","site_admin":false},"repo":{"id":31549380,"node_id":"MDEwOlJlcG9zaXRvcnkzMTU0OTM4MA==","name":"resin-cli","full_name":"resin-io/resin-cli","owner":{"login":"resin-io","id":6157842,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTc4NDI=","avatar_url":"https://avatars1.githubusercontent.com/u/6157842?v=4","gravatar_id":"","url":"https://api.github.com/users/resin-io","html_url":"https://github.com/resin-io","followers_url":"https://api.github.com/users/resin-io/followers","following_url":"https://api.github.com/users/resin-io/following{/other_user}","gists_url":"https://api.github.com/users/resin-io/gists{/gist_id}","starred_url":"https://api.github.com/users/resin-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/resin-io/subscriptions","organizations_url":"https://api.github.com/users/resin-io/orgs","repos_url":"https://api.github.com/users/resin-io/repos","events_url":"https://api.github.com/users/resin-io/events{/privacy}","received_events_url":"https://api.github.com/users/resin-io/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/resin-io/resin-cli","description":"The official resin.io CLI tool.","fork":false,"url":"https://api.github.com/repos/resin-io/resin-cli","forks_url":"https://api.github.com/repos/resin-io/resin-cli/forks","keys_url":"https://api.github.com/repos/resin-io/resin-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/resin-io/resin-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/resin-io/resin-cli/teams","hooks_url":"https://api.github.com/repos/resin-io/resin-cli/hooks","issue_events_url":"https://api.github.com/repos/resin-io/resin-cli/issues/events{/number}","events_url":"https://api.github.com/repos/resin-io/resin-cli/events","assignees_url":"https://api.github.com/repos/resin-io/resin-cli/assignees{/user}","branches_url":"https://api.github.com/repos/resin-io/resin-cli/branches{/branch}","tags_url":"https://api.github.com/repos/resin-io/resin-cli/tags","blobs_url":"https://api.github.com/repos/resin-io/resin-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/resin-io/resin-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/resin-io/resin-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/resin-io/resin-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/resin-io/resin-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/resin-io/resin-cli/languages","stargazers_url":"https://api.github.com/repos/resin-io/resin-cli/stargazers","contributors_url":"https://api.github.com/repos/resin-io/resin-cli/contributors","subscribers_url":"https://api.github.com/repos/resin-io/resin-cli/subscribers","subscription_url":"https://api.github.com/repos/resin-io/resin-cli/subscription","commits_url":"https://api.github.com/repos/resin-io/resin-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/resin-io/resin-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/resin-io/resin-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/resin-io/resin-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/resin-io/resin-cli/contents/{+path}","compare_url":"https://api.github.com/repos/resin-io/resin-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/resin-io/resin-cli/merges","archive_url":"https://api.github.com/repos/resin-io/resin-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/resin-io/resin-cli/downloads","issues_url":"https://api.github.com/repos/resin-io/resin-cli/issues{/number}","pulls_url":"https://api.github.com/repos/resin-io/resin-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/resin-io/resin-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/resin-io/resin-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/resin-io/resin-cli/labels{/name}","releases_url":"https://api.github.com/repos/resin-io/resin-cli/releases{/id}","deployments_url":"https://api.github.com/repos/resin-io/resin-cli/deployments","created_at":"2015-03-02T16:18:03Z","updated_at":"2018-06-13T13:52:20Z","pushed_at":"2018-06-13T14:34:48Z","git_url":"git://github.com/resin-io/resin-cli.git","ssh_url":"git@github.com:resin-io/resin-cli.git","clone_url":"https://github.com/resin-io/resin-cli.git","svn_url":"https://github.com/resin-io/resin-cli","homepage":"","size":2481,"stargazers_count":69,"watchers_count":69,"language":"CoffeeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":15,"mirror_url":null,"archived":false,"open_issues_count":145,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":15,"open_issues":145,"watchers":69,"default_branch":"master"}},"base":{"label":"resin-io:master","ref":"master","sha":"bc86359e6332277074887c9db0cabb1ae671dd30","user":{"login":"resin-io","id":6157842,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTc4NDI=","avatar_url":"https://avatars1.githubusercontent.com/u/6157842?v=4","gravatar_id":"","url":"https://api.github.com/users/resin-io","html_url":"https://github.com/resin-io","followers_url":"https://api.github.com/users/resin-io/followers","following_url":"https://api.github.com/users/resin-io/following{/other_user}","gists_url":"https://api.github.com/users/resin-io/gists{/gist_id}","starred_url":"https://api.github.com/users/resin-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/resin-io/subscriptions","organizations_url":"https://api.github.com/users/resin-io/orgs","repos_url":"https://api.github.com/users/resin-io/repos","events_url":"https://api.github.com/users/resin-io/events{/privacy}","received_events_url":"https://api.github.com/users/resin-io/received_events","type":"Organization","site_admin":false},"repo":{"id":31549380,"node_id":"MDEwOlJlcG9zaXRvcnkzMTU0OTM4MA==","name":"resin-cli","full_name":"resin-io/resin-cli","owner":{"login":"resin-io","id":6157842,"node_id":"MDEyOk9yZ2FuaXphdGlvbjYxNTc4NDI=","avatar_url":"https://avatars1.githubusercontent.com/u/6157842?v=4","gravatar_id":"","url":"https://api.github.com/users/resin-io","html_url":"https://github.com/resin-io","followers_url":"https://api.github.com/users/resin-io/followers","following_url":"https://api.github.com/users/resin-io/following{/other_user}","gists_url":"https://api.github.com/users/resin-io/gists{/gist_id}","starred_url":"https://api.github.com/users/resin-io/starred{/owner}{/repo}","subscriptions_url":"https://api.github.com/users/resin-io/subscriptions","organizations_url":"https://api.github.com/users/resin-io/orgs","repos_url":"https://api.github.com/users/resin-io/repos","events_url":"https://api.github.com/users/resin-io/events{/privacy}","received_events_url":"https://api.github.com/users/resin-io/received_events","type":"Organization","site_admin":false},"private":false,"html_url":"https://github.com/resin-io/resin-cli","description":"The official resin.io CLI tool.","fork":false,"url":"https://api.github.com/repos/resin-io/resin-cli","forks_url":"https://api.github.com/repos/resin-io/resin-cli/forks","keys_url":"https://api.github.com/repos/resin-io/resin-cli/keys{/key_id}","collaborators_url":"https://api.github.com/repos/resin-io/resin-cli/collaborators{/collaborator}","teams_url":"https://api.github.com/repos/resin-io/resin-cli/teams","hooks_url":"https://api.github.com/repos/resin-io/resin-cli/hooks","issue_events_url":"https://api.github.com/repos/resin-io/resin-cli/issues/events{/number}","events_url":"https://api.github.com/repos/resin-io/resin-cli/events","assignees_url":"https://api.github.com/repos/resin-io/resin-cli/assignees{/user}","branches_url":"https://api.github.com/repos/resin-io/resin-cli/branches{/branch}","tags_url":"https://api.github.com/repos/resin-io/resin-cli/tags","blobs_url":"https://api.github.com/repos/resin-io/resin-cli/git/blobs{/sha}","git_tags_url":"https://api.github.com/repos/resin-io/resin-cli/git/tags{/sha}","git_refs_url":"https://api.github.com/repos/resin-io/resin-cli/git/refs{/sha}","trees_url":"https://api.github.com/repos/resin-io/resin-cli/git/trees{/sha}","statuses_url":"https://api.github.com/repos/resin-io/resin-cli/statuses/{sha}","languages_url":"https://api.github.com/repos/resin-io/resin-cli/languages","stargazers_url":"https://api.github.com/repos/resin-io/resin-cli/stargazers","contributors_url":"https://api.github.com/repos/resin-io/resin-cli/contributors","subscribers_url":"https://api.github.com/repos/resin-io/resin-cli/subscribers","subscription_url":"https://api.github.com/repos/resin-io/resin-cli/subscription","commits_url":"https://api.github.com/repos/resin-io/resin-cli/commits{/sha}","git_commits_url":"https://api.github.com/repos/resin-io/resin-cli/git/commits{/sha}","comments_url":"https://api.github.com/repos/resin-io/resin-cli/comments{/number}","issue_comment_url":"https://api.github.com/repos/resin-io/resin-cli/issues/comments{/number}","contents_url":"https://api.github.com/repos/resin-io/resin-cli/contents/{+path}","compare_url":"https://api.github.com/repos/resin-io/resin-cli/compare/{base}...{head}","merges_url":"https://api.github.com/repos/resin-io/resin-cli/merges","archive_url":"https://api.github.com/repos/resin-io/resin-cli/{archive_format}{/ref}","downloads_url":"https://api.github.com/repos/resin-io/resin-cli/downloads","issues_url":"https://api.github.com/repos/resin-io/resin-cli/issues{/number}","pulls_url":"https://api.github.com/repos/resin-io/resin-cli/pulls{/number}","milestones_url":"https://api.github.com/repos/resin-io/resin-cli/milestones{/number}","notifications_url":"https://api.github.com/repos/resin-io/resin-cli/notifications{?since,all,participating}","labels_url":"https://api.github.com/repos/resin-io/resin-cli/labels{/name}","releases_url":"https://api.github.com/repos/resin-io/resin-cli/releases{/id}","deployments_url":"https://api.github.com/repos/resin-io/resin-cli/deployments","created_at":"2015-03-02T16:18:03Z","updated_at":"2018-06-13T13:52:20Z","pushed_at":"2018-06-13T14:34:48Z","git_url":"git://github.com/resin-io/resin-cli.git","ssh_url":"git@github.com:resin-io/resin-cli.git","clone_url":"https://github.com/resin-io/resin-cli.git","svn_url":"https://github.com/resin-io/resin-cli","homepage":"","size":2481,"stargazers_count":69,"watchers_count":69,"language":"CoffeeScript","has_issues":true,"has_projects":true,"has_downloads":true,"has_wiki":true,"has_pages":false,"forks_count":15,"mirror_url":null,"archived":false,"open_issues_count":145,"license":{"key":"apache-2.0","name":"Apache License 2.0","spdx_id":"Apache-2.0","url":"https://api.github.com/licenses/apache-2.0","node_id":"MDc6TGljZW5zZTI="},"forks":15,"open_issues":145,"watchers":69,"default_branch":"master"}},"_links":{"self":{"href":"https://api.github.com/repos/resin-io/resin-cli/pulls/895"},"html":{"href":"https://github.com/resin-io/resin-cli/pull/895"},"issue":{"href":"https://api.github.com/repos/resin-io/resin-cli/issues/895"},"comments":{"href":"https://api.github.com/repos/resin-io/resin-cli/issues/895/comments"},"review_comments":{"href":"https://api.github.com/repos/resin-io/resin-cli/pulls/895/comments"},"review_comment":{"href":"https://api.github.com/repos/resin-io/resin-cli/pulls/comments{/number}"},"commits":{"href":"https://api.github.com/repos/resin-io/resin-cli/pulls/895/commits"},"statuses":{"href":"https://api.github.com/repos/resin-io/resin-cli/statuses/225dca7df5ff5e76c5e8aa366af96f778ab41d31"}},"author_association":"MEMBER"}}
{"actor":{"display_login":"dfunckt"}}