@getbrevo/cli 0.0.1-alpha.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/CHANGELOG.md +13 -0
- package/LICENSE +21 -0
- package/README.md +182 -0
- package/dist/api/client.d.ts +23 -0
- package/dist/api/client.d.ts.map +1 -0
- package/dist/api/client.js +186 -0
- package/dist/api/client.js.map +1 -0
- package/dist/bin/index.d.ts +3 -0
- package/dist/bin/index.d.ts.map +1 -0
- package/dist/bin/index.js +179 -0
- package/dist/bin/index.js.map +1 -0
- package/dist/commands/app/create.d.ts +7 -0
- package/dist/commands/app/create.d.ts.map +1 -0
- package/dist/commands/app/create.js +261 -0
- package/dist/commands/app/create.js.map +1 -0
- package/dist/commands/app/credentials.d.ts +6 -0
- package/dist/commands/app/credentials.d.ts.map +1 -0
- package/dist/commands/app/credentials.js +106 -0
- package/dist/commands/app/credentials.js.map +1 -0
- package/dist/commands/app/delete.d.ts +6 -0
- package/dist/commands/app/delete.d.ts.map +1 -0
- package/dist/commands/app/delete.js +146 -0
- package/dist/commands/app/delete.js.map +1 -0
- package/dist/commands/app/list.d.ts +4 -0
- package/dist/commands/app/list.d.ts.map +1 -0
- package/dist/commands/app/list.js +46 -0
- package/dist/commands/app/list.js.map +1 -0
- package/dist/commands/app/scaffold.d.ts +5 -0
- package/dist/commands/app/scaffold.d.ts.map +1 -0
- package/dist/commands/app/scaffold.js +199 -0
- package/dist/commands/app/scaffold.js.map +1 -0
- package/dist/commands/app/start.d.ts +5 -0
- package/dist/commands/app/start.d.ts.map +1 -0
- package/dist/commands/app/start.js +138 -0
- package/dist/commands/app/start.js.map +1 -0
- package/dist/commands/app/test.d.ts +6 -0
- package/dist/commands/app/test.d.ts.map +1 -0
- package/dist/commands/app/test.js +495 -0
- package/dist/commands/app/test.js.map +1 -0
- package/dist/commands/app/update.d.ts +2 -0
- package/dist/commands/app/update.d.ts.map +1 -0
- package/dist/commands/app/update.js +324 -0
- package/dist/commands/app/update.js.map +1 -0
- package/dist/commands/definitions.d.ts +4 -0
- package/dist/commands/definitions.d.ts.map +1 -0
- package/dist/commands/definitions.js +197 -0
- package/dist/commands/definitions.js.map +1 -0
- package/dist/commands/init.d.ts +2 -0
- package/dist/commands/init.d.ts.map +1 -0
- package/dist/commands/init.js +90 -0
- package/dist/commands/init.js.map +1 -0
- package/dist/commands/login.d.ts +6 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +219 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/logout.d.ts +5 -0
- package/dist/commands/logout.d.ts.map +1 -0
- package/dist/commands/logout.js +54 -0
- package/dist/commands/logout.js.map +1 -0
- package/dist/commands/whoami.d.ts +4 -0
- package/dist/commands/whoami.d.ts.map +1 -0
- package/dist/commands/whoami.js +74 -0
- package/dist/commands/whoami.js.map +1 -0
- package/dist/container.d.ts +7 -0
- package/dist/container.d.ts.map +1 -0
- package/dist/container.js +28 -0
- package/dist/container.js.map +1 -0
- package/dist/lang/en.d.ts +110 -0
- package/dist/lang/en.d.ts.map +1 -0
- package/dist/lang/en.js +133 -0
- package/dist/lang/en.js.map +1 -0
- package/dist/lib/abort.d.ts +3 -0
- package/dist/lib/abort.d.ts.map +1 -0
- package/dist/lib/abort.js +30 -0
- package/dist/lib/abort.js.map +1 -0
- package/dist/lib/auth-guard.d.ts +3 -0
- package/dist/lib/auth-guard.d.ts.map +1 -0
- package/dist/lib/auth-guard.js +25 -0
- package/dist/lib/auth-guard.js.map +1 -0
- package/dist/lib/browser.d.ts +2 -0
- package/dist/lib/browser.d.ts.map +1 -0
- package/dist/lib/browser.js +16 -0
- package/dist/lib/browser.js.map +1 -0
- package/dist/lib/command-handler.d.ts +10 -0
- package/dist/lib/command-handler.d.ts.map +1 -0
- package/dist/lib/command-handler.js +16 -0
- package/dist/lib/command-handler.js.map +1 -0
- package/dist/lib/command-registry.d.ts +28 -0
- package/dist/lib/command-registry.d.ts.map +1 -0
- package/dist/lib/command-registry.js +54 -0
- package/dist/lib/command-registry.js.map +1 -0
- package/dist/lib/config.d.ts +76 -0
- package/dist/lib/config.d.ts.map +1 -0
- package/dist/lib/config.js +314 -0
- package/dist/lib/config.js.map +1 -0
- package/dist/lib/constants.d.ts +35 -0
- package/dist/lib/constants.d.ts.map +1 -0
- package/dist/lib/constants.js +97 -0
- package/dist/lib/constants.js.map +1 -0
- package/dist/lib/errors.d.ts +30 -0
- package/dist/lib/errors.d.ts.map +1 -0
- package/dist/lib/errors.js +66 -0
- package/dist/lib/errors.js.map +1 -0
- package/dist/lib/exit-codes.d.ts +9 -0
- package/dist/lib/exit-codes.d.ts.map +1 -0
- package/dist/lib/exit-codes.js +12 -0
- package/dist/lib/exit-codes.js.map +1 -0
- package/dist/lib/hidden-input.d.ts +2 -0
- package/dist/lib/hidden-input.d.ts.map +1 -0
- package/dist/lib/hidden-input.js +78 -0
- package/dist/lib/hidden-input.js.map +1 -0
- package/dist/lib/json-output.d.ts +6 -0
- package/dist/lib/json-output.d.ts.map +1 -0
- package/dist/lib/json-output.js +11 -0
- package/dist/lib/json-output.js.map +1 -0
- package/dist/lib/logger.d.ts +11 -0
- package/dist/lib/logger.d.ts.map +1 -0
- package/dist/lib/logger.js +87 -0
- package/dist/lib/logger.js.map +1 -0
- package/dist/lib/port.d.ts +12 -0
- package/dist/lib/port.d.ts.map +1 -0
- package/dist/lib/port.js +79 -0
- package/dist/lib/port.js.map +1 -0
- package/dist/lib/ui.d.ts +14 -0
- package/dist/lib/ui.d.ts.map +1 -0
- package/dist/lib/ui.js +75 -0
- package/dist/lib/ui.js.map +1 -0
- package/dist/lib/validators.d.ts +32 -0
- package/dist/lib/validators.d.ts.map +1 -0
- package/dist/lib/validators.js +96 -0
- package/dist/lib/validators.js.map +1 -0
- package/dist/services/account.d.ts +8 -0
- package/dist/services/account.d.ts.map +1 -0
- package/dist/services/account.js +15 -0
- package/dist/services/account.js.map +1 -0
- package/dist/services/app.d.ts +35 -0
- package/dist/services/app.d.ts.map +1 -0
- package/dist/services/app.js +117 -0
- package/dist/services/app.js.map +1 -0
- package/dist/services/browser-auth.d.ts +15 -0
- package/dist/services/browser-auth.d.ts.map +1 -0
- package/dist/services/browser-auth.js +187 -0
- package/dist/services/browser-auth.js.map +1 -0
- package/dist/services/normalize-app-id.d.ts +12 -0
- package/dist/services/normalize-app-id.d.ts.map +1 -0
- package/dist/services/normalize-app-id.js +38 -0
- package/dist/services/normalize-app-id.js.map +1 -0
- package/dist/services/oauth-refresh.d.ts +14 -0
- package/dist/services/oauth-refresh.d.ts.map +1 -0
- package/dist/services/oauth-refresh.js +70 -0
- package/dist/services/oauth-refresh.js.map +1 -0
- package/dist/templates/files/AGENTS.md.tmpl +41 -0
- package/dist/templates/files/CLAUDE.md.tmpl +50 -0
- package/dist/templates/files/README.md.tmpl +77 -0
- package/dist/templates/files/app-config.json.tmpl +23 -0
- package/dist/templates/files/gitignore.tmpl +9 -0
- package/dist/templates/files/src/oauth/.env.example.tmpl +13 -0
- package/dist/templates/files/src/oauth/.env.local.tmpl +10 -0
- package/dist/templates/files/src/oauth/handler.js.tmpl +203 -0
- package/dist/templates/files/src/oauth/package.json.tmpl +15 -0
- package/dist/templates/files/src/oauth/server.js.tmpl +226 -0
- package/dist/templates/files/src/oauth/token-store.js.tmpl +39 -0
- package/dist/templates/index.d.ts +36 -0
- package/dist/templates/index.d.ts.map +1 -0
- package/dist/templates/index.js +105 -0
- package/dist/templates/index.js.map +1 -0
- package/dist/types.d.ts +30 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +3 -0
- package/dist/types.js.map +1 -0
- package/package.json +81 -0
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
# {{APP_NAME}}
|
|
2
|
+
|
|
3
|
+
Brevo OAuth integration scaffolded with the Brevo CLI.
|
|
4
|
+
|
|
5
|
+
> **This is a starter / test app** to help you exercise the OAuth flow locally. Treat it as a reference — it is not intended for production use.
|
|
6
|
+
|
|
7
|
+
## Setup
|
|
8
|
+
|
|
9
|
+
1. Install dependencies for the oauth feature:
|
|
10
|
+
|
|
11
|
+
```bash
|
|
12
|
+
yarn --cwd src/oauth install
|
|
13
|
+
# or: npm --prefix src/oauth install
|
|
14
|
+
```
|
|
15
|
+
|
|
16
|
+
2. Start the local OAuth test server (the CLI reads `app-config.json` for port and redirect URLs):
|
|
17
|
+
|
|
18
|
+
```bash
|
|
19
|
+
brevo app start oauth
|
|
20
|
+
# or choose a port: brevo app start oauth --port 3000
|
|
21
|
+
```
|
|
22
|
+
|
|
23
|
+
3. Open the login URL printed by the CLI (typically `http://localhost:<port>/auth/login`). The OAuth server redirects back to `http://localhost:<port>/auth/callback` — make sure that URL is registered on your app's redirect list.
|
|
24
|
+
|
|
25
|
+
## Project structure
|
|
26
|
+
|
|
27
|
+
```
|
|
28
|
+
.
|
|
29
|
+
├── app-config.json App config — ID, scopes, redirect URLs
|
|
30
|
+
├── .gitignore Ignores .env.local and build artefacts
|
|
31
|
+
├── AGENTS.md AI agent guidance
|
|
32
|
+
├── CLAUDE.md Claude Code guidance
|
|
33
|
+
├── README.md (this file)
|
|
34
|
+
└── src/
|
|
35
|
+
└── oauth/ Self-contained OAuth test feature
|
|
36
|
+
├── package.json Dependencies & `yarn start` script
|
|
37
|
+
├── server.js Test server entry point
|
|
38
|
+
├── handler.js OAuth routes (/auth/login, /auth/callback)
|
|
39
|
+
├── token-store.js In-memory token management
|
|
40
|
+
├── .env.example Credential template (committed)
|
|
41
|
+
└── .env.local Local credentials (gitignored, chmod 600)
|
|
42
|
+
```
|
|
43
|
+
|
|
44
|
+
## Credentials
|
|
45
|
+
|
|
46
|
+
- Client ID and secret are written to `src/oauth/.env.local` during scaffold (file permissions `600`).
|
|
47
|
+
- Re-fetch them any time with `brevo app credentials --app-id {{APP_ID}} --reveal-secret`.
|
|
48
|
+
- `.env.local` is in `.gitignore` — **never commit it**.
|
|
49
|
+
|
|
50
|
+
## Security
|
|
51
|
+
|
|
52
|
+
- Never commit `src/oauth/.env.local` — it contains your client secret.
|
|
53
|
+
- The test server uses a random `state` parameter for CSRF protection and binds to localhost only.
|
|
54
|
+
- Access tokens are kept in process memory; restarting the server clears them.
|
|
55
|
+
|
|
56
|
+
## Updating your app
|
|
57
|
+
|
|
58
|
+
Push changes to Brevo directly from the CLI:
|
|
59
|
+
|
|
60
|
+
```bash
|
|
61
|
+
brevo app update --name "New Name"
|
|
62
|
+
brevo app update --redirect-url https://myapp.com/callback
|
|
63
|
+
brevo app update --name "New Name" --redirect-url https://myapp.com/callback
|
|
64
|
+
```
|
|
65
|
+
|
|
66
|
+
`--redirect-url` is repeatable and **appends** to existing URLs. Running `brevo app update` without flags pushes the full `app-config.json`. When flags are provided, the local `app-config.json` is updated automatically after a successful API call.
|
|
67
|
+
|
|
68
|
+
Outside this project directory, target an app by ID:
|
|
69
|
+
|
|
70
|
+
```bash
|
|
71
|
+
brevo app update --app-id {{APP_ID}} --name "New Name"
|
|
72
|
+
```
|
|
73
|
+
|
|
74
|
+
## Docs
|
|
75
|
+
|
|
76
|
+
- [Brevo API Documentation](https://developers.brevo.com)
|
|
77
|
+
- [Brevo CLI](https://github.com/getbrevo/brevo-cli)
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
{
|
|
2
|
+
"appId": "{{APP_ID}}",
|
|
3
|
+
"appName": "{{APP_NAME}}",
|
|
4
|
+
"auth": {
|
|
5
|
+
"type": "private",
|
|
6
|
+
"scopes": {{SCOPES_JSON}},
|
|
7
|
+
"redirectUrls": {{REDIRECT_URLS_JSON}}
|
|
8
|
+
},
|
|
9
|
+
"distribution": "private",
|
|
10
|
+
"permittedUrls": {
|
|
11
|
+
"fetch": [],
|
|
12
|
+
"img": [],
|
|
13
|
+
"iframe": [],
|
|
14
|
+
"js": [],
|
|
15
|
+
"css": []
|
|
16
|
+
},
|
|
17
|
+
"support": {
|
|
18
|
+
"supportEmail": "",
|
|
19
|
+
"documentationUrl": "",
|
|
20
|
+
"supportUrl": "",
|
|
21
|
+
"supportPhone": ""
|
|
22
|
+
}
|
|
23
|
+
}
|
|
@@ -0,0 +1,13 @@
|
|
|
1
|
+
# Brevo OAuth Credentials — copy this file to .env.local and fill in your values
|
|
2
|
+
# Get CLIENT_ID and CLIENT_SECRET: brevo app credentials --reveal-secret
|
|
3
|
+
|
|
4
|
+
CLIENT_ID=your_client_id_here
|
|
5
|
+
CLIENT_SECRET=your_client_secret_here
|
|
6
|
+
REDIRECT_URI={{REDIRECT_URI}}
|
|
7
|
+
|
|
8
|
+
# OAuth server
|
|
9
|
+
OAUTH_BASE={{OAUTH_BASE}}
|
|
10
|
+
OAUTH_REALM={{OAUTH_REALM}}
|
|
11
|
+
|
|
12
|
+
# Optional
|
|
13
|
+
# PORT=3009
|
|
@@ -0,0 +1,203 @@
|
|
|
1
|
+
// Test handler — for local OAuth testing only. Not for production use.
|
|
2
|
+
const fs = require('fs');
|
|
3
|
+
const path = require('path');
|
|
4
|
+
const express = require('express');
|
|
5
|
+
const axios = require('axios');
|
|
6
|
+
const tokenStore = require('./token-store');
|
|
7
|
+
|
|
8
|
+
const router = express.Router();
|
|
9
|
+
|
|
10
|
+
// Sanitise untrusted values before embedding in HTML to prevent XSS
|
|
11
|
+
function escapeHtml(str) {
|
|
12
|
+
return String(str).replace(/&/g, '&').replace(/</g, '<').replace(/>/g, '>').replace(/"/g, '"');
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
// Shared styles for token display pages (callback + refresh)
|
|
16
|
+
const PAGE_STYLES =
|
|
17
|
+
'<style>' +
|
|
18
|
+
'*,*::before,*::after{margin:0;padding:0;box-sizing:border-box}' +
|
|
19
|
+
'body{font-family:"Inter",-apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,sans-serif;background:rgb(250,245,227);min-height:100vh;display:flex;flex-direction:column;align-items:center;justify-content:center;padding:40px 16px}' +
|
|
20
|
+
'.page-card{background:#fff;border:1px solid #e3e3e3;border-radius:16px;padding:40px;width:100%;max-width:560px;text-align:center}' +
|
|
21
|
+
'.page-card h2{color:#1b1b1b;font-size:22px;margin-bottom:8px}' +
|
|
22
|
+
'.page-card .subtitle{color:#696969;font-size:14px;margin-bottom:24px}' +
|
|
23
|
+
'a{color:#1b1b1b}' +
|
|
24
|
+
'.token-wrapper{position:relative;margin-top:6px}' +
|
|
25
|
+
'.token-value{display:block;padding:10px;background:#fff;border:1px solid #cfcfcf;border-radius:8px;font-family:monospace;font-size:13px;filter:blur(5px);user-select:none;cursor:pointer;transition:filter .2s}' +
|
|
26
|
+
'.token-value.revealed{filter:none;user-select:text;cursor:text}' +
|
|
27
|
+
'.reveal-btn{position:absolute;top:50%;left:50%;transform:translate(-50%,-50%);background:#1b1b1b;color:#fff;border:none;padding:8px 16px;border-radius:16px;font-size:13px;font-weight:600;cursor:pointer}' +
|
|
28
|
+
'.reveal-btn.hidden{display:none}' +
|
|
29
|
+
'.request-details{text-align:left;margin:16px 0;background:#f5f5f5;border:1px solid #e3e3e3;padding:16px 20px;border-radius:16px;font-size:13px}' +
|
|
30
|
+
'.request-details strong{color:#1b1b1b}' +
|
|
31
|
+
'.request-details dt{font-weight:600;margin-top:8px;color:#1b1b1b}' +
|
|
32
|
+
'.request-details dd{margin:2px 0 0 0;font-family:monospace;word-break:break-all;color:#696969}' +
|
|
33
|
+
'.token-section{text-align:left;margin:16px 0;background:#f5f5f5;border:1px solid #e3e3e3;padding:20px;border-radius:16px;word-break:break-all}' +
|
|
34
|
+
'.footer-text{color:#858585;font-size:12px;margin-top:16px}' +
|
|
35
|
+
'</style>';
|
|
36
|
+
|
|
37
|
+
const REVEAL_SCRIPT =
|
|
38
|
+
'<script>document.querySelectorAll(".reveal-btn").forEach(function(btn){btn.addEventListener("click",function(){btn.parentElement.querySelector(".token-value").classList.add("revealed");btn.classList.add("hidden")})});</script>';
|
|
39
|
+
|
|
40
|
+
const CLIENT_ID = process.env.CLIENT_ID;
|
|
41
|
+
const CLIENT_SECRET = process.env.CLIENT_SECRET;
|
|
42
|
+
const REDIRECT_URI = process.env.REDIRECT_URI || '{{REDIRECT_URI}}';
|
|
43
|
+
const OAUTH_BASE = process.env.OAUTH_BASE || '{{OAUTH_BASE}}';
|
|
44
|
+
const OAUTH_REALM = process.env.OAUTH_REALM || '{{OAUTH_REALM}}';
|
|
45
|
+
|
|
46
|
+
const realmPath = `${OAUTH_BASE}/realms/${OAUTH_REALM}/oauth`;
|
|
47
|
+
|
|
48
|
+
// Read scopes from app-config.json (default to 'all' if missing/corrupt)
|
|
49
|
+
let SCOPES = 'all';
|
|
50
|
+
try {
|
|
51
|
+
const appConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'app-config.json'), 'utf-8'));
|
|
52
|
+
if (appConfig.auth && appConfig.auth.scopes) SCOPES = appConfig.auth.scopes.join(' ');
|
|
53
|
+
} catch {
|
|
54
|
+
console.warn('Warning: Could not read app-config.json — defaulting scopes to "all".');
|
|
55
|
+
}
|
|
56
|
+
|
|
57
|
+
// Step 1: Redirect user to authorization page
|
|
58
|
+
router.get('/login', (req, res) => {
|
|
59
|
+
const state = require('crypto').randomBytes(16).toString('hex');
|
|
60
|
+
tokenStore.setState(state);
|
|
61
|
+
|
|
62
|
+
const authUrl = `${realmPath}/authorize?` +
|
|
63
|
+
`response_type=code` +
|
|
64
|
+
`&client_id=${encodeURIComponent(CLIENT_ID)}` +
|
|
65
|
+
`&redirect_uri=${encodeURIComponent(REDIRECT_URI)}` +
|
|
66
|
+
`&state=${state}` +
|
|
67
|
+
`&scope=${encodeURIComponent(SCOPES)}`;
|
|
68
|
+
|
|
69
|
+
res.redirect(authUrl);
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
// Step 2: Handle the OAuth callback
|
|
73
|
+
router.get('/callback', async (req, res) => {
|
|
74
|
+
const { code, state, error, error_description } = req.query;
|
|
75
|
+
|
|
76
|
+
if (error) {
|
|
77
|
+
console.error('OAuth error:', error, error_description || '');
|
|
78
|
+
return res.status(400).send(`Authorization denied: ${escapeHtml(error_description || error)}`);
|
|
79
|
+
}
|
|
80
|
+
|
|
81
|
+
if (!code || typeof code !== 'string') {
|
|
82
|
+
return res.status(400).send('Missing authorization code.');
|
|
83
|
+
}
|
|
84
|
+
|
|
85
|
+
if (state !== tokenStore.getState()) {
|
|
86
|
+
return res.status(400).send('Invalid state parameter.');
|
|
87
|
+
}
|
|
88
|
+
|
|
89
|
+
try {
|
|
90
|
+
// Step 3: Exchange authorization code for tokens
|
|
91
|
+
const tokenResponse = await axios.post(
|
|
92
|
+
`${realmPath}/token`,
|
|
93
|
+
new URLSearchParams({
|
|
94
|
+
grant_type: 'authorization_code',
|
|
95
|
+
client_id: CLIENT_ID,
|
|
96
|
+
client_secret: CLIENT_SECRET,
|
|
97
|
+
code,
|
|
98
|
+
redirect_uri: REDIRECT_URI,
|
|
99
|
+
}),
|
|
100
|
+
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
|
|
101
|
+
);
|
|
102
|
+
|
|
103
|
+
const { access_token, refresh_token, expires_in } = tokenResponse.data;
|
|
104
|
+
tokenStore.saveTokens({ access_token, refresh_token, expires_in });
|
|
105
|
+
|
|
106
|
+
// Step 4: Verify the access token via userinfo
|
|
107
|
+
const userInfoResponse = await axios.get(`${realmPath}/userinfo`, {
|
|
108
|
+
headers: { Authorization: `Bearer ${access_token}` },
|
|
109
|
+
});
|
|
110
|
+
|
|
111
|
+
// Step 5: Log token status (tokens are only visible in the browser)
|
|
112
|
+
console.log(`\nAccess token received (expires in ${expires_in}s)`);
|
|
113
|
+
console.log('Refresh token received');
|
|
114
|
+
console.log('View tokens in your browser (masked — click to reveal).');
|
|
115
|
+
|
|
116
|
+
// Set no-cache headers — tokens are sensitive (local development only)
|
|
117
|
+
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
118
|
+
res.set('Pragma', 'no-cache');
|
|
119
|
+
res.set('Expires', '0');
|
|
120
|
+
res.send(
|
|
121
|
+
`<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Brevo OAuth — Success</title>${PAGE_STYLES}</head>` +
|
|
122
|
+
'<body>' +
|
|
123
|
+
'<div class="page-card">' +
|
|
124
|
+
'<h2>Authorization successful!</h2>' +
|
|
125
|
+
'<p class="subtitle">Tokens are masked for security. Click to reveal.</p>' +
|
|
126
|
+
'<div class="request-details"><strong>Authorization Request</strong><dl>' +
|
|
127
|
+
`<dt>Client ID</dt><dd>${escapeHtml(CLIENT_ID)}</dd>` +
|
|
128
|
+
`<dt>Redirect URI</dt><dd>${escapeHtml(REDIRECT_URI)}</dd>` +
|
|
129
|
+
`<dt>Scopes</dt><dd>${escapeHtml(SCOPES)}</dd>` +
|
|
130
|
+
'</dl></div>' +
|
|
131
|
+
'<div class="token-section">' +
|
|
132
|
+
`<p><strong>Access Token</strong> (expires in ${expires_in}s):</p>` +
|
|
133
|
+
'<div class="token-wrapper">' +
|
|
134
|
+
`<code class="token-value">${escapeHtml(access_token)}</code>` +
|
|
135
|
+
'<button class="reveal-btn">Click to reveal</button></div>' +
|
|
136
|
+
'<div style="margin-top:16px"><p><strong>Refresh Token:</strong></p>' +
|
|
137
|
+
'<div class="token-wrapper">' +
|
|
138
|
+
`<code class="token-value">${escapeHtml(refresh_token)}</code>` +
|
|
139
|
+
'<button class="reveal-btn">Click to reveal</button></div></div>' +
|
|
140
|
+
'</div>' +
|
|
141
|
+
'<p style="margin-top:16px"><a href="/auth/refresh">Refresh token</a></p>' +
|
|
142
|
+
'</div>' +
|
|
143
|
+
REVEAL_SCRIPT +
|
|
144
|
+
'</body></html>',
|
|
145
|
+
);
|
|
146
|
+
} catch (error) {
|
|
147
|
+
console.error('OAuth error:', error.response?.data || error.message);
|
|
148
|
+
res.status(500).send('Authorization failed. Check terminal for details.');
|
|
149
|
+
}
|
|
150
|
+
});
|
|
151
|
+
|
|
152
|
+
// Token refresh — exchanges the stored refresh token for a new access token
|
|
153
|
+
router.get('/refresh', async (req, res) => {
|
|
154
|
+
const refreshToken = tokenStore.getRefreshToken();
|
|
155
|
+
if (!refreshToken) {
|
|
156
|
+
return res.status(400).send('No refresh token available. Complete /auth/login first.');
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
try {
|
|
160
|
+
const tokenResponse = await axios.post(
|
|
161
|
+
`${realmPath}/token`,
|
|
162
|
+
new URLSearchParams({
|
|
163
|
+
grant_type: 'refresh_token',
|
|
164
|
+
client_id: CLIENT_ID,
|
|
165
|
+
client_secret: CLIENT_SECRET,
|
|
166
|
+
refresh_token: refreshToken,
|
|
167
|
+
}),
|
|
168
|
+
{ headers: { 'Content-Type': 'application/x-www-form-urlencoded' } },
|
|
169
|
+
);
|
|
170
|
+
|
|
171
|
+
const { access_token, refresh_token, expires_in } = tokenResponse.data;
|
|
172
|
+
tokenStore.saveTokens({ access_token, refresh_token, expires_in });
|
|
173
|
+
|
|
174
|
+
console.log(`\nAccess token refreshed (expires in ${expires_in}s)`);
|
|
175
|
+
console.log('View token in your browser (masked — click to reveal).');
|
|
176
|
+
|
|
177
|
+
res.set('Cache-Control', 'no-store, no-cache, must-revalidate');
|
|
178
|
+
res.set('Pragma', 'no-cache');
|
|
179
|
+
res.set('Expires', '0');
|
|
180
|
+
res.send(
|
|
181
|
+
`<html><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>Brevo OAuth — Token Refreshed</title>${PAGE_STYLES}</head>` +
|
|
182
|
+
'<body>' +
|
|
183
|
+
'<div class="page-card">' +
|
|
184
|
+
'<h2>Token refreshed!</h2>' +
|
|
185
|
+
'<p class="subtitle">Click to reveal the token.</p>' +
|
|
186
|
+
'<div class="token-section">' +
|
|
187
|
+
`<p><strong>Access Token</strong> (expires in ${expires_in}s):</p>` +
|
|
188
|
+
'<div class="token-wrapper">' +
|
|
189
|
+
`<code class="token-value">${escapeHtml(access_token)}</code>` +
|
|
190
|
+
'<button class="reveal-btn">Click to reveal</button></div>' +
|
|
191
|
+
'</div>' +
|
|
192
|
+
'<p class="footer-text">You can close this window and return to the terminal.</p>' +
|
|
193
|
+
'</div>' +
|
|
194
|
+
REVEAL_SCRIPT +
|
|
195
|
+
'</body></html>',
|
|
196
|
+
);
|
|
197
|
+
} catch (error) {
|
|
198
|
+
console.error('Refresh error:', error.response?.data || error.message);
|
|
199
|
+
res.status(500).send('Token refresh failed. Check terminal for details.');
|
|
200
|
+
}
|
|
201
|
+
});
|
|
202
|
+
|
|
203
|
+
module.exports = router;
|
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
{
|
|
2
|
+
"name": "{{APP_SLUG}}",
|
|
3
|
+
"version": "1.0.0",
|
|
4
|
+
"private": true,
|
|
5
|
+
"description": "My custom brevo app {{APP_NAME}}",
|
|
6
|
+
"main": "server.js",
|
|
7
|
+
"scripts": {
|
|
8
|
+
"start": "node server.js"
|
|
9
|
+
},
|
|
10
|
+
"dependencies": {
|
|
11
|
+
"axios": "^1.7.0",
|
|
12
|
+
"dotenv": "^16.4.0",
|
|
13
|
+
"express": "^4.21.0"
|
|
14
|
+
}
|
|
15
|
+
}
|
|
@@ -0,0 +1,226 @@
|
|
|
1
|
+
// Test server — for local OAuth testing only. Not for production use.
|
|
2
|
+
// Run with: brevo app start oauth
|
|
3
|
+
|
|
4
|
+
const path = require('path');
|
|
5
|
+
const fs = require('fs');
|
|
6
|
+
|
|
7
|
+
const envPath = path.join(__dirname, '.env.local');
|
|
8
|
+
if (!fs.existsSync(envPath)) {
|
|
9
|
+
console.error('\nError: .env.local not found at', envPath);
|
|
10
|
+
console.error('Copy .env.example to .env.local and fill in your credentials:\n');
|
|
11
|
+
console.error(` Source: ${path.join(__dirname, '.env.example')}\n`);
|
|
12
|
+
process.exit(1);
|
|
13
|
+
}
|
|
14
|
+
|
|
15
|
+
require('dotenv').config({ path: envPath });
|
|
16
|
+
|
|
17
|
+
if (!process.env.CLIENT_ID || !process.env.CLIENT_SECRET) {
|
|
18
|
+
console.error('\nError: CLIENT_ID and CLIENT_SECRET must be set in .env.local');
|
|
19
|
+
console.error('Get your credentials with: brevo app credentials --reveal-secret\n');
|
|
20
|
+
process.exit(1);
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
if (!process.env.OAUTH_BASE || process.env.OAUTH_BASE.startsWith('{{')) {
|
|
24
|
+
console.error('\nError: OAUTH_BASE is not set or contains a placeholder.');
|
|
25
|
+
console.error('Check your .env.local file and set a valid OAuth base URL.\n');
|
|
26
|
+
process.exit(1);
|
|
27
|
+
}
|
|
28
|
+
if (!process.env.OAUTH_REALM || process.env.OAUTH_REALM.startsWith('{{')) {
|
|
29
|
+
console.error('\nError: OAUTH_REALM is not set or contains a placeholder.');
|
|
30
|
+
console.error('Check your .env.local file and set a valid OAuth realm.\n');
|
|
31
|
+
process.exit(1);
|
|
32
|
+
}
|
|
33
|
+
|
|
34
|
+
const express = require('express');
|
|
35
|
+
const authRouter = require('./handler');
|
|
36
|
+
|
|
37
|
+
const app = express();
|
|
38
|
+
const PORT = process.env.PORT || 3009;
|
|
39
|
+
|
|
40
|
+
const REDIRECT_URI = process.env.REDIRECT_URI || '{{REDIRECT_URI}}';
|
|
41
|
+
let SCOPES = 'all';
|
|
42
|
+
try {
|
|
43
|
+
const appConfig = JSON.parse(fs.readFileSync(path.join(__dirname, '..', '..', 'app-config.json'), 'utf-8'));
|
|
44
|
+
if (appConfig.auth && appConfig.auth.scopes) SCOPES = appConfig.auth.scopes.join(' ');
|
|
45
|
+
} catch {
|
|
46
|
+
console.warn('Warning: Could not read app-config.json — defaulting scopes to "all".');
|
|
47
|
+
}
|
|
48
|
+
const AUTH_URL = `${process.env.OAUTH_BASE}/realms/${process.env.OAUTH_REALM}/oauth/authorize`
|
|
49
|
+
+ `?response_type=code`
|
|
50
|
+
+ `&client_id=${encodeURIComponent(process.env.CLIENT_ID)}`
|
|
51
|
+
+ `&redirect_uri=${encodeURIComponent(REDIRECT_URI)}`
|
|
52
|
+
+ `&state=<random>`
|
|
53
|
+
+ `&scope=${encodeURIComponent(SCOPES)}`;
|
|
54
|
+
|
|
55
|
+
app.use('/auth', authRouter);
|
|
56
|
+
|
|
57
|
+
app.use(express.urlencoded({ extended: true }));
|
|
58
|
+
|
|
59
|
+
app.get('/', (req, res) => {
|
|
60
|
+
res.send(`<!DOCTYPE html>
|
|
61
|
+
<html lang="en">
|
|
62
|
+
<head>
|
|
63
|
+
<meta charset="utf-8" />
|
|
64
|
+
<meta name="viewport" content="width=device-width, initial-scale=1" />
|
|
65
|
+
<title>{{APP_NAME}} — OAuth Test</title>
|
|
66
|
+
<style>
|
|
67
|
+
*, *::before, *::after { margin: 0; padding: 0; box-sizing: border-box; }
|
|
68
|
+
body {
|
|
69
|
+
font-family: "Inter", -apple-system, BlinkMacSystemFont, "Segoe UI", Roboto, sans-serif;
|
|
70
|
+
background: rgb(250, 245, 227);
|
|
71
|
+
min-height: 100vh;
|
|
72
|
+
display: flex;
|
|
73
|
+
flex-direction: column;
|
|
74
|
+
align-items: center;
|
|
75
|
+
justify-content: center;
|
|
76
|
+
padding: 40px 16px;
|
|
77
|
+
}
|
|
78
|
+
.brevo-logo {
|
|
79
|
+
margin-bottom: 32px;
|
|
80
|
+
display: flex;
|
|
81
|
+
align-items: center;
|
|
82
|
+
justify-content: center;
|
|
83
|
+
}
|
|
84
|
+
.brevo-logo svg { height: 48px; }
|
|
85
|
+
.card {
|
|
86
|
+
background: #fff;
|
|
87
|
+
border: 1px solid #e3e3e3;
|
|
88
|
+
border-radius: 16px;
|
|
89
|
+
padding: 40px;
|
|
90
|
+
width: 100%;
|
|
91
|
+
max-width: 500px;
|
|
92
|
+
text-align: center;
|
|
93
|
+
}
|
|
94
|
+
.card h2 {
|
|
95
|
+
color: #1b1b1b;
|
|
96
|
+
font-size: 22px;
|
|
97
|
+
margin-bottom: 6px;
|
|
98
|
+
}
|
|
99
|
+
.card p.sub {
|
|
100
|
+
color: #696969;
|
|
101
|
+
font-size: 14px;
|
|
102
|
+
margin-bottom: 24px;
|
|
103
|
+
}
|
|
104
|
+
.btn {
|
|
105
|
+
display: block;
|
|
106
|
+
width: 100%;
|
|
107
|
+
padding: 8px 16px;
|
|
108
|
+
border: none;
|
|
109
|
+
border-radius: 16px;
|
|
110
|
+
font-size: 16px;
|
|
111
|
+
font-weight: 600;
|
|
112
|
+
cursor: pointer;
|
|
113
|
+
text-decoration: none;
|
|
114
|
+
text-align: center;
|
|
115
|
+
margin-bottom: 12px;
|
|
116
|
+
height: 40px;
|
|
117
|
+
line-height: 24px;
|
|
118
|
+
}
|
|
119
|
+
.btn:hover { opacity: 0.9; }
|
|
120
|
+
.btn-primary { background: #1b1b1b; color: #fff; }
|
|
121
|
+
.btn-secondary {
|
|
122
|
+
background: #fff;
|
|
123
|
+
color: #1b1b1b;
|
|
124
|
+
border: 1px solid #cfcfcf;
|
|
125
|
+
}
|
|
126
|
+
.hint {
|
|
127
|
+
background: #f5f5f5;
|
|
128
|
+
border: 1px solid #e3e3e3;
|
|
129
|
+
border-radius: 16px;
|
|
130
|
+
padding: 10px 14px;
|
|
131
|
+
font-size: 13px;
|
|
132
|
+
color: #696969;
|
|
133
|
+
margin-bottom: 16px;
|
|
134
|
+
text-align: left;
|
|
135
|
+
}
|
|
136
|
+
.hint code {
|
|
137
|
+
background: rgba(0,0,0,0.06);
|
|
138
|
+
padding: 1px 5px;
|
|
139
|
+
border-radius: 3px;
|
|
140
|
+
font-size: 12px;
|
|
141
|
+
}
|
|
142
|
+
.auth-url {
|
|
143
|
+
background: #f5f5f5;
|
|
144
|
+
border: 1px solid #e3e3e3;
|
|
145
|
+
border-radius: 16px;
|
|
146
|
+
padding: 10px 14px;
|
|
147
|
+
font-size: 12px;
|
|
148
|
+
color: #696969;
|
|
149
|
+
text-align: left;
|
|
150
|
+
margin-bottom: 16px;
|
|
151
|
+
word-break: break-all;
|
|
152
|
+
}
|
|
153
|
+
.auth-url strong {
|
|
154
|
+
display: block;
|
|
155
|
+
font-size: 13px;
|
|
156
|
+
margin-bottom: 4px;
|
|
157
|
+
color: #1b1b1b;
|
|
158
|
+
}
|
|
159
|
+
.auth-url code {
|
|
160
|
+
font-family: monospace;
|
|
161
|
+
font-size: 11px;
|
|
162
|
+
color: #858585;
|
|
163
|
+
}
|
|
164
|
+
.footer {
|
|
165
|
+
margin-top: 16px;
|
|
166
|
+
color: #858585;
|
|
167
|
+
font-size: 12px;
|
|
168
|
+
}
|
|
169
|
+
.btn-primary, .btn-secondary { display: flex; align-items: center; justify-content: center; gap: 8px; }
|
|
170
|
+
.spinner {
|
|
171
|
+
display: none;
|
|
172
|
+
width: 16px;
|
|
173
|
+
height: 16px;
|
|
174
|
+
border: 2px solid rgba(0,0,0,0.15);
|
|
175
|
+
border-top-color: #1b1b1b;
|
|
176
|
+
border-radius: 50%;
|
|
177
|
+
animation: spin 0.6s linear infinite;
|
|
178
|
+
}
|
|
179
|
+
.btn-primary .spinner { border-color: rgba(255,255,255,0.3); border-top-color: #fff; }
|
|
180
|
+
.btn.loading .spinner { display: inline-block; }
|
|
181
|
+
.btn.loading { pointer-events: none; opacity: 0.7; }
|
|
182
|
+
@keyframes spin { to { transform: rotate(360deg); } }
|
|
183
|
+
</style>
|
|
184
|
+
</head>
|
|
185
|
+
<body>
|
|
186
|
+
<div class="brevo-logo">
|
|
187
|
+
<svg xmlns="http://www.w3.org/2000/svg" viewBox="0 0 90 28" fill="none">
|
|
188
|
+
<path fill="#0B996E" d="M73.825 17.178c0-4.037 2.55-6.876 6.175-6.876 3.626 0 6.216 2.838 6.216 6.876 0 4.04-2.59 6.716-6.216 6.716-3.626 0-6.175-2.8-6.175-6.716Zm-3.785 0c0 5.957 4.144 10.155 9.96 10.155 5.816 0 10-4.198 10-10.155 0-5.957-4.144-10.314-10-10.314-5.857 0-9.96 4.279-9.96 10.314ZM50.717 7.104l7.81 19.989h3.665l7.81-19.99h-3.945l-5.658 15.433h-.08L54.662 7.104h-3.945Zm-15.18 9.354c.239-3.679 2.67-6.156 5.977-6.156 2.867 0 5.02 1.84 5.338 4.598h-6.614c-2.35 0-3.626.28-4.58 1.56h-.121v-.002Zm-3.784.6c0 5.957 4.183 10.274 9.96 10.274 3.904 0 7.33-1.999 8.803-5.158l-3.186-1.6c-1.115 2.079-3.267 3.318-5.618 3.318-2.83 0-5.379-2.159-5.379-4.238 0-1.078.718-1.56 1.753-1.56h12.63v-1.078c0-5.997-3.825-10.155-9.323-10.155-5.497 0-9.641 4.278-9.641 10.195M20.916 27.09h3.586V14.82c0-2.64 1.632-4.519 3.905-4.519.956 0 1.951.32 2.43.759.359-.96.917-1.918 1.753-2.878-.957-.799-2.59-1.32-4.184-1.32-4.382 0-7.49 3.278-7.49 7.956v12.274-.002ZM3.586 13.86V4.104h5.896c1.992 0 3.307 1.16 3.307 2.918 0 1.999-1.713 3.518-5.218 4.677-2.39.76-3.466 1.4-3.865 2.16l-.12.001Zm0 9.795v-4.077c0-1.8 1.514-3.558 3.626-4.238 1.873-.64 3.425-1.28 4.74-1.959 1.754 1.04 2.829 2.838 2.829 4.718 0 3.198-3.028 5.556-7.132 5.556H3.586ZM0 27.093h7.968c6.057 0 10.597-3.798 10.597-8.835 0-2.76-1.394-5.237-3.864-6.837 1.275-1.28 1.873-2.759 1.873-4.558 0-3.717-2.67-6.196-6.693-6.196H0v26.426Z"/>
|
|
189
|
+
</svg>
|
|
190
|
+
</div>
|
|
191
|
+
|
|
192
|
+
<div class="card">
|
|
193
|
+
<h2>{{APP_NAME}}</h2>
|
|
194
|
+
<p class="sub">Local OAuth testing server</p>
|
|
195
|
+
<p class="hint">Before starting, make sure the redirect URL in <code>app-config.json</code> matches this server's port (<code>${PORT}</code>).</p>
|
|
196
|
+
<div class="auth-url">
|
|
197
|
+
<strong>Authorization URL</strong>
|
|
198
|
+
<code>${AUTH_URL}</code>
|
|
199
|
+
</div>
|
|
200
|
+
<a href="/auth/login" id="start-btn" class="btn btn-primary"><span>Start OAuth Flow</span><span class="spinner"></span></a>
|
|
201
|
+
<a href="/auth/refresh" id="refresh-btn" class="btn btn-secondary"><span>Refresh Token</span><span class="spinner"></span></a>
|
|
202
|
+
<p class="footer">Running on localhost · Not for production use</p>
|
|
203
|
+
</div>
|
|
204
|
+
<script>
|
|
205
|
+
document.querySelectorAll(".btn").forEach(function (btn) {
|
|
206
|
+
btn.addEventListener("click", function () { btn.classList.add("loading"); });
|
|
207
|
+
});
|
|
208
|
+
window.addEventListener("pageshow", function () {
|
|
209
|
+
document.querySelectorAll(".btn.loading").forEach(function (btn) { btn.classList.remove("loading"); });
|
|
210
|
+
});
|
|
211
|
+
</script>
|
|
212
|
+
</body>
|
|
213
|
+
</html>`);
|
|
214
|
+
});
|
|
215
|
+
|
|
216
|
+
app.listen(PORT, '127.0.0.1', () => {
|
|
217
|
+
const url = `http://localhost:${PORT}`;
|
|
218
|
+
console.log(`\nTest server running at ${url}`);
|
|
219
|
+
console.log(`Opening browser...\n`);
|
|
220
|
+
|
|
221
|
+
const { execFile } = require('child_process');
|
|
222
|
+
const open = process.platform === 'darwin' ? ['open', [url]]
|
|
223
|
+
: process.platform === 'win32' ? ['cmd', ['/c', 'start', url]]
|
|
224
|
+
: ['xdg-open', [url]];
|
|
225
|
+
execFile(open[0], open[1], () => {});
|
|
226
|
+
});
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
// In-memory token store — replace with your database in production
|
|
2
|
+
|
|
3
|
+
let tokens = {};
|
|
4
|
+
let csrfState = null;
|
|
5
|
+
|
|
6
|
+
module.exports = {
|
|
7
|
+
saveTokens({ access_token, refresh_token, expires_in }) {
|
|
8
|
+
tokens = {
|
|
9
|
+
accessToken: access_token,
|
|
10
|
+
refreshToken: refresh_token,
|
|
11
|
+
expiresAt: Date.now() + expires_in * 1000,
|
|
12
|
+
};
|
|
13
|
+
},
|
|
14
|
+
|
|
15
|
+
getAccessToken() {
|
|
16
|
+
return tokens.accessToken;
|
|
17
|
+
},
|
|
18
|
+
|
|
19
|
+
getRefreshToken() {
|
|
20
|
+
return tokens.refreshToken;
|
|
21
|
+
},
|
|
22
|
+
|
|
23
|
+
isExpired() {
|
|
24
|
+
return !tokens.expiresAt || Date.now() >= tokens.expiresAt;
|
|
25
|
+
},
|
|
26
|
+
|
|
27
|
+
setState(state) {
|
|
28
|
+
csrfState = state;
|
|
29
|
+
},
|
|
30
|
+
|
|
31
|
+
getState() {
|
|
32
|
+
return csrfState;
|
|
33
|
+
},
|
|
34
|
+
|
|
35
|
+
clear() {
|
|
36
|
+
tokens = {};
|
|
37
|
+
csrfState = null;
|
|
38
|
+
},
|
|
39
|
+
};
|
|
@@ -0,0 +1,36 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Replace all occurrences of the given keys in a template string.
|
|
3
|
+
* Keys should include delimiters (e.g. '{{APP_NAME}}').
|
|
4
|
+
*/
|
|
5
|
+
export declare function applyVars(template: string, vars: Record<string, string>): string;
|
|
6
|
+
/**
|
|
7
|
+
* Scaffold file manifest — maps output file paths to their .tmpl source files.
|
|
8
|
+
* Add new templates here; the scaffold command picks them up automatically.
|
|
9
|
+
*/
|
|
10
|
+
export interface TemplateFile {
|
|
11
|
+
/** Relative output path inside the scaffolded project */
|
|
12
|
+
outputPath: string;
|
|
13
|
+
/** Relative path inside templates/files/ */
|
|
14
|
+
templatePath: string;
|
|
15
|
+
}
|
|
16
|
+
export declare const TEMPLATE_MANIFEST: TemplateFile[];
|
|
17
|
+
/**
|
|
18
|
+
* Load all templates from disk, apply variable substitution, and return
|
|
19
|
+
* an array of { name, content } ready to write.
|
|
20
|
+
*/
|
|
21
|
+
export declare function loadAllTemplates(vars: Record<string, string>): Array<{
|
|
22
|
+
name: string;
|
|
23
|
+
content: string;
|
|
24
|
+
}>;
|
|
25
|
+
export declare const oauthServerJsTemplate: string;
|
|
26
|
+
export declare const oauthHandlerTemplate: string;
|
|
27
|
+
export declare const tokenStoreJsTemplate: string;
|
|
28
|
+
export declare const envExampleTemplate: string;
|
|
29
|
+
export declare const envLocalTemplate: string;
|
|
30
|
+
export declare const gitignoreTemplate: string;
|
|
31
|
+
export declare const packageJsonTemplate: string;
|
|
32
|
+
export declare const appConfigTemplate: string;
|
|
33
|
+
export declare const agentsMdTemplate: string;
|
|
34
|
+
export declare const claudeMdTemplate: string;
|
|
35
|
+
export declare const readmeTemplate: string;
|
|
36
|
+
//# sourceMappingURL=index.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/templates/index.ts"],"names":[],"mappings":"AAaA;;;GAGG;AACH,wBAAgB,SAAS,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAAG,MAAM,CAMhF;AAED;;;GAGG;AACH,MAAM,WAAW,YAAY;IAC3B,yDAAyD;IACzD,UAAU,EAAE,MAAM,CAAC;IACnB,4CAA4C;IAC5C,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,eAAO,MAAM,iBAAiB,EAAE,YAAY,EAqB3C,CAAC;AAEF;;;GAGG;AACH,wBAAgB,gBAAgB,CAC9B,IAAI,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,CAAC,GAC3B,KAAK,CAAC;IAAE,IAAI,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,CAAC,CAK1C;AAKD,eAAO,MAAM,qBAAqB,QAA2C,CAAC;AAC9E,eAAO,MAAM,oBAAoB,QAA4C,CAAC;AAC9E,eAAO,MAAM,oBAAoB,QAAgD,CAAC;AAClF,eAAO,MAAM,kBAAkB,QAA8C,CAAC;AAC9E,eAAO,MAAM,gBAAgB,QAA4C,CAAC;AAC1E,eAAO,MAAM,iBAAiB,QAAiC,CAAC;AAChE,eAAO,MAAM,mBAAmB,QAA8C,CAAC;AAC/E,eAAO,MAAM,iBAAiB,QAAuC,CAAC;AACtE,eAAO,MAAM,gBAAgB,QAAiC,CAAC;AAC/D,eAAO,MAAM,gBAAgB,QAAiC,CAAC;AAC/D,eAAO,MAAM,cAAc,QAAiC,CAAC"}
|