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
This commit is contained in:
Erwin
2026-03-28 03:27:44 +00:00
parent 3edb76f197
commit 5978d7acf6
13 changed files with 1885 additions and 59 deletions

3
.env.example Normal file
View File

@@ -0,0 +1,3 @@
# SimpleNote CLI Configuration
SIMPLENOTE_API_URL=http://localhost:3000/api/v1
SIMPLENOTE_TOKEN=snk_your_token_here

3
.gitignore vendored Normal file
View File

@@ -0,0 +1,3 @@
node_modules/
.env
*.log

132
README.md
View File

@@ -1,3 +1,131 @@
# simplenote-cli
# SimpleNote CLI
SimpleNote CLI - Agent communication CLI for document management
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 <id>
# Crear documento
simplenote doc create --title "API Design" --tags "backend,api" --type requirement --library <lib-id>
# Actualizar documento
simplenote doc update <id> --title "New Title" --content "New content"
# Eliminar documento
simplenote doc delete <id>
# Exportar como markdown
simplenote doc export <id>
# Agregar tags
simplenote doc add-tags <id> --tags "new-tag,another"
```
### Librerías
```bash
# Listar librerías raíz
simplenote lib list
# Ver contenido de librería
simplenote lib get <id>
# Crear librería
simplenote lib create --name "Backend Requirements"
simplenote lib create --name "API Specs" --parent <parent-id>
# Ver árbol completo
simplenote lib tree <id>
# Listar sub-librerías
simplenote lib list --parent <id>
```
### Tags
```bash
# Listar todos los tags
simplenote tag list
# Ver documentos con tag
simplenote tag docs backend
```
## Opciones Globales
- `--api-url <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

937
package-lock.json generated Normal file
View File

@@ -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"
}
}
}
}

View File

@@ -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"
}
}

124
src/api/client.js Normal file
View File

@@ -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;

79
src/commands/auth.js Normal file
View File

@@ -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 <token>')
.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;

230
src/commands/doc.js Normal file
View File

@@ -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 <tag>', 'Filter by tag')
.option('--library <id>', 'Filter by library ID')
.option('--type <type>', 'Filter by type (requirement, note, spec, general)')
.option('--status <status>', 'Filter by status (draft, approved, implemented)')
.option('--limit <number>', 'Max results', '50')
.option('--offset <number>', '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 <id>')
.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 <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;

18
src/commands/index.js Normal file
View File

@@ -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;

186
src/commands/lib.js Normal file
View File

@@ -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;

71
src/commands/tag.js Normal file
View File

@@ -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;

59
src/config/loader.js Normal file
View File

@@ -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 };

View File

@@ -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;
export function getClient() {
if (!_client) {
const config = getConfig();
_client = new SimpleNoteClient({
baseUrl: config.apiUrl,
token: config.token,
});
}
return _client;
}
const [command, ...rest] = args;
const program = new Command();
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);
}
}
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)),
});
main().catch(console.error);
// Register all subcommands
registerCommands(program);
// Default command when no args
program.action(() => {
program.help();
});
program.parse(process.argv);