Browse Source

stuck on POST bug (server not receiving payload)

main
deicidus 2 years ago
parent
commit
90b22aa611
  1. 15
      .prettierignore
  2. 13
      .prettierrc
  3. 44
      README.md
  4. 142
      index.js
  5. 24
      package-lock.json
  6. 6
      package.json
  7. 1711
      scripts/api.js
  8. 1
      scripts/bootstrap.js
  9. 12
      scripts/features.js
  10. 2
      scripts/manual.js
  11. 23
      scripts/priority.js
  12. 62
      scripts/session.js
  13. 2
      scripts/settings.js
  14. 0
      scripts/styles.js
  15. 6
      scripts/tests.js
  16. 2
      scripts/util.js
  17. 20
      scripts/welcome.js

15
.prettierignore

@ -0,0 +1,15 @@
.DS_Store
node_modules
/build
/dist
/production
/.svelte-kit
/package
.env
.env.*
!.env.example
# Ignore files for PNPM, NPM and YARN
pnpm-lock.yaml
package-lock.json
yarn.lock

13
.prettierrc

@ -0,0 +1,13 @@
{
printWidth: 80,
useTabs: false,
tabWidth: 2,
semi: false,
singleQuote: true,
quoteProps: 'as-needed',
trailingComma: 'none',
bracketSpacing: true,
arrowParens: 'avoid',
htmlWhitespaceSensitivity: strict,
vueIndentScriptAndStyle: true,
}

44
README.md

