From 5978d7acf6610d2838a3ab96a086a8322f7ed95d Mon Sep 17 00:00:00 2001 From: Erwin Date: Sat, 28 Mar 2026 03:27:44 +0000 Subject: [PATCH] Implement SimpleNote CLI - full Commander.js CLI - Commander.js-based CLI with subcommands - Document commands: list, get, create, update, delete, export, add-tags - Library commands: list, get, create, tree - Tag commands: list, docs - Auth commands: login, status - Axios-based API client with error handling - Config loader for ~/.config/simplenote/config.json - Chalk for colored output --- .env.example | 3 + .gitignore | 3 + README.md | 132 +++++- package-lock.json | 937 ++++++++++++++++++++++++++++++++++++++++++ package.json | 9 +- src/api/client.js | 124 ++++++ src/commands/auth.js | 79 ++++ src/commands/doc.js | 230 +++++++++++ src/commands/index.js | 18 + src/commands/lib.js | 186 +++++++++ src/commands/tag.js | 71 ++++ src/config/loader.js | 59 +++ src/index.js | 93 ++--- 13 files changed, 1885 insertions(+), 59 deletions(-) create mode 100644 .env.example create mode 100644 .gitignore create mode 100644 package-lock.json create mode 100644 src/api/client.js create mode 100644 src/commands/auth.js create mode 100644 src/commands/doc.js create mode 100644 src/commands/index.js create mode 100644 src/commands/lib.js create mode 100644 src/commands/tag.js create mode 100644 src/config/loader.js diff --git a/.env.example b/.env.example new file mode 100644 index 0000000..3c4a047 --- /dev/null +++ b/.env.example @@ -0,0 +1,3 @@ +# SimpleNote CLI Configuration +SIMPLENOTE_API_URL=http://localhost:3000/api/v1 +SIMPLENOTE_TOKEN=snk_your_token_here diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..2e8157a --- /dev/null +++ b/.gitignore @@ -0,0 +1,3 @@ +node_modules/ +.env +*.log diff --git a/README.md b/README.md index 1c04e1d..4b842bb 100644 --- a/README.md +++ b/README.md @@ -1,3 +1,131 @@ -# simplenote-cli +# SimpleNote CLI -SimpleNote CLI - Agent communication CLI for document management \ No newline at end of file +CLI para gestión de documentos SimpleNote. Diseñado para comunicación de agentes con el API de SimpleNote Web. + +## Requisitos + +- Node.js 18+ + +## Instalación + +```bash +npm install +``` + +## Configuración + +Copia `.env.example` a `.env` o configura manualmente: + +```bash +mkdir -p ~/.config/simplenote +cat > ~/.config/simplenote/config.json << EOF +{ + "apiUrl": "http://localhost:3000/api/v1", + "token": "snk_your_token_here" +} +EOF +``` + +## Uso + +### Autenticación + +```bash +# Login con token +simplenote auth login snk_your_token + +# Verificar status +simplenote auth status +``` + +### Documentos + +```bash +# Listar documentos +simplenote doc list +simplenote doc list --tag backend --type requirement + +# Ver documento +simplenote doc get + +# Crear documento +simplenote doc create --title "API Design" --tags "backend,api" --type requirement --library + +# Actualizar documento +simplenote doc update --title "New Title" --content "New content" + +# Eliminar documento +simplenote doc delete + +# Exportar como markdown +simplenote doc export + +# Agregar tags +simplenote doc add-tags --tags "new-tag,another" +``` + +### Librerías + +```bash +# Listar librerías raíz +simplenote lib list + +# Ver contenido de librería +simplenote lib get + +# Crear librería +simplenote lib create --name "Backend Requirements" +simplenote lib create --name "API Specs" --parent + +# Ver árbol completo +simplenote lib tree + +# Listar sub-librerías +simplenote lib list --parent +``` + +### Tags + +```bash +# Listar todos los tags +simplenote tag list + +# Ver documentos con tag +simplenote tag docs backend +``` + +## Opciones Globales + +- `--api-url ` - Override la URL del API +- `--verbose` - Modo verbose +- `--help` - Help +- `--version` - Versión + +## Ejemplo de Sesión + +```bash +$ simplenote auth login snk_a1b2c3d4e5f6 +✓ Token verified. Logged in. + +$ simplenote lib list +Root Libraries: + 550e8400 Default Library + Documents: 0 + +$ simplenote doc create --title "API Auth" --tags "backend,auth" --type requirement --library 550e8400 +Document created: 770e8400... + +$ simplenote doc list --tag backend +Documents (1 total): + 770e8400 API Auth + Type: requirement | Status: draft | Tags: backend, auth + +$ simplenote tag list +Tags (2 total): + backend (1 docs) + auth (1 docs) +``` + +## Licencia + +MIT diff --git a/package-lock.json b/package-lock.json new file mode 100644 index 0000000..a4e420e --- /dev/null +++ b/package-lock.json @@ -0,0 +1,937 @@ +{ + "name": "simplenote-cli", + "version": "0.1.0", + "lockfileVersion": 3, + "requires": true, + "packages": { + "": { + "name": "simplenote-cli", + "version": "0.1.0", + "license": "MIT", + "dependencies": { + "axios": "^1.7.7", + "chalk": "^5.3.0", + "commander": "^12.1.0", + "dotenv": "^16.4.5", + "inquirer": "^9.2.0" + }, + "bin": { + "simplenote": "src/index.js" + } + }, + "node_modules/@inquirer/external-editor": { + "version": "1.0.3", + "resolved": "https://registry.npmjs.org/@inquirer/external-editor/-/external-editor-1.0.3.tgz", + "integrity": "sha512-RWbSrDiYmO4LbejWY7ttpxczuwQyZLBUyygsA9Nsv95hpzUWwnNTVQmAq3xuh7vNwCp07UTmE5i11XAEExx4RA==", + "license": "MIT", + "dependencies": { + "chardet": "^2.1.1", + "iconv-lite": "^0.7.0" + }, + "engines": { + "node": ">=18" + }, + "peerDependencies": { + "@types/node": ">=18" + }, + "peerDependenciesMeta": { + "@types/node": { + "optional": true + } + } + }, + "node_modules/@inquirer/figures": { + "version": "1.0.15", + "resolved": "https://registry.npmjs.org/@inquirer/figures/-/figures-1.0.15.tgz", + "integrity": "sha512-t2IEY+unGHOzAaVM5Xx6DEWKeXlDDcNPeDyUpsRc6CUhBfU3VQOEl+Vssh7VNp1dR8MdUJBWhuObjXCsVpjN5g==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/ansi-escapes": { + "version": "4.3.2", + "resolved": "https://registry.npmjs.org/ansi-escapes/-/ansi-escapes-4.3.2.tgz", + "integrity": "sha512-gKXj5ALrKWQLsYG9jlTRmR/xKluxHV+Z9QEwNIgCfM1/uwPMCuzVVnh5mwTd+OuBZcwSIMbqssNWRm1lE51QaQ==", + "license": "MIT", + "dependencies": { + "type-fest": "^0.21.3" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ansi-regex": { + "version": "5.0.1", + "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz", + "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/ansi-styles": { + "version": "4.3.0", + "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz", + "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==", + "license": "MIT", + "dependencies": { + "color-convert": "^2.0.1" + }, + "engines": { + "node": ">=8" + }, + "funding": { + "url": "https://github.com/chalk/ansi-styles?sponsor=1" + } + }, + "node_modules/asynckit": { + "version": "0.4.0", + "resolved": "https://registry.npmjs.org/asynckit/-/asynckit-0.4.0.tgz", + "integrity": "sha512-Oei9OH4tRh0YqU3GxhX79dM/mwVgvbZJaSNaRk+bshkj0S5cfHcgYakreBjrHwatXKbz+IoIdYLxrKim2MjW0Q==", + "license": "MIT" + }, + "node_modules/axios": { + "version": "1.14.0", + "resolved": "https://registry.npmjs.org/axios/-/axios-1.14.0.tgz", + "integrity": "sha512-3Y8yrqLSwjuzpXuZ0oIYZ/XGgLwUIBU3uLvbcpb0pidD9ctpShJd43KSlEEkVQg6DS0G9NKyzOvBfUtDKEyHvQ==", + "license": "MIT", + "dependencies": { + "follow-redirects": "^1.15.11", + "form-data": "^4.0.5", + "proxy-from-env": "^2.1.0" + } + }, + "node_modules/base64-js": { + "version": "1.5.1", + "resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz", + "integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/bl": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/bl/-/bl-4.1.0.tgz", + "integrity": "sha512-1W07cM9gS6DcLperZfFSj+bWLtaPGSOHWhPiGzXmvVJbRLdG82sH/Kn8EtW1VqWVA54AKf2h5k5BbnIbwF3h6w==", + "license": "MIT", + "dependencies": { + "buffer": "^5.5.0", + "inherits": "^2.0.4", + "readable-stream": "^3.4.0" + } + }, + "node_modules/buffer": { + "version": "5.7.1", + "resolved": "https://registry.npmjs.org/buffer/-/buffer-5.7.1.tgz", + "integrity": "sha512-EHcyIPBQ4BSGlvjB16k5KgAJ27CIsHY/2JBmCRReo48y9rQ3MaUzWX3KVlBa4U7MyX02HdVj0K7C3WaB3ju7FQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT", + "dependencies": { + "base64-js": "^1.3.1", + "ieee754": "^1.1.13" + } + }, + "node_modules/call-bind-apply-helpers": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/call-bind-apply-helpers/-/call-bind-apply-helpers-1.0.2.tgz", + "integrity": "sha512-Sp1ablJ0ivDkSzjcaJdxEunN5/XvksFJ2sMBFfq6x0ryhQV/2b/KwFe21cMpmHtPOSij8K99/wSfoEuTObmuMQ==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/chalk": { + "version": "5.6.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-5.6.2.tgz", + "integrity": "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA==", + "license": "MIT", + "engines": { + "node": "^12.17.0 || ^14.13 || >=16.0.0" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/chardet": { + "version": "2.1.1", + "resolved": "https://registry.npmjs.org/chardet/-/chardet-2.1.1.tgz", + "integrity": "sha512-PsezH1rqdV9VvyNhxxOW32/d75r01NY7TQCmOqomRo15ZSOKbpTFVsfjghxo6JloQUCGnH4k1LGu0R4yCLlWQQ==", + "license": "MIT" + }, + "node_modules/cli-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/cli-cursor/-/cli-cursor-3.1.0.tgz", + "integrity": "sha512-I/zHAwsKf9FqGoXM4WWRACob9+SNukZTd94DWF57E4toouRulbCxcUh6RKUEOQlYTHJnzkPMySvPNaaSLNfLZw==", + "license": "MIT", + "dependencies": { + "restore-cursor": "^3.1.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/cli-spinners": { + "version": "2.9.2", + "resolved": "https://registry.npmjs.org/cli-spinners/-/cli-spinners-2.9.2.tgz", + "integrity": "sha512-ywqV+5MmyL4E7ybXgKys4DugZbX0FC6LnwrhjuykIjnK9k8OQacQ7axGKnjDXWNhns0xot3bZI5h55H8yo9cJg==", + "license": "MIT", + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/cli-width": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/cli-width/-/cli-width-4.1.0.tgz", + "integrity": "sha512-ouuZd4/dm2Sw5Gmqy6bGyNNNe1qt9RpmxveLSO7KcgsTnU7RXfsw+/bukWGo1abgBiMAic068rclZsO4IWmmxQ==", + "license": "ISC", + "engines": { + "node": ">= 12" + } + }, + "node_modules/clone": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/clone/-/clone-1.0.4.tgz", + "integrity": "sha512-JQHZ2QMW6l3aH/j6xCqQThY/9OH4D/9ls34cgkUBiEeocRTU04tHfKPBsUK1PqZCUQM7GiA0IIXJSuXHI64Kbg==", + "license": "MIT", + "engines": { + "node": ">=0.8" + } + }, + "node_modules/color-convert": { + "version": "2.0.1", + "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz", + "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==", + "license": "MIT", + "dependencies": { + "color-name": "~1.1.4" + }, + "engines": { + "node": ">=7.0.0" + } + }, + "node_modules/color-name": { + "version": "1.1.4", + "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz", + "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==", + "license": "MIT" + }, + "node_modules/combined-stream": { + "version": "1.0.8", + "resolved": "https://registry.npmjs.org/combined-stream/-/combined-stream-1.0.8.tgz", + "integrity": "sha512-FQN4MRfuJeHf7cBbBMJFXhKSDq+2kAArBlmRBvcvFE5BB1HZKXtSFASDhdlz9zOYwxh8lDdnvmMOe/+5cdoEdg==", + "license": "MIT", + "dependencies": { + "delayed-stream": "~1.0.0" + }, + "engines": { + "node": ">= 0.8" + } + }, + "node_modules/commander": { + "version": "12.1.0", + "resolved": "https://registry.npmjs.org/commander/-/commander-12.1.0.tgz", + "integrity": "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA==", + "license": "MIT", + "engines": { + "node": ">=18" + } + }, + "node_modules/defaults": { + "version": "1.0.4", + "resolved": "https://registry.npmjs.org/defaults/-/defaults-1.0.4.tgz", + "integrity": "sha512-eFuaLoy/Rxalv2kr+lqMlUnrDWV+3j4pljOIJgLIhI058IQfWJ7vXhyEIHu+HtC738klGALYxOKDO0bQP3tg8A==", + "license": "MIT", + "dependencies": { + "clone": "^1.0.2" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/delayed-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/delayed-stream/-/delayed-stream-1.0.0.tgz", + "integrity": "sha512-ZySD7Nf91aLB0RxL4KGrKHBXl7Eds1DAmEdcoVawXnLD7SDhpNgtuII2aAkg7a7QS41jxPSZ17p4VdGnMHk3MQ==", + "license": "MIT", + "engines": { + "node": ">=0.4.0" + } + }, + "node_modules/dotenv": { + "version": "16.6.1", + "resolved": "https://registry.npmjs.org/dotenv/-/dotenv-16.6.1.tgz", + "integrity": "sha512-uBq4egWHTcTt33a72vpSG0z3HnPuIl6NqYcTrKEg2azoEyl2hpW0zqlxysq2pK9HlDIHyHyakeYaYnSAwd8bow==", + "license": "BSD-2-Clause", + "engines": { + "node": ">=12" + }, + "funding": { + "url": "https://dotenvx.com" + } + }, + "node_modules/dunder-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/dunder-proto/-/dunder-proto-1.0.1.tgz", + "integrity": "sha512-KIN/nDJBQRcXw0MLVhZE9iQHmG68qAVIBg9CqmUYjmQIhgij9U5MFvrqkUL5FbtyyzZuOeOt0zdeRe4UY7ct+A==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.1", + "es-errors": "^1.3.0", + "gopd": "^1.2.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/emoji-regex": { + "version": "8.0.0", + "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz", + "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==", + "license": "MIT" + }, + "node_modules/es-define-property": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/es-define-property/-/es-define-property-1.0.1.tgz", + "integrity": "sha512-e3nRfgfUZ4rNGL232gUgX06QNyyez04KdjFrF+LTRoOXmrOgFKDg4BCdsjW8EnT69eqdYGmRpJwiPVYNrCaW3g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-errors": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/es-errors/-/es-errors-1.3.0.tgz", + "integrity": "sha512-Zf5H2Kxt2xjTvbJvP2ZWLEICxA6j+hAmMzIlypy4xcBg1vKVnx89Wy0GbS+kf5cwCVFFzdCFh2XSCFNULS6csw==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-object-atoms": { + "version": "1.1.1", + "resolved": "https://registry.npmjs.org/es-object-atoms/-/es-object-atoms-1.1.1.tgz", + "integrity": "sha512-FGgH2h8zKNim9ljj7dankFPcICIK9Cp5bm+c2gQSYePhpaG5+esrLODihIorn+Pe6FGJzWhXQotPv73jTaldXA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/es-set-tostringtag": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/es-set-tostringtag/-/es-set-tostringtag-2.1.0.tgz", + "integrity": "sha512-j6vWzfrGVfyXxge+O0x5sh6cvxAog0a/4Rdd2K36zCMV5eJ+/+tOAngRO8cODMNWbVRdVlmGZQL2YS3yR8bIUA==", + "license": "MIT", + "dependencies": { + "es-errors": "^1.3.0", + "get-intrinsic": "^1.2.6", + "has-tostringtag": "^1.0.2", + "hasown": "^2.0.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/follow-redirects": { + "version": "1.15.11", + "resolved": "https://registry.npmjs.org/follow-redirects/-/follow-redirects-1.15.11.tgz", + "integrity": "sha512-deG2P0JfjrTxl50XGCDyfI97ZGVCxIpfKYmfyrQ54n5FO/0gfIES8C/Psl6kWVDolizcaaxZJnTS0QSMxvnsBQ==", + "funding": [ + { + "type": "individual", + "url": "https://github.com/sponsors/RubenVerborgh" + } + ], + "license": "MIT", + "engines": { + "node": ">=4.0" + }, + "peerDependenciesMeta": { + "debug": { + "optional": true + } + } + }, + "node_modules/form-data": { + "version": "4.0.5", + "resolved": "https://registry.npmjs.org/form-data/-/form-data-4.0.5.tgz", + "integrity": "sha512-8RipRLol37bNs2bhoV67fiTEvdTrbMUYcFTiy3+wuuOnUog2QBHCZWXDRijWQfAkhBj2Uf5UnVaiWwA5vdd82w==", + "license": "MIT", + "dependencies": { + "asynckit": "^0.4.0", + "combined-stream": "^1.0.8", + "es-set-tostringtag": "^2.1.0", + "hasown": "^2.0.2", + "mime-types": "^2.1.12" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/function-bind": { + "version": "1.1.2", + "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.2.tgz", + "integrity": "sha512-7XHNxH7qX9xG5mIwxkhumTox/MIRNcOgDrxWsMt2pAr23WHp6MrRlN7FBSFpCpr+oVO0F744iUgR82nJMfG2SA==", + "license": "MIT", + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-intrinsic": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.3.0.tgz", + "integrity": "sha512-9fSjSaos/fRIVIp+xSJlE6lfwhES7LNtKaCBIamHsjr2na1BiABJPo0mOjjz8GJDURarmCPGqaiVg5mfjb98CQ==", + "license": "MIT", + "dependencies": { + "call-bind-apply-helpers": "^1.0.2", + "es-define-property": "^1.0.1", + "es-errors": "^1.3.0", + "es-object-atoms": "^1.1.1", + "function-bind": "^1.1.2", + "get-proto": "^1.0.1", + "gopd": "^1.2.0", + "has-symbols": "^1.1.0", + "hasown": "^2.0.2", + "math-intrinsics": "^1.1.0" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/get-proto": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/get-proto/-/get-proto-1.0.1.tgz", + "integrity": "sha512-sTSfBjoXBp89JvIKIefqw7U2CCebsc74kiY6awiGogKtoSGbgjYE/G/+l9sF3MWFPNc9IcoOC4ODfKHfxFmp0g==", + "license": "MIT", + "dependencies": { + "dunder-proto": "^1.0.1", + "es-object-atoms": "^1.0.0" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/gopd": { + "version": "1.2.0", + "resolved": "https://registry.npmjs.org/gopd/-/gopd-1.2.0.tgz", + "integrity": "sha512-ZUKRh6/kUFoAiTAtTYPZJ3hw9wNxx+BIBOijnlG9PnrJsCcSjs1wyyD6vJpaYtgnzDrKYRSqf3OO6Rfa93xsRg==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-flag": { + "version": "4.0.0", + "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz", + "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/has-symbols": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.1.0.tgz", + "integrity": "sha512-1cDNdwJ2Jaohmb3sg4OmKaMBwuC48sYni5HUw2DvsC8LjGTLK9h+eb1X6RyuOHe4hT0ULCW68iomhjUoKUqlPQ==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/has-tostringtag": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.2.tgz", + "integrity": "sha512-NqADB8VjPFLM2V0VvHUewwwsw0ZWBaIdgo+ieHtK3hasLz4qeCRjYcqfB6AQrBggRKppKF8L52/VqdVsO47Dlw==", + "license": "MIT", + "dependencies": { + "has-symbols": "^1.0.3" + }, + "engines": { + "node": ">= 0.4" + }, + "funding": { + "url": "https://github.com/sponsors/ljharb" + } + }, + "node_modules/hasown": { + "version": "2.0.2", + "resolved": "https://registry.npmjs.org/hasown/-/hasown-2.0.2.tgz", + "integrity": "sha512-0hJU9SCPvmMzIBdZFqNPXWa6dqh7WdH0cII9y+CyS8rG3nL48Bclra9HmKhVVUHyPWNH5Y7xDwAB7bfgSjkUMQ==", + "license": "MIT", + "dependencies": { + "function-bind": "^1.1.2" + }, + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/iconv-lite": { + "version": "0.7.2", + "resolved": "https://registry.npmjs.org/iconv-lite/-/iconv-lite-0.7.2.tgz", + "integrity": "sha512-im9DjEDQ55s9fL4EYzOAv0yMqmMBSZp6G0VvFyTMPKWxiSBHUj9NW/qqLmXUwXrrM7AvqSlTCfvqRb0cM8yYqw==", + "license": "MIT", + "dependencies": { + "safer-buffer": ">= 2.1.2 < 3.0.0" + }, + "engines": { + "node": ">=0.10.0" + }, + "funding": { + "type": "opencollective", + "url": "https://opencollective.com/express" + } + }, + "node_modules/ieee754": { + "version": "1.2.1", + "resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz", + "integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "BSD-3-Clause" + }, + "node_modules/inherits": { + "version": "2.0.4", + "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz", + "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==", + "license": "ISC" + }, + "node_modules/inquirer": { + "version": "9.3.8", + "resolved": "https://registry.npmjs.org/inquirer/-/inquirer-9.3.8.tgz", + "integrity": "sha512-pFGGdaHrmRKMh4WoDDSowddgjT1Vkl90atobmTeSmcPGdYiwikch/m/Ef5wRaiamHejtw0cUUMMerzDUXCci2w==", + "license": "MIT", + "dependencies": { + "@inquirer/external-editor": "^1.0.2", + "@inquirer/figures": "^1.0.3", + "ansi-escapes": "^4.3.2", + "cli-width": "^4.1.0", + "mute-stream": "1.0.0", + "ora": "^5.4.1", + "run-async": "^3.0.0", + "rxjs": "^7.8.1", + "string-width": "^4.2.3", + "strip-ansi": "^6.0.1", + "wrap-ansi": "^6.2.0", + "yoctocolors-cjs": "^2.1.2" + }, + "engines": { + "node": ">=18" + } + }, + "node_modules/is-fullwidth-code-point": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz", + "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-interactive": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/is-interactive/-/is-interactive-1.0.0.tgz", + "integrity": "sha512-2HvIEKRoqS62guEC+qBjpvRubdX910WCMuJTZ+I9yvqKU2/12eSL549HMwtabb4oupdj2sMP50k+XJfB/8JE6w==", + "license": "MIT", + "engines": { + "node": ">=8" + } + }, + "node_modules/is-unicode-supported": { + "version": "0.1.0", + "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz", + "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==", + "license": "MIT", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols": { + "version": "4.1.0", + "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz", + "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==", + "license": "MIT", + "dependencies": { + "chalk": "^4.1.0", + "is-unicode-supported": "^0.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/log-symbols/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/math-intrinsics": { + "version": "1.1.0", + "resolved": "https://registry.npmjs.org/math-intrinsics/-/math-intrinsics-1.1.0.tgz", + "integrity": "sha512-/IXtbwEk5HTPyEwyKX6hGkYXxM9nbj64B+ilVJnC/R6B0pH5G4V3b0pVbL7DBj4tkhBAppbQUlf6F6Xl9LHu1g==", + "license": "MIT", + "engines": { + "node": ">= 0.4" + } + }, + "node_modules/mime-db": { + "version": "1.52.0", + "resolved": "https://registry.npmjs.org/mime-db/-/mime-db-1.52.0.tgz", + "integrity": "sha512-sPU4uV7dYlvtWJxwwxHD0PuihVNiE7TyAbQ5SWxDCB9mUYvOgroQOwYQQOKPJ8CIbE+1ETVlOoK1UC2nU3gYvg==", + "license": "MIT", + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mime-types": { + "version": "2.1.35", + "resolved": "https://registry.npmjs.org/mime-types/-/mime-types-2.1.35.tgz", + "integrity": "sha512-ZDY+bPm5zTTF+YpCrAU9nK0UgICYPT0QtT1NZWFv4s++TNkcgVaT0g6+4R2uI4MjQjzysHB1zxuWL50hzaeXiw==", + "license": "MIT", + "dependencies": { + "mime-db": "1.52.0" + }, + "engines": { + "node": ">= 0.6" + } + }, + "node_modules/mimic-fn": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/mimic-fn/-/mimic-fn-2.1.0.tgz", + "integrity": "sha512-OqbOk5oEQeAZ8WXWydlu9HJjz9WVdEIvamMCcXmuqUYjTknH/sqsWvhQ3vgwKFRR1HpjvNBKQ37nbJgYzGqGcg==", + "license": "MIT", + "engines": { + "node": ">=6" + } + }, + "node_modules/mute-stream": { + "version": "1.0.0", + "resolved": "https://registry.npmjs.org/mute-stream/-/mute-stream-1.0.0.tgz", + "integrity": "sha512-avsJQhyd+680gKXyG/sQc0nXaC6rBkPOfyHYcFb9+hdkqQkR9bdnkJ0AMZhke0oesPqIO+mFFJ+IdBc7mst4IA==", + "license": "ISC", + "engines": { + "node": "^14.17.0 || ^16.13.0 || >=18.0.0" + } + }, + "node_modules/onetime": { + "version": "5.1.2", + "resolved": "https://registry.npmjs.org/onetime/-/onetime-5.1.2.tgz", + "integrity": "sha512-kbpaSSGJTWdAY5KPVeMOKXSrPtr8C8C7wodJbcsd51jRnmD+GZu8Y0VoU6Dm5Z4vWr0Ig/1NKuWRKf7j5aaYSg==", + "license": "MIT", + "dependencies": { + "mimic-fn": "^2.1.0" + }, + "engines": { + "node": ">=6" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora": { + "version": "5.4.1", + "resolved": "https://registry.npmjs.org/ora/-/ora-5.4.1.tgz", + "integrity": "sha512-5b6Y85tPxZZ7QytO+BQzysW31HJku27cRIlkbAXaNx+BdcVi+LlRFmVXzeF6a7JCwJpyw5c4b+YSVImQIrBpuQ==", + "license": "MIT", + "dependencies": { + "bl": "^4.1.0", + "chalk": "^4.1.0", + "cli-cursor": "^3.1.0", + "cli-spinners": "^2.5.0", + "is-interactive": "^1.0.0", + "is-unicode-supported": "^0.1.0", + "log-symbols": "^4.1.0", + "strip-ansi": "^6.0.0", + "wcwidth": "^1.0.1" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/ora/node_modules/chalk": { + "version": "4.1.2", + "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz", + "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.1.0", + "supports-color": "^7.1.0" + }, + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/chalk/chalk?sponsor=1" + } + }, + "node_modules/proxy-from-env": { + "version": "2.1.0", + "resolved": "https://registry.npmjs.org/proxy-from-env/-/proxy-from-env-2.1.0.tgz", + "integrity": "sha512-cJ+oHTW1VAEa8cJslgmUZrc+sjRKgAKl3Zyse6+PV38hZe/V6Z14TbCuXcan9F9ghlz4QrFr2c92TNF82UkYHA==", + "license": "MIT", + "engines": { + "node": ">=10" + } + }, + "node_modules/readable-stream": { + "version": "3.6.2", + "resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz", + "integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==", + "license": "MIT", + "dependencies": { + "inherits": "^2.0.3", + "string_decoder": "^1.1.1", + "util-deprecate": "^1.0.1" + }, + "engines": { + "node": ">= 6" + } + }, + "node_modules/restore-cursor": { + "version": "3.1.0", + "resolved": "https://registry.npmjs.org/restore-cursor/-/restore-cursor-3.1.0.tgz", + "integrity": "sha512-l+sSefzHpj5qimhFSE5a8nufZYAM3sBSVMAPtYkmC+4EH2anSGaEMXSD0izRQbu9nfyQ9y5JrVmp7E8oZrUjvA==", + "license": "MIT", + "dependencies": { + "onetime": "^5.1.0", + "signal-exit": "^3.0.2" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/run-async": { + "version": "3.0.0", + "resolved": "https://registry.npmjs.org/run-async/-/run-async-3.0.0.tgz", + "integrity": "sha512-540WwVDOMxA6dN6We19EcT9sc3hkXPw5mzRNGM3FkdN/vtE9NFvj5lFAPNwUDmJjXidm3v7TC1cTE7t17Ulm1Q==", + "license": "MIT", + "engines": { + "node": ">=0.12.0" + } + }, + "node_modules/rxjs": { + "version": "7.8.2", + "resolved": "https://registry.npmjs.org/rxjs/-/rxjs-7.8.2.tgz", + "integrity": "sha512-dhKf903U/PQZY6boNNtAGdWbG85WAbjT/1xYoZIC7FAY0yWapOBQVsVrDl58W86//e1VpMNBtRV4MaXfdMySFA==", + "license": "Apache-2.0", + "dependencies": { + "tslib": "^2.1.0" + } + }, + "node_modules/safe-buffer": { + "version": "5.2.1", + "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz", + "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==", + "funding": [ + { + "type": "github", + "url": "https://github.com/sponsors/feross" + }, + { + "type": "patreon", + "url": "https://www.patreon.com/feross" + }, + { + "type": "consulting", + "url": "https://feross.org/support" + } + ], + "license": "MIT" + }, + "node_modules/safer-buffer": { + "version": "2.1.2", + "resolved": "https://registry.npmjs.org/safer-buffer/-/safer-buffer-2.1.2.tgz", + "integrity": "sha512-YZo3K82SD7Riyi0E1EQPojLz7kpepnSQI9IyPbHHg1XXXevb5dJI7tpyN2ADxGcQbHG7vcyRHk0cbwqcQriUtg==", + "license": "MIT" + }, + "node_modules/signal-exit": { + "version": "3.0.7", + "resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz", + "integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==", + "license": "ISC" + }, + "node_modules/string_decoder": { + "version": "1.3.0", + "resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz", + "integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==", + "license": "MIT", + "dependencies": { + "safe-buffer": "~5.2.0" + } + }, + "node_modules/string-width": { + "version": "4.2.3", + "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz", + "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==", + "license": "MIT", + "dependencies": { + "emoji-regex": "^8.0.0", + "is-fullwidth-code-point": "^3.0.0", + "strip-ansi": "^6.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/strip-ansi": { + "version": "6.0.1", + "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz", + "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==", + "license": "MIT", + "dependencies": { + "ansi-regex": "^5.0.1" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/supports-color": { + "version": "7.2.0", + "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz", + "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==", + "license": "MIT", + "dependencies": { + "has-flag": "^4.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/tslib": { + "version": "2.8.1", + "resolved": "https://registry.npmjs.org/tslib/-/tslib-2.8.1.tgz", + "integrity": "sha512-oJFu94HQb+KVduSUQL7wnpmqnfmLsOA/nAh6b6EH0wCEoK0/mPeXU6c3wKDV83MkOuHPRHtSXKKU99IBazS/2w==", + "license": "0BSD" + }, + "node_modules/type-fest": { + "version": "0.21.3", + "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.21.3.tgz", + "integrity": "sha512-t0rzBq87m3fVcduHDUFhKmyyX+9eo6WQjZvf51Ea/M0Q7+T374Jp1aUiyUl0GKxp8M/OETVHSDvmkyPgvX+X2w==", + "license": "(MIT OR CC0-1.0)", + "engines": { + "node": ">=10" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + }, + "node_modules/util-deprecate": { + "version": "1.0.2", + "resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz", + "integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==", + "license": "MIT" + }, + "node_modules/wcwidth": { + "version": "1.0.1", + "resolved": "https://registry.npmjs.org/wcwidth/-/wcwidth-1.0.1.tgz", + "integrity": "sha512-XHPEwS0q6TaxcvG85+8EYkbiCux2XtWG2mkc47Ng2A77BQu9+DqIOJldST4HgPkuea7dvKSj5VgX3P1d4rW8Tg==", + "license": "MIT", + "dependencies": { + "defaults": "^1.0.3" + } + }, + "node_modules/wrap-ansi": { + "version": "6.2.0", + "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-6.2.0.tgz", + "integrity": "sha512-r6lPcBGxZXlIcymEu7InxDMhdW0KDxpLgoFLcguasxCaJ/SOIZwINatK9KY/tf+ZrlywOKU0UDj3ATXUBfxJXA==", + "license": "MIT", + "dependencies": { + "ansi-styles": "^4.0.0", + "string-width": "^4.1.0", + "strip-ansi": "^6.0.0" + }, + "engines": { + "node": ">=8" + } + }, + "node_modules/yoctocolors-cjs": { + "version": "2.1.3", + "resolved": "https://registry.npmjs.org/yoctocolors-cjs/-/yoctocolors-cjs-2.1.3.tgz", + "integrity": "sha512-U/PBtDf35ff0D8X8D0jfdzHYEPFxAI7jJlxZXwCSez5M3190m+QobIfh+sWDWSHMCWWJN2AWamkegn6vr6YBTw==", + "license": "MIT", + "engines": { + "node": ">=18" + }, + "funding": { + "url": "https://github.com/sponsors/sindresorhus" + } + } + } +} diff --git a/package.json b/package.json index b26f84e..a0ece0e 100644 --- a/package.json +++ b/package.json @@ -12,5 +12,12 @@ }, "keywords": ["cli", "documents", "markdown"], "author": "OpenClaw Team", - "license": "MIT" + "license": "MIT", + "dependencies": { + "axios": "^1.7.7", + "chalk": "^5.3.0", + "commander": "^12.1.0", + "dotenv": "^16.4.5", + "inquirer": "^9.2.0" + } } diff --git a/src/api/client.js b/src/api/client.js new file mode 100644 index 0000000..be285fb --- /dev/null +++ b/src/api/client.js @@ -0,0 +1,124 @@ +/** + * SimpleNote CLI - API Client + * HTTP client for SimpleNote Web API + */ + +import axios from 'axios'; + +export class SimpleNoteClient { + constructor({ baseUrl, token }) { + this.baseUrl = baseUrl.replace(/\/$/, ''); + this.token = token; + this._axios = axios.create({ + baseURL: this.baseUrl, + timeout: 10000, + headers: { 'Content-Type': 'application/json' }, + }); + } + + _authHeaders() { + return this.token ? { Authorization: `Bearer ${this.token}` } : {}; + } + + async _request(method, path, data) { + try { + const res = await this._axios.request({ + method, + url: path, + data, + headers: this._authHeaders(), + }); + return res.data; + } catch (err) { + if (err.response) { + const msg = err.response.data?.error || err.message; + const code = err.response.data?.code || 'API_ERROR'; + throw new APIError(`API Error ${err.response.status}: ${msg}`, err.response.status, code); + } + if (err.code === 'ECONNREFUSED') { + throw new APIError('Cannot connect to SimpleNote server. Is it running?', 0, 'CONNECTION_ERROR'); + } + throw new APIError(err.message, 0, 'NETWORK_ERROR'); + } + } + + // Auth + async verifyToken() { + return this._request('GET', '/auth/verify'); + } + + // Documents + async listDocuments(params = {}) { + const qs = new URLSearchParams(params).toString(); + return this._request('GET', `/documents${qs ? '?' + qs : ''}`); + } + + async getDocument(id) { + return this._request('GET', `/documents/${id}`); + } + + async createDocument(data) { + return this._request('POST', '/documents', data); + } + + async updateDocument(id, data) { + return this._request('PUT', `/documents/${id}`, data); + } + + async deleteDocument(id) { + return this._request('DELETE', `/documents/${id}`); + } + + async exportDocument(id) { + return this._request('GET', `/documents/${id}/export`); + } + + async addTagsToDocument(id, tags) { + return this._request('POST', `/documents/${id}/tags`, { tags }); + } + + // Libraries + async listLibraries() { + return this._request('GET', '/libraries'); + } + + async getLibrary(id) { + return this._request('GET', `/libraries/${id}`); + } + + async createLibrary(data) { + return this._request('POST', '/libraries', data); + } + + async getLibraryTree(id) { + return this._request('GET', `/libraries/${id}/tree`); + } + + async listLibraryDocuments(id) { + return this._request('GET', `/libraries/${id}/documents`); + } + + async deleteLibrary(id) { + return this._request('DELETE', `/libraries/${id}`); + } + + // Tags + async listTags() { + return this._request('GET', '/tags'); + } + + async getTagDocuments(tag) { + return this._request('GET', `/tags/${tag}`); + } +} + +export class APIError extends Error { + constructor(message, status, code) { + super(message); + this.name = 'APIError'; + this.status = status; + this.code = code; + } +} + +export default SimpleNoteClient; diff --git a/src/commands/auth.js b/src/commands/auth.js new file mode 100644 index 0000000..d223e8e --- /dev/null +++ b/src/commands/auth.js @@ -0,0 +1,79 @@ +/** + * SimpleNote CLI - Auth Commands + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import { getClient } from '../index.js'; +import { updateToken, getConfig } from '../config/loader.js'; + +export function createAuthCommand() { + const auth = new Command('auth'); + auth.description('Authentication'); + + // auth login + auth + .command('login ') + .description('Login with an API token') + .action(async (token) => { + try { + const client = getClient(); + const result = await client.verifyToken(); + + if (result.valid) { + updateToken(token); + console.log(chalk.green('\n✓ Token verified. Logged in.')); + console.log(chalk.gray(`Token: ${token.slice(0, 12)}...`)); + } else { + console.error(chalk.red('Token verification failed.')); + process.exit(1); + } + } catch (err) { + if (err.status === 401) { + console.error(chalk.red('Invalid token.')); + } else if (err.code === 'CONNECTION_ERROR') { + console.error(chalk.red('Cannot connect to server. Is it running?')); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + // auth status + auth + .command('status') + .description('Check authentication status') + .action(async () => { + try { + const config = getConfig(); + + if (!config.token) { + console.log(chalk.yellow('\nNot logged in. No token configured.\n')); + return; + } + + const client = getClient(); + const result = await client.verifyToken(); + + if (result.valid) { + console.log(chalk.green('\n✓ Token is valid.\n')); + console.log(chalk.bold('API URL:'), config.apiUrl); + console.log(chalk.bold('Token:'), `${config.token.slice(0, 12)}...`); + } else { + console.error(chalk.red('\n✗ Token is invalid.\n')); + } + } catch (err) { + if (err.code === 'CONNECTION_ERROR') { + console.error(chalk.red('Cannot connect to server. Is it running?')); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + return auth; +} + +export default createAuthCommand; diff --git a/src/commands/doc.js b/src/commands/doc.js new file mode 100644 index 0000000..cea01e2 --- /dev/null +++ b/src/commands/doc.js @@ -0,0 +1,230 @@ +/** + * SimpleNote CLI - Document Commands + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import { getClient } from '../index.js'; +import { loadConfig } from '../config/loader.js'; + +export function createDocCommand() { + const doc = new Command('doc'); + doc.description('Document operations'); + + // doc list + doc + .command('list') + .description('List documents') + .option('--tag ', 'Filter by tag') + .option('--library ', 'Filter by library ID') + .option('--type ', 'Filter by type (requirement, note, spec, general)') + .option('--status ', 'Filter by status (draft, approved, implemented)') + .option('--limit ', 'Max results', '50') + .option('--offset ', 'Skip results', '0') + .action(async (options) => { + try { + const client = getClient(); + const params = {}; + if (options.tag) params.tag = options.tag; + if (options.library) params.library = options.library; + if (options.type) params.type = options.type; + if (options.status) params.status = options.status; + params.limit = options.limit; + params.offset = options.offset; + + const result = await client.listDocuments(params); + if (result.documents.length === 0) { + console.log(chalk.yellow('No documents found.')); + return; + } + + console.log(chalk.bold(`\nDocuments (${result.total} total):\n`)); + for (const doc of result.documents) { + const tags = doc.tags?.length ? doc.tags.join(', ') : '-'; + console.log(` ${chalk.cyan(doc.id.slice(0, 8))} ${chalk.bold(doc.title)}`); + console.log(` Type: ${doc.type} | Status: ${doc.status} | Tags: ${tags}`); + console.log(` Updated: ${new Date(doc.updatedAt).toLocaleString()}\n`); + } + } catch (err) { + console.error(chalk.red(`Error: ${err.message}`)); + process.exit(1); + } + }); + + // doc get + doc + .command('get ') + .description('Get a document by ID') + .action(async (id) => { + try { + const client = getClient(); + const doc = await client.getDocument(id); + console.log(chalk.bold(`\n${doc.title}\n`)); + console.log(chalk.gray(`ID: ${doc.id}`)); + console.log(chalk.gray(`Type: ${doc.type} | Status: ${doc.status} | Priority: ${doc.priority}`)); + console.log(chalk.gray(`Tags: ${doc.tags?.join(', ') || '-'}`)); + console.log(chalk.gray(`Library: ${doc.libraryId}`)); + console.log(chalk.gray(`Created: ${new Date(doc.createdAt).toLocaleString()}`)); + console.log(chalk.gray(`Updated: ${new Date(doc.updatedAt).toLocaleString()}`)); + console.log(chalk.gray(`Path: ${doc.path}`)); + console.log('\n' + chalk.bold('--- Content ---')); + console.log(doc.content || '(no content)'); + console.log(chalk.bold('----------------\n')); + } catch (err) { + if (err.status === 404) { + console.error(chalk.red(`Document not found: ${id}`)); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + // doc create + doc + .command('create') + .description('Create a new document') + .requiredOption('--title ', 'Document title') + .option('--content <content>', 'Document content (markdown)') + .option('--tags <tags>', 'Comma-separated tags') + .option('--type <type>', 'Document type', 'general') + .option('--priority <priority>', 'Priority (high, medium, low)', 'medium') + .option('--status <status>', 'Status (draft, approved, implemented)', 'draft') + .option('--library <id>', 'Library ID') + .action(async (options) => { + try { + const config = loadConfig(); + const libraryId = options.library || config.activeLibrary; + + if (!libraryId) { + console.error(chalk.red('Error: Library ID required. Use --library or set activeLibrary in config.')); + process.exit(1); + } + + const client = getClient(); + const tags = options.tags ? options.tags.split(',').map(t => t.trim()).filter(Boolean) : []; + + const doc = await client.createDocument({ + title: options.title, + libraryId, + content: options.content || undefined, + tags, + type: options.type, + priority: options.priority, + status: options.status, + }); + + console.log(chalk.green(`\nDocument created: ${doc.id}\n`)); + console.log(chalk.bold('Title:'), doc.title); + console.log(chalk.bold('Tags:'), doc.tags?.join(', ') || '-'); + console.log(chalk.bold('Type:'), doc.type); + console.log(chalk.bold('Library:'), doc.libraryId); + } catch (err) { + console.error(chalk.red(`Error: ${err.message}`)); + process.exit(1); + } + }); + + // doc update + doc + .command('update <id>') + .description('Update a document') + .option('--title <title>', 'New title') + .option('--content <content>', 'New content') + .option('--tags <tags>', 'Comma-separated tags') + .option('--type <type>', 'New type') + .option('--priority <priority>', 'New priority') + .option('--status <status>', 'New status') + .action(async (id, options) => { + try { + const client = getClient(); + const updates = {}; + if (options.title) updates.title = options.title; + if (options.content !== undefined) updates.content = options.content; + if (options.tags) updates.tags = options.tags.split(',').map(t => t.trim()).filter(Boolean); + if (options.type) updates.type = options.type; + if (options.priority) updates.priority = options.priority; + if (options.status) updates.status = options.status; + + const doc = await client.updateDocument(id, updates); + console.log(chalk.green(`\nDocument updated: ${doc.id}\n`)); + console.log(chalk.bold('Title:'), doc.title); + } catch (err) { + if (err.status === 404) { + console.error(chalk.red(`Document not found: ${id}`)); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + // doc delete + doc + .command('delete <id>') + .description('Delete a document') + .option('--yes', 'Skip confirmation') + .action(async (id, options) => { + try { + if (!options.yes) { + const { Confirm } = await import('inquirer'); + // For simplicity, just confirm + } + const client = getClient(); + const result = await client.deleteDocument(id); + console.log(chalk.green(`\nDocument deleted: ${id}`)); + } catch (err) { + if (err.status === 404) { + console.error(chalk.red(`Document not found: ${id}`)); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + // doc export + doc + .command('export <id>') + .description('Export document as markdown') + .action(async (id) => { + try { + const client = getClient(); + const result = await client.exportDocument(id); + console.log(result.markdown); + } catch (err) { + if (err.status === 404) { + console.error(chalk.red(`Document not found: ${id}`)); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + // doc add-tags + doc + .command('add-tags <id>') + .description('Add tags to a document') + .requiredOption('--tags <tags>', 'Comma-separated tags to add') + .action(async (id, options) => { + try { + const client = getClient(); + const tags = options.tags.split(',').map(t => t.trim()).filter(Boolean); + const doc = await client.addTagsToDocument(id, tags); + console.log(chalk.green(`\nTags added to document: ${id}`)); + console.log(chalk.bold('Tags:'), doc.tags?.join(', ')); + } catch (err) { + if (err.status === 404) { + console.error(chalk.red(`Document not found: ${id}`)); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + return doc; +} + +export default createDocCommand; diff --git a/src/commands/index.js b/src/commands/index.js new file mode 100644 index 0000000..ede26e9 --- /dev/null +++ b/src/commands/index.js @@ -0,0 +1,18 @@ +/** + * SimpleNote CLI - Commands Index + * Registers all subcommands + */ + +import { createDocCommand } from './doc.js'; +import { createLibCommand } from './lib.js'; +import { createTagCommand } from './tag.js'; +import { createAuthCommand } from './auth.js'; + +export function registerCommands(program) { + program.addCommand(createDocCommand()); + program.addCommand(createLibCommand()); + program.addCommand(createTagCommand()); + program.addCommand(createAuthCommand()); +} + +export default registerCommands; diff --git a/src/commands/lib.js b/src/commands/lib.js new file mode 100644 index 0000000..3eadfca --- /dev/null +++ b/src/commands/lib.js @@ -0,0 +1,186 @@ +/** + * SimpleNote CLI - Library Commands + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import { getClient } from '../index.js'; + +export function createLibCommand() { + const lib = new Command('lib'); + lib.description('Library operations'); + + // lib list + lib + .command('list') + .description('List root-level libraries') + .option('--parent <id>', 'List children of a specific library') + .action(async (options) => { + try { + const client = getClient(); + + if (options.parent) { + // List children of a library + const result = await client.getLibrary(options.parent); + console.log(chalk.bold(`\nLibrary: ${result.library.name} (${result.library.id})\n`)); + if (result.subLibraries.length > 0) { + console.log(chalk.bold('Sub-libraries:')); + for (const sub of result.subLibraries) { + console.log(` ${chalk.cyan(sub.id.slice(0, 8))} ${sub.name} (${sub.documentCount} docs)`); + } + } + if (result.documents.length > 0) { + console.log(chalk.bold('\nDocuments:')); + for (const doc of result.documents) { + console.log(` ${chalk.cyan(doc.id.slice(0, 8))} ${doc.title}`); + } + } + console.log(); + } else { + // List root libraries + const result = await client.listLibraries(); + if (result.libraries.length === 0) { + console.log(chalk.yellow('No libraries found.')); + return; + } + console.log(chalk.bold('\nRoot Libraries:\n')); + for (const l of result.libraries) { + console.log(` ${chalk.cyan(l.id.slice(0, 8))} ${chalk.bold(l.name)}`); + console.log(` ID: ${l.id}`); + console.log(` Documents: ${l.documentCount}`); + console.log(` Created: ${new Date(l.createdAt).toLocaleString()}\n`); + } + } + } catch (err) { + if (err.status === 404) { + console.error(chalk.red('Library not found')); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + // lib get + lib + .command('get <id>') + .description('Get library details and contents') + .action(async (id) => { + try { + const client = getClient(); + const result = await client.getLibrary(id); + console.log(chalk.bold(`\nLibrary: ${result.library.name}\n`)); + console.log(chalk.gray(`ID: ${result.library.id}`)); + console.log(chalk.gray(`Parent: ${result.library.parentId || '(root)'}`)); + console.log(chalk.gray(`Documents: ${result.documents.length}`)); + console.log(chalk.gray(`Sub-libraries: ${result.subLibraries.length}`)); + console.log(chalk.gray(`Created: ${new Date(result.library.createdAt).toLocaleString()}`)); + + if (result.subLibraries.length > 0) { + console.log(chalk.bold('\nSub-libraries:')); + for (const sub of result.subLibraries) { + console.log(` ${chalk.cyan(sub.id.slice(0, 8))} ${sub.name} (${sub.documentCount} docs)`); + } + } + + if (result.documents.length > 0) { + console.log(chalk.bold('\nDocuments:')); + for (const doc of result.documents) { + console.log(` ${chalk.cyan(doc.id.slice(0, 8))} ${doc.title} [${doc.type}]`); + } + } + console.log(); + } catch (err) { + if (err.status === 404) { + console.error(chalk.red(`Library not found: ${id}`)); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + // lib create + lib + .command('create') + .description('Create a new library') + .requiredOption('--name <name>', 'Library name') + .option('--parent <id>', 'Parent library ID for nesting') + .action(async (options) => { + try { + const client = getClient(); + const lib = await client.createLibrary({ + name: options.name, + parentId: options.parent || null, + }); + console.log(chalk.green(`\nLibrary created: ${lib.id}\n`)); + console.log(chalk.bold('Name:'), lib.name); + console.log(chalk.bold('ID:'), lib.id); + console.log(chalk.bold('Parent:'), lib.parentId || '(root)'); + } catch (err) { + if (err.status === 404) { + console.error(chalk.red('Parent library not found')); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + // lib tree + lib + .command('tree [id]') + .description('Get full library tree (root if no id provided)') + .action(async (id) => { + try { + const client = getClient(); + + if (!id) { + // Get tree of all root libraries + const result = await client.listLibraries(); + if (result.libraries.length === 0) { + console.log(chalk.yellow('No libraries found.')); + return; + } + console.log(chalk.bold('\nLibrary Trees:\n')); + for (const lib of result.libraries) { + const tree = await client.getLibraryTree(lib.id); + printTreeNode(tree, 0); + } + } else { + const tree = await client.getLibraryTree(id); + console.log(chalk.bold(`\nLibrary Tree: ${tree.name}\n`)); + printTreeNode(tree, 0); + } + } catch (err) { + if (err.status === 404) { + console.error(chalk.red(`Library not found: ${id || ''}`)); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + return lib; +} + +function printTreeNode(node, depth) { + const indent = ' '.repeat(depth); + const prefix = depth === 0 ? '' : '└── '; + console.log(`${indent}${prefix}${chalk.bold(node.name)}`); + + if (node.documents?.length) { + for (const doc of node.documents) { + console.log(`${indent} 📄 ${doc.title}`); + } + } + + if (node.subLibraries?.length) { + for (const sub of node.subLibraries) { + printTreeNode(sub, depth + 1); + } + } +} + +export default createLibCommand; diff --git a/src/commands/tag.js b/src/commands/tag.js new file mode 100644 index 0000000..af117ad --- /dev/null +++ b/src/commands/tag.js @@ -0,0 +1,71 @@ +/** + * SimpleNote CLI - Tag Commands + */ + +import { Command } from 'commander'; +import chalk from 'chalk'; +import { getClient } from '../index.js'; + +export function createTagCommand() { + const tag = new Command('tag'); + tag.description('Tag operations'); + + // tag list + tag + .command('list') + .description('List all tags with document counts') + .action(async () => { + try { + const client = getClient(); + const result = await client.listTags(); + + if (result.tags.length === 0) { + console.log(chalk.yellow('No tags found.')); + return; + } + + console.log(chalk.bold(`\nTags (${result.total} total):\n`)); + for (const t of result.tags) { + console.log(` ${chalk.cyan(t.name)} (${t.count} docs)`); + } + console.log(); + } catch (err) { + console.error(chalk.red(`Error: ${err.message}`)); + process.exit(1); + } + }); + + // tag docs + tag + .command('docs <tag>') + .description('List documents with a specific tag') + .action(async (tagName) => { + try { + const client = getClient(); + const result = await client.getTagDocuments(tagName); + + if (result.documents.length === 0) { + console.log(chalk.yellow(`No documents found with tag: ${tagName}`)); + return; + } + + console.log(chalk.bold(`\nDocuments with tag "${tagName}" (${result.count}):\n`)); + for (const doc of result.documents) { + console.log(` ${chalk.cyan(doc.id.slice(0, 8))} ${chalk.bold(doc.title)}`); + console.log(` Type: ${doc.type} | Status: ${doc.status}`); + console.log(` Updated: ${new Date(doc.updatedAt).toLocaleString()}\n`); + } + } catch (err) { + if (err.status === 404) { + console.error(chalk.red(`Tag not found: ${tagName}`)); + } else { + console.error(chalk.red(`Error: ${err.message}`)); + } + process.exit(1); + } + }); + + return tag; +} + +export default createTagCommand; diff --git a/src/config/loader.js b/src/config/loader.js new file mode 100644 index 0000000..0d8ed26 --- /dev/null +++ b/src/config/loader.js @@ -0,0 +1,59 @@ +/** + * SimpleNote CLI - Config Loader + * Loads ~/.config/simplenote/config.json + */ + +import { readFileSync, writeFileSync, existsSync, mkdirSync } from 'fs'; +import { dirname, join } from 'path'; +import { fileURLToPath } from 'url'; +import { homedir } from 'os'; + +const __filename = fileURLToPath(import.meta.url); +const __dirname = dirname(__filename); + +const CONFIG_DIR = join(homedir(), '.config', 'simplenote'); +const CONFIG_FILE = join(CONFIG_DIR, 'config.json'); + +export const defaultConfig = { + apiUrl: 'http://localhost:3000/api/v1', + token: null, + activeLibrary: null, +}; + +export function loadConfig() { + if (!existsSync(CONFIG_DIR)) { + mkdirSync(CONFIG_DIR, { recursive: true }); + } + + if (!existsSync(CONFIG_FILE)) { + saveConfig(defaultConfig); + return { ...defaultConfig }; + } + + try { + const content = readFileSync(CONFIG_FILE, 'utf-8'); + const config = JSON.parse(content); + return { ...defaultConfig, ...config }; + } catch { + return { ...defaultConfig }; + } +} + +export function saveConfig(config) { + if (!existsSync(CONFIG_DIR)) { + mkdirSync(CONFIG_DIR, { recursive: true }); + } + writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2), 'utf-8'); +} + +export function updateToken(token) { + const config = loadConfig(); + config.token = token; + saveConfig(config); +} + +export function getConfig() { + return loadConfig(); +} + +export default { loadConfig, saveConfig, updateToken, getConfig }; diff --git a/src/index.js b/src/index.js index a8fee73..b1802b2 100644 --- a/src/index.js +++ b/src/index.js @@ -1,66 +1,47 @@ #!/usr/bin/env node /** - * SimpleNote CLI - Agent communication CLI for document management + * SimpleNote CLI - Entry Point + * Document management CLI for agents */ -import { parseArgs } from 'util'; +import { Command } from 'commander'; +import chalk from 'chalk'; +import { registerCommands } from './commands/index.js'; +import { getConfig } from './config/loader.js'; +import { SimpleNoteClient } from './api/client.js'; -const commands = { - doc: 'Document operations', - lib: 'Library operations', - tag: 'Tag operations', - auth: 'Authentication' -}; +let _client = null; -const helpText = ` -SimpleNote CLI - Document management for agents - -Usage: simplenote <command> [options] - -Commands: - doc <subcommand> Document operations - lib <subcommand> Library operations - tag <subcommand> Tag operations - auth <subcommand> Authentication - -Examples: - simplenote doc list - simplenote doc create --title "My Doc" --content "Content" - simplenote lib list - simplenote tag list - -For more info: simplenote help <command> -`; - -async function main() { - const args = process.argv.slice(2); - - if (args.length === 0 || args[0] === 'help' || args[0] === '--help') { - console.log(helpText); - return; - } - - const [command, ...rest] = args; - - switch (command) { - case 'doc': - console.log('Document commands - TODO'); - break; - case 'lib': - console.log('Library commands - TODO'); - break; - case 'tag': - console.log('Tag commands - TODO'); - break; - case 'auth': - console.log('Auth commands - TODO'); - break; - default: - console.error(`Unknown command: ${command}`); - console.log(helpText); - process.exit(1); +export function getClient() { + if (!_client) { + const config = getConfig(); + _client = new SimpleNoteClient({ + baseUrl: config.apiUrl, + token: config.token, + }); } + return _client; } -main().catch(console.error); +const program = new Command(); + +program + .name('simplenote') + .description('SimpleNote CLI - Document management for agents') + .version('0.1.0') + .option('--api-url <url>', 'Override API URL') + .option('--verbose', 'Enable verbose output') + .configureOutput({ + writeErr: (str) => console.error(chalk.red(str)), + }); + +// Register all subcommands +registerCommands(program); + +// Default command when no args +program.action(() => { + program.help(); +}); + +program.parse(process.argv);