feat(architecture): add complete technical architecture for SimpleNote
- ARCHITECTURE.md: main architecture document - api-spec.yaml: full OpenAPI 3.0 spec - folder-structure.md: detailed folder layout - data-format.md: JSON schemas for .meta.json, .library.json, .tag-index.json - env-template.md: environment variables documentation - cli-protocol.md: CLI-to-API communication protocol
This commit is contained in:
413
ARCHITECTURE.md
Normal file
413
ARCHITECTURE.md
Normal file
@@ -0,0 +1,413 @@
|
||||
# SimpleNote - Arquitectura Técnica
|
||||
|
||||
## 1. Visión General
|
||||
|
||||
Sistema de gestión de documentos basado en archivos Markdown con API REST. Diseñado para reemplazar Joplin con una arquitectura más simple.
|
||||
|
||||
```
|
||||
┌─────────────────┐ ┌──────────────────┐
|
||||
│ simplenote-cli │◄────►│ simplenote-web │
|
||||
│ (Commander.js) │ │ (Express + API) │
|
||||
└─────────────────┘ └────────┬─────────┘
|
||||
│
|
||||
┌────────▼─────────┐
|
||||
│ File System │
|
||||
│ (Markdown + JSON)│
|
||||
└───────────────────┘
|
||||
```
|
||||
|
||||
## 2. Componentes
|
||||
|
||||
### 2.1 simplenote-web
|
||||
- **Rol**: API REST + Frontend web
|
||||
- **Puerto default**: 3000
|
||||
- **Estructura interna**: Express.js con routers modulares
|
||||
|
||||
### 2.2 simplenote-cli
|
||||
- **Rol**: Cliente de línea de comandos
|
||||
- **Conexión**: HTTP al API de simplenote-web
|
||||
- **Config**: `~/.config/simplenote/config.json`
|
||||
|
||||
## 3. Arquitectura de Datos
|
||||
|
||||
### 3.1 Estructura de Librerías (Filesystem)
|
||||
|
||||
```
|
||||
data/ # DATA_ROOT configurable
|
||||
└── libraries/
|
||||
└── {library-id}/
|
||||
├── .library.json # Metadata de librería
|
||||
├── documents/
|
||||
│ └── {document-id}/
|
||||
│ ├── index.md # Contenido
|
||||
│ └── .meta.json # Metadata del documento
|
||||
└── sub-libraries/
|
||||
└── {child-lib-id}/... # Anidamiento recursivo
|
||||
```
|
||||
|
||||
### 3.2 JSON Manifests
|
||||
|
||||
**`.library.json`** — Metadata de librería:
|
||||
```json
|
||||
{
|
||||
"id": "uuid-v4",
|
||||
"name": "Nombre de Librería",
|
||||
"parentId": "parent-uuid | null",
|
||||
"path": "/libraries/uuid",
|
||||
"createdAt": "ISO8601",
|
||||
"updatedAt": "ISO8601"
|
||||
}
|
||||
```
|
||||
|
||||
**`.meta.json`** — Metadata de documento:
|
||||
```json
|
||||
{
|
||||
"id": "uuid-v4",
|
||||
"title": "string",
|
||||
"tags": ["tag1", "tag2"],
|
||||
"type": "requirement|note|spec|general",
|
||||
"status": "draft|approved|implemented",
|
||||
"priority": "high|medium|low",
|
||||
"createdBy": "agent-id",
|
||||
"createdAt": "ISO8601",
|
||||
"updatedAt": "ISO8601",
|
||||
"libraryId": "uuid"
|
||||
}
|
||||
```
|
||||
|
||||
**`.tag-index.json`** — Índice global de tags (en DATA_ROOT):
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"updatedAt": "ISO8601",
|
||||
"tags": {
|
||||
"backend": ["doc-1", "doc-2"],
|
||||
"api": ["doc-1", "doc-3"],
|
||||
"auth": ["doc-2"]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### 3.3 Formato de Documento (Markdown)
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: REQ-001
|
||||
title: Título del Requerimiento
|
||||
type: requirement
|
||||
priority: high
|
||||
status: draft
|
||||
tags: [backend, api]
|
||||
createdBy: agent-id
|
||||
createdAt: 2026-03-28
|
||||
---
|
||||
|
||||
# Título
|
||||
|
||||
## Descripción
|
||||
Descripción del requerimiento.
|
||||
|
||||
## Criterios de Aceptación
|
||||
- [ ] Criterio 1
|
||||
- [ ] Criterio 2
|
||||
```
|
||||
|
||||
## 4. API REST
|
||||
|
||||
### 4.1 Base URL
|
||||
```
|
||||
/api/v1
|
||||
```
|
||||
|
||||
### 4.2 Autenticación
|
||||
- **Método**: Token Bearer en header `Authorization`
|
||||
- **Formato**: `Authorization: Bearer <token>`
|
||||
- **Admin tokens**: Generados en setup inicial, almacenados en `.auth-tokens.json`
|
||||
|
||||
### 4.3 Endpoints
|
||||
|
||||
#### Auth
|
||||
| Method | Endpoint | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| POST | `/api/v1/auth/token` | Generar token (admin) |
|
||||
| GET | `/api/v1/auth/verify` | Verificar token válido |
|
||||
|
||||
#### Documents
|
||||
| Method | Endpoint | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/v1/documents` | Listar (filtros: tag, library, type) |
|
||||
| GET | `/api/v1/documents/:id` | Obtener documento + metadata |
|
||||
| POST | `/api/v1/documents` | Crear documento |
|
||||
| PUT | `/api/v1/documents/:id` | Actualizar documento |
|
||||
| DELETE | `/api/v1/documents/:id` | Eliminar documento |
|
||||
| GET | `/api/v1/documents/:id/export` | Exportar como Markdown |
|
||||
|
||||
#### Libraries
|
||||
| Method | Endpoint | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/v1/libraries` | Listar librerías raíz |
|
||||
| GET | `/api/v1/libraries/:id` | Ver contenido de librería |
|
||||
| POST | `/api/v1/libraries` | Crear librería |
|
||||
| GET | `/api/v1/libraries/:id/tree` | Árbol completo de sublibrerías |
|
||||
| DELETE | `/api/v1/libraries/:id` | Eliminar librería |
|
||||
|
||||
#### Tags
|
||||
| Method | Endpoint | Descripción |
|
||||
|--------|----------|-------------|
|
||||
| GET | `/api/v1/tags` | Listar todos los tags |
|
||||
| GET | `/api/v1/tags/:tag/documents` | Docs con tag específico |
|
||||
| POST | `/api/v1/documents/:id/tags` | Agregar tags a documento |
|
||||
|
||||
## 5. Middleware de Auth
|
||||
|
||||
### 5.1 Flujo de Tokens
|
||||
|
||||
1. **Setup inicial**: Se genera admin token (almacenado en `.auth-tokens.json`)
|
||||
2. **CLI login**: `simplenote auth login <token>` → almacena token en config local
|
||||
3. **Requests**: Token enviado en header `Authorization: Bearer <token>`
|
||||
4. **Verificación**: Middleware busca token en `.auth-tokens.json`
|
||||
|
||||
### 5.2 Implementación
|
||||
|
||||
```javascript
|
||||
// authMiddleware.js
|
||||
async function authMiddleware(req, res, next) {
|
||||
const token = req.headers.authorization?.replace('Bearer ', '');
|
||||
if (!token) {
|
||||
return res.status(401).json({ error: 'Token required' });
|
||||
}
|
||||
|
||||
const tokens = await readJSON('.auth-tokens.json');
|
||||
if (!tokens.valid.includes(token)) {
|
||||
return res.status(401).json({ error: 'Invalid token' });
|
||||
}
|
||||
|
||||
req.token = token;
|
||||
next();
|
||||
}
|
||||
```
|
||||
|
||||
### 5.3 Archivo `.auth-tokens.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"tokens": [
|
||||
{
|
||||
"token": "snk_xxxxx",
|
||||
"label": "cli-default",
|
||||
"createdAt": "ISO8601"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
## 6. Estrategia de Indexación de Tags
|
||||
|
||||
### 6.1write-through
|
||||
Cuando se crea/actualiza/elimina un documento:
|
||||
1. Se actualiza su `.meta.json` con los tags
|
||||
2. Se reconstruye el `.tag-index.json` global
|
||||
|
||||
### 6.2 Optimización
|
||||
- El índice se rebuild en memoria al iniciar (rápido para <10k docs)
|
||||
- Para sistemas grandes: rebuild incremental (solo afecta docs modificados)
|
||||
- El índice es un archivo JSON plano para búsqueda O(1) por tag
|
||||
|
||||
### 6.3 API de Búsqueda
|
||||
```javascript
|
||||
// GET /api/v1/tags/backend
|
||||
// → Lee .tag-index.json → returns ["doc-1", "doc-2"]
|
||||
|
||||
// GET /api/v1/documents?tag=backend
|
||||
// → Busca en índice → filtra docs en memoria → retorna
|
||||
```
|
||||
|
||||
## 7. CLI API Client
|
||||
|
||||
### 7.1 Configuración Local
|
||||
```json
|
||||
// ~/.config/simplenote/config.json
|
||||
{
|
||||
"apiUrl": "http://localhost:3000/api/v1",
|
||||
"token": "snk_xxxxx",
|
||||
"activeLibrary": "default"
|
||||
}
|
||||
```
|
||||
|
||||
### 7.2 Cliente HTTP
|
||||
|
||||
```javascript
|
||||
// cli/src/api/client.js
|
||||
class SimpleNoteClient {
|
||||
constructor(baseUrl, token) {
|
||||
this.baseUrl = baseUrl;
|
||||
this.token = token;
|
||||
}
|
||||
|
||||
async request(method, path, body) {
|
||||
const res = await fetch(`${this.baseUrl}${path}`, {
|
||||
method,
|
||||
headers: {
|
||||
'Authorization': `Bearer ${this.token}`,
|
||||
'Content-Type': 'application/json'
|
||||
},
|
||||
body: body ? JSON.stringify(body) : undefined
|
||||
});
|
||||
|
||||
if (!res.ok) {
|
||||
const err = await res.json().catch(() => ({ error: res.statusText }));
|
||||
throw new Error(err.error || `HTTP ${res.status}`);
|
||||
}
|
||||
|
||||
return res.json();
|
||||
}
|
||||
|
||||
// Documents
|
||||
listDocuments(params) { return this.request('GET', '/documents', null); }
|
||||
getDocument(id) { return this.request('GET', `/documents/${id}`); }
|
||||
createDocument(data) { return this.request('POST', '/documents', data); }
|
||||
updateDocument(id, data) { return this.request('PUT', `/documents/${id}`, data); }
|
||||
deleteDocument(id) { return this.request('DELETE', `/documents/${id}`); }
|
||||
|
||||
// Libraries
|
||||
listLibraries() { return this.request('GET', '/libraries'); }
|
||||
createLibrary(data) { return this.request('POST', '/libraries', data); }
|
||||
|
||||
// Tags
|
||||
listTags() { return this.request('GET', '/tags'); }
|
||||
getTagDocuments(tag) { return this.request('GET', `/tags/${tag}/documents`); }
|
||||
|
||||
// Auth
|
||||
verifyToken() { return this.request('GET', '/auth/verify'); }
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Dependencias NPM
|
||||
|
||||
### simplenote-web
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"express": "^4.18.2",
|
||||
"uuid": "^9.0.0",
|
||||
"marked": "^11.0.0",
|
||||
"gray-matter": "^4.0.3"
|
||||
},
|
||||
"devDependencies": {
|
||||
"nodemon": "^3.0.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
### simplenote-cli
|
||||
```json
|
||||
{
|
||||
"dependencies": {
|
||||
"commander": "^11.1.0",
|
||||
"axios": "^1.6.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 9. Variables de Entorno
|
||||
|
||||
| Variable | Default | Descripción |
|
||||
|----------|---------|-------------|
|
||||
| `PORT` | 3000 | Puerto del servidor |
|
||||
| `DATA_ROOT` | `./data` | Raíz de documentos |
|
||||
| `HOST` | `0.0.0.0` | Host de binding |
|
||||
| `LOG_LEVEL` | `info` | Nivel de logging |
|
||||
| `CORS_ORIGIN` | `*` | Orígenes CORS permitidos |
|
||||
|
||||
## 10. Estructura de Archivos del Proyecto
|
||||
|
||||
```
|
||||
simplenote-web/
|
||||
├── src/
|
||||
│ ├── index.js # Entry point
|
||||
│ ├── app.js # Express setup
|
||||
│ ├── routes/
|
||||
│ │ ├── documents.js
|
||||
│ │ ├── libraries.js
|
||||
│ │ ├── tags.js
|
||||
│ │ └── auth.js
|
||||
│ ├── services/
|
||||
│ │ ├── documentService.js
|
||||
│ │ ├── libraryService.js
|
||||
│ │ └── tagService.js
|
||||
│ ├── middleware/
|
||||
│ │ └── auth.js
|
||||
│ ├── utils/
|
||||
│ │ ├── markdown.js
|
||||
│ │ ├── fsHelper.js
|
||||
│ │ └── uuid.js
|
||||
│ └── indexers/
|
||||
│ └── tagIndexer.js
|
||||
├── data/ # .gitkeep
|
||||
├── tests/
|
||||
├── package.json
|
||||
└── README.md
|
||||
|
||||
simplenote-cli/
|
||||
├── src/
|
||||
│ ├── index.js # Entry point (Commander)
|
||||
│ ├── commands/
|
||||
│ │ ├── doc.js
|
||||
│ │ ├── lib.js
|
||||
│ │ └── tag.js
|
||||
│ ├── api/
|
||||
│ │ └── client.js
|
||||
│ └── config/
|
||||
│ └── loader.js
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## 11. Flujo de Operaciones
|
||||
|
||||
### 11.1 Crear Documento (CLI)
|
||||
```
|
||||
simplenote doc create --title "REQ-001" --tags "backend,api"
|
||||
→ POST /api/v1/documents
|
||||
→ Service: genera UUID, crea /data/libraries/{lib}/documents/{id}/index.md
|
||||
→ Service: crea /data/libraries/{lib}/documents/{id}/.meta.json
|
||||
→ Service: rebuild .tag-index.json
|
||||
→ Response: { id, path, tags, ... }
|
||||
```
|
||||
|
||||
### 11.2 Crear Documento (Web)
|
||||
```
|
||||
Form POST → /api/v1/documents
|
||||
→ Mismo flujo que CLI
|
||||
→ Web reconstruye lista con filtros
|
||||
```
|
||||
|
||||
### 11.3 Búsqueda por Tag
|
||||
```
|
||||
GET /api/v1/tags/backend
|
||||
→ tagIndexer: lee .tag-index.json
|
||||
→ Returns: ["doc-id-1", "doc-id-2"]
|
||||
|
||||
GET /api/v1/documents?tag=backend
|
||||
→ Busca en índice → filtra docs → retorna con metadata
|
||||
```
|
||||
|
||||
## 12. Seguridad
|
||||
|
||||
- Tokens con prefijo `snk_` para identificación fácil
|
||||
- Tokens almacenados en texto plano (sistema multi-usuario confiado)
|
||||
- Para producción futura: hash bcrypt + salt
|
||||
- CORS configurable para restringir acceso web
|
||||
|
||||
## 13. Líneas de Código Estimadas
|
||||
|
||||
| Componente | LOC aprox |
|
||||
|------------|-----------|
|
||||
| API REST (Express) | ~600 |
|
||||
| Services (document, library, tag) | ~400 |
|
||||
| Middleware auth | ~50 |
|
||||
| Tag indexer | ~100 |
|
||||
| CLI client | ~300 |
|
||||
| CLI commands | ~400 |
|
||||
| **Total** | ~1850 |
|
||||
877
api-spec.yaml
Normal file
877
api-spec.yaml
Normal file
@@ -0,0 +1,877 @@
|
||||
openapi: 3.0.3
|
||||
info:
|
||||
title: SimpleNote API
|
||||
description: REST API for SimpleNote document management system
|
||||
version: 1.0.0
|
||||
contact:
|
||||
name: SimpleNote Team
|
||||
|
||||
servers:
|
||||
- url: http://localhost:3000/api/v1
|
||||
description: Local development server
|
||||
|
||||
tags:
|
||||
- name: Auth
|
||||
description: Authentication and token management
|
||||
- name: Documents
|
||||
description: Document CRUD operations
|
||||
- name: Libraries
|
||||
description: Library (folder) management
|
||||
- name: Tags
|
||||
description: Tag-based search and management
|
||||
|
||||
paths:
|
||||
# ============ AUTH ============
|
||||
/auth/token:
|
||||
post:
|
||||
tags: [Auth]
|
||||
summary: Generate a new API token
|
||||
description: Admin-only endpoint to generate a new bearer token
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [label]
|
||||
properties:
|
||||
label:
|
||||
type: string
|
||||
description: Human-readable label for the token
|
||||
example: "cli-default"
|
||||
responses:
|
||||
'200':
|
||||
description: Token generated successfully
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
token:
|
||||
type: string
|
||||
example: "snk_a1b2c3d4e5f6..."
|
||||
label:
|
||||
type: string
|
||||
example: "cli-default"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
'401':
|
||||
description: Unauthorized - invalid admin token
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'400':
|
||||
description: Missing required fields
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/auth/verify:
|
||||
get:
|
||||
tags: [Auth]
|
||||
summary: Verify current token is valid
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: Token is valid
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
valid:
|
||||
type: boolean
|
||||
example: true
|
||||
token:
|
||||
type: string
|
||||
example: "snk_a1b2c3d4e5f6..."
|
||||
'401':
|
||||
description: Token is invalid or missing
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
# ============ DOCUMENTS ============
|
||||
/documents:
|
||||
get:
|
||||
tags: [Documents]
|
||||
summary: List all documents
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: tag
|
||||
in: query
|
||||
description: Filter by tag
|
||||
schema:
|
||||
type: string
|
||||
example: "backend"
|
||||
- name: library
|
||||
in: query
|
||||
description: Filter by library ID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
- name: type
|
||||
in: query
|
||||
description: Filter by document type
|
||||
schema:
|
||||
type: string
|
||||
enum: [requirement, note, spec, general]
|
||||
example: "requirement"
|
||||
- name: status
|
||||
in: query
|
||||
description: Filter by status
|
||||
schema:
|
||||
type: string
|
||||
enum: [draft, approved, implemented]
|
||||
example: "draft"
|
||||
- name: limit
|
||||
in: query
|
||||
description: Max results to return
|
||||
schema:
|
||||
type: integer
|
||||
default: 50
|
||||
example: 20
|
||||
- name: offset
|
||||
in: query
|
||||
description: Skip first N results
|
||||
schema:
|
||||
type: integer
|
||||
default: 0
|
||||
example: 0
|
||||
responses:
|
||||
'200':
|
||||
description: List of documents
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
documents:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Document'
|
||||
total:
|
||||
type: integer
|
||||
example: 42
|
||||
limit:
|
||||
type: integer
|
||||
example: 20
|
||||
offset:
|
||||
type: integer
|
||||
example: 0
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
post:
|
||||
tags: [Documents]
|
||||
summary: Create a new document
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [title, libraryId]
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
description: Document title
|
||||
example: "API Authentication Design"
|
||||
libraryId:
|
||||
type: string
|
||||
description: Target library ID
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
content:
|
||||
type: string
|
||||
description: Markdown content (optional, defaults to template)
|
||||
example: "# API Authentication\n\n## Description\n..."
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["backend", "api", "auth"]
|
||||
type:
|
||||
type: string
|
||||
enum: [requirement, note, spec, general]
|
||||
default: general
|
||||
priority:
|
||||
type: string
|
||||
enum: [high, medium, low]
|
||||
default: medium
|
||||
status:
|
||||
type: string
|
||||
enum: [draft, approved, implemented]
|
||||
default: draft
|
||||
responses:
|
||||
'201':
|
||||
description: Document created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Document'
|
||||
'400':
|
||||
description: Invalid request body
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: Library not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/documents/{id}:
|
||||
get:
|
||||
tags: [Documents]
|
||||
summary: Get a document by ID
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Document UUID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440001"
|
||||
responses:
|
||||
'200':
|
||||
description: Document found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Document'
|
||||
'404':
|
||||
description: Document not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
put:
|
||||
tags: [Documents]
|
||||
summary: Update a document
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Document UUID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440001"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
title:
|
||||
type: string
|
||||
example: "Updated Title"
|
||||
content:
|
||||
type: string
|
||||
example: "# Updated Content\n\nNew markdown..."
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["backend", "api"]
|
||||
type:
|
||||
type: string
|
||||
enum: [requirement, note, spec, general]
|
||||
priority:
|
||||
type: string
|
||||
enum: [high, medium, low]
|
||||
status:
|
||||
type: string
|
||||
enum: [draft, approved, implemented]
|
||||
responses:
|
||||
'200':
|
||||
description: Document updated
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Document'
|
||||
'400':
|
||||
description: Invalid request body
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: Document not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
delete:
|
||||
tags: [Documents]
|
||||
summary: Delete a document
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Document UUID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440001"
|
||||
responses:
|
||||
'200':
|
||||
description: Document deleted
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
deleted:
|
||||
type: boolean
|
||||
example: true
|
||||
id:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440001"
|
||||
'404':
|
||||
description: Document not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/documents/{id}/export:
|
||||
get:
|
||||
tags: [Documents]
|
||||
summary: Export document as raw Markdown
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Document UUID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440001"
|
||||
responses:
|
||||
'200':
|
||||
description: Raw Markdown file
|
||||
content:
|
||||
text/markdown:
|
||||
schema:
|
||||
type: string
|
||||
example: |
|
||||
---
|
||||
id: REQ-001
|
||||
title: API Authentication
|
||||
---
|
||||
# API Authentication
|
||||
|
||||
## Description
|
||||
...
|
||||
'404':
|
||||
description: Document not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/documents/{id}/tags:
|
||||
post:
|
||||
tags: [Tags]
|
||||
summary: Add tags to a document
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Document UUID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440001"
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [tags]
|
||||
properties:
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["new-tag", "another-tag"]
|
||||
responses:
|
||||
'200':
|
||||
description: Tags added
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Document'
|
||||
'400':
|
||||
description: Invalid tags array
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: Document not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
# ============ LIBRARIES ============
|
||||
/libraries:
|
||||
get:
|
||||
tags: [Libraries]
|
||||
summary: List root-level libraries
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: List of root libraries
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
libraries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Library'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
post:
|
||||
tags: [Libraries]
|
||||
summary: Create a new library
|
||||
security:
|
||||
- BearerAuth: []
|
||||
requestBody:
|
||||
required: true
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
required: [name]
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
description: Library name
|
||||
example: "Backend Requirements"
|
||||
parentId:
|
||||
type: string
|
||||
nullable: true
|
||||
description: Parent library ID for nesting (null for root)
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
responses:
|
||||
'201':
|
||||
description: Library created
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Library'
|
||||
'400':
|
||||
description: Invalid request body
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'404':
|
||||
description: Parent library not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/libraries/{id}:
|
||||
get:
|
||||
tags: [Libraries]
|
||||
summary: Get library contents (documents and sub-libraries)
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Library UUID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
responses:
|
||||
'200':
|
||||
description: Library contents
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
library:
|
||||
$ref: '#/components/schemas/Library'
|
||||
documents:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Document'
|
||||
subLibraries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Library'
|
||||
'404':
|
||||
description: Library not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/libraries/{id}/tree:
|
||||
get:
|
||||
tags: [Libraries]
|
||||
summary: Get full subtree of a library
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Library UUID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
responses:
|
||||
'200':
|
||||
description: Full library tree
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/LibraryTree'
|
||||
'404':
|
||||
description: Library not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/libraries/{id}/documents:
|
||||
get:
|
||||
tags: [Documents]
|
||||
summary: List documents in a specific library
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: id
|
||||
in: path
|
||||
required: true
|
||||
description: Library UUID
|
||||
schema:
|
||||
type: string
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
responses:
|
||||
'200':
|
||||
description: List of documents in library
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
libraryId:
|
||||
type: string
|
||||
documents:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Document'
|
||||
'404':
|
||||
description: Library not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
# ============ TAGS ============
|
||||
/tags:
|
||||
get:
|
||||
tags: [Tags]
|
||||
summary: List all tags with document counts
|
||||
security:
|
||||
- BearerAuth: []
|
||||
responses:
|
||||
'200':
|
||||
description: All tags with counts
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
name:
|
||||
type: string
|
||||
example: "backend"
|
||||
count:
|
||||
type: integer
|
||||
example: 5
|
||||
total:
|
||||
type: integer
|
||||
example: 15
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
/tags/{tag}:
|
||||
get:
|
||||
tags: [Tags]
|
||||
summary: Get all documents with a specific tag
|
||||
security:
|
||||
- BearerAuth: []
|
||||
parameters:
|
||||
- name: tag
|
||||
in: path
|
||||
required: true
|
||||
description: Tag name
|
||||
schema:
|
||||
type: string
|
||||
example: "backend"
|
||||
responses:
|
||||
'200':
|
||||
description: Documents with tag
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
type: object
|
||||
properties:
|
||||
tag:
|
||||
type: string
|
||||
example: "backend"
|
||||
documents:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/Document'
|
||||
count:
|
||||
type: integer
|
||||
example: 5
|
||||
'404':
|
||||
description: Tag not found
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
'401':
|
||||
description: Unauthorized
|
||||
content:
|
||||
application/json:
|
||||
schema:
|
||||
$ref: '#/components/schemas/Error'
|
||||
|
||||
components:
|
||||
securitySchemes:
|
||||
BearerAuth:
|
||||
type: http
|
||||
scheme: bearer
|
||||
bearerFormat: JWT
|
||||
description: API token with `snk_` prefix
|
||||
|
||||
schemas:
|
||||
Error:
|
||||
type: object
|
||||
properties:
|
||||
error:
|
||||
type: string
|
||||
example: "Document not found"
|
||||
code:
|
||||
type: string
|
||||
example: "NOT_FOUND"
|
||||
|
||||
Document:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "550e8400-e29b-41d4-a716-446655440001"
|
||||
title:
|
||||
type: string
|
||||
example: "API Authentication Design"
|
||||
path:
|
||||
type: string
|
||||
example: "/libraries/550e8400/.../documents/550e8401/index.md"
|
||||
content:
|
||||
type: string
|
||||
description: Raw markdown content
|
||||
example: "# API Authentication\n\n## Description\n..."
|
||||
tags:
|
||||
type: array
|
||||
items:
|
||||
type: string
|
||||
example: ["backend", "api", "auth"]
|
||||
type:
|
||||
type: string
|
||||
enum: [requirement, note, spec, general]
|
||||
example: "requirement"
|
||||
status:
|
||||
type: string
|
||||
enum: [draft, approved, implemented]
|
||||
example: "draft"
|
||||
priority:
|
||||
type: string
|
||||
enum: [high, medium, low]
|
||||
example: "high"
|
||||
libraryId:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
createdBy:
|
||||
type: string
|
||||
description: Agent or user ID who created the document
|
||||
example: "agent-001"
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-28T10:00:00Z"
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-28T12:30:00Z"
|
||||
|
||||
Library:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
example: "550e8400-e29b-41d4-a716-446655440000"
|
||||
name:
|
||||
type: string
|
||||
example: "Backend Requirements"
|
||||
parentId:
|
||||
type: string
|
||||
nullable: true
|
||||
format: uuid
|
||||
description: Parent library ID, null for root
|
||||
example: null
|
||||
path:
|
||||
type: string
|
||||
description: Absolute path to library folder
|
||||
example: "/data/libraries/550e8400"
|
||||
documentCount:
|
||||
type: integer
|
||||
description: Total documents in this library (excludes sub-libraries)
|
||||
example: 12
|
||||
createdAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-28T09:00:00Z"
|
||||
updatedAt:
|
||||
type: string
|
||||
format: date-time
|
||||
example: "2026-03-28T09:00:00Z"
|
||||
|
||||
LibraryTree:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
format: uuid
|
||||
name:
|
||||
type: string
|
||||
documents:
|
||||
type: array
|
||||
items:
|
||||
type: object
|
||||
properties:
|
||||
id:
|
||||
type: string
|
||||
title:
|
||||
type: string
|
||||
subLibraries:
|
||||
type: array
|
||||
items:
|
||||
$ref: '#/components/schemas/LibraryTree'
|
||||
274
cli-protocol.md
Normal file
274
cli-protocol.md
Normal file
@@ -0,0 +1,274 @@
|
||||
# SimpleNote CLI - Protocolo de Comunicación
|
||||
|
||||
## 1. Overview
|
||||
|
||||
El CLI (`simplenote-cli`) se comunica con el servidor (`simplenote-web`) exclusivamente
|
||||
vía HTTP REST usando el API documentado en `api-spec.yaml`.
|
||||
|
||||
No hay comunicación peer-to-peer ni protocolos binarios. Todo es JSON sobre HTTP.
|
||||
|
||||
```
|
||||
┌──────────────────┐ HTTP/REST ┌─────────────────┐
|
||||
│ simplenote-cli │ ◄──────────────────► │ simplenote-web │
|
||||
│ (Commander.js) │ Bearer token auth │ (Express.js) │
|
||||
└──────────────────┘ └─────────────────┘
|
||||
│ │
|
||||
└──── ~/.config/simplenote/config.json ──┘
|
||||
```
|
||||
|
||||
## 2. Cliente HTTP
|
||||
|
||||
### 2.1 Clase `SimpleNoteClient`
|
||||
|
||||
```javascript
|
||||
// src/api/client.js
|
||||
const axios = require('axios');
|
||||
|
||||
class SimpleNoteClient {
|
||||
constructor({ baseUrl, token }) {
|
||||
this.baseUrl = baseUrl.replace(/\/$/, ''); // strip trailing slash
|
||||
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;
|
||||
throw new Error(`API Error ${err.response.status}: ${msg}`);
|
||||
}
|
||||
throw err;
|
||||
}
|
||||
}
|
||||
|
||||
// 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`); }
|
||||
|
||||
// Documents > Tags
|
||||
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`);
|
||||
}
|
||||
|
||||
// Tags
|
||||
async listTags() { return this._request('GET', '/tags'); }
|
||||
async getTagDocuments(tag) { return this._request('GET', `/tags/${tag}`); }
|
||||
}
|
||||
```
|
||||
|
||||
## 3. Flujo de Auth
|
||||
|
||||
### 3.1 Login Inicial
|
||||
|
||||
```bash
|
||||
simplenote auth login snk_a1b2c3d4e5f6...
|
||||
```
|
||||
|
||||
Flujo:
|
||||
1. CLI guarda token en `~/.config/simplenote/config.json`
|
||||
2. CLI llama `GET /api/v1/auth/verify` para validar
|
||||
3. Si 200 → login exitoso. Si 401 → token inválido.
|
||||
|
||||
### 3.2 Requests Subsecuentes
|
||||
|
||||
Todas las requests incluyen:
|
||||
```
|
||||
Authorization: Bearer <token>
|
||||
```
|
||||
|
||||
### 3.3 Verificación de Status
|
||||
|
||||
```bash
|
||||
simplenote auth status
|
||||
```
|
||||
→ `GET /auth/verify` → muestra si token es válido.
|
||||
|
||||
## 4. Comandos CLI Detallados
|
||||
|
||||
### 4.1 Documents
|
||||
|
||||
```bash
|
||||
# Listar con filtros
|
||||
simplenote doc list --tag backend --library 550e8400... --type requirement
|
||||
simplenote doc list --tag api --limit 10
|
||||
|
||||
# Ver documento
|
||||
simplenote doc get 770e8400-e29b-41d4-a716-446655440002
|
||||
|
||||
# Crear
|
||||
simplenote doc create \
|
||||
--title "API Authentication" \
|
||||
--content "# API Authentication\n\n..." \
|
||||
--tags "backend,api,auth" \
|
||||
--type requirement \
|
||||
--priority high \
|
||||
--library 550e8400-e29b-41d4-a716-446655440000
|
||||
|
||||
# Actualizar
|
||||
simplenote doc update 770e8400... --title "Nuevo título" --content "..."
|
||||
simplenote doc update 770e8400... --status approved
|
||||
|
||||
# Eliminar
|
||||
simplenote doc delete 770e8400...
|
||||
|
||||
# Exportar como markdown
|
||||
simplenote doc export 770e8400...
|
||||
|
||||
# Agregar tags
|
||||
simplenote doc add-tags 770e8400... --tags "new-tag,another"
|
||||
```
|
||||
|
||||
### 4.2 Libraries
|
||||
|
||||
```bash
|
||||
# Listar librerías raíz
|
||||
simplenote lib list
|
||||
|
||||
# Listar con padre
|
||||
simplenote lib list --parent 550e8400...
|
||||
|
||||
# Ver contenido
|
||||
simplenote lib get 550e8400...
|
||||
|
||||
# Crear
|
||||
simplenote lib create --name "API Specs"
|
||||
simplenote lib create --name "Sub Librería" --parent 550e8400...
|
||||
|
||||
# Ver árbol completo
|
||||
simplenote lib tree 550e8400...
|
||||
```
|
||||
|
||||
### 4.3 Tags
|
||||
|
||||
```bash
|
||||
# Listar todos los tags
|
||||
simplenote tag list
|
||||
|
||||
# Ver docs con tag
|
||||
simplenote tag docs backend
|
||||
```
|
||||
|
||||
### 4.4 Auth
|
||||
|
||||
```bash
|
||||
# Login con token
|
||||
simplenote auth login snk_xxxxx
|
||||
|
||||
# Verificar status
|
||||
simplenote auth status
|
||||
```
|
||||
|
||||
## 5. Manejo de Errores
|
||||
|
||||
```javascript
|
||||
// Errores de API se transforman en mensajes claros
|
||||
try {
|
||||
await client.getDocument('non-existent-id');
|
||||
} catch (err) {
|
||||
console.error(err.message);
|
||||
// "API Error 404: Document not found"
|
||||
}
|
||||
```
|
||||
|
||||
Códigos de error CLI:
|
||||
- `1` — Error general (network, parse, etc)
|
||||
- `2` — Token inválido / auth fallida
|
||||
- `3` — Recurso no encontrado (404)
|
||||
- `4` — Validación de input
|
||||
|
||||
## 6. Configuración de Conexión
|
||||
|
||||
```javascript
|
||||
// ~/.config/simplenote/config.json
|
||||
{
|
||||
"apiUrl": "http://localhost:3000/api/v1",
|
||||
"token": "snk_xxxxx",
|
||||
"activeLibrary": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
Override por línea de comandos:
|
||||
```bash
|
||||
simplenote --api-url http://custom:3000/api/v1 doc list
|
||||
```
|
||||
|
||||
## 7. Dependencias CLI
|
||||
|
||||
```json
|
||||
// package.json
|
||||
{
|
||||
"dependencies": {
|
||||
"commander": "^11.1.0",
|
||||
"axios": "^1.6.0",
|
||||
"chalk": "^5.3.0",
|
||||
"inquirer": "^9.2.0"
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
## 8. Ejemplo de Sesión Completa
|
||||
|
||||
```bash
|
||||
$ simplenote auth login snk_a1b2c3d4e5f6
|
||||
✓ Token verified. Logged in.
|
||||
|
||||
$ simplenote lib list
|
||||
[
|
||||
{ "id": "550e8400...", "name": "Backend Requirements", "documentCount": 5 }
|
||||
]
|
||||
|
||||
$ simplenote doc create \
|
||||
--title "Token Auth" \
|
||||
--tags "backend,auth" \
|
||||
--type requirement \
|
||||
--library 550e8400...
|
||||
{
|
||||
"id": "770e8400...",
|
||||
"title": "Token Auth",
|
||||
"tags": ["backend", "auth"],
|
||||
...
|
||||
}
|
||||
|
||||
$ simplenote tag docs backend
|
||||
[
|
||||
{ "id": "770e8400...", "title": "Token Auth", ... }
|
||||
]
|
||||
|
||||
$ simplenote doc get 770e8400...
|
||||
# Muestra documento formateado con content + metadata
|
||||
```
|
||||
345
data-format.md
Normal file
345
data-format.md
Normal file
@@ -0,0 +1,345 @@
|
||||
# SimpleNote - Formato de Datos
|
||||
|
||||
## 1. Archivos JSON de Metadata
|
||||
|
||||
Todos los archivos de metadata son JSON planos, almacenados junto a su contenido en el filesystem.
|
||||
|
||||
---
|
||||
|
||||
## 2. `.library.json`
|
||||
|
||||
Define una librería (equivalente a una carpeta/directorio).
|
||||
|
||||
**Ubicación**: `data/libraries/{library-uuid}/.library.json`
|
||||
|
||||
**Schema**:
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["id", "name", "path", "createdAt", "updatedAt"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid",
|
||||
"description": "UUID único de la librería"
|
||||
},
|
||||
"name": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 255,
|
||||
"description": "Nombre visible de la librería"
|
||||
},
|
||||
"parentId": {
|
||||
"type": ["string", "null"],
|
||||
"format": "uuid",
|
||||
"description": "ID del padre directo. Null para root."
|
||||
},
|
||||
"path": {
|
||||
"type": "string",
|
||||
"description": "Ruta relativa desde DATA_ROOT (ej: libraries/uuid)"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Timestamp de creación ISO8601"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Timestamp de última modificación ISO8601"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ejemplo**:
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Backend Requirements",
|
||||
"parentId": null,
|
||||
"path": "libraries/550e8400",
|
||||
"createdAt": "2026-03-28T09:00:00Z",
|
||||
"updatedAt": "2026-03-28T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
**Ejemplo anidado**:
|
||||
```json
|
||||
{
|
||||
"id": "660e8400-e29b-41d4-a716-446655440001",
|
||||
"name": "API Specs",
|
||||
"parentId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"path": "libraries/550e8400/sub-libraries/660e8400",
|
||||
"createdAt": "2026-03-28T10:00:00Z",
|
||||
"updatedAt": "2026-03-28T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 3. `.meta.json`
|
||||
|
||||
Metadata de un documento individual.
|
||||
|
||||
**Ubicación**: `data/libraries/{lib-uuid}/documents/{doc-uuid}/.meta.json`
|
||||
|
||||
**Schema**:
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["id", "title", "tags", "type", "libraryId", "createdAt", "updatedAt"],
|
||||
"properties": {
|
||||
"id": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"title": {
|
||||
"type": "string",
|
||||
"minLength": 1,
|
||||
"maxLength": 500
|
||||
},
|
||||
"tags": {
|
||||
"type": "array",
|
||||
"items": { "type": "string" },
|
||||
"uniqueItems": true
|
||||
},
|
||||
"type": {
|
||||
"type": "string",
|
||||
"enum": ["requirement", "note", "spec", "general"]
|
||||
},
|
||||
"status": {
|
||||
"type": "string",
|
||||
"enum": ["draft", "approved", "implemented"]
|
||||
},
|
||||
"priority": {
|
||||
"type": "string",
|
||||
"enum": ["high", "medium", "low"]
|
||||
},
|
||||
"libraryId": {
|
||||
"type": "string",
|
||||
"format": "uuid"
|
||||
},
|
||||
"createdBy": {
|
||||
"type": "string",
|
||||
"description": "Agent ID o user ID que creó el documento"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ejemplo**:
|
||||
```json
|
||||
{
|
||||
"id": "770e8400-e29b-41d4-a716-446655440002",
|
||||
"title": "API Authentication Design",
|
||||
"tags": ["backend", "api", "auth"],
|
||||
"type": "requirement",
|
||||
"status": "draft",
|
||||
"priority": "high",
|
||||
"libraryId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"createdBy": "agent-001",
|
||||
"createdAt": "2026-03-28T10:00:00Z",
|
||||
"updatedAt": "2026-03-28T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 4. `.tag-index.json`
|
||||
|
||||
Índice global de tags. Rebuild completo o incremental.
|
||||
|
||||
**Ubicación**: `data/.tag-index.json`
|
||||
|
||||
**Schema**:
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["version", "updatedAt", "tags"],
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"const": 1,
|
||||
"description": "Versión del formato de índice"
|
||||
},
|
||||
"updatedAt": {
|
||||
"type": "string",
|
||||
"format": "date-time",
|
||||
"description": "Última rebuild del índice"
|
||||
},
|
||||
"tags": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "array",
|
||||
"items": { "type": "string", "format": "uuid" },
|
||||
"uniqueItems": true
|
||||
},
|
||||
"description": "Map tag → array de document IDs"
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ejemplo**:
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"updatedAt": "2026-03-28T12:00:00Z",
|
||||
"tags": {
|
||||
"backend": [
|
||||
"770e8400-e29b-41d4-a716-446655440002",
|
||||
"880e8400-e29b-41d4-a716-446655440003"
|
||||
],
|
||||
"api": [
|
||||
"770e8400-e29b-41d4-a716-446655440002"
|
||||
],
|
||||
"auth": [
|
||||
"770e8400-e29b-41d4-a716-446655440002",
|
||||
"990e8400-e29b-41d4-a716-446655440004"
|
||||
]
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 5. `.auth-tokens.json`
|
||||
|
||||
Tokens de API válidos.
|
||||
|
||||
**Ubicación**: `data/.auth-tokens.json`
|
||||
|
||||
**Schema**:
|
||||
```json
|
||||
{
|
||||
"$schema": "http://json-schema.org/draft-07/schema#",
|
||||
"type": "object",
|
||||
"required": ["version", "tokens"],
|
||||
"properties": {
|
||||
"version": {
|
||||
"type": "integer",
|
||||
"const": 1
|
||||
},
|
||||
"tokens": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "object",
|
||||
"required": ["token", "label", "createdAt"],
|
||||
"properties": {
|
||||
"token": {
|
||||
"type": "string",
|
||||
"pattern": "^snk_"
|
||||
},
|
||||
"label": {
|
||||
"type": "string"
|
||||
},
|
||||
"createdAt": {
|
||||
"type": "string",
|
||||
"format": "date-time"
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
**Ejemplo**:
|
||||
```json
|
||||
{
|
||||
"version": 1,
|
||||
"tokens": [
|
||||
{
|
||||
"token": "snk_a1b2c3d4e5f6g7h8i9j0",
|
||||
"label": "cli-default",
|
||||
"createdAt": "2026-03-28T00:00:00Z"
|
||||
},
|
||||
{
|
||||
"token": "snk_k9j8i7h6g5f4e3d2c1b",
|
||||
"label": "hiro-workstation",
|
||||
"createdAt": "2026-03-28T01:00:00Z"
|
||||
}
|
||||
]
|
||||
}
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 6. Documento Markdown (`index.md`)
|
||||
|
||||
Archivo de contenido con frontmatter YAML.
|
||||
|
||||
### 6.1 Frontmatter
|
||||
|
||||
```yaml
|
||||
---
|
||||
id: REQ-001
|
||||
title: Título del Requerimiento
|
||||
type: requirement # requirement | note | spec | general
|
||||
priority: high # high | medium | low
|
||||
status: draft # draft | approved | implemented
|
||||
tags: [backend, api]
|
||||
createdBy: agent-001
|
||||
createdAt: 2026-03-28
|
||||
---
|
||||
```
|
||||
|
||||
### 6.2 Body
|
||||
|
||||
Markdown standard con headers, listas, código, etc.
|
||||
|
||||
### 6.3 Ejemplo Completo
|
||||
|
||||
```markdown
|
||||
---
|
||||
id: REQ-001
|
||||
title: API Authentication Design
|
||||
type: requirement
|
||||
priority: high
|
||||
status: draft
|
||||
tags: [backend, api, auth]
|
||||
createdBy: agent-001
|
||||
createdAt: 2026-03-28
|
||||
---
|
||||
|
||||
# API Authentication Design
|
||||
|
||||
## Descripción
|
||||
El sistema debe soportar autenticación via tokens Bearer para todas las rutas
|
||||
protegidas bajo `/api/v1/*` excepto `/api/v1/auth/token`.
|
||||
|
||||
## Criterios de Aceptación
|
||||
- [ ] Endpoint POST /api/v1/auth/token acepta credenciales y retorna token
|
||||
- [ ] Middleware extrae token del header `Authorization: Bearer <token>`
|
||||
- [ ] Middleware retorna 401 si header ausente o token inválido
|
||||
- [ ] Token tiene prefijo `snk_` para identificación fácil
|
||||
|
||||
## Notas
|
||||
Tokens en esta versión son secretos compartidos. Para producción se recomienda
|
||||
OAuth2 o JWT firmados.
|
||||
```
|
||||
|
||||
---
|
||||
|
||||
## 7. Tabla Resumen de Archivos
|
||||
|
||||
| Archivo | Ubicación | Propósito |
|
||||
|---------|-----------|-----------|
|
||||
| `.library.json` | `libraries/{id}/` | Metadata de librería |
|
||||
| `.meta.json` | `libraries/{lib}/documents/{id}/` | Metadata de documento |
|
||||
| `index.md` | `libraries/{lib}/documents/{id}/` | Contenido del documento |
|
||||
| `.tag-index.json` | `DATA_ROOT/` | Índice global tag → docs |
|
||||
| `.auth-tokens.json` | `DATA_ROOT/` | Tokens API válidos |
|
||||
| `config.json` | `~/.config/simplenote/` | Config local del CLI |
|
||||
116
env-template.md
Normal file
116
env-template.md
Normal file
@@ -0,0 +1,116 @@
|
||||
# SimpleNote - Variables de Entorno
|
||||
|
||||
## Template: `.env.example`
|
||||
|
||||
Copia este archivo a `.env` en la raíz de `simplenote-web/`.
|
||||
|
||||
```env
|
||||
# ============ SERVER ============
|
||||
PORT=3000
|
||||
HOST=0.0.0.0
|
||||
|
||||
# ============ DATA ============
|
||||
# Raíz donde se almacenan documentos y archivos de índice
|
||||
# Default: ./data (relativo al proyecto)
|
||||
DATA_ROOT=./data
|
||||
|
||||
# ============ AUTH ============
|
||||
# Token admin inicial (generado en setup). No exponer en cliente.
|
||||
ADMIN_TOKEN=snk_initial_admin_token_change_me
|
||||
|
||||
# ============ LOGGING ============
|
||||
LOG_LEVEL=info
|
||||
# Opciones: trace, debug, info, warn, error, fatal
|
||||
|
||||
# ============ CORS ============
|
||||
# Orígenes permitidos para requests cross-origin
|
||||
# Default: * (permitir todos)
|
||||
CORS_ORIGIN=*
|
||||
|
||||
# Para desarrollo local:
|
||||
# CORS_ORIGIN=http://localhost:5173
|
||||
|
||||
# Para producción:
|
||||
# CORS_ORIGIN=https://simplenote.example.com
|
||||
|
||||
# ============ API ============
|
||||
# Versión del API en URLs
|
||||
API_PREFIX=/api/v1
|
||||
|
||||
# ============ RATE LIMITING (opcional) ============
|
||||
# Requests por minuto por IP
|
||||
# RATE_LIMIT_ENABLED=true
|
||||
# RATE_LIMIT_WINDOW_MS=60000
|
||||
# RATE_LIMIT_MAX=100
|
||||
```
|
||||
|
||||
## Detalle de Variables
|
||||
|
||||
### `PORT`
|
||||
- **Tipo**: integer
|
||||
- **Default**: `3000`
|
||||
- **Descripción**: Puerto TCP donde Express escucha conexiones entrantes.
|
||||
|
||||
### `HOST`
|
||||
- **Tipo**: string
|
||||
- **Default**: `0.0.0.0`
|
||||
- **Descripción**: Host de binding. `0.0.0.0` = todas las interfaces.
|
||||
|
||||
### `DATA_ROOT`
|
||||
- **Tipo**: string (ruta)
|
||||
- **Default**: `./data`
|
||||
- **Descripción**: Directorio raíz donde se guardan:
|
||||
- `libraries/` — estructura de carpetas con documentos
|
||||
- `.tag-index.json` — índice global de tags
|
||||
- `.auth-tokens.json` — tokens de API
|
||||
|
||||
### `LOG_LEVEL`
|
||||
- **Tipo**: enum
|
||||
- **Default**: `info`
|
||||
- **Opciones**: `trace`, `debug`, `info`, `warn`, `error`, `fatal`
|
||||
- **Descripción**: Nivel mínimo de logs que se emiten.
|
||||
|
||||
### `CORS_ORIGIN`
|
||||
- **Tipo**: string
|
||||
- **Default**: `*`
|
||||
- **Descripción**: Valor del header `Access-Control-Allow-Origin`. Usar dominio
|
||||
específico en producción para seguridad.
|
||||
|
||||
### `API_PREFIX`
|
||||
- **Tipo**: string
|
||||
- **Default**: `/api/v1`
|
||||
- **Descripción**: Prefijo para todas las rutas del API. Cambiar permite versionado.
|
||||
|
||||
---
|
||||
|
||||
## CLI Config: `~/.config/simplenote/config.json`
|
||||
|
||||
```json
|
||||
{
|
||||
"apiUrl": "http://localhost:3000/api/v1",
|
||||
"token": "snk_your_token_here",
|
||||
"activeLibrary": "550e8400-e29b-41d4-a716-446655440000"
|
||||
}
|
||||
```
|
||||
|
||||
| Campo | Descripción |
|
||||
|-------|-------------|
|
||||
| `apiUrl` | URL base del API (sin trailing slash) |
|
||||
| `token` | Token Bearer para autenticación |
|
||||
| `activeLibrary` | ID de librería activa por defecto |
|
||||
|
||||
---
|
||||
|
||||
## Flags de Línea de Comandos (CLI)
|
||||
|
||||
```bash
|
||||
# Override apiUrl
|
||||
simplenote --api-url http://custom:3000/api/v1 doc list
|
||||
|
||||
# Modo verbose
|
||||
simplenote --verbose doc list
|
||||
|
||||
# Help
|
||||
simplenote help
|
||||
simplenote doc help create
|
||||
```
|
||||
170
folder-structure.md
Normal file
170
folder-structure.md
Normal file
@@ -0,0 +1,170 @@
|
||||
# SimpleNote - Estructura de Carpetas
|
||||
|
||||
## Estructura General del Repositorio
|
||||
|
||||
```
|
||||
simplenote-web/
|
||||
├── src/
|
||||
│ ├── index.js # Entry point (bind port, listen)
|
||||
│ ├── app.js # Express app setup, middleware, routes
|
||||
│ ├── config/
|
||||
│ │ └── index.js # Env vars loader y defaults
|
||||
│ ├── routes/
|
||||
│ │ ├── index.js # Router principal (mount /api/v1/*)
|
||||
│ │ ├── documents.js # /documents routes
|
||||
│ │ ├── libraries.js # /libraries routes
|
||||
│ │ ├── tags.js # /tags routes
|
||||
│ │ └── auth.js # /auth routes
|
||||
│ ├── services/
|
||||
│ │ ├── documentService.js # Lógica de documentos
|
||||
│ │ ├── libraryService.js # Lógica de librerías
|
||||
│ │ └── tagService.js # Lógica de tags e indexación
|
||||
│ ├── middleware/
|
||||
│ │ ├── auth.js # Middleware de autenticación Bearer
|
||||
│ │ └── errorHandler.js # Handler global de errores
|
||||
│ ├── utils/
|
||||
│ │ ├── markdown.js # Parseo y serialización de Markdown
|
||||
│ │ ├── fsHelper.js # Helpers de filesystem (safe paths, etc)
|
||||
│ │ ├── uuid.js # Wrapper de uuid/v4
|
||||
│ │ └── errors.js # Clases de errores custom (NotFound, etc)
|
||||
│ └── indexers/
|
||||
│ └── tagIndexer.js # Rebuild y query del .tag-index.json
|
||||
├── data/ # Raíz de documentos (DATA_ROOT)
|
||||
│ ├── .tag-index.json # Índice global de tags
|
||||
│ ├── .auth-tokens.json # Tokens de API válidos
|
||||
│ └── libraries/
|
||||
│ └── {uuid}/
|
||||
│ ├── .library.json
|
||||
│ ├── documents/
|
||||
│ │ └── {uuid}/
|
||||
│ │ ├── index.md
|
||||
│ │ └── .meta.json
|
||||
│ └── sub-libraries/
|
||||
│ └── {child-uuid}/...
|
||||
├── tests/
|
||||
│ ├── unit/
|
||||
│ │ ├── services/
|
||||
│ │ │ ├── documentService.test.js
|
||||
│ │ │ ├── libraryService.test.js
|
||||
│ │ │ └── tagService.test.js
|
||||
│ │ └── utils/
|
||||
│ │ └── markdown.test.js
|
||||
│ └── integration/
|
||||
│ └── api.test.js
|
||||
├── scripts/
|
||||
│ └── init-data.js # Script de inicialización (crea default lib)
|
||||
├── package.json
|
||||
├── .env.example
|
||||
└── README.md
|
||||
|
||||
simplenote-cli/
|
||||
├── src/
|
||||
│ ├── index.js # Entry point (Commander program)
|
||||
│ ├── api/
|
||||
│ │ └── client.js # SimpleNoteClient (axios-based)
|
||||
│ ├── commands/
|
||||
│ │ ├── index.js # Registra todos los subcommands
|
||||
│ │ ├── doc.js # simplenote doc <subcmd>
|
||||
│ │ ├── lib.js # simplenote lib <subcmd>
|
||||
│ │ └── tag.js # simplenote tag <subcmd>
|
||||
│ └── config/
|
||||
│ ├── loader.js # Carga ~/.config/simplenote/config.json
|
||||
│ └── errors.js # Errores CLI custom
|
||||
├── package.json
|
||||
└── README.md
|
||||
```
|
||||
|
||||
## Estructura de Datos en Disco (DATA_ROOT)
|
||||
|
||||
```
|
||||
data/ # DATA_ROOT (default: ./data)
|
||||
├── .tag-index.json # Tag index global
|
||||
├── .auth-tokens.json # Auth tokens
|
||||
└── libraries/
|
||||
└── {library-uuid}/
|
||||
├── .library.json
|
||||
├── documents/
|
||||
│ └── {doc-uuid}/
|
||||
│ ├── index.md # Contenido Markdown
|
||||
│ └── .meta.json # Metadata (tags, timestamps)
|
||||
└── sub-libraries/
|
||||
└── {child-uuid}/
|
||||
├── .library.json
|
||||
└── documents/
|
||||
└── ... (recursivo)
|
||||
```
|
||||
|
||||
## Archivos de Metadata
|
||||
|
||||
### `.library.json` (por librería)
|
||||
```json
|
||||
{
|
||||
"id": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"name": "Backend Requirements",
|
||||
"parentId": null,
|
||||
"path": "libraries/550e8400",
|
||||
"createdAt": "2026-03-28T09:00:00Z",
|
||||
"updatedAt": "2026-03-28T09:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### `.meta.json` (por documento)
|
||||
```json
|
||||
{
|
||||
"id": "550e8401-e29b-41d4-a716-446655440001",
|
||||
"title": "API Authentication",
|
||||
"tags": ["backend", "api", "auth"],
|
||||
"type": "requirement",
|
||||
"status": "draft",
|
||||
"priority": "high",
|
||||
"libraryId": "550e8400-e29b-41d4-a716-446655440000",
|
||||
"createdBy": "agent-001",
|
||||
"createdAt": "2026-03-28T10:00:00Z",
|
||||
"updatedAt": "2026-03-28T10:00:00Z"
|
||||
}
|
||||
```
|
||||
|
||||
### `index.md` (contenido)
|
||||
```markdown
|
||||
---
|
||||
id: REQ-001
|
||||
title: API Authentication
|
||||
type: requirement
|
||||
priority: high
|
||||
status: draft
|
||||
tags: [backend, api, auth]
|
||||
createdBy: agent-001
|
||||
createdAt: 2026-03-28
|
||||
---
|
||||
|
||||
# API Authentication
|
||||
|
||||
## Descripción
|
||||
El sistema debe soportar autenticación via tokens Bearer.
|
||||
|
||||
## Criterios de Aceptación
|
||||
- [ ] Endpoint POST /api/auth/token
|
||||
- [ ] Middleware valida Bearer token
|
||||
- [ ] Retorna 401 si token inválido
|
||||
```
|
||||
|
||||
## Archivos de Configuración Local (CLI)
|
||||
|
||||
```
|
||||
~/.config/simplenote/
|
||||
└── config.json # Config CLI local
|
||||
{
|
||||
"apiUrl": "http://localhost:3000/api/v1",
|
||||
"token": "snk_xxxxx",
|
||||
"activeLibrary": "default"
|
||||
}
|
||||
```
|
||||
|
||||
## Archivos de Configuración del Servidor
|
||||
|
||||
```
|
||||
simplenote-web/
|
||||
├── .env.example # Template de variables de entorno
|
||||
├── .env # No commitear (contiene secrets)
|
||||
└── .gitignore # Ignora .env, data/, node_modules/
|
||||
```
|
||||
Reference in New Issue
Block a user