@ -1,6 +1,6 @@
# ao-cli
`ao-cli` (alias `ao`) is a command-line interface (CLI) that helps you install, use, and configure the Autonomous Organization (AO). `ao-cli` is a Node/JavaScript CLI tool that wraps the functionality of Alchemy, AO administration, plus key AO features into one convenient interface. Command-line social networking.
`ao-cli` (alias `ao`) is a command-line interface (CLI) that helps you install, use, and configure the Autonomous Organization (AO). Command-line social networking for hackers.
To run immediately:
@ -10,10 +10,46 @@ To install:
`npm i -g @autonomousorganization/ao-cli`
Then you can run with `ao-cli`. (Inside the menu you will find an option to add 'ao' as a shortcut.)
Then you can run with `ao-cli`. (In the menus you will find an option to add `ao` as an alias.)
### Version History
## Features
These features work right now:
* Browse the [AO User Manual](https://git.coalitionofinvisiblecolleges.org/autonomousorganization/ao-manual) and automatically download and keep it updated
* Manages your AO configuration file for you
* Wraps the functionality of (some of) Zen's Alchemy suite of scripts (system configuration, AO installation)
* `ao-cli` can self-update to the newest version
* Run AO unit tests to verify the up-to-spec functioning of the system's running AO API server
## Upcoming Features
These features are planned and many are mocked up in the menus:
* Join the AO .onion bootstrapping network and find public AO chatrooms p2p over tor
* Operate essential AO client features (like creating and sending cards p2p via tor)
* Easily install and configure your AO server installation
* Easily use hardware-owner-only god-mode features for your AO server including resetting any password or deleting any member
* Easily monitor your AO server status and start/stop the service
* Easily switch between serving different AO frontends: `ao-svelte`, `ao-3` (Vue), or `ao-react`
* Easily install/uninstall and turn on/off option AO features
* Easily update all your remote AOs at once
* Easily install your preferred flavor of Unix on any unsecured Windows computer given its IP address (j/k)
* Full interactive wizard to walk you through setting up and connecting new AO hardware resources to your AO server
## Important Locations
* `~/.ao/` Your AO saved data folder
* `~/.ao/database.sqlite3` Location of your AO database (copy to back up)
* `~/ao-cli/` Typical location for `ao-cli`
* `~/ao-svelte/` Typical location for `ao-svelte`
* `~/ao-3/` Typical location for `ao-3`
* `~/.ao/ao-manual/` Typical location of the AO manual (Markdown files)
* `~/Alchemy/` Typical location of Zen's Alchemy
## Version History
* 0.0.8 Added self-update feature and --version/-v arg
* 0.0.6 User manual downloads and updates automatically from [official ao-manual repo](https://git.coalitionofinvisiblecolleges.org/autonomousorganization/ao-manual)
* 0.0.5 Added browsable manual (must download ao-svelte)
* 0.0.5 Added browsable manual
* 0.0.1 Menus prototyped

142
index.js

@ -10,10 +10,13 @@ import { printManualPage, manualFolderAsMenu, AO_MANUAL_PATH } from './scripts/m
import { isFolder, loadJsonFile } from './scripts/files.js'
import { sleep } from './scripts/util.js'
import { tests } from './scripts/tests.js'
import { headerStyle } from './scripts/chalkStyles.js'
import { headerStyle, greenChalk } from './scripts/styles.js'
import './scripts/strings.js'
import { installAoAlias, getAoCliVersion, selfUpdate, downloadManual, updateManual } from './scripts/features.js'
import { startPublicBootstrap } from './scripts/bootstrap.js'
import { isLoggedIn, loginPrompt, logout } from './scripts/session.js'
import { AO_DEFAULT_HOSTNAME } from './scripts/api.js'
import { getTopPriorityText } from './scripts/priority.js'
// These should become .env variables that are loaded intelligently
let distro
@ -32,15 +35,13 @@ function exitIfRoot() {
// Prints the AO Main Menu and executes the user's choice
async function mainMenu() {
console.log(`\n${headerStyle('AO Main Menu')}\n`)
const mainMenuChoices = [
'Chat',
'Alchemy',
'Deck',
let mainMenuChoices = [
'AO',
'Features',
'Admin',
'Tests',
'Alchemy',
'Manual',
'Log Out',
'Exit',
'Exit'
]
const answer = await inquirer.prompt({
name: 'main_menu',
@ -50,20 +51,17 @@ async function mainMenu() {
pageSize: mainMenuChoices.length
})
switch(answer.main_menu) {
case 'Chat':
while(await chatMenu()) {}
case 'AO':
while(await useAoMenu()) {}
break
case 'Alchemy':
while(await alchemyMenu()) {}
break
case 'Deck':
await todoList('My Todo List', ['Add full AO install process to ao-cli in convenient format', 'Add AO server unit tests to ao-cli', 'Get groceries', 'Play music every day'])
case 'Features':
while(await featuresMenu()) {}
break
case 'Admin':
while(await adminMenu()) {}
break
case 'Tests':
while(await testsMenu()) {}
case 'Alchemy':
while(await alchemyMenu()) {}
break
case 'Manual':
if(!isFolder(AO_MANUAL_PATH)) {
@ -78,14 +76,11 @@ async function mainMenu() {
updateManual()
}
await printManualPage(AO_MANUAL_PATH) // Fencepost case - print overview page
let previousChoice = 0
let previousChoice
do {
previousChoice = await manualFolderAsMenu(AO_MANUAL_PATH, 'AO User Manual', 'Back to Main Menu', previousChoice + 1)
} while(previousChoice !== false)
break
case 'Log Out':
await spinnerWait('Logging out... (just kidding)')
break
case 'Exit':
farewell()
await sleep(310)
@ -94,6 +89,50 @@ async function mainMenu() {
return true
}
// Prints the Use AO Menu and executes the user's choice
async function useAoMenu() {
const loggedIn = isLoggedIn()
console.log(`\n${headerStyle('AO')}\n`)
console.log('Top priority:', await getTopPriorityText())
let aoMenuChoices = []
if(loggedIn) {
aoMenuChoices.push(
'Chat',
'Deck',
)
}
aoMenuChoices.push(
loggedIn ? 'Log Out' : 'Log In',
'Back to Main Menu'
)
const answer = await inquirer.prompt({
name: 'ao_menu',
type: 'list',
message: 'Please choose:',
choices: aoMenuChoices,
pageSize: aoMenuChoices.length
})
switch(answer.ao_menu) {
case 'Chat':
while(await chatMenu()) {}
break
case 'Deck':
await todoList('My Todo List', ['Add full AO install process to ao-cli in convenient format', 'Add AO server unit tests to ao-cli', 'Get groceries', 'Play music every day'])
break
case 'Log In':
console.log('\nao-cli will use the AO API to log into the AO server at', (aoEnv('AO_CLI_TARGET_HOSTNAME') || AO_DEFAULT_HOSTNAME) + '.')
await loginPrompt()
break
case 'Log Out':
await logout()
//await spinnerWait('Logging out...')
break
case 'Back to Main Menu':
return false
}
return true
}
// Prints a menu that allows you to join the global AO chatrooms
let publicBootstrapStarted = false
async function chatMenu() {
@ -159,6 +198,7 @@ async function chatMenu() {
}
// Prints the AO Admin Menu and executes the user's choice
// Maybe Alchemy menu should be installation and update, and admin menu should be more configuration & AO member admin
async function adminMenu() {
console.log(`\n${headerStyle('AO Admin Menu')}`)
const adminChoices = [
@ -166,12 +206,13 @@ async function adminMenu() {
'Update ao-cli',
'Check AO install',
'Update AO',
'Configure AO features',
'Switch AO target server',
'Switch AO database',
'Import/Export state/decks',
'Check AO service',
'Watch logs now',
'Start/Stop AO service',
'Tests',
'Back to Main Menu'
]
const answer = await inquirer.prompt({
@ -190,9 +231,7 @@ async function adminMenu() {
break
case 'Check AO install':
case 'Update AO':
case 'Configure AO features':
while(await featuresMenu()) {}
break
case 'Switch AO target server':
case 'Switch AO database':
case 'Import/Export state/decks':
case 'Check AO service':
@ -200,6 +239,9 @@ async function adminMenu() {
case 'Start/Stop AO service':
console.log("Not yet implemented.")
break
case 'Tests':
while(await testsMenu()) {}
break
default:
return false
}
@ -262,21 +304,29 @@ async function alchemyMenu() {
// Prints the Configure AO Features menu and executes the user's choice
async function featuresMenu() {
console.log(`\n${headerStyle('Configure AO Features')}`)
const status = {
off: ' ' + chalk.grey('Off') + ' ',
ins: chalk.yellow('Installed') + ' ',
ena: ' ' + greenChalk('Enabled') + ' ',
run: ' ' + greenChalk('Running') + ' ',
err: ' ' + chalk.red('Error') + ' '
}
let widest = 9
const features = [
'nginx host AO publicly over the world wide web',
'SSL/Certbot HTTPS for public web AO',
'Tor connect AOs p2p',
'Bitcoin payments',
'Lightning payments',
'Jitsi secure video chat',
'Signal notifications',
'File hosting file attachments on cards',
'youtube-dl cache web videos',
'Borg backup',
'Encryption serverside secret messages',
'Themes custom themes',
'Glossary custom glossary',
'Jubilee monthly points creation event',
`Tor ${status.run} connect AOs p2p`,
`Bitcoin ${status.run} payments`,
`Lightning ${status.ins} payments`,
`nginx ${status.ins} host AO publicly over the world wide web`,
`SSL/Certbot ${status.ins} HTTPS for public web AO`,
`Jitsi ${status.err} secure video chat`,
`Signal ${status.off} notifications`,
`File hosting ${status.err} file attachments on cards`,
`youtube-dl ${status.ins} cache web videos`,
`Borg ${status.ins} backup`,
`Encryption ${status.err} serverside secret messages`,
`Themes ${status.err} custom themes`,
`Glossary ${status.err} custom glossary`,
`Jubilee ${status.ena} monthly points creation event`,
'Back to Main Menu'
]
const answer = await inquirer.prompt({
@ -296,16 +346,6 @@ async function featuresMenu() {
return true
}
// Ask the user for their name and returns it
async function askName() {
const answer = await inquirer.prompt({
name: 'member_name',
type: 'input',
message: 'What username would you like?'
})
return answer.member_name
}
// Prints the given todoItems (array of strings) and allows items to be checked and unchecked
async function todoList(title, todoItems) {
console.log(`\n${headerStyle(title)}`)
@ -349,7 +389,11 @@ async function main() {
exitIfRoot()
// Loading screen, display some quick info during the fun animation
distro = aoEnv('DISTRO')
if(!distro) {
distro = detectOS()
setAoEnv('DISTRO', distro)
}
if(checkAoEnvFile()) {
console.log('AO .env file exists at', AO_ENV_FILE_PATH)
} else {

24
package-lock.json generated

@ -30,6 +30,9 @@
},
"bin": {
"ao-cli": "index.js"
},
"devDependencies": {
"prettier": "^2.6.2"
}
},
"node_modules/@babel/code-frame": {
@ -1500,6 +1503,21 @@
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"node_modules/prettier": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
"integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
"dev": true,
"bin": {
"prettier": "bin-prettier.js"
},
"engines": {
"node": ">=10.13.0"
},
"funding": {
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/qs": {
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",
@ -3135,6 +3153,12 @@
"resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
"integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
},
"prettier": {
"version": "2.6.2",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-2.6.2.tgz",
"integrity": "sha512-PkUpF+qoXTqhOeWL9fu7As8LXsIUZ1WYaJiY/a7McAQzxjk82OF0tibkFXVCDImZtWxbvojFjerkiLb0/q8mew==",
"dev": true
},
"qs": {
"version": "6.10.3",
"resolved": "https://registry.npmjs.org/qs/-/qs-6.10.3.tgz",

6
package.json

@ -7,7 +7,8 @@
"type": "module",
"scripts": {
"start": "node .",
"version": "node . -v"
"version": "node . -v",
"prettier": "npx prettier"
},
"keywords": [
"AO",
@ -41,5 +42,8 @@
},
"publishConfig": {
"access": "public"
},
"devDependencies": {
"prettier": "^2.6.2"
}
}

1711
scripts/api.js

File diff suppressed because it is too large Load Diff

1
scripts/bootstrap.js vendored

@ -1,6 +1,7 @@
// The bootstrapping module uses the glossary in peers.json (later will use members from DB?)
// to look up tor addresses for the give shortname or SSH public key.
// We could just do all this in the AO, but the bootstrapper is for public / loose ties and the AO's explicit p2p is for close / private ties.
// The other main difference is that the AO stores data, and the chat server does not (ao-cli only uses database for Use AO Features).
// The bootstrapper occasionally queries all of the tor addresses in your address book.
// If they are an AO with bootstrapping turned on, the AO server will respond with its public directory information.
// Since you have connected to them via their .onion address, it is assumed they are a known trusted party,

12
scripts/features.js

@ -8,6 +8,16 @@ import { fileURLToPath } from 'url'
const __filename = fileURLToPath(import.meta.url)
const __dirname = path.dirname(__filename)
// It is possible to run ao-cli with npx @autonomousorganization/ao-cli. In this case, it can help you install it permanently.
export function installAoCli() {
try {
execSync('npm i -g @autonomousorganization/ao-cli 2>&1')
console.log('Installed ao-cli.')
} catch(err) {
console.log('Error installing ao-cli:', err)
}
}
// Adds a line to .bashrc to make 'ao' an alias for 'ao-cli', to simplify using the AO from the command line
export function installAoAlias() {
try {
@ -31,7 +41,7 @@ export async function selfUpdate() {
const result = execSync('npm update -g @autonomousorganization/ao-cli 2>&1')
const afterVersionNumber = await getAoCliVersion()
if(beforeVersionNumber === afterVersionNumber) {
console.log("AO version is already current.")
console.log("ao-cli version is already current.")
} else {
console.log('\nao-cli self-updated automatically from version', beforeVersionNumber, 'to version', afterVersionNumber, 'from the official npm repository.')
}

2
scripts/manual.js

@ -3,7 +3,7 @@ import chalk from 'chalk'
import inquirer from 'inquirer'
import { loadYamlMarkdownFile, lsFolder, isFolder } from './files.js'
import { repeatString, centerLines } from './strings.js'
import { headerStyle, manualTitleStyle } from './chalkStyles.js'
import { headerStyle, manualTitleStyle } from './styles.js'
import { basename } from 'path'
import { marked } from 'marked'
import TerminalRenderer from 'marked-terminal'

23
scripts/priority.js

@ -0,0 +1,23 @@
import { aoEnv } from './settings.js'
import { getCard } from './api.js'
// Prints the text (.name) of the first card prioritized in the logged-in users member card
export async function getTopPriorityText() {
// Get the first priority of my member card
const memberId = aoEnv('AO_CLI_SESSION_MEMBERID')
if(!memberId) {
return 'Not logged in'
}
const fetchedCards = await getCard(memberId, 'priority')
console.log('fetch result:', fetchedCards)
if(!fetchedCards || fetchedCards.length < 2) {
return 'None'
}
const firstPriorityCard = fetchedCards[1]
return firstPriorityCard.name
}
// Makes an API request to get the first prioritized card in the member card of the logged-in user
async function getFirstPriorityCard() {
}

62
scripts/session.js

@ -0,0 +1,62 @@
import { createSession, logout as apiLogout } from './api.js'
import { aoEnv, setAoEnv } from './settings.js'
import { askQuestionText } from './welcome.js'
// Returns true if there is a session cookie for ao-cli saved in the AO .env file (=ready to make session requests)
export function isLoggedIn() {
const username = aoEnv('AO_CLI_SESSION_USERNAME')
const sessionId = aoEnv('AO_CLI_SESSION_ID')
const sessionToken = aoEnv('AO_CLI_SESSION_TOKEN')
return username && sessionId && sessionToken
}
// Interactive prompt to log in. Performs the login request.
export async function loginPrompt() {
const username = await askQuestionText('Username:')
const password = await askQuestionText('Password:', { type: 'password' })
await login(username, password)
}
export async function login(username, password) {
try {
console.log('Attempting login as', username, 'with password', '*'.repeat(password.length))
const response = await createSession(username, password)
if(response) {
setAoEnv('AO_CLI_SESSION_USERNAME', username)
setAoEnv('AO_CLI_SESSION_MEMBERID', response.memberId) // might not need to save this actually
setAoEnv('AO_CLI_SESSION_ID', response.session)
setAoEnv('AO_CLI_SESSION_TOKEN', response.token)
console.log('Logged in as', username + '.', 'memberId:', response.memberId)
return true
} else {
console.log('Login failed. Response:', response)
return false
}
} catch(err) {
if(err.status === 401) {
console.log("No account on the AO server matched the username and password you entered. (401 Unauthorized)")
} else {
console.log(err)
}
return false
}
}
export async function logout() {
try {
console.log('Logging out...')
const response = await apiLogout()
if(response.statusCode === 200) {
setAoEnv('AO_CLI_SESSION_USERNAME', null)
setAoEnv('AO_CLI_SESSION_ID', null)
setAoEnv('AO_CLI_SESSION_TOKEN', null)
console.log('Logged out')
} else {
console.log('Logout failed. Response:', response)
return false
}
} catch(err) {
console.log(err)
}
return true
}

2
scripts/settings.js

@ -99,7 +99,7 @@ export function setAoEnv(variable, value) {
}
if(value === null) {
delete parsedFile.variable
delete parsedFile[variable]
} else {
parsedFile[variable] = value
}

0
scripts/chalkStyles.js → scripts/styles.js

6
scripts/tests.js

@ -2,14 +2,14 @@
// The tests actually happen so your database will be modified (future: allow switching databases or automatically switch)
// The tests use an AO API file saved in the same directory; this file must be kept up-to-date
// Maybe in the future a precompiled api.js created from api.ts can be hosted so that ao-cli does not have to compile any TypeScript
import api from './api.js'
import { createSession, logout } from './api.js'
async function testLoginAndOut() {
const username = 'ao'
const password = 'ao'
try {
console.log('Attempting login as', username, 'with password', '*'.repeat(password.length))
const response = await api.createSession(username, password)
const response = await createSession(username, password)
if(response === true) {
console.log('Logged in as', username)
} else {
@ -26,7 +26,7 @@ async function testLoginAndOut() {
try {
console.log('Logging out...')
const response = await api.logout()
const response = await logout()
if(response.statusCode === 200) {
console.log('Logged out')
} else {

2
scripts/util.js

@ -16,3 +16,5 @@ export function selectRandom(arrayToChooseFrom) {
export function sleep(ms = 550) {
return new Promise((r) => setTimeout(r, ms))
}
export const isObject = (obj) => Object.prototype.toString.call(obj) === '[object Object]'

20
scripts/welcome.js

@ -1,6 +1,7 @@
import chalk from 'chalk'
import inquirer from 'inquirer'
import { selectRandom } from './util.js'
import { greenChalk, theAO, theMenu } from './chalkStyles.js'
import { greenChalk, theAO, theMenu } from './styles.js'
// Different sets of messages that can be randomly selected from
const welcomeMessages = [
@ -22,7 +23,9 @@ const welcomeMessages = [
`You are offered a choice between two pills. However, you have secretly built up an immunity to both pills, and trick your opponent into taking one. Inconceviably, you are in ${theAO}.`,
`A young man with spiky hair and golden skin appears before you in a halo of light. He guides you to ${theAO}.`,
`You enter a gap between two hedges and, after struggling through the brush, emerge into a sunny estate garden. You've found the AO.`,
`You find a small animal burrow dug near the riverside. Crawling in, you find a network of caves that lead to the AO.`
`You find a small animal burrow dug-out near the riverside. Crawling in, you find a network of caves that lead to ${theAO}.`,
`You receive a handwritten letter in the mail, which reads, in fine calligraphy:, "Dear —, You are in ${theAO}."`,
`An unexpected calm settles over you. The ${greenChalk.bold('AO')} is here: Here is ${theAO}.`
]
const menuMessages = [
`You see ${theMenu}:`,
@ -89,6 +92,19 @@ export function roger() {
return selectRandom(rogerMessages)
}
// Returns a random farewell message
export function farewell() {
console.log(chalk.yellow.bold(selectRandom(farewellMessages)))
}
// Ask the user the given question and returns their textual response
export async function askQuestionText(prompt = 'Please enter a string:', promptOptions = {}) {
let options = {
name: 'text',
type: 'input',
message: prompt
}
Object.assign(options, promptOptions)
const answer = await inquirer.prompt(options)
return answer.text
}
Loading…
Cancel
Save