kopia lustrzana https://github.com/transitive-bullshit/chatgpt-api
feat: init
commit
8ef2c77a10
|
@ -0,0 +1,14 @@
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
# This is an example .env file.
|
||||||
|
#
|
||||||
|
# All of these environment vars must be defined either in your environment or in
|
||||||
|
# a local .env file in order to run this app.
|
||||||
|
#
|
||||||
|
# @see https://nextjs.org/docs/basic-features/environment-variables for details.
|
||||||
|
# ------------------------------------------------------------------------------
|
||||||
|
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
# Replicate API
|
||||||
|
# -----------------------------------------------------------------------------
|
||||||
|
|
||||||
|
REPLICATE_API_TOKEN=
|
|
@ -0,0 +1 @@
|
||||||
|
github: [transitive-bullshit]
|
|
@ -0,0 +1,51 @@
|
||||||
|
name: CI
|
||||||
|
|
||||||
|
on: [push, pull_request]
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
test:
|
||||||
|
name: Test Node.js ${{ matrix.node-version }}
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
strategy:
|
||||||
|
fail-fast: false
|
||||||
|
matrix:
|
||||||
|
node-version:
|
||||||
|
- 18
|
||||||
|
- 16
|
||||||
|
- 14
|
||||||
|
|
||||||
|
steps:
|
||||||
|
- name: Checkout
|
||||||
|
uses: actions/checkout@v3
|
||||||
|
|
||||||
|
- name: Install Node.js
|
||||||
|
uses: actions/setup-node@v3
|
||||||
|
with:
|
||||||
|
node-version: ${{ matrix.node-version }}
|
||||||
|
|
||||||
|
- name: Install pnpm
|
||||||
|
uses: pnpm/action-setup@v2
|
||||||
|
id: pnpm-install
|
||||||
|
with:
|
||||||
|
version: 7
|
||||||
|
run_install: false
|
||||||
|
|
||||||
|
- name: Get pnpm store directory
|
||||||
|
id: pnpm-cache
|
||||||
|
shell: bash
|
||||||
|
run: |
|
||||||
|
echo "STORE_PATH=$(pnpm store path)" >> $GITHUB_OUTPUT
|
||||||
|
|
||||||
|
- uses: actions/cache@v3
|
||||||
|
name: Setup pnpm cache
|
||||||
|
with:
|
||||||
|
path: ${{ steps.pnpm-cache.outputs.STORE_PATH }}
|
||||||
|
key: ${{ runner.os }}-pnpm-store-${{ hashFiles('**/pnpm-lock.yaml') }}
|
||||||
|
restore-keys: |
|
||||||
|
${{ runner.os }}-pnpm-store-
|
||||||
|
|
||||||
|
- name: Install dependencies
|
||||||
|
run: pnpm install --frozen-lockfile
|
||||||
|
|
||||||
|
- name: Run test
|
||||||
|
run: pnpm run test
|
|
@ -0,0 +1,49 @@
|
||||||
|
# See https://help.github.com/articles/ignoring-files/ for more about ignoring files.
|
||||||
|
|
||||||
|
*.swp
|
||||||
|
|
||||||
|
# dependencies
|
||||||
|
/node_modules
|
||||||
|
/.pnp
|
||||||
|
.pnp.js
|
||||||
|
|
||||||
|
# testing
|
||||||
|
/coverage
|
||||||
|
|
||||||
|
# next.js
|
||||||
|
/.next/
|
||||||
|
/out/
|
||||||
|
|
||||||
|
# production
|
||||||
|
/build
|
||||||
|
|
||||||
|
# misc
|
||||||
|
.DS_Store
|
||||||
|
*.pem
|
||||||
|
|
||||||
|
# debug
|
||||||
|
npm-debug.log*
|
||||||
|
yarn-debug.log*
|
||||||
|
yarn-error.log*
|
||||||
|
.pnpm-debug.log*
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env*.local
|
||||||
|
|
||||||
|
# vercel
|
||||||
|
.vercel
|
||||||
|
|
||||||
|
# typescript
|
||||||
|
*.tsbuildinfo
|
||||||
|
next-env.d.ts
|
||||||
|
|
||||||
|
# local env files
|
||||||
|
.env
|
||||||
|
.env.local
|
||||||
|
.env.build
|
||||||
|
.env.development.local
|
||||||
|
.env.test.local
|
||||||
|
.env.production.local
|
||||||
|
|
||||||
|
# data dumps
|
||||||
|
out/
|
|
@ -0,0 +1,4 @@
|
||||||
|
#!/usr/bin/env sh
|
||||||
|
. "$(dirname -- "$0")/_/husky.sh"
|
||||||
|
|
||||||
|
npm run pre-commit
|
|
@ -0,0 +1 @@
|
||||||
|
enable-pre-post-scripts=true
|
|
@ -0,0 +1,6 @@
|
||||||
|
.snapshots/
|
||||||
|
build/
|
||||||
|
dist/
|
||||||
|
node_modules/
|
||||||
|
.next/
|
||||||
|
.vercel/
|
|
@ -0,0 +1,16 @@
|
||||||
|
module.exports = {
|
||||||
|
plugins: [require('@trivago/prettier-plugin-sort-imports')],
|
||||||
|
singleQuote: true,
|
||||||
|
jsxSingleQuote: true,
|
||||||
|
semi: false,
|
||||||
|
useTabs: false,
|
||||||
|
tabWidth: 2,
|
||||||
|
bracketSpacing: true,
|
||||||
|
bracketSameLine: false,
|
||||||
|
arrowParens: 'always',
|
||||||
|
trailingComma: 'none',
|
||||||
|
importOrder: ['^node:.*', '<THIRD_PARTY_MODULES>', '^[./]'],
|
||||||
|
importOrderSeparation: true,
|
||||||
|
importOrderSortSpecifiers: true,
|
||||||
|
importOrderGroupNamespaceSpecifiers: true
|
||||||
|
}
|
|
@ -0,0 +1,21 @@
|
||||||
|
MIT License
|
||||||
|
|
||||||
|
Copyright (c) 2022 Travis Fischer
|
||||||
|
|
||||||
|
Permission is hereby granted, free of charge, to any person obtaining a copy
|
||||||
|
of this software and associated documentation files (the "Software"), to deal
|
||||||
|
in the Software without restriction, including without limitation the rights
|
||||||
|
to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
|
||||||
|
copies of the Software, and to permit persons to whom the Software is
|
||||||
|
furnished to do so, subject to the following conditions:
|
||||||
|
|
||||||
|
The above copyright notice and this permission notice shall be included in all
|
||||||
|
copies or substantial portions of the Software.
|
||||||
|
|
||||||
|
THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
|
||||||
|
IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
|
||||||
|
FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
|
||||||
|
AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
|
||||||
|
LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
|
||||||
|
OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
|
||||||
|
SOFTWARE.
|
|
@ -0,0 +1,78 @@
|
||||||
|
{
|
||||||
|
"name": "chatgpt",
|
||||||
|
"version": "0.0.1",
|
||||||
|
"description": "Node.js wrapper around ChatGPT. Uses headless Chrome as a temporary solution until the official API is released.",
|
||||||
|
"author": "Travis Fischer <travis@transitivebullsh.it>",
|
||||||
|
"repository": "transitive-bullshit/chatgpt-api",
|
||||||
|
"license": "MIT",
|
||||||
|
"type": "module",
|
||||||
|
"source": "./src/index.ts",
|
||||||
|
"exports": {
|
||||||
|
"import": "./build/index.js",
|
||||||
|
"default": "./build/index.js",
|
||||||
|
"types": "./build/index.d.ts"
|
||||||
|
},
|
||||||
|
"files": [
|
||||||
|
"build"
|
||||||
|
],
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"scripts": {
|
||||||
|
"build": "tsup",
|
||||||
|
"dev": "tsup --watch",
|
||||||
|
"clean": "del build",
|
||||||
|
"prebuild": "run-s clean",
|
||||||
|
"predev": "run-s clean",
|
||||||
|
"pretest": "run-s build",
|
||||||
|
"docs": "typedoc",
|
||||||
|
"prepare": "husky install",
|
||||||
|
"pre-commit": "lint-staged",
|
||||||
|
"test": "run-p test:*",
|
||||||
|
"test:prettier": "prettier '**/*.{js,jsx,ts,tsx}' --check"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"html-to-md": "npm:@fisch0920/html-to-md@^0.8.1",
|
||||||
|
"joplin-turndown": "^4.0.30",
|
||||||
|
"node-html-markdown": "^1.2.2",
|
||||||
|
"playwright": "^1.28.1",
|
||||||
|
"turndown": "^7.1.1"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@trivago/prettier-plugin-sort-imports": "^4.0.0",
|
||||||
|
"@types/node": "^18.11.9",
|
||||||
|
"del-cli": "^5.0.0",
|
||||||
|
"delay": "^5.0.0",
|
||||||
|
"husky": "^8.0.2",
|
||||||
|
"lint-staged": "^13.0.3",
|
||||||
|
"npm-run-all": "^4.1.5",
|
||||||
|
"openapi-types": "^12.0.2",
|
||||||
|
"ora": "^6.1.2",
|
||||||
|
"prettier": "^2.8.0",
|
||||||
|
"tsup": "^6.5.0",
|
||||||
|
"tsx": "^3.12.1",
|
||||||
|
"typedoc": "^0.23.21",
|
||||||
|
"typedoc-plugin-markdown": "^3.13.6",
|
||||||
|
"typescript": "^4.9.3"
|
||||||
|
},
|
||||||
|
"lint-staged": {
|
||||||
|
"*.{ts,tsx}": [
|
||||||
|
"prettier --write"
|
||||||
|
]
|
||||||
|
},
|
||||||
|
"keywords": [
|
||||||
|
"openai",
|
||||||
|
"chatgpt",
|
||||||
|
"gpt",
|
||||||
|
"gpt3",
|
||||||
|
"gpt4",
|
||||||
|
"chatbot",
|
||||||
|
"chat",
|
||||||
|
"machine learning",
|
||||||
|
"conversation",
|
||||||
|
"conversational ai",
|
||||||
|
"ai",
|
||||||
|
"ml",
|
||||||
|
"bot"
|
||||||
|
]
|
||||||
|
}
|
Plik diff jest za duży
Load Diff
|
@ -0,0 +1,34 @@
|
||||||
|
# ChatGPT API <!-- omit in toc -->
|
||||||
|
|
||||||
|
> Node.js wrapper around ChatGPT. Uses headless Chrome as a temporary solution until the official API is released.
|
||||||
|
|
||||||
|
[](https://www.npmjs.com/package/chatgpt) [](https://github.com/transitive-bullshit/chatgpt-api/actions/workflows/test.yml) [](https://github.com/transitive-bullshit/chatgpt-api/blob/main/license) [](https://prettier.io)
|
||||||
|
|
||||||
|
- [Intro](#intro)
|
||||||
|
- [Docs](#docs)
|
||||||
|
- [Todo](#todo)
|
||||||
|
- [Related](#related)
|
||||||
|
- [License](#license)
|
||||||
|
|
||||||
|
## Intro
|
||||||
|
|
||||||
|
TODO
|
||||||
|
|
||||||
|
## Docs
|
||||||
|
|
||||||
|
See the [auto-generated docs](./docs/modules.md).
|
||||||
|
|
||||||
|
## Todo
|
||||||
|
|
||||||
|
- [ ] Add message and conversation IDs
|
||||||
|
- [ ] Add support for streaming responses
|
||||||
|
|
||||||
|
## Related
|
||||||
|
|
||||||
|
- Inspired by the [Go module](https://github.com/danielgross/whatsapp-gpt) by [Daniel Gross](https://github.com/danielgross)
|
||||||
|
|
||||||
|
## License
|
||||||
|
|
||||||
|
MIT © [Travis Fischer](https://transitivebullsh.it)
|
||||||
|
|
||||||
|
Support my open source work by <a href="https://twitter.com/transitive_bs">following me on twitter <img src="https://storage.googleapis.com/saasify-assets/twitter-logo.svg" alt="twitter" height="24px" align="center"></a>
|
|
@ -0,0 +1,170 @@
|
||||||
|
import delay from 'delay'
|
||||||
|
import html2md from 'html-to-md'
|
||||||
|
import { type ChromiumBrowserContext, type Page, chromium } from 'playwright'
|
||||||
|
|
||||||
|
export class ChatGPTAPI {
|
||||||
|
protected _userDataDir: string
|
||||||
|
protected _headless: boolean
|
||||||
|
protected _markdown: boolean
|
||||||
|
protected _chatUrl: string
|
||||||
|
|
||||||
|
protected _browser: ChromiumBrowserContext
|
||||||
|
protected _page: Page
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param opts.userDataDir — Path to a directory for storing persistent chromium session data
|
||||||
|
* @param opts.chatUrl — OpenAI chat URL
|
||||||
|
* @param opts.headless - Whether or not to use headless mode
|
||||||
|
* @param opts.markdown — Whether or not to parse chat messages as markdown
|
||||||
|
*/
|
||||||
|
constructor(
|
||||||
|
opts: {
|
||||||
|
/** @defaultValue `'/tmp/chatgpt'` **/
|
||||||
|
userDataDir?: string
|
||||||
|
|
||||||
|
/** @defaultValue `'https://chat.openai.com/'` **/
|
||||||
|
chatUrl?: string
|
||||||
|
|
||||||
|
/** @defaultValue `false` **/
|
||||||
|
headless?: boolean
|
||||||
|
|
||||||
|
/** @defaultValue `true` **/
|
||||||
|
markdown?: boolean
|
||||||
|
} = {}
|
||||||
|
) {
|
||||||
|
const {
|
||||||
|
userDataDir = '/tmp/chatgpt',
|
||||||
|
chatUrl = 'https://chat.openai.com/',
|
||||||
|
headless = false,
|
||||||
|
markdown = true
|
||||||
|
} = opts
|
||||||
|
|
||||||
|
this._userDataDir = userDataDir
|
||||||
|
this._headless = !!headless
|
||||||
|
this._chatUrl = chatUrl
|
||||||
|
this._markdown = !!markdown
|
||||||
|
}
|
||||||
|
|
||||||
|
async init() {
|
||||||
|
this._browser = await chromium.launchPersistentContext(this._userDataDir, {
|
||||||
|
headless: this._headless
|
||||||
|
})
|
||||||
|
|
||||||
|
this._page = await this._browser.newPage()
|
||||||
|
await this._page.goto(this._chatUrl)
|
||||||
|
|
||||||
|
// dismiss welcome modal
|
||||||
|
do {
|
||||||
|
const modalSelector = '[data-headlessui-state="open"]'
|
||||||
|
if (!(await this._page.isVisible(modalSelector, { timeout: 500 }))) {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
|
||||||
|
const modal = await this._page.locator(modalSelector)
|
||||||
|
if (modal) {
|
||||||
|
await modal.locator('button').last().click()
|
||||||
|
} else {
|
||||||
|
break
|
||||||
|
}
|
||||||
|
} while (true)
|
||||||
|
|
||||||
|
return this._page
|
||||||
|
}
|
||||||
|
|
||||||
|
async getIsSignedIn() {
|
||||||
|
const inputBox = await this._getInputBox()
|
||||||
|
return !!inputBox
|
||||||
|
}
|
||||||
|
|
||||||
|
async getLastMessage(): Promise<string | null> {
|
||||||
|
const messages = await this.getMessages()
|
||||||
|
|
||||||
|
if (messages) {
|
||||||
|
return messages[messages.length - 1]
|
||||||
|
} else {
|
||||||
|
return null
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async getPrompts(): Promise<string[]> {
|
||||||
|
// Get all prompts
|
||||||
|
const messages = await this._page.$$(
|
||||||
|
'[class*="ConversationItem__Message"]:has([class*="ConversationItem__ActionButtons"]):has([class*="ConversationItem__Role"] [class*="Avatar__Wrapper"])'
|
||||||
|
)
|
||||||
|
|
||||||
|
// prompts are always plaintext
|
||||||
|
return Promise.all(messages.map((a) => a.innerText()))
|
||||||
|
}
|
||||||
|
|
||||||
|
async getMessages(): Promise<string[]> {
|
||||||
|
// Get all complete messages
|
||||||
|
// (in-progress messages that are being streamed back don't contain action buttons)
|
||||||
|
const messages = await this._page.$$(
|
||||||
|
'[class*="ConversationItem__Message"]:has([class*="ConversationItem__ActionButtons"]):not(:has([class*="ConversationItem__Role"] [class*="Avatar__Wrapper"]))'
|
||||||
|
)
|
||||||
|
|
||||||
|
if (this._markdown) {
|
||||||
|
const htmlMessages = await Promise.all(messages.map((a) => a.innerHTML()))
|
||||||
|
|
||||||
|
const markdownMessages = htmlMessages.map((messageHtml) => {
|
||||||
|
// parse markdown from message HTML
|
||||||
|
messageHtml = messageHtml.replace('Copy code</button>', '</button>')
|
||||||
|
return html2md(messageHtml, {
|
||||||
|
ignoreTags: [
|
||||||
|
'button',
|
||||||
|
'svg',
|
||||||
|
'style',
|
||||||
|
'form',
|
||||||
|
'noscript',
|
||||||
|
'script',
|
||||||
|
'meta',
|
||||||
|
'head'
|
||||||
|
],
|
||||||
|
skipTags: ['button', 'svg']
|
||||||
|
})
|
||||||
|
})
|
||||||
|
|
||||||
|
return markdownMessages
|
||||||
|
} else {
|
||||||
|
// plaintext
|
||||||
|
const plaintextMessages = await Promise.all(
|
||||||
|
messages.map((a) => a.innerText())
|
||||||
|
)
|
||||||
|
return plaintextMessages
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
async sendMessage(message: string): Promise<string> {
|
||||||
|
const inputBox = await this._getInputBox()
|
||||||
|
if (!inputBox) throw new Error('not signed in')
|
||||||
|
|
||||||
|
const lastMessage = await this.getLastMessage()
|
||||||
|
|
||||||
|
await inputBox.click()
|
||||||
|
await inputBox.fill(message)
|
||||||
|
await inputBox.press('Enter')
|
||||||
|
|
||||||
|
do {
|
||||||
|
await delay(1000)
|
||||||
|
|
||||||
|
// TODO: this logic needs some work because we can have repeat messages...
|
||||||
|
const newLastMessage = await this.getLastMessage()
|
||||||
|
if (
|
||||||
|
newLastMessage &&
|
||||||
|
lastMessage?.toLowerCase() !== newLastMessage?.toLowerCase()
|
||||||
|
) {
|
||||||
|
return newLastMessage
|
||||||
|
}
|
||||||
|
} while (true)
|
||||||
|
}
|
||||||
|
|
||||||
|
async close() {
|
||||||
|
return await this._browser.close()
|
||||||
|
}
|
||||||
|
|
||||||
|
protected async _getInputBox(): Promise<any> {
|
||||||
|
return this._page.$(
|
||||||
|
'div[class*="PromptTextarea__TextareaWrapper"] textarea'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
}
|
|
@ -0,0 +1,52 @@
|
||||||
|
import delay from 'delay'
|
||||||
|
import { oraPromise } from 'ora'
|
||||||
|
|
||||||
|
import { ChatGPTAPI } from './chatgpt-api'
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Example CLI for testing functionality.
|
||||||
|
*/
|
||||||
|
async function main() {
|
||||||
|
const api = new ChatGPTAPI()
|
||||||
|
await api.init()
|
||||||
|
|
||||||
|
const isSignedIn = await api.getIsSignedIn()
|
||||||
|
|
||||||
|
if (!isSignedIn) {
|
||||||
|
// Wait until the user signs in via the chromium browser
|
||||||
|
await oraPromise(
|
||||||
|
new Promise<void>(async (resolve, reject) => {
|
||||||
|
try {
|
||||||
|
await delay(1000)
|
||||||
|
const isSignedIn = await api.getIsSignedIn()
|
||||||
|
if (isSignedIn) {
|
||||||
|
return resolve()
|
||||||
|
}
|
||||||
|
} catch (err) {
|
||||||
|
return reject(err)
|
||||||
|
}
|
||||||
|
}),
|
||||||
|
'Please sign in to ChatGPT'
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
const response = await api.sendMessage(
|
||||||
|
// 'Write a TypeScript function for conway sort.'
|
||||||
|
'Write a python version of bubble sort. Do not include example usage.'
|
||||||
|
)
|
||||||
|
// const prompts = await api.getPrompts()
|
||||||
|
// const messages = await api.getMessages()
|
||||||
|
// console.log('prompts', prompts)
|
||||||
|
// console.log('messages', messages)
|
||||||
|
|
||||||
|
// Wait forever; useful for debugging chromium session
|
||||||
|
// await new Promise(() => {})
|
||||||
|
|
||||||
|
await api.close()
|
||||||
|
|
||||||
|
return response
|
||||||
|
}
|
||||||
|
|
||||||
|
main().then((res) => {
|
||||||
|
console.log(res)
|
||||||
|
})
|
|
@ -0,0 +1 @@
|
||||||
|
export * from './chatgpt-api'
|
|
@ -0,0 +1,20 @@
|
||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
"target": "es2020",
|
||||||
|
"lib": ["esnext"],
|
||||||
|
"allowJs": true,
|
||||||
|
"skipLibCheck": true,
|
||||||
|
"strict": false,
|
||||||
|
"forceConsistentCasingInFileNames": true,
|
||||||
|
"esModuleInterop": true,
|
||||||
|
"module": "esnext",
|
||||||
|
"moduleResolution": "node",
|
||||||
|
"resolveJsonModule": true,
|
||||||
|
"isolatedModules": true,
|
||||||
|
"baseUrl": ".",
|
||||||
|
"outDir": "build",
|
||||||
|
"noEmit": true
|
||||||
|
},
|
||||||
|
"exclude": ["node_modules", "build"],
|
||||||
|
"include": ["**/*.ts"]
|
||||||
|
}
|
|
@ -0,0 +1,14 @@
|
||||||
|
import { defineConfig } from 'tsup'
|
||||||
|
|
||||||
|
export default defineConfig({
|
||||||
|
entry: ['src/index.ts'],
|
||||||
|
outDir: 'build',
|
||||||
|
target: 'node14',
|
||||||
|
platform: 'node',
|
||||||
|
format: ['esm'],
|
||||||
|
splitting: false,
|
||||||
|
sourcemap: true,
|
||||||
|
minify: true,
|
||||||
|
shims: false,
|
||||||
|
dts: true
|
||||||
|
})
|
|
@ -0,0 +1,14 @@
|
||||||
|
{
|
||||||
|
"$schema": "https://typedoc.org/schema.json",
|
||||||
|
"entryPoints": ["./src/index.ts"],
|
||||||
|
"exclude": ["**/*.test.ts"],
|
||||||
|
"plugin": ["typedoc-plugin-markdown"],
|
||||||
|
"out": "docs",
|
||||||
|
"hideBreadcrumbs": false,
|
||||||
|
"hideInPageTOC": false,
|
||||||
|
"excludePrivate": true,
|
||||||
|
"excludeProtected": true,
|
||||||
|
"excludeExternals": true,
|
||||||
|
"excludeInternal": true,
|
||||||
|
"entryDocument": "readme.md"
|
||||||
|
}
|
Ładowanie…
Reference in New Issue