@push.rocks/smartpuppeteer
Simplified access to Puppeteer with environment-aware browser startup helpers.
Issue Reporting and Security
For reporting bugs, issues, or security vulnerabilities, please visit community.foss.global/. This is the central community hub for all issue reporting. Developers who sign and comply with our contribution agreement and go through identification can also get a code.foss.global/ account to submit Pull Requests directly.
Install
Install @push.rocks/smartpuppeteer with pnpm:
pnpm add @push.rocks/smartpuppeteer
Puppeteer 25 requires Node.js 22.12.0 or newer.
Usage
@push.rocks/smartpuppeteer simplifies interaction with Puppeteer, providing easier ways to launch Puppeteer instances considering environment constraints, such as running in a CI pipeline or as root, which necessitates certain flags for Chrome.
Here, we give a comprehensive guide to using @push.rocks/smartpuppeteer in various scenarios, using ESM syntax and TypeScript.
Basic Setup
Firstly, let’s set up the basic environment for using @push.rocks/smartpuppeteer:
import { getEnvAwareBrowserInstance, IncognitoBrowser, puppeteer } from '@push.rocks/smartpuppeteer';
// Usually, you would initialize the browser instance at the start of your script or application logic
const initializeBrowser = async () => {
const browser = await getEnvAwareBrowserInstance({
launchOptions: {
headless: true,
defaultViewport: { width: 1280, height: 720 },
args: ['--lang=en-US'],
},
});
return browser;
};
getEnvAwareBrowserInstance() checks google-chrome, chromium, and chromium-browser in that order. Missing candidates are skipped safely. When none resolve, Puppeteer chooses its default executable.
Caller launchOptions are passed to Puppeteer. Caller arguments are retained when the environment requires --no-sandbox and --disable-setuid-sandbox, and the required arguments are added without duplication. This no-sandbox behavior remains limited to root users, CI environments, or callers that explicitly set forceNoSandbox; disabling Chromium's sandbox reduces process isolation. The existing pipe transport remains enabled by default; set usePipe: false to request Puppeteer's WebSocket transport. Executable discovery is skipped when launchOptions.browser, launchOptions.channel, or launchOptions.executablePath is present.
Set requireSandbox: true when a caller must fail closed instead of accepting the environment-aware no-sandbox behavior. This rejects forceNoSandbox, known sandbox-disabling Chromium arguments, and Linux launches as root. It also prevents CI from adding no-sandbox arguments. This option prevents SmartPuppeteer from disabling Chromium's sandbox; a successful process launch is still not an independent verification of Chromium's internal sandbox state.
Use resolveBrowserExecutablePath() directly when you need to inspect the selected executable or provide your own ordered candidate list:
import { resolveBrowserExecutablePath } from '@push.rocks/smartpuppeteer';
const executablePath: string | undefined = resolveBrowserExecutablePath([
'chromium',
'chromium-browser',
]);
Opening a Page and Navigating
After obtaining a browser instance, you commonly want to open a page and navigate to a URL:
const openPage = async (browser: puppeteer.Browser) => {
const page = await browser.newPage();
await page.goto('https://www.example.com');
const pageTitle = await page.title();
console.log(`Page title: ${pageTitle}`);
// Always close the browser after you are done to free resources
await browser.close();
};
// Utilize the async function
initializeBrowser()
.then(openPage)
.catch(console.error);
Using Incognito Mode for Isolated Sessions
@push.rocks/smartpuppeteer offers easy management of incognito sessions, allowing isolated environments within the same browser instance:
const useIncognitoBrowser = async () => {
const incognitoBrowser = new IncognitoBrowser();
await incognitoBrowser.start(); // Initializes a new incognito browser instance
const context = await incognitoBrowser.getNewIncognitoContext();
const page = await context.newPage();
await page.goto('https://www.privacyfocusedsite.com');
// Perform actions in the isolated session
// Tidy up
await incognitoBrowser.stop(); // Stops the incognito browser and closes all its pages and contexts
};
useIncognitoBrowser()
.then(() => console.log('Incognito session used successfully'))
.catch(console.error);
Advanced Configuration
@push.rocks/smartpuppeteer allows further customization for launching the Puppeteer browser, such as disabling the sandbox environment (not recommended for production).
Live Browser Sessions
LiveBrowserSession provides a transport-neutral runtime for remote browser and agent adapters. It owns one Chromium process and one default-context profile. All tabs and popups therefore share cookies, local storage, cache, and other profile state. Puppeteer creates an ephemeral profile when neither launchOptions.userDataDir nor a --user-data-dir argument is supplied. Either explicit form persists potentially sensitive authentication and browsing data, must not be shared concurrently between Chromium processes, and must be protected by the caller.
import {
LiveBrowserSession,
type ILiveBrowserFrame,
} from '@push.rocks/smartpuppeteer';
const session = new LiveBrowserSession({
viewport: {
width: 1280,
height: 720,
deviceScaleFactor: 1,
},
screencast: {
format: 'jpeg',
quality: 80,
maxWidth: 1280,
maxHeight: 720,
everyNthFrame: 1,
maxOutstandingFrames: 3,
firstFrameTimeoutMs: 5000,
},
launchOptions: {
headless: true,
// userDataDir: '/explicit/profile/path',
},
});
const unsubscribe = session.onEvent((event) => {
if (event.type !== 'frame') {
return;
}
const frame: ILiveBrowserFrame = event.frame;
console.log(frame.mimeType, frame.data.byteLength);
void session.acknowledgeFrame({
tabId: frame.tabId,
sequence: frame.sequence,
generation: frame.generation,
viewportRevision: frame.viewportRevision,
});
});
await session.start();
try {
const state = session.getState();
const tabId = state.activeTabId!;
await session.navigate({
tabId,
url: 'data:text/html,<title>Live session</title><button>Continue</button>',
});
const observation = await session.observe({ tabId });
console.log(observation.text);
const currentState = session.getState();
const currentTab = currentState.tabs.find((tab) => tab.id === tabId)!;
await session.click({
tabId,
generation: currentTab.generation,
viewportRevision: currentState.viewportRevision,
selector: 'button',
});
} finally {
unsubscribe();
await session.stop();
}
Only the active tab is streamed. Frames carry a session-monotonic sequence, tab/CDP generation, viewport revision, viewport, MIME type, encoded dimensions, screencast metadata, and binary Uint8Array data. Every delivered frame must be acknowledged with all four identity fields. A delivered frame remains pending until it is acknowledged, dropped, or retired by the runtime; mismatched, duplicate, stale, retired, or operationally failed acknowledgements return { accepted: false }. Operational acknowledgement failures also emit an error event whose code is frame_acknowledgement_failed. screencast.maxOutstandingFrames is an integer from 1 through liveBrowserMaxOutstandingFrames (64) and defaults to liveBrowserDefaultMaxOutstandingFrames (3). At capacity, SmartPuppeteer retires and CDP-acknowledges the oldest pending frame before publishing the next one. Tab switches, navigation, resize, page cleanup, disconnect, and shutdown also retire pending frames and await their single CDP acknowledgement attempt before detaching the screencast session.
The SmartPuppeteer outstanding-frame bound covers only the private frame-to-Page.screencastFrameAck lifecycle. It is not a binary transport window, and acknowledgeFrame() is not an application transport acknowledgement. A higher-level runtime must maintain and bound its application frame window separately. refreshScreencast() retires the active stream and resolves with an ILiveBrowserFrameIdentity for the exact first validated frame from a new generation. Its tabId, sequence, generation, and viewportRevision fields can be passed directly to acknowledgeFrame(). screencast.firstFrameTimeoutMs bounds both the CDP restart and first-frame arrival, accepts integers from 100 through 60,000, and defaults to 5,000. Screencast format accepts jpeg or png, quality accepts integers from 0 through 100, maxWidth and maxHeight accept integers from 1 through 4096 subject to an 8,294,400-pixel combined ceiling, and everyNthFrame accepts integers from 1 through 100.
A refresh timeout or restart failure on an otherwise healthy tab is recoverable. The refreshScreencast() promise rejects, an error event with code screencast_refresh_failed and fatal: false is emitted, and the tab stays open with streaming: false while the session keeps running; raw input for that tab is rejected until a new generation starts. Any of refreshScreencast(), setViewport(), or activateTab() on that tab then starts a new screencast generation. Only browser loss keeps the failure fatal: a disconnected Chromium reports browser_disconnected (or a fatal screencast_refresh_failed if the refresh observes the loss first) and shuts the session down, and page or CDP-session loss remains tab-scoped as described below.
updateScreencastOptions({ quality, maxWidth, maxHeight, everyNthFrame }) changes the encoder settings of a session at runtime. Each supplied field is validated with the same bounds as the constructor, the merged result must still satisfy the pixel-area ceiling, and unknown keys such as format are rejected. Accepted values are stored for every later screencast start. When the active tab is streaming, the call restarts the screencast with a new generation exactly like refreshScreencast() and resolves with the same ILiveBrowserFrameIdentity; otherwise it stores the values and resolves with null.
const identity = await session.updateScreencastOptions({
quality: 45,
maxWidth: 640,
maxHeight: 360,
});
if (identity) {
await session.acknowledgeFrame(identity);
}
Synchronous screencast invalidation, including renderer-initiated navigation, immediately publishes a state event with streaming: false. The tab remains nonstreaming in public state until the replacement generation starts, so viewers can fence input before asynchronous page metadata refresh completes.
The live API includes:
- Lifecycle and state:
start(),stop(),terminate(),refreshScreencast(),updateScreencastOptions(),getProcessState(),onEvent(), andgetState() - Tabs and navigation:
createTab(),activateTab(),closeTab(),navigate(),back(),forward(), andreload() - Viewport and raw input:
setViewport(),dispatchMouse(),dispatchWheel(),dispatchKey(), andinsertText() - Agent-oriented actions:
click(),fill(), andpress()with bounded selectors and timeouts - Capture and observation:
captureSnapshot()returns viewport-only JPEG or PNG bytes;observe()returns bounded URL, title, tab state, and textual accessibility content without image bytes - Optional evaluation:
evaluate()returns bounded JSON values when the session explicitly setsallowEvaluation: true
Coordinate, keyboard, text, and semantic input messages include tabId, generation, and viewportRevision. This rejects input derived from an old stream generation, resize, or tab state. Snapshot, observation, and semantic operations are serialized with lifecycle mutations; inactive tabs receive the current session viewport before use.
For dispatchMouse() movement, pass the DOM buttons bitmask to describe held buttons. The session translates that mask to Chromium's move-button state, so native HTML drag-and-drop works even when a pointer move has button: "none". Down/up events retain their changed-button identity.
dispatchWheel() merges bursts. Up to four mouseWheel CDP dispatches run concurrently per tab. When that bounded pipeline is full, further dispatchWheel() calls for the same tab and stream accumulate: deltaX and deltaY are summed and clamped to ±1,000,000, and the latest x, y, and modifiers win. The accumulated input is sent as one CDP event when pipeline capacity becomes available, and every caller's promise resolves when its deltas have been sent or rejects if that merged send fails. Ordering with other raw input on the same tab is preserved: a dispatchMouse(), dispatchKey(), or insertText() call issued after wheel input is sent only after the preceding wheel dispatch has been acknowledged by Chromium, and wheel input issued after such a call starts a new batch behind it. viewport takes precedence over launchOptions.defaultViewport; null falls back to 800x600. The runtime canonicalizes every page to a desktop, non-touch viewport because mobile emulation flags are outside the public viewport contract. Viewport dimensions, device scale factor, and physical pixel area are bounded, and full-page snapshots are intentionally unsupported. CDP sessions and CDP frame identifiers remain private implementation details. LiveBrowserSession owns launch cancellation, so callers cannot supply launchOptions.signal. It supports only Chromium over CDP and rejects Firefox or WebDriver BiDi launch selections.
start(), refreshScreencast(), updateScreencastOptions(), tab and navigation methods, setViewport(), captureSnapshot(), observe(), semantic actions, and evaluate() accept a trailing { signal } operation argument. A pre-aborted operation is never admitted. An operation aborted while queued is removed immediately. An active operation receives cancellation when its Puppeteer or CDP primitive supports it; otherwise its promise rejects only after the underlying work settles, and it continues to occupy the serialized queue until then. An active refreshScreencast() or updateScreencastOptions() completes the restorative restart before settling caller cancellation, unless session shutdown or page invalidation revokes that restart. Cancellation therefore does not promise that an already-started browser side effect did not occur. stop(), frame acknowledgement, event/state access, and direct raw input are intentionally not caller-cancellable.
Optional browser guards can be enabled when composing a higher-level runtime:
const guardedSession = new LiveBrowserSession({
requireSandbox: true,
launchOptions: {
args: [
'--proxy-server=http://127.0.0.1:8080',
'--proxy-bypass-list=<-loopback>',
],
},
security: {
denyDownloads: true,
denyFileChoosers: true,
denyPermissions: true,
httpNavigationOnly: true,
proxyCredentials: {
username: 'proxy-user',
password: 'proxy-password',
},
},
});
denyDownloads installs a default-context download denial at launch. denyPermissions applies an empty browser-wide permission grant before the first page is exposed, causing unlisted permissions to be denied. denyFileChoosers installs persistent CDP cancellation on each registered page. httpNavigationOnly limits URLs passed to createTab() and navigate() to http: and https:; it does not inspect or rewrite renderer-initiated navigation.
proxyCredentials handles authenticated-proxy challenges for existing and future page, dedicated-worker, and service-worker traffic. SmartPuppeteer supplies credentials only when CDP identifies the challenge source as Proxy; origin-server and unknown challenges are cancelled without credentials, and a repeated challenge for the same request is cancelled. Page and dedicated-worker setup is queued from Puppeteer's public CDP session-attachment event. Service workers use an independent, filtered browser-target attachment scope that enables Fetch and Network before resuming each worker. A hidden lifecycle target releases stopped workers and redundant workers after their tracked requests drain so Chromium can destroy them normally; later restarts are paused and protected again before execution. Setup failure rejects start() and closes the browser. During a running session, an unexpected browser security-session detach, a live target without a protected replacement session, or a proxy protocol failure is fatal and requests termination; Linux termination is confirmed only when terminate() resolves. The caller still owns proxy selection, Chromium proxy arguments, bypass rules, DNS behavior, egress policy, and proxy trust. Credentials remain in process memory for the session lifetime.
On Linux, Puppeteer launches Chromium as a dedicated process-group and session leader. getProcessState() reports the generation, root PID, process-group ID, and Node.js exit state. terminate({ gracefulTimeoutMs, forceTimeoutMs }) is idempotent for concurrent callers, first requests normal shutdown, then freezes and kills the owned process group if the graceful deadline expires. It resolves with confirmedDead: true only after /proc contains no member of that owned process group, the Node.js child exit is observed, and session shutdown settles. PID reuse, permission failures, surviving members, or an unsettled shutdown reject confirmation and keep restart blocked. Confirmed process-group termination is Linux-only; use stop() for portable best-effort lifecycle cleanup.
Evaluation is disabled by default. Once enabled, it accepts a JavaScript expression, runs it in a dedicated main-frame isolated world, awaits its result, and returns only JSON-compatible values:
const evaluationSession = new LiveBrowserSession({ allowEvaluation: true });
await evaluationSession.start();
const result = await evaluationSession.evaluate(
`({ title: document.title, links: document.links.length })`,
{ timeoutMs: 2000, maxOutputBytes: 65536 },
);
The default evaluation limits are a 5-second timeout, 256 KiB transferred output, depth 16, 10,000 total nodes, 64 KiB per string or key, 1,000 array entries, and 1,000 object keys. Hard ceilings are 30 seconds, 1 MiB output, depth 32, 50,000 nodes, 256 KiB per string or key, and 10,000 array entries or object keys. Expressions are limited to 256 KiB of UTF-8 source. Results reject non-finite numbers, undefined, bigint, symbols, functions, sparse or extended arrays, accessors, non-plain objects, cycles, and repeated object references. Output is normalized and measured inside the renderer before bounded JSON text is transferred, then measured again before host parsing.
evaluate() is trusted-caller code execution, not a JavaScript sandbox. An expression can mutate or navigate the page, initiate network activity, consume renderer resources, or crash the renderer. User-started asynchronous work can outlive a returned result, timeout, or caller cancellation; a higher-level runtime that requires strict quiescence must stop or quarantine the browser. Keep evaluation disabled unless a higher-level policy explicitly authorizes it.
LiveBrowserSession remains a browser runtime, not a complete security policy layer. The optional guards do not authenticate callers, authorize actions, enforce network egress, own profile-directory cleanup, isolate operating-system resources, or contain a compromised Chromium process. Adapters must apply those controls before invoking it. stop() rejects active and queued operations and aborts Chromium independently of Puppeteer operation timeouts. Closing the final usable tab also stops the session. Browser-wide loss stops the runtime without automatic relaunch. Page or CDP loss is tab-scoped: another usable tab becomes active when possible, otherwise the runtime stops. Popup registration queue saturation emits popup_registration_capacity_exceeded and stops the session rather than leaving an untracked page.
Page dialogs
Each tab state exposes an optional dialog with id, type, message, defaultPrompt, and url. Types are alert, confirm, prompt, and beforeunload. Subscribe with onEvent() and present these as website dialogs in the host UI. Responses use the exact pending dialog identity:
await session.respondToDialog({ tabId, dialogId: dialog.id, accept: true,
...(dialog.type === 'prompt' ? { promptText: 'User response' } : {}) });
Responses bypass the document operation queue so a paused navigation or evaluation can finish. Stale or duplicate responses throw LiveBrowserInputStaleError (code: 'stale_input'). Raw input also throws this error when its document or viewport is stale, while LiveBrowserDialogPendingError (code: 'dialog_pending') means a pending dialog blocks input. Keep held-input tracking until the dialog closes, then send the releases. Hosts should wait for current state and a newly presented frame before accepting more input.
Native WebRTC video
Enable video for native Chromium tab capture and WebRTC delivery:
const session = new LiveBrowserSession({
requireSandbox: true,
viewport: { width: 1280, height: 800, deviceScaleFactor: 1 },
screencast: { enabled: false },
video: { gpu: 'auto', maxFrameRate: 30, maxBitrate: 16_000_000, qualityPreference: 'detail' },
});
await session.start();
const offer = await session.openVideoPeer('authorized-viewer');
// Send offer to the authorized receiver over your existing signaling transport.
// Receiver creates an RTCPeerConnection using offer.iceServers/iceTransportPolicy,
// applies offer.description, and returns its gathered answer description.
await session.answerVideoPeer(offer.peerId, offer.negotiationId, answerDescription);
const statistics = await session.getVideoStatistics(offer.peerId);
await session.closeVideoPeer(offer.peerId);
await session.stop();
Capture is a native tab MediaStreamTrack in a fixed packaged private extension. Video does not pass through JPEG, base64, a JavaScript canvas, or a Node video encoder. Chromium owns congestion control, codec selection, and adaptation. qualityPreference defaults to 'detail', maintaining resolution for readable text while adapting frame rate. 'motion' maintains frame rate and permits resolution reduction; 'balanced' uses Chromium’s balanced policy. There is no additional application downscaling loop. The default bitrate ceiling is 16 Mbps per viewer. Native capture requests a minimum cadence of 30 fps (or the configured maximum when lower) so input on an otherwise static page does not wait for a slow idle capture tick. Chromium remains responsible for encoding unchanged content efficiently. The source is shared by viewers, each with an independent peer and encoding budget. Document navigation preserves capture and existing peer connections. Resizing replaces the native capture tracks on those same peers, with proportional capture limits so high display scales do not introduce encoded borders. Switching tabs invalidates peers. Input remains fenced to its document generation. Closing the last peer stops capture. Opening another peer starts it again. The default capacity is eight viewers, bounded to 32.
Direct ICE with no external service is the default, suitable for reachable LAN and VPN peers. The host can supply iceServers and iceTransportPolicy: 'relay' for deployments requiring TURN. Relay-only configuration requires a TURN URL. Signaling and peer IDs must be authorized by the embedding host; never let a visited page configure peers or ICE services. SDP is limited to 48 KiB and one video media section; audio and data channels are not accepted. Offers finish ICE gathering before returning, and peers have a 30-second connection deadline.
The private extension requires Chromium's CDP pipe (the default); --disable-extensions is incompatible. It has no content scripts, externally connected messages, or web-accessible resources. Existing page WebRTC routing restrictions remain in force. The extension's host-authorized peer traffic is independent of the HTTP proxy, so the host must permit the intended LAN/VPN path.
gpu: 'auto' enables GPU rendering when Chrome and its drivers support it; Linux headless capture uses ANGLE Vulkan unless a caller selected another ANGLE backend. gpu: 'disabled' explicitly uses software rendering. getState().videoAcceleration reports Chrome's renderer, compositing and video-encoding capability; getVideoStatistics() reports the encoder actually used. GPU rendering does not imply hardware video encoding.
Every offer includes a unique negotiationId, required when submitting its answer, and its source { tabId, generation, viewportRevision, viewport }. Tab activation retires existing peers; navigation and viewport changes preserve them. Receivers must reject stale negotiation, reopen after a tab change, and allow input only after current state and a matching video presentation arrive. openVideoPeer() and answerVideoPeer() participate in the serialized browser operation queue. closeVideoPeer() is idempotent, and the private media queue is bounded. Statistics reads run independently of media mutations, coalesce per peer, and reject results from retired negotiations; a slow read cannot hold shutdown or source replacement behind it.
screencast.enabled defaults to true for frame consumers. Set it to false when using video alone, then use setFrameCaptureEnabled(true/false) to follow actual JPEG/PNG subscriber demand. This leaves input and video available. Snapshot capture remains independent. refreshScreencast() requires frame capture to be enabled.
Selected-tab DevTools
Set allowDevTools: true to let an authenticated host open a CDP attachment with
session.openDevTools({ tabId, onMessage, onClose }). The returned connection exposes
tabId, browserVersion, send(message) and idempotent close(). onMessage is awaited
for transport backpressure. Call send concurrently: a paused evaluation must leave
Debugger.resume available. Forward protocol responses from onMessage; the send
promise reports completion, rather than carrying the CDP response.
Attachments inspect the selected page and its attached frame/worker descendants through public Puppeteer CDP sessions. They survive page navigation and viewport changes, and close when the page or browser ends. They never expose the browser debugging endpoint or private video-extension targets. The host must authorize every command and close the attachment when that authority ends.
An explicit method policy supports Elements, Console, Sources, Network observation and
target-local profiling. Browser administration, arbitrary target discovery/attachment,
host files, downloads, Fetch interception, browser-wide tracing, and unscoped storage
access return CDP error -32000. Proxy authorization headers are redacted. Command and
output queues are bounded; liveBrowserDevToolsLimits publishes their limits. Output admission charges UTF-8 payload bytes plus a per-entry allowance instead of imposing a small event-count ceiling. Overflow reasons distinguish a single oversized message from a receiver backlog and include method and byte counts without message content. Four
interrupt slots remain available independently of the 64 ordinary command slots. Slow
or overflowing receivers close their inspector without stopping browser input or video;
an attachment that cannot detach safely terminates the owned browser incarnation.
Handling Browser Events
It's important to handle browser events, such as disconnections, which might occur due to various reasons:
const browserWithEventHandling = async () => {
const browser = await getEnvAwareBrowserInstance();
browser.on('disconnected', () => {
console.log('Browser disconnected. Handling reconnection...');
// Implement reconnection logic here
});
// Utilize the browser for tasks
};
browserWithEventHandling()
.then(() => console.log('Handled browser events successfully'))
.catch(console.error);
Rotation of Browsers and Pages
In scenarios such as web scraping or automated testing, you might want to rotate between browser instances or pages to manage memory usage or simulate new sessions:
const rotateBrowserInstances = async (incognitoBrowser: IncognitoBrowser) => {
// Assuming incognitoBrowser is already initialized and started
await incognitoBrowser.rotateBrowser(); // Closes the current browser and starts a new instance
// Now you have a fresh browser instance
};
// Example usage
const incognitoBrowser = new IncognitoBrowser();
incognitoBrowser.start()
.then(() => rotateBrowserInstances(incognitoBrowser))
.catch(console.error);
@push.rocks/smartpuppeteer with its encapsulated features and simplified API provides an efficient way to harness the power of Puppeteer without getting bogged down by its complexities. Whether you are handling web scraping, automated testing, or any task requiring browser automation, @push.rocks/smartpuppeteer streamlines the process, making it more accessible and manageable even for those new to Puppeteer.
License and Legal Information
This repository contains open-source code licensed under the MIT License. A copy of the license can be found in the repository license file.
Please note: The MIT License does not grant permission to use the trade names, trademarks, service marks, or product names of the project, except as required for reasonable and customary use in describing the origin of the work and reproducing the content of the NOTICE file.
Trademarks
This project is owned and maintained by Task Venture Capital GmbH. The names and logos associated with Task Venture Capital GmbH and any related products or services are trademarks of Task Venture Capital GmbH or third parties, and are not included within the scope of the MIT license granted herein.
Use of these trademarks must comply with Task Venture Capital GmbH's Trademark Guidelines or the guidelines of the respective third-party owners, and any usage must be approved in writing. Third-party trademarks used herein are the property of their respective owners and used only in a descriptive manner, e.g. for an implementation of an API or similar.
Company Information
Task Venture Capital GmbH
Registered at District Court Bremen HRB 35230 HB, Germany
For any legal inquiries or further information, please contact us via email at hello@task.vc.
By using this repository, you acknowledge that you have read this section, agree to comply with its terms, and understand that the licensing of the code does not imply endorsement by Task Venture Capital GmbH of any derivative works.