@promptbook/cli 0.114.0-21 → 0.114.0-23
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/esm/index.es.js +780 -103
- package/esm/index.es.js.map +1 -1
- package/esm/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +5 -4
- package/esm/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +3 -2
- package/esm/scripts/run-codex-prompts/prompts/promptRunnerAttribution.d.ts +14 -8
- package/esm/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +15 -0
- package/esm/scripts/run-codex-prompts/runners/claude-code/parseClaudeCodeOutputEvents.d.ts +3 -0
- package/esm/scripts/run-codex-prompts/runners/claude-code/parseClaudeCodeSubscriptionUsage.d.ts +11 -0
- package/esm/scripts/run-codex-prompts/runners/openai-codex/OpenAiCodexRunner.d.ts +8 -0
- package/esm/scripts/run-codex-prompts/runners/openai-codex/getCodexSubscriptionUsage.d.ts +19 -0
- package/esm/scripts/run-codex-prompts/runners/types/HarnessSubscriptionUsage.d.ts +43 -0
- package/esm/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +6 -0
- package/esm/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +2 -0
- package/esm/scripts/run-codex-prompts/ui/buildSubscriptionUsageSessionRows.d.ts +11 -0
- package/esm/scripts/run-codex-prompts/ui/refreshCoderRunUiSubscriptionUsage.d.ts +14 -0
- package/esm/scripts/utils/emojiTags/validSinglePictogramEmojis.d.ts +17 -0
- package/esm/src/utils/misc/emojis.d.ts +3 -2
- package/esm/src/version.d.ts +1 -1
- package/package.json +1 -1
- package/src/other/templates/getTemplatesPipelineCollection.ts +859 -822
- package/src/utils/misc/emojis.ts +3 -3
- package/src/version.ts +2 -2
- package/src/versions.txt +2 -0
- package/umd/index.umd.js +779 -102
- package/umd/index.umd.js.map +1 -1
- package/umd/scripts/run-codex-prompts/prompts/buildPromptStatusDetails.d.ts +5 -4
- package/umd/scripts/run-codex-prompts/prompts/markPromptFailed.d.ts +3 -2
- package/umd/scripts/run-codex-prompts/prompts/promptRunnerAttribution.d.ts +14 -8
- package/umd/scripts/run-codex-prompts/runners/claude-code/ClaudeCodeRunner.d.ts +15 -0
- package/umd/scripts/run-codex-prompts/runners/claude-code/parseClaudeCodeOutputEvents.d.ts +3 -0
- package/umd/scripts/run-codex-prompts/runners/claude-code/parseClaudeCodeSubscriptionUsage.d.ts +11 -0
- package/umd/scripts/run-codex-prompts/runners/openai-codex/OpenAiCodexRunner.d.ts +8 -0
- package/umd/scripts/run-codex-prompts/runners/openai-codex/getCodexSubscriptionUsage.d.ts +19 -0
- package/umd/scripts/run-codex-prompts/runners/types/HarnessSubscriptionUsage.d.ts +43 -0
- package/umd/scripts/run-codex-prompts/ui/CoderRunUiState.d.ts +6 -0
- package/umd/scripts/run-codex-prompts/ui/buildCoderRunUiFrame.d.ts +2 -0
- package/umd/scripts/run-codex-prompts/ui/buildSubscriptionUsageSessionRows.d.ts +11 -0
- package/umd/scripts/run-codex-prompts/ui/refreshCoderRunUiSubscriptionUsage.d.ts +14 -0
- package/umd/scripts/utils/emojiTags/validSinglePictogramEmojis.d.ts +17 -0
- package/umd/src/utils/misc/emojis.d.ts +3 -2
- package/umd/src/version.d.ts +1 -1
- package/esm/scripts/find-fresh-emoji-tags/utils/emojis.d.ts +0 -16
- package/umd/scripts/find-fresh-emoji-tags/utils/emojis.d.ts +0 -16
package/esm/index.es.js
CHANGED
|
@@ -10,7 +10,7 @@ import * as fs from 'fs';
|
|
|
10
10
|
import { createWriteStream, mkdirSync, writeFileSync, statSync, readFileSync, existsSync, promises } from 'fs';
|
|
11
11
|
import * as dotenv from 'dotenv';
|
|
12
12
|
import * as readline from 'readline';
|
|
13
|
-
import {
|
|
13
|
+
import { createInterface, emitKeypressEvents, cursorTo, clearLine } from 'readline';
|
|
14
14
|
import { forTime, forEver } from 'waitasecond';
|
|
15
15
|
import hexEncoder from 'crypto-js/enc-hex';
|
|
16
16
|
import sha256 from 'crypto-js/sha256';
|
|
@@ -48,7 +48,7 @@ const BOOK_LANGUAGE_VERSION = '2.0.0';
|
|
|
48
48
|
* @generated
|
|
49
49
|
* @see https://github.com/webgptorg/promptbook
|
|
50
50
|
*/
|
|
51
|
-
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-
|
|
51
|
+
const PROMPTBOOK_ENGINE_VERSION = '0.114.0-23';
|
|
52
52
|
/**
|
|
53
53
|
* TODO: string_promptbook_version should be constrained to the all versions of Promptbook engine
|
|
54
54
|
* Note: [💞] Ignore a discrepancy between file name and entity name
|
|
@@ -25547,6 +25547,29 @@ async function waitForSkippableWorldTimeDeadline(options) {
|
|
|
25547
25547
|
}
|
|
25548
25548
|
}
|
|
25549
25549
|
|
|
25550
|
+
/**
|
|
25551
|
+
* Merges a newly reported subset of subscription windows into the latest complete snapshot.
|
|
25552
|
+
*
|
|
25553
|
+
* Some harnesses emit an update only for the limit whose state changed. Keeping the previous windows means a
|
|
25554
|
+
* five-hour update cannot make a still-valid weekly limit disappear from the dashboard. Existing order is preserved
|
|
25555
|
+
* and newly discovered windows are appended, which keeps the terminal presentation stable between refreshes.
|
|
25556
|
+
*
|
|
25557
|
+
* @private internal utility of `ptbk coder`
|
|
25558
|
+
*/
|
|
25559
|
+
function mergeHarnessSubscriptionUsage(previousSubscriptionUsage, latestSubscriptionUsage) {
|
|
25560
|
+
if (previousSubscriptionUsage === undefined) {
|
|
25561
|
+
return latestSubscriptionUsage;
|
|
25562
|
+
}
|
|
25563
|
+
const latestLimitsByLabel = new Map(latestSubscriptionUsage.limits.map((latestLimit) => [latestLimit.label, latestLimit]));
|
|
25564
|
+
const previousLimitLabels = new Set(previousSubscriptionUsage.limits.map((previousLimit) => previousLimit.label));
|
|
25565
|
+
return {
|
|
25566
|
+
limits: [
|
|
25567
|
+
...previousSubscriptionUsage.limits.map((previousLimit) => { var _a; return (_a = latestLimitsByLabel.get(previousLimit.label)) !== null && _a !== void 0 ? _a : previousLimit; }),
|
|
25568
|
+
...latestSubscriptionUsage.limits.filter((latestLimit) => !previousLimitLabels.has(latestLimit.label)),
|
|
25569
|
+
],
|
|
25570
|
+
};
|
|
25571
|
+
}
|
|
25572
|
+
|
|
25550
25573
|
/**
|
|
25551
25574
|
* Base delimiter used for passing large prompts through stdin.
|
|
25552
25575
|
*/
|
|
@@ -25880,6 +25903,122 @@ function parseClaudeCodeJsonOutput(output) {
|
|
|
25880
25903
|
}
|
|
25881
25904
|
}
|
|
25882
25905
|
|
|
25906
|
+
/**
|
|
25907
|
+
* Human-readable labels for known Claude Code rolling subscription-limit windows.
|
|
25908
|
+
*/
|
|
25909
|
+
const CLAUDE_CODE_RATE_LIMIT_LABELS = {
|
|
25910
|
+
five_hour: '5h',
|
|
25911
|
+
seven_day: '7d',
|
|
25912
|
+
seven_day_opus: '7d Opus',
|
|
25913
|
+
seven_day_sonnet: '7d Sonnet',
|
|
25914
|
+
overage: 'Overage',
|
|
25915
|
+
};
|
|
25916
|
+
/**
|
|
25917
|
+
* Fallback label used when a newer Claude Code release adds a rate-limit window unknown to Promptbook.
|
|
25918
|
+
*/
|
|
25919
|
+
const UNKNOWN_CLAUDE_CODE_RATE_LIMIT_LABEL = 'Limit';
|
|
25920
|
+
/**
|
|
25921
|
+
* Converts Claude Code stream-json rate-limit events into the shared subscription-usage snapshot.
|
|
25922
|
+
*
|
|
25923
|
+
* Claude Code can emit one event for each applicable limit, so the parser preserves every distinct window instead of
|
|
25924
|
+
* assuming the familiar 5-hour and seven-day pair. This lets a new vendor-side limit appear in the dashboard without
|
|
25925
|
+
* changing its renderer.
|
|
25926
|
+
*
|
|
25927
|
+
* @private internal utility of the Claude Code runner
|
|
25928
|
+
*/
|
|
25929
|
+
function parseClaudeCodeSubscriptionUsage(output) {
|
|
25930
|
+
const limitsByType = new Map();
|
|
25931
|
+
for (const event of parseClaudeCodeOutputEvents(output)) {
|
|
25932
|
+
const limit = parseClaudeCodeSubscriptionUsageLimit(event);
|
|
25933
|
+
if (limit) {
|
|
25934
|
+
limitsByType.set(resolveClaudeCodeRateLimitType(event), limit);
|
|
25935
|
+
}
|
|
25936
|
+
}
|
|
25937
|
+
const limits = [...limitsByType.values()];
|
|
25938
|
+
return limits.length === 0 ? undefined : { limits };
|
|
25939
|
+
}
|
|
25940
|
+
/**
|
|
25941
|
+
* Parses one Claude Code stream event when it contains a usable subscription-limit update.
|
|
25942
|
+
*
|
|
25943
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
25944
|
+
*/
|
|
25945
|
+
function parseClaudeCodeSubscriptionUsageLimit(event) {
|
|
25946
|
+
if (event.type !== 'rate_limit_event' || !event.rate_limit_info) {
|
|
25947
|
+
return undefined;
|
|
25948
|
+
}
|
|
25949
|
+
const rateLimitType = resolveClaudeCodeRateLimitType(event);
|
|
25950
|
+
const usedPercentage = resolveClaudeCodeUsedPercentage(event.rate_limit_info);
|
|
25951
|
+
if (usedPercentage === undefined) {
|
|
25952
|
+
return undefined;
|
|
25953
|
+
}
|
|
25954
|
+
const resetsAt = resolveClaudeCodeResetTimestamp(event.rate_limit_info);
|
|
25955
|
+
return {
|
|
25956
|
+
label: formatClaudeCodeRateLimitTypeLabel(rateLimitType),
|
|
25957
|
+
usedPercentage,
|
|
25958
|
+
...(resetsAt !== undefined && { resetsAt }),
|
|
25959
|
+
};
|
|
25960
|
+
}
|
|
25961
|
+
/**
|
|
25962
|
+
* Formats known and newly introduced Claude Code rate-limit types for a stable terminal label.
|
|
25963
|
+
*
|
|
25964
|
+
* A newer limit type remains distinct in the dashboard instead of making several unrelated windows all appear as
|
|
25965
|
+
* a generic `Limit` row.
|
|
25966
|
+
*
|
|
25967
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
25968
|
+
*/
|
|
25969
|
+
function formatClaudeCodeRateLimitTypeLabel(rateLimitType) {
|
|
25970
|
+
const knownLabel = CLAUDE_CODE_RATE_LIMIT_LABELS[rateLimitType];
|
|
25971
|
+
if (knownLabel) {
|
|
25972
|
+
return knownLabel;
|
|
25973
|
+
}
|
|
25974
|
+
const words = rateLimitType.split(/[\s_-]+/u).filter(Boolean);
|
|
25975
|
+
if (words.length === 0) {
|
|
25976
|
+
return UNKNOWN_CLAUDE_CODE_RATE_LIMIT_LABEL;
|
|
25977
|
+
}
|
|
25978
|
+
return words.map((word) => `${word.slice(0, 1).toUpperCase()}${word.slice(1)}`).join(' ');
|
|
25979
|
+
}
|
|
25980
|
+
/**
|
|
25981
|
+
* Resolves the stable rate-limit type key emitted by Claude Code across camel- and snake-case payload versions.
|
|
25982
|
+
*
|
|
25983
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
25984
|
+
*/
|
|
25985
|
+
function resolveClaudeCodeRateLimitType(event) {
|
|
25986
|
+
var _a;
|
|
25987
|
+
const rateLimitInfo = event.rate_limit_info;
|
|
25988
|
+
const rateLimitType = (_a = rateLimitInfo === null || rateLimitInfo === void 0 ? void 0 : rateLimitInfo.rateLimitType) !== null && _a !== void 0 ? _a : rateLimitInfo === null || rateLimitInfo === void 0 ? void 0 : rateLimitInfo.rate_limit_type;
|
|
25989
|
+
return typeof rateLimitType === 'string' && rateLimitType.trim() !== ''
|
|
25990
|
+
? rateLimitType.trim()
|
|
25991
|
+
: UNKNOWN_CLAUDE_CODE_RATE_LIMIT_LABEL;
|
|
25992
|
+
}
|
|
25993
|
+
/**
|
|
25994
|
+
* Converts Claude's fractional utilization value to a displayed percentage.
|
|
25995
|
+
*
|
|
25996
|
+
* A rejected limit event may omit utilization, but it still conveys a precise zero-remaining state and is therefore
|
|
25997
|
+
* rendered as 100 percent consumed.
|
|
25998
|
+
*
|
|
25999
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
26000
|
+
*/
|
|
26001
|
+
function resolveClaudeCodeUsedPercentage(rateLimitInfo) {
|
|
26002
|
+
const utilization = rateLimitInfo.utilization;
|
|
26003
|
+
if (typeof utilization === 'number' && Number.isFinite(utilization)) {
|
|
26004
|
+
const usedPercentage = utilization <= 1 ? utilization * 100 : utilization;
|
|
26005
|
+
if (usedPercentage >= 0 && usedPercentage <= 100) {
|
|
26006
|
+
return usedPercentage;
|
|
26007
|
+
}
|
|
26008
|
+
}
|
|
26009
|
+
return rateLimitInfo.status === 'rejected' ? 100 : undefined;
|
|
26010
|
+
}
|
|
26011
|
+
/**
|
|
26012
|
+
* Reads Claude's optional Unix reset timestamp from either current or earlier stream payload naming.
|
|
26013
|
+
*
|
|
26014
|
+
* @private helper of `parseClaudeCodeSubscriptionUsage`
|
|
26015
|
+
*/
|
|
26016
|
+
function resolveClaudeCodeResetTimestamp(rateLimitInfo) {
|
|
26017
|
+
var _a;
|
|
26018
|
+
const resetsAt = (_a = rateLimitInfo.resetsAt) !== null && _a !== void 0 ? _a : rateLimitInfo.resets_at;
|
|
26019
|
+
return typeof resetsAt === 'number' && Number.isFinite(resetsAt) && resetsAt > 0 ? resetsAt : undefined;
|
|
26020
|
+
}
|
|
26021
|
+
|
|
25883
26022
|
/**
|
|
25884
26023
|
* Polling interval used while waiting for Claude Code session limits to reset.
|
|
25885
26024
|
*/
|
|
@@ -25895,6 +26034,14 @@ class ClaudeCodeRunner {
|
|
|
25895
26034
|
this.options = options;
|
|
25896
26035
|
this.name = 'claude-code';
|
|
25897
26036
|
}
|
|
26037
|
+
/**
|
|
26038
|
+
* Returns the latest subscription-limit snapshot emitted by this Claude Code session.
|
|
26039
|
+
*
|
|
26040
|
+
* Claude exposes these values in the normal stream after a response, so no separate quota-only model call is made.
|
|
26041
|
+
*/
|
|
26042
|
+
async getSubscriptionUsage() {
|
|
26043
|
+
return this.subscriptionUsage;
|
|
26044
|
+
}
|
|
25898
26045
|
/**
|
|
25899
26046
|
* Runs the prompt using Claude Code and parses usage output.
|
|
25900
26047
|
*/
|
|
@@ -25908,6 +26055,7 @@ class ClaudeCodeRunner {
|
|
|
25908
26055
|
prompt,
|
|
25909
26056
|
resumeSessionId,
|
|
25910
26057
|
}).catch(async (error) => {
|
|
26058
|
+
this.updateSubscriptionUsage(error instanceof Error ? error.message : String(error));
|
|
25911
26059
|
const sessionLimit = extractClaudeCodeSessionLimitFromError(error);
|
|
25912
26060
|
if (!sessionLimit) {
|
|
25913
26061
|
throw error;
|
|
@@ -25922,6 +26070,7 @@ class ClaudeCodeRunner {
|
|
|
25922
26070
|
continue;
|
|
25923
26071
|
}
|
|
25924
26072
|
const sessionLimit = extractClaudeCodeSessionLimitFromOutput(output);
|
|
26073
|
+
this.updateSubscriptionUsage(output);
|
|
25925
26074
|
if (sessionLimit) {
|
|
25926
26075
|
resurrectionCount++;
|
|
25927
26076
|
await waitForClaudeCodeSessionLimitReset(sessionLimit, resurrectionCount, options);
|
|
@@ -25951,6 +26100,18 @@ class ClaudeCodeRunner {
|
|
|
25951
26100
|
preserveArtifactsOnSuccess: options.preserveArtifactsOnSuccess,
|
|
25952
26101
|
});
|
|
25953
26102
|
}
|
|
26103
|
+
/**
|
|
26104
|
+
* Keeps the newest usable Claude subscription-limit values reported by the stream.
|
|
26105
|
+
*
|
|
26106
|
+
* A stream can omit these values for API-key users or unsupported plan types; retaining the prior snapshot avoids
|
|
26107
|
+
* a temporary omission erasing a still-valid dashboard value while a long queue is running.
|
|
26108
|
+
*/
|
|
26109
|
+
updateSubscriptionUsage(output) {
|
|
26110
|
+
const subscriptionUsage = parseClaudeCodeSubscriptionUsage(output);
|
|
26111
|
+
if (subscriptionUsage) {
|
|
26112
|
+
this.subscriptionUsage = mergeHarnessSubscriptionUsage(this.subscriptionUsage, subscriptionUsage);
|
|
26113
|
+
}
|
|
26114
|
+
}
|
|
25954
26115
|
}
|
|
25955
26116
|
/**
|
|
25956
26117
|
* Waits until the Claude Code session can be resumed, keeping terminal status clear.
|
|
@@ -26858,6 +27019,292 @@ function resolveCodexPricing(modelName) {
|
|
|
26858
27019
|
return (_c = OPENAI_MODELS.find((model) => model.modelName === CODEX_FALLBACK_PRICING_MODEL)) === null || _c === void 0 ? void 0 : _c.pricing;
|
|
26859
27020
|
}
|
|
26860
27021
|
|
|
27022
|
+
/**
|
|
27023
|
+
* Arguments which start the Codex app server over JSON-RPC stdio.
|
|
27024
|
+
*/
|
|
27025
|
+
const CODEX_APP_SERVER_ARGUMENTS = ['app-server', '--stdio'];
|
|
27026
|
+
/**
|
|
27027
|
+
* Maximum time spent waiting for the optional Codex account-rate-limit snapshot.
|
|
27028
|
+
*/
|
|
27029
|
+
const CODEX_SUBSCRIPTION_USAGE_REQUEST_TIMEOUT_MS = 10 * 1000;
|
|
27030
|
+
/**
|
|
27031
|
+
* JSON-RPC request id used for the required app-server initialization handshake.
|
|
27032
|
+
*/
|
|
27033
|
+
const INITIALIZE_REQUEST_ID = 1;
|
|
27034
|
+
/**
|
|
27035
|
+
* JSON-RPC request id used for the Codex account rate-limit snapshot.
|
|
27036
|
+
*/
|
|
27037
|
+
const RATE_LIMITS_REQUEST_ID = 2;
|
|
27038
|
+
/**
|
|
27039
|
+
* Client name sent to the Codex app server while Promptbook reads subscription limits.
|
|
27040
|
+
*/
|
|
27041
|
+
const PROMPTBOOK_CODEX_APP_SERVER_CLIENT_NAME = 'ptbk-coder';
|
|
27042
|
+
/**
|
|
27043
|
+
* Client version sent to the Codex app server while Promptbook reads subscription limits.
|
|
27044
|
+
*
|
|
27045
|
+
* The app-server protocol only requires a non-empty version string, and the CLI's own version is intentionally not
|
|
27046
|
+
* coupled to Promptbook's package version.
|
|
27047
|
+
*/
|
|
27048
|
+
const PROMPTBOOK_CODEX_APP_SERVER_CLIENT_VERSION = '1';
|
|
27049
|
+
/**
|
|
27050
|
+
* Reads the current Codex subscription limit windows through its local app-server protocol.
|
|
27051
|
+
*
|
|
27052
|
+
* Codex versions without this optional protocol, API-key sessions, and transient account failures simply provide no
|
|
27053
|
+
* snapshot. Subscription usage is contextual UI information, so it must never make a coding prompt fail.
|
|
27054
|
+
*
|
|
27055
|
+
* @private internal utility of the OpenAI Codex runner
|
|
27056
|
+
*/
|
|
27057
|
+
async function getCodexSubscriptionUsage(codexCommand) {
|
|
27058
|
+
try {
|
|
27059
|
+
return await requestCodexSubscriptionUsage(codexCommand);
|
|
27060
|
+
}
|
|
27061
|
+
catch (_a) {
|
|
27062
|
+
return undefined;
|
|
27063
|
+
}
|
|
27064
|
+
}
|
|
27065
|
+
/**
|
|
27066
|
+
* Performs the small Codex app-server JSON-RPC exchange which reads rate limits.
|
|
27067
|
+
*
|
|
27068
|
+
* The app server requires a completed `initialize` handshake before it accepts account methods. Closing standard input
|
|
27069
|
+
* after the response lets the short-lived helper exit without sharing the lifecycle of the coding process itself.
|
|
27070
|
+
*
|
|
27071
|
+
* @private helper of `getCodexSubscriptionUsage`
|
|
27072
|
+
*/
|
|
27073
|
+
function requestCodexSubscriptionUsage(codexCommand) {
|
|
27074
|
+
return new Promise((resolve) => {
|
|
27075
|
+
const codexAppServerProcess = spawn(codexCommand, CODEX_APP_SERVER_ARGUMENTS, {
|
|
27076
|
+
shell: process.platform === 'win32',
|
|
27077
|
+
stdio: 'pipe',
|
|
27078
|
+
});
|
|
27079
|
+
const outputReader = createInterface({ input: codexAppServerProcess.stdout });
|
|
27080
|
+
let requestTimeout;
|
|
27081
|
+
let isSettled = false;
|
|
27082
|
+
// Draining stderr prevents a protocol diagnostic from blocking the short-lived child process. The response is
|
|
27083
|
+
// deliberately not surfaced because missing usage must not distract from an otherwise healthy coding run.
|
|
27084
|
+
codexAppServerProcess.stderr.resume();
|
|
27085
|
+
codexAppServerProcess.stdin.on('error', () => undefined);
|
|
27086
|
+
/**
|
|
27087
|
+
* Stops the helper process and settles this optional request exactly once.
|
|
27088
|
+
*/
|
|
27089
|
+
const settle = (subscriptionUsage) => {
|
|
27090
|
+
if (isSettled) {
|
|
27091
|
+
return;
|
|
27092
|
+
}
|
|
27093
|
+
isSettled = true;
|
|
27094
|
+
if (requestTimeout) {
|
|
27095
|
+
clearTimeout(requestTimeout);
|
|
27096
|
+
}
|
|
27097
|
+
outputReader.close();
|
|
27098
|
+
codexAppServerProcess.stdin.end();
|
|
27099
|
+
if (codexAppServerProcess.exitCode === null && !codexAppServerProcess.killed) {
|
|
27100
|
+
codexAppServerProcess.kill();
|
|
27101
|
+
}
|
|
27102
|
+
resolve(subscriptionUsage);
|
|
27103
|
+
};
|
|
27104
|
+
/**
|
|
27105
|
+
* Sends one JSON-RPC message to the Codex app server.
|
|
27106
|
+
*/
|
|
27107
|
+
const sendJsonRpcMessage = (message) => {
|
|
27108
|
+
try {
|
|
27109
|
+
codexAppServerProcess.stdin.write(`${JSON.stringify(message)}\n`);
|
|
27110
|
+
}
|
|
27111
|
+
catch (_a) {
|
|
27112
|
+
settle(undefined);
|
|
27113
|
+
}
|
|
27114
|
+
};
|
|
27115
|
+
requestTimeout = setTimeout(() => settle(undefined), CODEX_SUBSCRIPTION_USAGE_REQUEST_TIMEOUT_MS);
|
|
27116
|
+
codexAppServerProcess.on('error', () => settle(undefined));
|
|
27117
|
+
codexAppServerProcess.on('close', () => settle(undefined));
|
|
27118
|
+
outputReader.on('line', (line) => {
|
|
27119
|
+
const response = parseJsonRecord(line);
|
|
27120
|
+
if (!response) {
|
|
27121
|
+
return;
|
|
27122
|
+
}
|
|
27123
|
+
if (response.id === INITIALIZE_REQUEST_ID) {
|
|
27124
|
+
if (!isJsonRecord(response.result)) {
|
|
27125
|
+
settle(undefined);
|
|
27126
|
+
return;
|
|
27127
|
+
}
|
|
27128
|
+
sendJsonRpcMessage({ jsonrpc: '2.0', method: 'initialized' });
|
|
27129
|
+
sendJsonRpcMessage({
|
|
27130
|
+
jsonrpc: '2.0',
|
|
27131
|
+
id: RATE_LIMITS_REQUEST_ID,
|
|
27132
|
+
method: 'account/rateLimits/read',
|
|
27133
|
+
});
|
|
27134
|
+
return;
|
|
27135
|
+
}
|
|
27136
|
+
if (response.id === RATE_LIMITS_REQUEST_ID) {
|
|
27137
|
+
settle(buildCodexSubscriptionUsage(response.result));
|
|
27138
|
+
}
|
|
27139
|
+
});
|
|
27140
|
+
sendJsonRpcMessage({
|
|
27141
|
+
jsonrpc: '2.0',
|
|
27142
|
+
id: INITIALIZE_REQUEST_ID,
|
|
27143
|
+
method: 'initialize',
|
|
27144
|
+
params: {
|
|
27145
|
+
clientInfo: {
|
|
27146
|
+
name: PROMPTBOOK_CODEX_APP_SERVER_CLIENT_NAME,
|
|
27147
|
+
version: PROMPTBOOK_CODEX_APP_SERVER_CLIENT_VERSION,
|
|
27148
|
+
},
|
|
27149
|
+
capabilities: {
|
|
27150
|
+
experimentalApi: true,
|
|
27151
|
+
},
|
|
27152
|
+
},
|
|
27153
|
+
});
|
|
27154
|
+
});
|
|
27155
|
+
}
|
|
27156
|
+
/**
|
|
27157
|
+
* Converts the raw Codex app-server rate-limit response into Promptbook's harness-neutral subscription snapshot.
|
|
27158
|
+
*
|
|
27159
|
+
* Modern Codex versions can report several metered buckets, while older versions expose a single compatibility
|
|
27160
|
+
* bucket. In both cases every primary and secondary rolling window is preserved for the terminal dashboard.
|
|
27161
|
+
*
|
|
27162
|
+
* @private internal utility of the OpenAI Codex runner
|
|
27163
|
+
*/
|
|
27164
|
+
function buildCodexSubscriptionUsage(response) {
|
|
27165
|
+
if (!isJsonRecord(response)) {
|
|
27166
|
+
return undefined;
|
|
27167
|
+
}
|
|
27168
|
+
const snapshots = resolveCodexRateLimitSnapshots(response);
|
|
27169
|
+
const hasMultipleSnapshots = snapshots.length > 1;
|
|
27170
|
+
const limits = snapshots.flatMap(({ identifier, value }) => buildCodexSubscriptionUsageLimits({
|
|
27171
|
+
snapshot: value,
|
|
27172
|
+
identifier,
|
|
27173
|
+
hasMultipleSnapshots,
|
|
27174
|
+
}));
|
|
27175
|
+
return limits.length === 0 ? undefined : { limits };
|
|
27176
|
+
}
|
|
27177
|
+
/**
|
|
27178
|
+
* Resolves either Codex's multi-bucket rate-limit response or its compatible single-bucket fallback.
|
|
27179
|
+
*
|
|
27180
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
27181
|
+
*/
|
|
27182
|
+
function resolveCodexRateLimitSnapshots(response) {
|
|
27183
|
+
const rateLimitsByLimitId = response.rateLimitsByLimitId;
|
|
27184
|
+
if (isJsonRecord(rateLimitsByLimitId)) {
|
|
27185
|
+
const snapshots = Object.entries(rateLimitsByLimitId)
|
|
27186
|
+
.filter(([, value]) => isJsonRecord(value))
|
|
27187
|
+
.map(([identifier, value]) => ({ identifier, value: value }));
|
|
27188
|
+
if (snapshots.length > 0) {
|
|
27189
|
+
return snapshots;
|
|
27190
|
+
}
|
|
27191
|
+
}
|
|
27192
|
+
return isJsonRecord(response.rateLimits) ? [{ value: response.rateLimits }] : [];
|
|
27193
|
+
}
|
|
27194
|
+
/**
|
|
27195
|
+
* Creates every displayed rolling window from one Codex rate-limit bucket.
|
|
27196
|
+
*
|
|
27197
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
27198
|
+
*/
|
|
27199
|
+
function buildCodexSubscriptionUsageLimits(options) {
|
|
27200
|
+
var _a;
|
|
27201
|
+
const { snapshot, identifier, hasMultipleSnapshots } = options;
|
|
27202
|
+
const bucketLabel = hasMultipleSnapshots ? (_a = readString(snapshot.limitName)) !== null && _a !== void 0 ? _a : identifier : undefined;
|
|
27203
|
+
return [
|
|
27204
|
+
buildCodexSubscriptionUsageLimit(snapshot.primary, bucketLabel, 'Primary'),
|
|
27205
|
+
buildCodexSubscriptionUsageLimit(snapshot.secondary, bucketLabel, 'Secondary'),
|
|
27206
|
+
].filter((limit) => limit !== undefined);
|
|
27207
|
+
}
|
|
27208
|
+
/**
|
|
27209
|
+
* Converts one raw Codex rate-limit window to a displayable subscription usage limit.
|
|
27210
|
+
*
|
|
27211
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
27212
|
+
*/
|
|
27213
|
+
function buildCodexSubscriptionUsageLimit(rawWindow, bucketLabel, fallbackWindowLabel) {
|
|
27214
|
+
var _a;
|
|
27215
|
+
if (!isJsonRecord(rawWindow)) {
|
|
27216
|
+
return undefined;
|
|
27217
|
+
}
|
|
27218
|
+
const usedPercentage = readPercentage(rawWindow.usedPercent);
|
|
27219
|
+
if (usedPercentage === undefined) {
|
|
27220
|
+
return undefined;
|
|
27221
|
+
}
|
|
27222
|
+
const windowLabel = (_a = formatCodexRateLimitWindowDuration(rawWindow.windowDurationMins)) !== null && _a !== void 0 ? _a : fallbackWindowLabel;
|
|
27223
|
+
const label = bucketLabel ? `${bucketLabel} ${windowLabel}` : windowLabel;
|
|
27224
|
+
const resetsAt = readUnixTimestamp(rawWindow.resetsAt);
|
|
27225
|
+
return {
|
|
27226
|
+
label,
|
|
27227
|
+
usedPercentage,
|
|
27228
|
+
...(resetsAt !== undefined && { resetsAt }),
|
|
27229
|
+
};
|
|
27230
|
+
}
|
|
27231
|
+
/**
|
|
27232
|
+
* Formats the duration of one Codex rolling rate-limit window for compact terminal display.
|
|
27233
|
+
*
|
|
27234
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
27235
|
+
*/
|
|
27236
|
+
function formatCodexRateLimitWindowDuration(value) {
|
|
27237
|
+
const durationMinutes = readNonNegativeFiniteNumber(value);
|
|
27238
|
+
if (durationMinutes === undefined || durationMinutes === 0) {
|
|
27239
|
+
return undefined;
|
|
27240
|
+
}
|
|
27241
|
+
const MINUTES_PER_HOUR = 60;
|
|
27242
|
+
const MINUTES_PER_DAY = 24 * MINUTES_PER_HOUR;
|
|
27243
|
+
if (durationMinutes % MINUTES_PER_DAY === 0) {
|
|
27244
|
+
return `${durationMinutes / MINUTES_PER_DAY}d`;
|
|
27245
|
+
}
|
|
27246
|
+
if (durationMinutes % MINUTES_PER_HOUR === 0) {
|
|
27247
|
+
return `${durationMinutes / MINUTES_PER_HOUR}h`;
|
|
27248
|
+
}
|
|
27249
|
+
return `${durationMinutes}m`;
|
|
27250
|
+
}
|
|
27251
|
+
/**
|
|
27252
|
+
* Parses one JSON-RPC line when it is an object.
|
|
27253
|
+
*
|
|
27254
|
+
* @private helper of `getCodexSubscriptionUsage`
|
|
27255
|
+
*/
|
|
27256
|
+
function parseJsonRecord(line) {
|
|
27257
|
+
try {
|
|
27258
|
+
const value = JSON.parse(line);
|
|
27259
|
+
return isJsonRecord(value) ? value : undefined;
|
|
27260
|
+
}
|
|
27261
|
+
catch (_a) {
|
|
27262
|
+
return undefined;
|
|
27263
|
+
}
|
|
27264
|
+
}
|
|
27265
|
+
/**
|
|
27266
|
+
* Checks whether a value is a JSON object.
|
|
27267
|
+
*
|
|
27268
|
+
* @private helper of `getCodexSubscriptionUsage`
|
|
27269
|
+
*/
|
|
27270
|
+
function isJsonRecord(value) {
|
|
27271
|
+
return typeof value === 'object' && value !== null && !Array.isArray(value);
|
|
27272
|
+
}
|
|
27273
|
+
/**
|
|
27274
|
+
* Reads one non-empty string from an untrusted JSON object.
|
|
27275
|
+
*
|
|
27276
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
27277
|
+
*/
|
|
27278
|
+
function readString(value) {
|
|
27279
|
+
return typeof value === 'string' && value.trim() !== '' ? value.trim() : undefined;
|
|
27280
|
+
}
|
|
27281
|
+
/**
|
|
27282
|
+
* Reads a finite number which cannot be negative.
|
|
27283
|
+
*
|
|
27284
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
27285
|
+
*/
|
|
27286
|
+
function readNonNegativeFiniteNumber(value) {
|
|
27287
|
+
return typeof value === 'number' && Number.isFinite(value) && value >= 0 ? value : undefined;
|
|
27288
|
+
}
|
|
27289
|
+
/**
|
|
27290
|
+
* Reads a valid percentage from the Codex app-server response.
|
|
27291
|
+
*
|
|
27292
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
27293
|
+
*/
|
|
27294
|
+
function readPercentage(value) {
|
|
27295
|
+
const percentage = readNonNegativeFiniteNumber(value);
|
|
27296
|
+
return percentage !== undefined && percentage <= 100 ? percentage : undefined;
|
|
27297
|
+
}
|
|
27298
|
+
/**
|
|
27299
|
+
* Reads a Unix timestamp in seconds from the Codex app-server response.
|
|
27300
|
+
*
|
|
27301
|
+
* @private helper of `buildCodexSubscriptionUsage`
|
|
27302
|
+
*/
|
|
27303
|
+
function readUnixTimestamp(value) {
|
|
27304
|
+
const timestamp = readNonNegativeFiniteNumber(value);
|
|
27305
|
+
return timestamp !== undefined && timestamp > 0 ? timestamp : undefined;
|
|
27306
|
+
}
|
|
27307
|
+
|
|
26861
27308
|
/**
|
|
26862
27309
|
* Detects which login method Codex used from the captured CLI output.
|
|
26863
27310
|
*
|
|
@@ -27045,6 +27492,15 @@ class OpenAiCodexRunner {
|
|
|
27045
27492
|
jitterRatio: RATE_LIMIT_BACKOFF_JITTER_RATIO,
|
|
27046
27493
|
});
|
|
27047
27494
|
}
|
|
27495
|
+
/**
|
|
27496
|
+
* Reads the current ChatGPT subscription quota snapshot through Codex's local app server.
|
|
27497
|
+
*
|
|
27498
|
+
* API-key sessions and unsupported Codex versions simply return no snapshot, which keeps this optional dashboard
|
|
27499
|
+
* information from affecting the actual prompt execution.
|
|
27500
|
+
*/
|
|
27501
|
+
async getSubscriptionUsage() {
|
|
27502
|
+
return await getCodexSubscriptionUsage(this.options.codexCommand);
|
|
27503
|
+
}
|
|
27048
27504
|
/**
|
|
27049
27505
|
* Runs the Codex prompt in a temporary script and waits for completion output.
|
|
27050
27506
|
*/
|
|
@@ -27624,6 +28080,90 @@ function assertPriorityBoundary(optionName, priority) {
|
|
|
27624
28080
|
}
|
|
27625
28081
|
// Note: [🟡] Code for priority filtering [priorityFilter](scripts/run-codex-prompts/prompts/priorityFilter.ts) should never be published outside of `@promptbook/cli`
|
|
27626
28082
|
|
|
28083
|
+
/**
|
|
28084
|
+
* Label shown on the first subscription-usage row in the terminal Session box.
|
|
28085
|
+
*/
|
|
28086
|
+
const SUBSCRIPTION_USAGE_SESSION_LABEL = 'Usage';
|
|
28087
|
+
/**
|
|
28088
|
+
* Number of milliseconds in one calendar-free day used for compact reset countdowns.
|
|
28089
|
+
*/
|
|
28090
|
+
const ONE_DAY_MS = 24 * 60 * 60 * 1000;
|
|
28091
|
+
/**
|
|
28092
|
+
* Builds every available subscription-limit row for the terminal Session box.
|
|
28093
|
+
*
|
|
28094
|
+
* The first row carries the `Usage` label and subsequent rows stay aligned beneath it, allowing a harness to report
|
|
28095
|
+
* any number of windows instead of making the dashboard assume a fixed 5-hour/weekly pair.
|
|
28096
|
+
*
|
|
28097
|
+
* @private internal utility of coder run UI
|
|
28098
|
+
*/
|
|
28099
|
+
function buildSubscriptionUsageSessionRows(subscriptionUsage, currentTimeMs = Date.now()) {
|
|
28100
|
+
if (!subscriptionUsage || subscriptionUsage.limits.length === 0) {
|
|
28101
|
+
return [];
|
|
28102
|
+
}
|
|
28103
|
+
return subscriptionUsage.limits.map((limit, index) => ({
|
|
28104
|
+
label: index === 0 ? SUBSCRIPTION_USAGE_SESSION_LABEL : '',
|
|
28105
|
+
value: formatSubscriptionUsageLimit(limit, currentTimeMs),
|
|
28106
|
+
}));
|
|
28107
|
+
}
|
|
28108
|
+
/**
|
|
28109
|
+
* Formats one remaining subscription limit and its optional reset time for the terminal dashboard.
|
|
28110
|
+
*
|
|
28111
|
+
* @private helper of `buildSubscriptionUsageSessionRows`
|
|
28112
|
+
*/
|
|
28113
|
+
function formatSubscriptionUsageLimit(limit, currentTimeMs) {
|
|
28114
|
+
const remainingPercentage = Math.round(Math.max(0, Math.min(100, 100 - limit.usedPercentage)));
|
|
28115
|
+
const remainingPercentageColor = resolveRemainingPercentageColor(remainingPercentage);
|
|
28116
|
+
const resetText = formatSubscriptionUsageReset(limit.resetsAt, currentTimeMs);
|
|
28117
|
+
const label = limit.label.trim() || SUBSCRIPTION_USAGE_SESSION_LABEL;
|
|
28118
|
+
return [
|
|
28119
|
+
colors.bold(label),
|
|
28120
|
+
remainingPercentageColor(`${remainingPercentage}% remaining`),
|
|
28121
|
+
...(resetText ? [colors.gray(resetText)] : []),
|
|
28122
|
+
].join(' · ');
|
|
28123
|
+
}
|
|
28124
|
+
/**
|
|
28125
|
+
* Chooses an attention color for the remaining subscription percentage.
|
|
28126
|
+
*
|
|
28127
|
+
* @private helper of `buildSubscriptionUsageSessionRows`
|
|
28128
|
+
*/
|
|
28129
|
+
function resolveRemainingPercentageColor(remainingPercentage) {
|
|
28130
|
+
if (remainingPercentage === 0) {
|
|
28131
|
+
return colors.red;
|
|
28132
|
+
}
|
|
28133
|
+
if (remainingPercentage <= 20) {
|
|
28134
|
+
return colors.yellow;
|
|
28135
|
+
}
|
|
28136
|
+
return colors.green;
|
|
28137
|
+
}
|
|
28138
|
+
/**
|
|
28139
|
+
* Formats the optional future reset timestamp of one subscription limit.
|
|
28140
|
+
*
|
|
28141
|
+
* @private helper of `buildSubscriptionUsageSessionRows`
|
|
28142
|
+
*/
|
|
28143
|
+
function formatSubscriptionUsageReset(resetsAt, currentTimeMs) {
|
|
28144
|
+
if (resetsAt === undefined) {
|
|
28145
|
+
return undefined;
|
|
28146
|
+
}
|
|
28147
|
+
const remainingDurationMs = resetsAt * 1000 - currentTimeMs;
|
|
28148
|
+
if (remainingDurationMs <= 0) {
|
|
28149
|
+
return 'resets now';
|
|
28150
|
+
}
|
|
28151
|
+
return `resets in ${formatSubscriptionUsageDuration(remainingDurationMs)}`;
|
|
28152
|
+
}
|
|
28153
|
+
/**
|
|
28154
|
+
* Formats a reset countdown with days when a weekly-style limit is still several days away.
|
|
28155
|
+
*
|
|
28156
|
+
* @private helper of `buildSubscriptionUsageSessionRows`
|
|
28157
|
+
*/
|
|
28158
|
+
function formatSubscriptionUsageDuration(durationMs) {
|
|
28159
|
+
const dayCount = Math.floor(durationMs / ONE_DAY_MS);
|
|
28160
|
+
if (dayCount === 0) {
|
|
28161
|
+
return formatDurationMs(durationMs);
|
|
28162
|
+
}
|
|
28163
|
+
const remainingDurationMs = durationMs - dayCount * ONE_DAY_MS;
|
|
28164
|
+
return remainingDurationMs === 0 ? `${dayCount}d` : `${dayCount}d ${formatDurationMs(remainingDurationMs)}`;
|
|
28165
|
+
}
|
|
28166
|
+
|
|
27627
28167
|
/**
|
|
27628
28168
|
* Minimum width used for the rich coder-run frame.
|
|
27629
28169
|
*/
|
|
@@ -27729,6 +28269,7 @@ function buildSessionRows(options, bodyWidth, pausePresentation) {
|
|
|
27729
28269
|
value: runnerParts.join(' · '),
|
|
27730
28270
|
},
|
|
27731
28271
|
...configurationRows,
|
|
28272
|
+
...buildSubscriptionUsageSessionRows(options.subscriptionUsage),
|
|
27732
28273
|
...buildScriptPathSessionRows(options.currentScriptPaths || [], bodyWidth),
|
|
27733
28274
|
{
|
|
27734
28275
|
label: 'This run',
|
|
@@ -28274,6 +28815,13 @@ class CoderRunUiState extends EventEmitter {
|
|
|
28274
28815
|
this.agentStatusTableRows = [...agentStatusTableRows];
|
|
28275
28816
|
this.emitChange();
|
|
28276
28817
|
}
|
|
28818
|
+
/**
|
|
28819
|
+
* Replaces the optional harness subscription usage shown in the Session box.
|
|
28820
|
+
*/
|
|
28821
|
+
setSubscriptionUsage(subscriptionUsage) {
|
|
28822
|
+
this.subscriptionUsage = subscriptionUsage;
|
|
28823
|
+
this.emitChange();
|
|
28824
|
+
}
|
|
28277
28825
|
/**
|
|
28278
28826
|
* Sets or clears the Enter-key action label shown in the controls panel.
|
|
28279
28827
|
*/
|
|
@@ -29265,6 +29813,7 @@ function renderCoderRunUi(startTime, options = {}) {
|
|
|
29265
29813
|
pauseTargetLabel: getPauseTargetLabel(),
|
|
29266
29814
|
isEndAfterCurrentPromptRequested: getEndAfterCurrentPromptState(),
|
|
29267
29815
|
config: state.config,
|
|
29816
|
+
subscriptionUsage: state.subscriptionUsage,
|
|
29268
29817
|
agentVisual: state.agentVisual,
|
|
29269
29818
|
phase: state.phase,
|
|
29270
29819
|
currentPromptLabel: state.currentPromptLabel,
|
|
@@ -72041,48 +72590,6 @@ function $shuffleItems(...items) {
|
|
|
72041
72590
|
}
|
|
72042
72591
|
// TODO: [🧠][👵] Figure out something between rotateItems and shuffleItems which is more generic and receives a ruleset how to reordeto the array in some general way
|
|
72043
72592
|
|
|
72044
|
-
/**
|
|
72045
|
-
* All possible emoji chars like "🍆", "🍡", "🍤"...
|
|
72046
|
-
* Note: this will be needed to update annually - now updated at 2022-01-19
|
|
72047
|
-
*
|
|
72048
|
-
* @see https://getemoji.com/
|
|
72049
|
-
*/
|
|
72050
|
-
const EMOJIS_IN_CATEGORIES = {
|
|
72051
|
-
Smileys: '😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳 😏 😒 😞 😔 😟 😕 🙁 ☹️ 😣 😖 😫 😩 🥺 😢 😭 😤 😠 😡 🤬 🤯 😳 🥵 🥶 😱 😨 😰 😥 😓 🤗 🤔 🤭 🤫 🤥 😶 😐 😑 😬 🙄 😯 😦 😧 😮 😲 🥱 😴 🤤 😪 😵 🤐 🥴 🤢 🤮 🤧 😷 🤒 🤕 🤑 🤠 😈 👿 👹 👺 🤡 💩 👻 💀 ☠️ 👽 👾 🤖 🎃 😺 😸 😹 😻 😼 😽 🙀 😿 😾'.split(' '),
|
|
72052
|
-
'Gestures and Body Parts': '👋 🤚 🖐 ✋ 🖖 👌 🤌 🤏 ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍 👎 ✊ 👊 🤛 🤜 👏 🙌 👐 🤲 🤝 🙏 ✍️ 💅 🤳 💪 🦾 🦵 🦿 🦶 👣 👂 🦻 👃 🫀 🫁 🧠 🦷 🦴 👀 👁 👅 👄 💋 🩸'.split(' '),
|
|
72053
|
-
'People and Fantasy': '👶 👧 🧒 👦 👩 🧑 👨 👩🦱 🧑🦱 👨🦱 👩🦰 🧑🦰 👨🦰 👱♀️ 👱 👱♂️ 👩🦳 🧑🦳 👨🦳 👩🦲 🧑🦲 👨🦲 🧔 👵 🧓 👴 👲 👳♀️ 👳 👳♂️ 🧕 👮♀️ 👮 👮♂️ 👷♀️ 👷 👷♂️ 💂♀️ 💂 💂♂️ 🕵️♀️ 🕵️ 🕵️♂️ 👩⚕️ 🧑⚕️ 👨⚕️ 👩🌾 🧑🌾 👨🌾 👩🍳 🧑🍳 👨🍳 👩🎓 🧑🎓 👨🎓 👩🎤 🧑🎤 👨🎤 👩🏫 🧑🏫 👨🏫 👩🏭 🧑🏭 👨🏭 👩💻 🧑💻 👨💻 👩💼 🧑💼 👨💼 👩🔧 🧑🔧 👨🔧 👩🔬 🧑🔬 👨🔬 👩🎨 🧑🎨 👨🎨 👩🚒 🧑🚒 👨🚒 👩✈️ 🧑✈️ 👨✈️ 👩🚀 🧑🚀 👨🚀 👩⚖️ 🧑⚖️ 👨⚖️ 👰♀️ 👰 👰♂️ 🤵♀️ 🤵 🤵♂️ 👸 🤴 🥷 🦸♀️ 🦸 🦸♂️ 🦹♀️ 🦹 🦹♂️ 🤶 🧑🎄 🎅 🧙♀️ 🧙 🧙♂️ 🧝♀️ 🧝 🧝♂️ 🧛♀️ 🧛 🧛♂️ 🧟♀️ 🧟 🧟♂️ 🧞♀️ 🧞 🧞♂️ 🧜♀️ 🧜 🧜♂️ 🧚♀️ 🧚 🧚♂️ 👼 🤰 🤱 👩🍼 🧑🍼 👨🍼 🙇♀️ 🙇 🙇♂️ 💁♀️ 💁 💁♂️ 🙅♀️ 🙅 🙅♂️ 🙆♀️ 🙆 🙆♂️ 🙋♀️ 🙋 🙋♂️ 🧏♀️ 🧏 🧏♂️ 🤦♀️ 🤦 🤦♂️ 🤷♀️ 🤷 🤷♂️ 🙎♀️ 🙎 🙎♂️ 🙍♀️ 🙍 🙍♂️ 💇♀️ 💇 💇♂️ 💆♀️ 💆 💆♂️ 🧖♀️ 🧖 🧖♂️ 💅 🤳 💃 🕺 👯♀️ 👯 👯♂️ 🕴 👩🦽 🧑🦽 👨🦽 👩🦼 🧑🦼 👨🦼 🚶♀️ 🚶 🚶♂️ 👩🦯 🧑🦯 👨🦯 🧎♀️ 🧎 🧎♂️ 🏃♀️ 🏃 🏃♂️ 🧍♀️ 🧍 🧍♂️ 👭 🧑🤝🧑 👬 👫 👩❤️👩 💑 👨❤️👨 👩❤️👨 👩❤️💋👩 💏 👨❤️💋👨 👩❤️💋👨 👪 👨👩👦 👨👩👧 👨👩👧👦 👨👩👦👦 👨👩👧👧 👨👨👦 👨👨👧 👨👨👧👦 👨👨👦👦 👨👨👧👧 👩👩👦 👩👩👧 👩👩👧👦 👩👩👦👦 👩👩👧👧 👨👦 👨👦👦 👨👧 👨👧👦 👨👧👧 👩👦 👩👦👦 👩👧 👩👧👦 👩👧👧 🗣 👤 👥 🫂'.split(' '),
|
|
72054
|
-
'Clothing and Accessories': '🧳 🌂 ☂️ 🧵 🪡 🪢 🧶 👓 🕶 🥽 🥼 🦺 👔 👕 👖 🧣 🧤 🧥 🧦 👗 👘 🥻 🩴 🩱 🩲 🩳 👙 👚 👛 👜 👝 🎒 👞 👟 🥾 🥿 👠 👡 🩰 👢 👑 👒 🎩 🎓 🧢 ⛑ 🪖 💄 💍 💼'.split(' '),
|
|
72055
|
-
'Pale Emojis': '👋🏻 🤚🏻 🖐🏻 ✋🏻 🖖🏻 👌🏻 🤌🏻 🤏🏻 ✌🏻 🤞🏻 🤟🏻 🤘🏻 🤙🏻 👈🏻 👉🏻 👆🏻 🖕🏻 👇🏻 ☝🏻 👍🏻 👎🏻 ✊🏻 👊🏻 🤛🏻 🤜🏻 👏🏻 🙌🏻 👐🏻 🤲🏻 🙏🏻 ✍🏻 💅🏻 🤳🏻 💪🏻 🦵🏻 🦶🏻 👂🏻 🦻🏻 👃🏻 👶🏻 👧🏻 🧒🏻 👦🏻 👩🏻 🧑🏻 👨🏻 👩🏻🦱 🧑🏻🦱 👨🏻🦱 👩🏻🦰 🧑🏻🦰 👨🏻🦰 👱🏻♀️ 👱🏻 👱🏻♂️ 👩🏻🦳 🧑🏻🦳 👨🏻🦳 👩🏻🦲 🧑🏻🦲 👨🏻🦲 🧔🏻 👵🏻 🧓🏻 👴🏻 👲🏻 👳🏻♀️ 👳🏻 👳🏻♂️ 🧕🏻 👮🏻♀️ 👮🏻 👮🏻♂️ 👷🏻♀️ 👷🏻 👷🏻♂️ 💂🏻♀️ 💂🏻 💂🏻♂️ 🕵🏻♀️ 🕵🏻 🕵🏻♂️ 👩🏻⚕️ 🧑🏻⚕️ 👨🏻⚕️ 👩🏻🌾 🧑🏻🌾 👨🏻🌾 👩🏻🍳 🧑🏻🍳 👨🏻🍳 👩🏻🎓 🧑🏻🎓 👨🏻🎓 👩🏻🎤 🧑🏻🎤 👨🏻🎤 👩🏻🏫 🧑🏻🏫 👨🏻🏫 👩🏻🏭 🧑🏻🏭 👨🏻🏭 👩🏻💻 🧑🏻💻 👨🏻💻 👩🏻💼 🧑🏻💼 👨🏻💼 👩🏻🔧 🧑🏻🔧 👨🏻🔧 👩🏻🔬 🧑🏻🔬 👨🏻🔬 👩🏻🎨 🧑🏻🎨 👨🏻🎨 👩🏻🚒 🧑🏻🚒 👨🏻🚒 👩🏻✈️ 🧑🏻✈️ 👨🏻✈️ 👩🏻🚀 🧑🏻🚀 👨🏻🚀 👩🏻⚖️ 🧑🏻⚖️ 👨🏻⚖️ 👰🏻♀️ 👰🏻 👰🏻♂️ 🤵🏻♀️ 🤵🏻 🤵🏻♂️ 👸🏻 🤴🏻 🥷🏻 🦸🏻♀️ 🦸🏻 🦸🏻♂️ 🦹🏻♀️ 🦹🏻 🦹🏻♂️ 🤶🏻 🧑🏻🎄 🎅🏻 🧙🏻♀️ 🧙🏻 🧙🏻♂️ 🧝🏻♀️ 🧝🏻 🧝🏻♂️ 🧛🏻♀️ 🧛🏻 🧛🏻♂️ 🧜🏻♀️ 🧜🏻 🧜🏻♂️ 🧚🏻♀️ 🧚🏻 🧚🏻♂️ 👼🏻 🤰🏻 🤱🏻 👩🏻🍼 🧑🏻🍼 👨🏻🍼 🙇🏻♀️ 🙇🏻 🙇🏻♂️ 💁🏻♀️ 💁🏻 💁🏻♂️ 🙅🏻♀️ 🙅🏻 🙅🏻♂️ 🙆🏻♀️ 🙆🏻 🙆🏻♂️ 🙋🏻♀️ 🙋🏻 🙋🏻♂️ 🧏🏻♀️ 🧏🏻 🧏🏻♂️ 🤦🏻♀️ 🤦🏻 🤦🏻♂️ 🤷🏻♀️ 🤷🏻 🤷🏻♂️ 🙎🏻♀️ 🙎🏻 🙎🏻♂️ 🙍🏻♀️ 🙍🏻 🙍🏻♂️ 💇🏻♀️ 💇🏻 💇🏻♂️ 💆🏻♀️ 💆🏻 💆🏻♂️ 🧖🏻♀️ 🧖🏻 🧖🏻♂️ 💃🏻 🕺🏻 🕴🏻 👩🏻🦽 🧑🏻🦽 👨🏻🦽 👩🏻🦼 🧑🏻🦼 👨🏻🦼 🚶🏻♀️ 🚶🏻 🚶🏻♂️ 👩🏻🦯 🧑🏻🦯 👨🏻🦯 🧎🏻♀️ 🧎🏻 🧎🏻♂️ 🏃🏻♀️ 🏃🏻 🏃🏻♂️ 🧍🏻♀️ 🧍🏻 🧍🏻♂️ 👭🏻 🧑🏻🤝🧑🏻 👬🏻 👫🏻 🧗🏻♀️ 🧗🏻 🧗🏻♂️ 🏇🏻 🏂🏻 🏌🏻♀️ 🏌🏻 🏌🏻♂️ 🏄🏻♀️ 🏄🏻 🏄🏻♂️ 🚣🏻♀️ 🚣🏻 🚣🏻♂️ 🏊🏻♀️ 🏊🏻 🏊🏻♂️ ⛹🏻♀️ ⛹🏻 ⛹🏻♂️ 🏋🏻♀️ 🏋🏻 🏋🏻♂️ 🚴🏻♀️ 🚴🏻 🚴🏻♂️ 🚵🏻♀️ 🚵🏻 🚵🏻♂️ 🤸🏻♀️ 🤸🏻 🤸🏻♂️ 🤽🏻♀️ 🤽🏻 🤽🏻♂️ 🤾🏻♀️ 🤾🏻 🤾🏻♂️ 🤹🏻♀️ 🤹🏻 🤹🏻♂️ 🧘🏻♀️ 🧘🏻 🧘🏻♂️ 🛀🏻 🛌'.split(' '),
|
|
72056
|
-
'Cream White Emojis': '👋🏼 🤚🏼 🖐🏼 ✋🏼 🖖🏼 👌🏼 🤌🏼 🤏🏼 ✌🏼 🤞🏼 🤟🏼 🤘🏼 🤙🏼 👈🏼 👉🏼 👆🏼 🖕🏼 👇🏼 ☝🏼 👍🏼 👎🏼 ✊🏼 👊🏼 🤛🏼 🤜🏼 👏🏼 🙌🏼 👐🏼 🤲🏼 🙏🏼 ✍🏼 💅🏼 🤳🏼 💪🏼 🦵🏼 🦶🏼 👂🏼 🦻🏼 👃🏼 👶🏼 👧🏼 🧒🏼 👦🏼 👩🏼 🧑🏼 👨🏼 👩🏼🦱 🧑🏼🦱 👨🏼🦱 👩🏼🦰 🧑🏼🦰 👨🏼🦰 👱🏼♀️ 👱🏼 👱🏼♂️ 👩🏼🦳 🧑🏼🦳 👨🏼🦳 👩🏼🦲 🧑🏼🦲 👨🏼🦲 🧔🏼 👵🏼 🧓🏼 👴🏼 👲🏼 👳🏼♀️ 👳🏼 👳🏼♂️ 🧕🏼 👮🏼♀️ 👮🏼 👮🏼♂️ 👷🏼♀️ 👷🏼 👷🏼♂️ 💂🏼♀️ 💂🏼 💂🏼♂️ 🕵🏼♀️ 🕵🏼 🕵🏼♂️ 👩🏼⚕️ 🧑🏼⚕️ 👨🏼⚕️ 👩🏼🌾 🧑🏼🌾 👨🏼🌾 👩🏼🍳 🧑🏼🍳 👨🏼🍳 👩🏼🎓 🧑🏼🎓 👨🏼🎓 👩🏼🎤 🧑🏼🎤 👨🏼🎤 👩🏼🏫 🧑🏼🏫 👨🏼🏫 👩🏼🏭 🧑🏼🏭 👨🏼🏭 👩🏼💻 🧑🏼💻 👨🏼💻 👩🏼💼 🧑🏼💼 👨🏼💼 👩🏼🔧 🧑🏼🔧 👨🏼🔧 👩🏼🔬 🧑🏼🔬 👨🏼🔬 👩🏼🎨 🧑🏼🎨 👨🏼🎨 👩🏼🚒 🧑🏼🚒 👨🏼🚒 👩🏼✈️ 🧑🏼✈️ 👨🏼✈️ 👩🏼🚀 🧑🏼🚀 👨🏼🚀 👩🏼⚖️ 🧑🏼⚖️ 👨🏼⚖️ 👰🏼♀️ 👰🏼 👰🏼♂️ 🤵🏼♀️ 🤵🏼 🤵🏼♂️ 👸🏼 🤴🏼 🥷🏼 🦸🏼♀️ 🦸🏼 🦸🏼♂️ 🦹🏼♀️ 🦹🏼 🦹🏼♂️ 🤶🏼 🧑🏼🎄 🎅🏼 🧙🏼♀️ 🧙🏼 🧙🏼♂️ 🧝🏼♀️ 🧝🏼 🧝🏼♂️ 🧛🏼♀️ 🧛🏼 🧛🏼♂️ 🧜🏼♀️ 🧜🏼 🧜🏼♂️ 🧚🏼♀️ 🧚🏼 🧚🏼♂️ 👼🏼 🤰🏼 🤱🏼 👩🏼🍼 🧑🏼🍼 👨🏼🍼 🙇🏼♀️ 🙇🏼 🙇🏼♂️ 💁🏼♀️ 💁🏼 💁🏼♂️ 🙅🏼♀️ 🙅🏼 🙅🏼♂️ 🙆🏼♀️ 🙆🏼 🙆🏼♂️ 🙋🏼♀️ 🙋🏼 🙋🏼♂️ 🧏🏼♀️ 🧏🏼 🧏🏼♂️ 🤦🏼♀️ 🤦🏼 🤦🏼♂️ 🤷🏼♀️ 🤷🏼 🤷🏼♂️ 🙎🏼♀️ 🙎🏼 🙎🏼♂️ 🙍🏼♀️ 🙍🏼 🙍🏼♂️ 💇🏼♀️ 💇🏼 💇🏼♂️ 💆🏼♀️ 💆🏼 💆🏼♂️ 🧖🏼♀️ 🧖🏼 🧖🏼♂️ 💃🏼 🕺🏼 🕴🏼 👩🏼🦽 🧑🏼🦽 👨🏼🦽 👩🏼🦼 🧑🏼🦼 👨🏼🦼 🚶🏼♀️ 🚶🏼 🚶🏼♂️ 👩🏼🦯 🧑🏼🦯 👨🏼🦯 🧎🏼♀️ 🧎🏼 🧎🏼♂️ 🏃🏼♀️ 🏃🏼 🏃🏼♂️ 🧍🏼♀️ 🧍🏼 🧍🏼♂️ 👭🏼 🧑🏼🤝🧑🏼 👬🏼 👫🏼 🧗🏼♀️ 🧗🏼 🧗🏼♂️ 🏇🏼 🏂🏼 🏌🏼♀️ 🏌🏼 🏌🏼♂️ 🏄🏼♀️ 🏄🏼 🏄🏼♂️ 🚣🏼♀️ 🚣🏼 🚣🏼♂️ 🏊🏼♀️ 🏊🏼 🏊🏼♂️ ⛹🏼♀️ ⛹🏼 ⛹🏼♂️ 🏋🏼♀️ 🏋🏼 🏋🏼♂️ 🚴🏼♀️ 🚴🏼 🚴🏼♂️ 🚵🏼♀️ 🚵🏼 🚵🏼♂️ 🤸🏼♀️ 🤸🏼 🤸🏼♂️ 🤽🏼♀️ 🤽🏼 🤽🏼♂️ 🤾🏼♀️ 🤾🏼 🤾🏼♂️ 🤹🏼♀️ 🤹🏼 🤹🏼♂️ 🧘🏼♀️ 🧘🏼 🧘🏼♂️ 🛀🏼 🛌'.split(' '),
|
|
72057
|
-
'Brown Emojis': '👋🏽 🤚🏽 🖐🏽 ✋🏽 🖖🏽 👌🏽 🤌🏽 🤏🏽 ✌🏽 🤞🏽 🤟🏽 🤘🏽 🤙🏽 👈🏽 👉🏽 👆🏽 🖕🏽 👇🏽 ☝🏽 👍🏽 👎🏽 ✊🏽 👊🏽 🤛🏽 🤜🏽 👏🏽 🙌🏽 👐🏽 🤲🏽 🙏🏽 ✍🏽 💅🏽 🤳🏽 💪🏽 🦵🏽 🦶🏽 👂🏽 🦻🏽 👃🏽 👶🏽 👧🏽 🧒🏽 👦🏽 👩🏽 🧑🏽 👨🏽 👩🏽🦱 🧑🏽🦱 👨🏽🦱 👩🏽🦰 🧑🏽🦰 👨🏽🦰 👱🏽♀️ 👱🏽 👱🏽♂️ 👩🏽🦳 🧑🏽🦳 👨🏽🦳 👩🏽🦲 🧑🏽🦲 👨🏽🦲 🧔🏽 👵🏽 🧓🏽 👴🏽 👲🏽 👳🏽♀️ 👳🏽 👳🏽♂️ 🧕🏽 👮🏽♀️ 👮🏽 👮🏽♂️ 👷🏽♀️ 👷🏽 👷🏽♂️ 💂🏽♀️ 💂🏽 💂🏽♂️ 🕵🏽♀️ 🕵🏽 🕵🏽♂️ 👩🏽⚕️ 🧑🏽⚕️ 👨🏽⚕️ 👩🏽🌾 🧑🏽🌾 👨🏽🌾 👩🏽🍳 🧑🏽🍳 👨🏽🍳 👩🏽🎓 🧑🏽🎓 👨🏽🎓 👩🏽🎤 🧑🏽🎤 👨🏽🎤 👩🏽🏫 🧑🏽🏫 👨🏽🏫 👩🏽🏭 🧑🏽🏭 👨🏽🏭 👩🏽💻 🧑🏽💻 👨🏽💻 👩🏽💼 🧑🏽💼 👨🏽💼 👩🏽🔧 🧑🏽🔧 👨🏽🔧 👩🏽🔬 🧑🏽🔬 👨🏽🔬 👩🏽🎨 🧑🏽🎨 👨🏽🎨 👩🏽🚒 🧑🏽🚒 👨🏽🚒 👩🏽✈️ 🧑🏽✈️ 👨🏽✈️ 👩🏽🚀 🧑🏽🚀 👨🏽🚀 👩🏽⚖️ 🧑🏽⚖️ 👨🏽⚖️ 👰🏽♀️ 👰🏽 👰🏽♂️ 🤵🏽♀️ 🤵🏽 🤵🏽♂️ 👸🏽 🤴🏽 🥷🏽 🦸🏽♀️ 🦸🏽 🦸🏽♂️ 🦹🏽♀️ 🦹🏽 🦹🏽♂️ 🤶🏽 🧑🏽🎄 🎅🏽 🧙🏽♀️ 🧙🏽 🧙🏽♂️ 🧝🏽♀️ 🧝🏽 🧝🏽♂️ 🧛🏽♀️ 🧛🏽 🧛🏽♂️ 🧜🏽♀️ 🧜🏽 🧜🏽♂️ 🧚🏽♀️ 🧚🏽 🧚🏽♂️ 👼🏽 🤰🏽 🤱🏽 👩🏽🍼 🧑🏽🍼 👨🏽🍼 🙇🏽♀️ 🙇🏽 🙇🏽♂️ 💁🏽♀️ 💁🏽 💁🏽♂️ 🙅🏽♀️ 🙅🏽 🙅🏽♂️ 🙆🏽♀️ 🙆🏽 🙆🏽♂️ 🙋🏽♀️ 🙋🏽 🙋🏽♂️ 🧏🏽♀️ 🧏🏽 🧏🏽♂️ 🤦🏽♀️ 🤦🏽 🤦🏽♂️ 🤷🏽♀️ 🤷🏽 🤷🏽♂️ 🙎🏽♀️ 🙎🏽 🙎🏽♂️ 🙍🏽♀️ 🙍🏽 🙍🏽♂️ 💇🏽♀️ 💇🏽 💇🏽♂️ 💆🏽♀️ 💆🏽 💆🏽♂️ 🧖🏽♀️ 🧖🏽 🧖🏽♂️ 💃🏽 🕺🏽 🕴🏽 👩🏽🦽 🧑🏽🦽 👨🏽🦽 👩🏽🦼 🧑🏽🦼 👨🏽🦼 🚶🏽♀️ 🚶🏽 🚶🏽♂️ 👩🏽🦯 🧑🏽🦯 👨🏽🦯 🧎🏽♀️ 🧎🏽 🧎🏽♂️ 🏃🏽♀️ 🏃🏽 🏃🏽♂️ 🧍🏽♀️ 🧍🏽 🧍🏽♂️ 👭🏽 🧑🏽🤝🧑🏽 👬🏽 👫🏽 🧗🏽♀️ 🧗🏽 🧗🏽♂️ 🏇🏽 🏂🏽 🏌🏽♀️ 🏌🏽 🏌🏽♂️ 🏄🏽♀️ 🏄🏽 🏄🏽♂️ 🚣🏽♀️ 🚣🏽 🚣🏽♂️ 🏊🏽♀️ 🏊🏽 🏊🏽♂️ ⛹🏽♀️ ⛹🏽 ⛹🏽♂️ 🏋🏽♀️ 🏋🏽 🏋🏽♂️ 🚴🏽♀️ 🚴🏽 🚴🏽♂️ 🚵🏽♀️ 🚵🏽 🚵🏽♂️ 🤸🏽♀️ 🤸🏽 🤸🏽♂️ 🤽🏽♀️ 🤽🏽 🤽🏽♂️ 🤾🏽♀️ 🤾🏽 🤾🏽♂️ 🤹🏽♀️ 🤹🏽 🤹🏽♂️ 🧘🏽♀️ 🧘🏽 🧘🏽♂️ 🛀🏽 🛌'.split(' '),
|
|
72058
|
-
'Dark Brown Emojis': '👋🏾 🤚🏾 🖐🏾 ✋🏾 🖖🏾 👌🏾 🤌🏾 🤏🏾 ✌🏾 🤞🏾 🤟🏾 🤘🏾 🤙🏾 👈🏾 👉🏾 👆🏾 🖕🏾 👇🏾 ☝🏾 👍🏾 👎🏾 ✊🏾 👊🏾 🤛🏾 🤜🏾 👏🏾 🙌🏾 👐🏾 🤲🏾 🙏🏾 ✍🏾 💅🏾 🤳🏾 💪🏾 🦵🏾 🦶🏾 👂🏾 🦻🏾 👃🏾 👶🏾 👧🏾 🧒🏾 👦🏾 👩🏾 🧑🏾 👨🏾 👩🏾🦱 🧑🏾🦱 👨🏾🦱 👩🏾🦰 🧑🏾🦰 👨🏾🦰 👱🏾♀️ 👱🏾 👱🏾♂️ 👩🏾🦳 🧑🏾🦳 👨🏾🦳 👩🏾🦲 🧑🏾🦲 👨🏾🦲 🧔🏾 👵🏾 🧓🏾 👴🏾 👲🏾 👳🏾♀️ 👳🏾 👳🏾♂️ 🧕🏾 👮🏾♀️ 👮🏾 👮🏾♂️ 👷🏾♀️ 👷🏾 👷🏾♂️ 💂🏾♀️ 💂🏾 💂🏾♂️ 🕵🏾♀️ 🕵🏾 🕵🏾♂️ 👩🏾⚕️ 🧑🏾⚕️ 👨🏾⚕️ 👩🏾🌾 🧑🏾🌾 👨🏾🌾 👩🏾🍳 🧑🏾🍳 👨🏾🍳 👩🏾🎓 🧑🏾🎓 👨🏾🎓 👩🏾🎤 🧑🏾🎤 👨🏾🎤 👩🏾🏫 🧑🏾🏫 👨🏾🏫 👩🏾🏭 🧑🏾🏭 👨🏾🏭 👩🏾💻 🧑🏾💻 👨🏾💻 👩🏾💼 🧑🏾💼 👨🏾💼 👩🏾🔧 🧑🏾🔧 👨🏾🔧 👩🏾🔬 🧑🏾🔬 👨🏾🔬 👩🏾🎨 🧑🏾🎨 👨🏾🎨 👩🏾🚒 🧑🏾🚒 👨🏾🚒 👩🏾✈️ 🧑🏾✈️ 👨🏾✈️ 👩🏾🚀 🧑🏾🚀 👨🏾🚀 👩🏾⚖️ 🧑🏾⚖️ 👨🏾⚖️ 👰🏾♀️ 👰🏾 👰🏾♂️ 🤵🏾♀️ 🤵🏾 🤵🏾♂️ 👸🏾 🤴🏾 🥷🏾 🦸🏾♀️ 🦸🏾 🦸🏾♂️ 🦹🏾♀️ 🦹🏾 🦹🏾♂️ 🤶🏾 🧑🏾🎄 🎅🏾 🧙🏾♀️ 🧙🏾 🧙🏾♂️ 🧝🏾♀️ 🧝🏾 🧝🏾♂️ 🧛🏾♀️ 🧛🏾 🧛🏾♂️ 🧜🏾♀️ 🧜🏾 🧜🏾♂️ 🧚🏾♀️ 🧚🏾 🧚🏾♂️ 👼🏾 🤰🏾 🤱🏾 👩🏾🍼 🧑🏾🍼 👨🏾🍼 🙇🏾♀️ 🙇🏾 🙇🏾♂️ 💁🏾♀️ 💁🏾 💁🏾♂️ 🙅🏾♀️ 🙅🏾 🙅🏾♂️ 🙆🏾♀️ 🙆🏾 🙆🏾♂️ 🙋🏾♀️ 🙋🏾 🙋🏾♂️ 🧏🏾♀️ 🧏🏾 🧏🏾♂️ 🤦🏾♀️ 🤦🏾 🤦🏾♂️ 🤷🏾♀️ 🤷🏾 🤷🏾♂️ 🙎🏾♀️ 🙎🏾 🙎🏾♂️ 🙍🏾♀️ 🙍🏾 🙍🏾♂️ 💇🏾♀️ 💇🏾 💇🏾♂️ 💆🏾♀️ 💆🏾 💆🏾♂️ 🧖🏾♀️ 🧖🏾 🧖🏾♂️ 💃🏾 🕺🏾 🕴🏿 👩🏾🦽 🧑🏾🦽 👨🏾🦽 👩🏾🦼 🧑🏾🦼 👨🏾🦼 🚶🏾♀️ 🚶🏾 🚶🏾♂️ 👩🏾🦯 🧑🏾🦯 👨🏾🦯 🧎🏾♀️ 🧎🏾 🧎🏾♂️ 🏃🏾♀️ 🏃🏾 🏃🏾♂️ 🧍🏾♀️ 🧍🏾 🧍🏾♂️ 👭🏾 🧑🏾🤝🧑🏾 👬🏾 👫🏾 🧗🏾♀️ 🧗🏾 🧗🏾♂️ 🏇🏾 🏂🏾 🏌🏾♀️ 🏌🏾 🏌🏾♂️ 🏄🏾♀️ 🏄🏾 🏄🏾♂️ 🚣🏾♀️ 🚣🏾 🚣🏾♂️ 🏊🏾♀️ 🏊🏾 🏊🏾♂️ ⛹🏾♀️ ⛹🏾 ⛹🏾♂️ 🏋🏾♀️ 🏋🏾 🏋🏾♂️ 🚴🏾♀️ 🚴🏾 🚴🏾♂️ 🚵🏾♀️ 🚵🏾 🚵🏾♂️ 🤸🏾♀️ 🤸🏾 🤸🏾♂️ 🤽🏾♀️ 🤽🏾 🤽🏾♂️ 🤾🏾♀️ 🤾🏾 🤾🏾♂️ 🤹🏾♀️ 🤹🏾 🤹🏾♂️ 🧘🏾♀️ 🧘🏾 🧘🏾♂️ 🛀🏾 🛌'.split(' '),
|
|
72059
|
-
'Black Emojis': '👋🏿 🤚🏿 🖐🏿 ✋🏿 🖖🏿 👌🏿 🤌🏿 🤏🏿 ✌🏿 🤞🏿 🤟🏿 🤘🏿 🤙🏿 👈🏿 👉🏿 👆🏿 🖕🏿 👇🏿 ☝🏿 👍🏿 👎🏿 ✊🏿 👊🏿 🤛🏿 🤜🏿 👏🏿 🙌🏿 👐🏿 🤲🏿 🙏🏿 ✍🏿 💅🏿 🤳🏿 💪🏿 🦵🏿 🦶🏿 👂🏿 🦻🏿 👃🏿 👶🏿 👧🏿 🧒🏿 👦🏿 👩🏿 🧑🏿 👨🏿 👩🏿🦱 🧑🏿🦱 👨🏿🦱 👩🏿🦰 🧑🏿🦰 👨🏿🦰 👱🏿♀️ 👱🏿 👱🏿♂️ 👩🏿🦳 🧑🏿🦳 👨🏿🦳 👩🏿🦲 🧑🏿🦲 👨🏿🦲 🧔🏿 👵🏿 🧓🏿 👴🏿 👲🏿 👳🏿♀️ 👳🏿 👳🏿♂️ 🧕🏿 👮🏿♀️ 👮🏿 👮🏿♂️ 👷🏿♀️ 👷🏿 👷🏿♂️ 💂🏿♀️ 💂🏿 💂🏿♂️ 🕵🏿♀️ 🕵🏿 🕵🏿♂️ 👩🏿⚕️ 🧑🏿⚕️ 👨🏿⚕️ 👩🏿🌾 🧑🏿🌾 👨🏿🌾 👩🏿🍳 🧑🏿🍳 👨🏿🍳 👩🏿🎓 🧑🏿🎓 👨🏿🎓 👩🏿🎤 🧑🏿🎤 👨🏿🎤 👩🏿🏫 🧑🏿🏫 👨🏿🏫 👩🏿🏭 🧑🏿🏭 👨🏿🏭 👩🏿💻 🧑🏿💻 👨🏿💻 👩🏿💼 🧑🏿💼 👨🏿💼 👩🏿🔧 🧑🏿🔧 👨🏿🔧 👩🏿🔬 🧑🏿🔬 👨🏿🔬 👩🏿🎨 🧑🏿🎨 👨🏿🎨 👩🏿🚒 🧑🏿🚒 👨🏿🚒 👩🏿✈️ 🧑🏿✈️ 👨🏿✈️ 👩🏿🚀 🧑🏿🚀 👨🏿🚀 👩🏿⚖️ 🧑🏿⚖️ 👨🏿⚖️ 👰🏿♀️ 👰🏿 👰🏿♂️ 🤵🏿♀️ 🤵🏿 🤵🏿♂️ 👸🏿 🤴🏿 🥷🏿 🦸🏿♀️ 🦸🏿 🦸🏿♂️ 🦹🏿♀️ 🦹🏿 🦹🏿♂️ 🤶🏿 🧑🏿🎄 🎅🏿 🧙🏿♀️ 🧙🏿 🧙🏿♂️ 🧝🏿♀️ 🧝🏿 🧝🏿♂️ 🧛🏿♀️ 🧛🏿 🧛🏿♂️ 🧜🏿♀️ 🧜🏿 🧜🏿♂️ 🧚🏿♀️ 🧚🏿 🧚🏿♂️ 👼🏿 🤰🏿 🤱🏿 👩🏿🍼 🧑🏿🍼 👨🏿🍼 🙇🏿♀️ 🙇🏿 🙇🏿♂️ 💁🏿♀️ 💁🏿 💁🏿♂️ 🙅🏿♀️ 🙅🏿 🙅🏿♂️ 🙆🏿♀️ 🙆🏿 🙆🏿♂️ 🙋🏿♀️ 🙋🏿 🙋🏿♂️ 🧏🏿♀️ 🧏🏿 🧏🏿♂️ 🤦🏿♀️ 🤦🏿 🤦🏿♂️ 🤷🏿♀️ 🤷🏿 🤷🏿♂️ 🙎🏿♀️ 🙎🏿 🙎🏿♂️ 🙍🏿♀️ 🙍🏿 🙍🏿♂️ 💇🏿♀️ 💇🏿 💇🏿♂️ 💆🏿♀️ 💆🏿 💆🏿♂️ 🧖🏿♀️ 🧖🏿 🧖🏿♂️ 💃🏿 🕺🏿 🕴🏿 👩🏿🦽 🧑🏿🦽 👨🏿🦽 👩🏿🦼 🧑🏿🦼 👨🏿🦼 🚶🏿♀️ 🚶🏿 🚶🏿♂️ 👩🏿🦯 🧑🏿🦯 👨🏿🦯 🧎🏿♀️ 🧎🏿 🧎🏿♂️ 🏃🏿♀️ 🏃🏿 🏃🏿♂️ 🧍🏿♀️ 🧍🏿 🧍🏿♂️ 👭🏿 🧑🏿🤝🧑🏿 👬🏿 👫🏿 🧗🏿♀️ 🧗🏿 🧗🏿♂️ 🏇🏿 🏂🏿 🏌🏿♀️ 🏌🏿 🏌🏿♂️ 🏄🏿♀️ 🏄🏿 🏄🏿♂️ 🚣🏿♀️ 🚣🏿 🚣🏿♂️ 🏊🏿♀️ 🏊🏿 🏊🏿♂️ ⛹🏿♀️ ⛹🏿 ⛹🏿♂️ 🏋🏿♀️ 🏋🏿 🏋🏿♂️ 🚴🏿♀️ 🚴🏿 🚴🏿♂️ 🚵🏿♀️ 🚵🏿 🚵🏿♂️ 🤸🏿♀️ 🤸🏿 🤸🏿♂️ 🤽🏿♀️ 🤽🏿 🤽🏿♂️ 🤾🏿♀️ 🤾🏿 🤾🏿♂️ 🤹🏿♀️ 🤹🏿 🤹🏿♂️ 🧘🏿♀️ 🧘🏿 🧘🏿♂️ 🛀🏿 🛌🏿'.split(' '),
|
|
72060
|
-
'Animals & Nature': '🐶 🐱 🐭 🐹 🐰 🦊 🐻 🐼 🐻❄️ 🐨 🐯 🦁 🐮 🐷 🐽 🐸 🐵 🙈 🙉 🙊 🐒 🐔 🐧 🐦 🐤 🐣 🐥 🦆 🦅 🦉 🦇 🐺 🐗 🐴 🦄 🐝 🪱 🐛 🦋 🐌 🐞 🐜 🪰 🪲 🪳 🦟 🦗 🕷 🕸 🦂 🐢 🐍 🦎 🦖 🦕 🐙 🦑 🦐 🦞 🦀 🐡 🐠 🐟 🐬 🐳 🐋 🦈 🐊 🐅 🐆 🦓 🦍 🦧 🦣 🐘 🦛 🦏 🐪 🐫 🦒 🦘 🦬 🐃 🐂 🐄 🐎 🐖 🐏 🐑 🦙 🐐 🦌 🐕 🐩 🦮 🐕🦺 🐈 🐈⬛ 🪶 🐓 🦃 🦤 🦚 🦜 🦢 🦩 🕊 🐇 🦝 🦨 🦡 🦫 🦦 🦥 🐁 🐀 🐿 🦔 🐾 🐉 🐲 🌵 🎄 🌲 🌳 🌴 🪵 🌱 🌿 ☘️ 🍀 🎍 🪴 🎋 🍃 🍂 🍁 🍄 🐚 🪨 🌾 💐 🌷 🌹 🥀 🌺 🌸 🌼 🌻 🌞 🌝 🌛 🌜 🌚 🌕 🌖 🌗 🌘 🌑 🌒 🌓 🌔 🌙 🌎 🌍 🌏 🪐 💫 ⭐️ 🌟 ✨ ⚡️ ☄️ 💥 🔥 🌪 🌈 ☀️ 🌤 ⛅️ 🌥 ☁️ 🌦 🌧 ⛈ 🌩 🌨 ❄️ ☃️ ⛄️ 🌬 💨 💧 💦 ☔️ ☂️ 🌊 🌫'.split(' '),
|
|
72061
|
-
'Food & Drink': '🍏 🍎 🍐 🍊 🍋 🍌 🍉 🍇 🍓 🫐 🍈 🍒 🍑 🥭 🍍 🥥 🥝 🍅 🍆 🥑 🥦 🥬 🥒 🌶 🫑 🌽 🥕 🫒 🧄 🧅 🥔 🍠 🥐 🥯 🍞 🥖 🥨 🧀 🥚 🍳 🧈 🥞 🧇 🥓 🥩 🍗 🍖 🦴 🌭 🍔 🍟 🍕 🫓 🥪 🥙 🧆 🌮 🌯 🫔 🥗 🥘 🫕 🥫 🍝 🍜 🍲 🍛 🍣 🍱 🥟 🦪 🍤 🍙 🍚 🍘 🍥 🥠 🥮 🍢 🍡 🍧 🍨 🍦 🥧 🧁 🍰 🎂 🍮 🍭 🍬 🍫 🍿 🍩 🍪 🌰 🥜 🍯 🥛 🍼 🫖 ☕️ 🍵 🧃 🥤 🧋 🍶 🍺 🍻 🥂 🍷 🥃 🍸 🍹 🧉 🍾 🧊 🥄 🍴 🍽 🥣 🥡 🥢 🧂'.split(' '),
|
|
72062
|
-
'Activity and Sports': '⚽️ 🏀 🏈 ⚾️ 🥎 🎾 🏐 🏉 🥏 🎱 🪀 🏓 🏸 🏒 🏑 🥍 🏏 🪃 🥅 ⛳️ 🪁 🏹 🎣 🤿 🥊 🥋 🎽 🛹 🛼 🛷 ⛸ 🥌 🎿 ⛷ 🏂 🪂 🏋️♀️ 🏋️ 🏋️♂️ 🤼♀️ 🤼 🤼♂️ 🤸♀️ 🤸 🤸♂️ ⛹️♀️ ⛹️ ⛹️♂️ 🤺 🤾♀️ 🤾 🤾♂️ 🏌️♀️ 🏌️ 🏌️♂️ 🏇 🧘♀️ 🧘 🧘♂️ 🏄♀️ 🏄 🏄♂️ 🏊♀️ 🏊 🏊♂️ 🤽♀️ 🤽 🤽♂️ 🚣♀️ 🚣 🚣♂️ 🧗♀️ 🧗 🧗♂️ 🚵♀️ 🚵 🚵♂️ 🚴♀️ 🚴 🚴♂️ 🏆 🥇 🥈 🥉 🏅 🎖 🏵 🎗 🎫 🎟 🎪 🤹 🤹♂️ 🤹♀️ 🎭 🩰 🎨 🎬 🎤 🎧 🎼 🎹 🥁 🪘 🎷 🎺 🪗 🎸 🪕 🎻 🎲 ♟ 🎯 🎳 🎮 🎰 🧩'.split(' '),
|
|
72063
|
-
'Travel & Places': '🚗 🚕 🚙 🚌 🚎 🏎 🚓 🚑 🚒 🚐 🛻 🚚 🚛 🚜 🦯 🦽 🦼 🛴 🚲 🛵 🏍 🛺 🚨 🚔 🚍 🚘 🚖 🚡 🚠 🚟 🚃 🚋 🚞 🚝 🚄 🚅 🚈 🚂 🚆 🚇 🚊 🚉 ✈️ 🛫 🛬 🛩 💺 🛰 🚀 🛸 🚁 🛶 ⛵️ 🚤 🛥 🛳 ⛴ 🚢 ⚓️ 🪝 ⛽️ 🚧 🚦 🚥 🚏 🗺 🗿 🗽 🗼 🏰 🏯 🏟 🎡 🎢 🎠 ⛲️ ⛱ 🏖 🏝 🏜 🌋 ⛰ 🏔 🗻 🏕 ⛺️ 🛖 🏠 🏡 🏘 🏚 🏗 🏭 🏢 🏬 🏣 🏤 🏥 🏦 🏨 🏪 🏫 🏩 💒 🏛 ⛪️ 🕌 🕍 🛕 🕋 ⛩ 🛤 🛣 🗾 🎑 🏞 🌅 🌄 🌠 🎇 🎆 🌇 🌆 🏙 🌃 🌌 🌉 🌁'.split(' '),
|
|
72064
|
-
Objects: '⌚️ 📱 📲 💻 ⌨️ 🖥 🖨 🖱 🖲 🕹 🗜 💽 💾 💿 📀 📼 📷 📸 📹 🎥 📽 🎞 📞 ☎️ 📟 📠 📺 📻 🎙 🎚 🎛 🧭 ⏱ ⏲ ⏰ 🕰 ⌛️ ⏳ 📡 🔋 🔌 💡 🔦 🕯 🪔 🧯 🛢 💸 💵 💴 💶 💷 🪙 💰 💳 💎 ⚖️ 🪜 🧰 🪛 🔧 🔨 ⚒ 🛠 ⛏ 🪚 🔩 ⚙️ 🪤 🧱 ⛓ 🧲 🔫 💣 🧨 🪓 🔪 🗡 ⚔️ 🛡 🚬 ⚰️ 🪦 ⚱️ 🏺 🔮 📿 🧿 💈 ⚗️ 🔭 🔬 🕳 🩹 🩺 💊 💉 🩸 🧬 🦠 🧫 🧪 🌡 🧹 🪠 🧺 🧻 🚽 🚰 🚿 🛁 🛀 🧼 🪥 🪒 🧽 🪣 🧴 🛎 🔑 🗝 🚪 🪑 🛋 🛏 🛌 🧸 🪆 🖼 🪞 🪟 🛍 🛒 🎁 🎈 🎏 🎀 🪄 🪅 🎊 🎉 🎎 🏮 🎐 🧧 ✉️ 📩 📨 📧 💌 📥 📤 📦 🏷 🪧 📪 📫 📬 📭 📮 📯 📜 📃 📄 📑 🧾 📊 📈 📉 🗒 🗓 📆 📅 🗑 📇 🗃 🗳 🗄 📋 📁 📂 🗂 🗞 📰 📓 📔 📒 📕 📗 📘 📙 📚 📖 🔖 🧷 🔗 📎 🖇 📐 📏 🧮 📌 📍 ✂️ 🖊 🖋 ✒️ 🖌 🖍 📝 ✏️ 🔍 🔎 🔏 🔐 🔒 🔓'.split(' '),
|
|
72065
|
-
Symbols: '❤️ 🧡 💛 💚 💙 💜 🖤 🤍 🤎 💔 ❣️ 💕 💞 💓 💗 💖 💘 💝 💟 ☮️ ✝️ ☪️ 🕉 ☸️ ✡️ 🔯 🕎 ☯️ ☦️ 🛐 ⛎ ♈️ ♉️ ♊️ ♋️ ♌️ ♍️ ♎️ ♏️ ♐️ ♑️ ♒️ ♓️ 🆔 ⚛️ 🉑 ☢️ ☣️ 📴 📳 🈶 🈚️ 🈸 🈺 🈷️ ✴️ 🆚 💮 🉐 ㊙️ ㊗️ 🈴 🈵 🈹 🈲 🅰️ 🅱️ 🆎 🆑 🅾️ 🆘 ❌ ⭕️ 🛑 ⛔️ 📛 🚫 💯 💢 ♨️ 🚷 🚯 🚳 🚱 🔞 📵 🚭 ❗️ ❕ ❓ ❔ ‼️ ⁉️ 🔅 🔆 〽️ ⚠️ 🚸 🔱 ⚜️ 🔰 ♻️ ✅ 🈯️ 💹 ❇️ ✳️ ❎ 🌐 💠 Ⓜ️ 🌀 💤 🏧 🚾 ♿️ 🅿️ 🛗 🈳 🈂️ 🛂 🛃 🛄 🛅 🚹 🚺 🚼 ⚧ 🚻 🚮 🎦 📶 🈁 🔣 ℹ️ 🔤 🔡 🔠 🆖 🆗 🆙 🆒 🆕 🆓 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 🔢 #️⃣ *️⃣ ⏏️ ▶️ ⏸ ⏯ ⏹ ⏺ ⏭ ⏮ ⏩ ⏪ ⏫ ⏬ ◀️ 🔼 🔽 ➡️ ⬅️ ⬆️ ⬇️ ↗️ ↘️ ↙️ ↖️ ↕️ ↔️ ↪️ ↩️ ⤴️ ⤵️ 🔀 🔁 🔂 🔄 🔃 🎵 🎶 ➕ ➖ ➗ ✖️ ♾ 💲 💱 ™️ ©️ ®️ 〰️ ➰ ➿ 🔚 🔙 🔛 🔝 🔜 ✔️ ☑️ 🔘 🔴 🟠 🟡 🟢 🔵 🟣 ⚫️ ⚪️ 🟤 🔺 🔻 🔸 🔹 🔶 🔷 🔳 🔲 ▪️ ▫️ ◾️ ◽️ ◼️ ◻️ 🟥 🟧 🟨 🟩 🟦 🟪 ⬛️ ⬜️ 🟫 🔈 🔇 🔉 🔊 🔔 🔕 📣 📢 👁🗨 💬 💭 🗯 ♠️ ♣️ ♥️ ♦️ 🃏 🎴 🀄️ 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧'.split(' '),
|
|
72066
|
-
'Non-Emoji Symbols': '✢ ✣ ✤ ✥ ✦ ✧ ★ ☆ ✯ ✡︎ ✩ ✪ ✫ ✬ ✭ ✮ ✶ ✷ ✵ ✸ ✹ → ⇒ ⟹ ⇨ ⇾ ➾ ⇢ ☛ ☞ ➔ ➜ ➙ ➛ ➝ ➞ ♠︎ ♣︎ ♥︎ ♦︎ ♤ ♧ ♡ ♢ ♚ ♛ ♜ ♝ ♞ ♟ ♔ ♕ ♖ ♗ ♘ ♙ ⚀ ⚁ ⚂ ⚃ ⚄ ⚅ 🂠 ⚈ ⚉ ⚆ ⚇ 𓀀 𓀁 𓀂 𓀃 𓀄 𓀅 𓀆 𓀇 𓀈 𓀉 𓀊 𓀋 𓀌 𓀍 𓀎 𓀏 𓀐 𓀑 𓀒 𓀓 𓀔 𓀕 𓀖 𓀗 𓀘 𓀙 𓀚 𓀛 𓀜 𓀝'.split(' '),
|
|
72067
|
-
Flags: '🏳️ 🏴 🏁 🚩 🏳️🌈 🏳️⚧️ 🏴☠️'.split(' '),
|
|
72068
|
-
Countries: '🇦🇫 🇦🇽 🇦🇱 🇩🇿 🇦🇸 🇦🇩 🇦🇴 🇦🇮 🇦🇶 🇦🇬 🇦🇷 🇦🇲 🇦🇼 🇦🇺 🇦🇹 🇦🇿 🇧🇸 🇧🇭 🇧🇩 🇧🇧 🇧🇾 🇧🇪 🇧🇿 🇧🇯 🇧🇲 🇧🇹 🇧🇴 🇧🇦 🇧🇼 🇧🇷 🇮🇴 🇻🇬 🇧🇳 🇧🇬 🇧🇫 🇧🇮 🇰🇭 🇨🇲 🇨🇦 🇮🇨 🇨🇻 🇧🇶 🇰🇾 🇨🇫 🇹🇩 🇨🇱 🇨🇳 🇨🇽 🇨🇨 🇨🇴 🇰🇲 🇨🇬 🇨🇩 🇨🇰 🇨🇷 🇨🇮 🇭🇷 🇨🇺 🇨🇼 🇨🇾 🇨🇿 🇩🇰 🇩🇯 🇩🇲 🇩🇴 🇪🇨 🇪🇬 🇸🇻 🇬🇶 🇪🇷 🇪🇪 🇪🇹 🇪🇺 🇫🇰 🇫🇴 🇫🇯 🇫🇮 🇫🇷 🇬🇫 🇵🇫 🇹🇫 🇬🇦 🇬🇲 🇬🇪 🇩🇪 🇬🇭 🇬🇮 🇬🇷 🇬🇱 🇬🇩 🇬🇵 🇬🇺 🇬🇹 🇬🇬 🇬🇳 🇬🇼 🇬🇾 🇭🇹 🇭🇳 🇭🇰 🇭🇺 🇮🇸 🇮🇳 🇮🇩 🇮🇷 🇮🇶 🇮🇪 🇮🇲 🇮🇱 🇮🇹 🇯🇲 🇯🇵 🎌 🇯🇪 🇯🇴 🇰🇿 🇰🇪 🇰🇮 🇽🇰 🇰🇼 🇰🇬 🇱🇦 🇱🇻 🇱🇧 🇱🇸 🇱🇷 🇱🇾 🇱🇮 🇱🇹 🇱🇺 🇲🇴 🇲🇰 🇲🇬 🇲🇼 🇲🇾 🇲🇻 🇲🇱 🇲🇹 🇲🇭 🇲🇶 🇲🇷 🇲🇺 🇾🇹 🇲🇽 🇫🇲 🇲🇩 🇲🇨 🇲🇳 🇲🇪 🇲🇸 🇲🇦 🇲🇿 🇲🇲 🇳🇦 🇳🇷 🇳🇵 🇳🇱 🇳🇨 🇳🇿 🇳🇮 🇳🇪 🇳🇬 🇳🇺 🇳🇫 🇰🇵 🇲🇵 🇳🇴 🇴🇲 🇵🇰 🇵🇼 🇵🇸 🇵🇦 🇵🇬 🇵🇾 🇵🇪 🇵🇭 🇵🇳 🇵🇱 🇵🇹 🇵🇷 🇶🇦 🇷🇪 🇷🇴 🇷🇺 🇷🇼 🇼🇸 🇸🇲 🇸🇦 🇸🇳 🇷🇸 🇸🇨 🇸🇱 🇸🇬 🇸🇽 🇸🇰 🇸🇮 🇬🇸 🇸🇧 🇸🇴 🇿🇦 🇰🇷 🇸🇸 🇪🇸 🇱🇰 🇧🇱 🇸🇭 🇰🇳 🇱🇨 🇵🇲 🇻🇨 🇸🇩 🇸🇷 🇸🇿 🇸🇪 🇨🇭 🇸🇾 🇹🇼 🇹🇯 🇹🇿 🇹🇭 🇹🇱 🇹🇬 🇹🇰 🇹🇴 🇹🇹 🇹🇳 🇹🇷 🇹🇲 🇹🇨 🇹🇻 🇻🇮 🇺🇬 🇺🇦 🇦🇪 🇬🇧 🇺🇳 🇺🇸 🇺🇾 🇺🇿 🇻🇺 🇻🇦 🇻🇪 🇻🇳 🇼🇫 🇪🇭 🇾🇪 🇿🇲 🇿🇼'.split(' '),
|
|
72069
|
-
'New Emojis 2020': '🥲 🥸 🤌 🤌🏻 🤌🏼 🤌🏽 🤌🏾 🤌🏿 🫀 🫁 🥷 🤵♀️ 🤵🏻♀️ 🤵🏼♀️ 🤵🏽♀️ 🤵🏾♀️ 🤵🏿♀️ 🤵♂️ 🤵🏻♂️ 🤵🏼♂️ 🤵🏽♂️ 🤵🏾♂️ 🤵🏿♂️ 👰♀️ 👰🏻♀️ 👰🏼♀️ 👰🏽♀️ 👰🏾♀️ 👰🏿♀️ 👰♂️ 👰🏻♂️ 👰🏼♂️ 👰🏽♂️ 👰🏾♂️ 👰🏿♂️ 👩🍼 👩🏻🍼 👩🏼🍼 👩🏽🍼 👩🏾🍼 👩🏿🍼 🧑🍼 🧑🏻🍼 🧑🏼🍼 🧑🏽🍼 🧑🏾🍼 🧑🏿🍼 👨🍼 👨🏻🍼 👨🏼🍼 👨🏽🍼 👨🏾🍼 👨🏿🍼 🧑🎄 🧑🏻🎄 🧑🏼🎄 🧑🏽🎄 🧑🏾🎄 🧑🏿🎄 🫂 🐈⬛ 🦬 🦣 🦫 🐻❄️ 🦤 🪶 🦭 🪲 🪳 🪰 🪱 🪴 🫐 🫒 🫑 🫓 🫔 🫕 🫖 🧋 🪨 🪵 🛖 🛻 🛼 🪄 🪅 🪆 🪡 🪢 🩴 🪖 🪗 🪘 🪙 🪃 🪚 🪛 🪝 🪜 🛗 🪞 🪟 🪠 🪤 🪣 🪥 🪦 🪧 🏳️⚧️'.split(' '),
|
|
72070
|
-
'New Emoji 2021': '😮💨 😵💫 😶🌫️ ❤️🔥 ❤️🩹 🧔♀️ 🧔🏻♀️ 🧔🏼♀️ 🧔🏽♀️ 🧔🏾♀️ 🧔🏿♀️ 🧔♂️ 🧔🏻♂️ 🧔🏼♂️ 🧔🏽♂️ 🧔🏾♂️ 🧔🏿♂️ 💑🏻 💑🏼 💑🏽 💑🏾 💑🏿 💏🏻 💏🏼 💏🏽 💏🏾 💏🏿 👨🏻❤️👨🏻 👨🏻❤️👨🏼 👨🏻❤️👨🏽 👨🏻❤️👨🏾 👨🏻❤️👨🏿 👨🏼❤️👨🏻 👨🏼❤️👨🏼 👨🏼❤️👨🏽 👨🏼❤️👨🏾 👨🏼❤️👨🏿 👨🏽❤️👨🏻 👨🏽❤️👨🏼 👨🏽❤️👨🏽 👨🏽❤️👨🏾 👨🏽❤️👨🏿 👨🏾❤️👨🏻 👨🏾❤️👨🏼 👨🏾❤️👨🏽 👨🏾❤️👨🏾 👨🏾❤️👨🏿 👨🏿❤️👨🏻 👨🏿❤️👨🏼 👨🏿❤️👨🏽 👨🏿❤️👨🏾 👨🏿❤️👨🏿 👩🏻❤️👨🏻 👩🏻❤️👨🏼 👩🏻❤️👨🏽 👩🏻❤️👨🏾 👩🏻❤️👨🏿 👩🏻❤️👩🏻 👩🏻❤️👩🏼 👩🏻❤️👩🏽 👩🏻❤️👩🏾 👩🏻❤️👩🏿 👩🏼❤️👨🏻 👩🏼❤️👨🏼 👩🏼❤️👨🏽 👩🏼❤️👨🏾 👩🏼❤️👨🏿 👩🏼❤️👩🏻 👩🏼❤️👩🏼 👩🏼❤️👩🏽 👩🏼❤️👩🏾 👩🏼❤️👩🏿 👩🏽❤️👨🏻 👩🏽❤️👨🏼 👩🏽❤️👨🏽 👩🏽❤️👨🏾 👩🏽❤️👨🏿 👩🏽❤️👩🏻 👩🏽❤️👩🏼 👩🏽❤️👩🏽 👩🏽❤️👩🏾 👩🏽❤️👩🏿 👩🏾❤️👨🏻 👩🏾❤️👨🏼 👩🏾❤️👨🏽 👩🏾❤️👨🏾 👩🏾❤️👨🏿 👩🏾❤️👩🏻 👩🏾❤️👩🏼 👩🏾❤️👩🏽 👩🏾❤️👩🏾 👩🏾❤️👩🏿 👩🏿❤️👨🏻 👩🏿❤️👨🏼 👩🏿❤️👨🏽 👩🏿❤️👨🏾 👩🏿❤️👨🏿 👩🏿❤️👩🏻 👩🏿❤️👩🏼 👩🏿❤️👩🏽 👩🏿❤️👩🏾 👩🏿❤️👩🏿 🧑🏻❤️🧑🏼 🧑🏻❤️🧑🏽 🧑🏻❤️🧑🏾 🧑🏻❤️🧑🏿 🧑🏼❤️🧑🏻 🧑🏼❤️🧑🏽 🧑🏼❤️🧑🏾 🧑🏼❤️🧑🏿 🧑🏽❤️🧑🏻 🧑🏽❤️🧑🏼 🧑🏽❤️🧑🏾 🧑🏽❤️🧑🏿 🧑🏾❤️🧑🏻 🧑🏾❤️🧑🏼 🧑🏾❤️🧑🏽 🧑🏾❤️🧑🏿 🧑🏿❤️🧑🏻 🧑🏿❤️🧑🏼 🧑🏿❤️🧑🏽 🧑🏿❤️🧑🏾 👨🏻❤️💋👨🏻 👨🏻❤️💋👨🏼 👨🏻❤️💋👨🏽 👨🏻❤️💋👨🏾 👨🏻❤️💋👨🏿 👨🏼❤️💋👨🏻 👨🏼❤️💋👨🏼 👨🏼❤️💋👨🏽 👨🏼❤️💋👨🏾 👨🏼❤️💋👨🏿 👨🏽❤️💋👨🏻 👨🏽❤️💋👨🏼 👨🏽❤️💋👨🏽 👨🏽❤️💋👨🏾 👨🏽❤️💋👨🏿 👨🏾❤️💋👨🏻 👨🏾❤️💋👨🏼 👨🏾❤️💋👨🏽 👨🏾❤️💋👨🏾 👨🏾❤️💋👨🏿 👨🏿❤️💋👨🏻 👨🏿❤️💋👨🏼 👨🏿❤️💋👨🏽 👨🏿❤️💋👨🏾 👨🏿❤️💋👨🏿 👩🏻❤️💋👨🏻 👩🏻❤️💋👨🏼 👩🏻❤️💋👨🏽 👩🏻❤️💋👨🏾 👩🏻❤️💋👨🏿 👩🏻❤️💋👩🏻 👩🏻❤️💋👩🏼 👩🏻❤️💋👩🏽 👩🏻❤️💋👩🏾 👩🏻❤️💋👩🏿 👩🏼❤️💋👨🏻 👩🏼❤️💋👨🏼 👩🏼❤️💋👨🏽 👩🏼❤️💋👨🏾 👩🏼❤️💋👨🏿 👩🏼❤️💋👩🏻 👩🏼❤️💋👩🏼 👩🏼❤️💋👩🏽 👩🏼❤️💋👩🏾 👩🏼❤️💋👩🏿 👩🏽❤️💋👨🏻 👩🏽❤️💋👨🏼 👩🏽❤️💋👨🏽 👩🏽❤️💋👨🏾 👩🏽❤️💋👨🏿 👩🏽❤️💋👩🏻 👩🏽❤️💋👩🏼 👩🏽❤️💋👩🏽 👩🏽❤️💋👩🏾 👩🏽❤️💋👩🏿 👩🏾❤️💋👨🏻 👩🏾❤️💋👨🏼 👩🏾❤️💋👨🏽 👩🏾❤️💋👨🏾 👩🏾❤️💋👨🏿 👩🏾❤️💋👩🏻 👩🏾❤️💋👩🏼 👩🏾❤️💋👩🏽 👩🏾❤️💋👩🏾 👩🏾❤️💋👩🏿 👩🏿❤️💋👨🏻 👩🏿❤️💋👨🏼 👩🏿❤️💋👨🏽 👩🏿❤️💋👨🏾 👩🏿❤️💋👨🏿 👩🏿❤️💋👩🏻 👩🏿❤️💋👩🏼 👩🏿❤️💋👩🏽 👩🏿❤️💋👩🏾 👩🏿❤️💋👩🏿 🧑🏻❤️💋🧑🏼 🧑🏻❤️💋🧑🏽 🧑🏻❤️💋🧑🏾 🧑🏻❤️💋🧑🏿 🧑🏼❤️💋🧑🏻 🧑🏼❤️💋🧑🏽 🧑🏼❤️💋🧑🏾 🧑🏼❤️💋🧑🏿 🧑🏽❤️💋🧑🏻 🧑🏽❤️💋🧑🏼 🧑🏽❤️💋🧑🏾 🧑🏽❤️💋🧑🏿 🧑🏾❤️💋🧑🏻 🧑🏾❤️💋🧑🏼 🧑🏾❤️💋🧑🏽 🧑🏾❤️💋🧑🏿 🧑🏿❤️💋🧑🏻 🧑🏿❤️💋🧑🏼 🧑🏿❤️💋🧑🏽 🧑🏿❤️💋🧑🏾'.split(' '),
|
|
72071
|
-
'New Emojis (Discorered randomly)': '🖌️'.split(' '),
|
|
72072
|
-
'Emojis used in WebGPT': ['▶', '❤'],
|
|
72073
|
-
};
|
|
72074
|
-
/**
|
|
72075
|
-
* All possible emoji chars like "🍆", "🍡", "🍤", "🇨🇼", "🧑🏿❤️💋🧑🏻"...
|
|
72076
|
-
*/
|
|
72077
|
-
const EMOJIS = new Set(Object.values(EMOJIS_IN_CATEGORIES).flat());
|
|
72078
|
-
/**
|
|
72079
|
-
* Emojis which are single pictograms like "🍆", "🍡", "🍤"...
|
|
72080
|
-
*/
|
|
72081
|
-
const EMOJIS_OF_SINGLE_PICTOGRAM = new Set(Array.from(EMOJIS).filter((emoji) => emoji.length <= 2));
|
|
72082
|
-
// TODO: [💴] DRY - just one version of emojis.ts
|
|
72083
|
-
// TODO: Mirror from Collboard or some common package
|
|
72084
|
-
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
72085
|
-
|
|
72086
72593
|
/**
|
|
72087
72594
|
* Default file globs scanned for emoji tags.
|
|
72088
72595
|
*/
|
|
@@ -72102,7 +72609,7 @@ const EMOJI_TAG_SCAN_CACHE_FILE_PATH = `${PTBK_CODER_CACHE_DIRECTORY_PATH}/emoji
|
|
|
72102
72609
|
/**
|
|
72103
72610
|
* Current schema version of the persisted emoji-tag scan cache.
|
|
72104
72611
|
*/
|
|
72105
|
-
const EMOJI_TAG_SCAN_CACHE_VERSION =
|
|
72612
|
+
const EMOJI_TAG_SCAN_CACHE_VERSION = 2;
|
|
72106
72613
|
/**
|
|
72107
72614
|
* Scans repository files for bracketed emoji tags while reusing per-file cache entries for unchanged files.
|
|
72108
72615
|
*/
|
|
@@ -72114,8 +72621,9 @@ async function scanEmojiTagUsage(options) {
|
|
|
72114
72621
|
const tagPrefix = (_d = options.tagPrefix) !== null && _d !== void 0 ? _d : '';
|
|
72115
72622
|
const filesToScan = await findFilesToScan(rootDir, includeGlobs, ignoreGlobs);
|
|
72116
72623
|
const matcher = buildEmojiTagMatcher(options.candidateEmojis, tagPrefix);
|
|
72624
|
+
const candidateEmojisFingerprint = createCandidateEmojisFingerprint(options.candidateEmojis);
|
|
72117
72625
|
const usedEmojis = new Set();
|
|
72118
|
-
const existingCache = await readEmojiTagScanCache(rootDir);
|
|
72626
|
+
const existingCache = await readEmojiTagScanCache(rootDir, candidateEmojisFingerprint);
|
|
72119
72627
|
const nextCacheFiles = { ...existingCache.files };
|
|
72120
72628
|
let isCacheDirty = false;
|
|
72121
72629
|
let scannedFileCount = 0;
|
|
@@ -72149,6 +72657,7 @@ async function scanEmojiTagUsage(options) {
|
|
|
72149
72657
|
if (isCacheDirty) {
|
|
72150
72658
|
await writeEmojiTagScanCache(rootDir, {
|
|
72151
72659
|
version: EMOJI_TAG_SCAN_CACHE_VERSION,
|
|
72660
|
+
candidateEmojisFingerprint,
|
|
72152
72661
|
files: nextCacheFiles,
|
|
72153
72662
|
});
|
|
72154
72663
|
}
|
|
@@ -72231,13 +72740,13 @@ function updateCachedFile(cachedFile, mtimeMs, size, tagPrefix, emojis) {
|
|
|
72231
72740
|
/**
|
|
72232
72741
|
* Loads the persisted emoji-tag scan cache and falls back to an empty cache when it is missing or invalid.
|
|
72233
72742
|
*/
|
|
72234
|
-
async function readEmojiTagScanCache(rootDir) {
|
|
72743
|
+
async function readEmojiTagScanCache(rootDir, candidateEmojisFingerprint) {
|
|
72235
72744
|
try {
|
|
72236
72745
|
const cacheContent = await readFile(join(rootDir, EMOJI_TAG_SCAN_CACHE_FILE_PATH), 'utf-8');
|
|
72237
|
-
return normalizeEmojiTagScanCache(JSON.parse(cacheContent));
|
|
72746
|
+
return normalizeEmojiTagScanCache(JSON.parse(cacheContent), candidateEmojisFingerprint);
|
|
72238
72747
|
}
|
|
72239
72748
|
catch (_a) {
|
|
72240
|
-
return createEmptyEmojiTagScanCache();
|
|
72749
|
+
return createEmptyEmojiTagScanCache(candidateEmojisFingerprint);
|
|
72241
72750
|
}
|
|
72242
72751
|
}
|
|
72243
72752
|
/**
|
|
@@ -72255,9 +72764,12 @@ async function writeEmojiTagScanCache(rootDir, cache) {
|
|
|
72255
72764
|
/**
|
|
72256
72765
|
* Normalizes one parsed cache payload into the current typed cache shape.
|
|
72257
72766
|
*/
|
|
72258
|
-
function normalizeEmojiTagScanCache(value) {
|
|
72259
|
-
if (!isPlainObject(value) ||
|
|
72260
|
-
|
|
72767
|
+
function normalizeEmojiTagScanCache(value, candidateEmojisFingerprint) {
|
|
72768
|
+
if (!isPlainObject(value) ||
|
|
72769
|
+
value.version !== EMOJI_TAG_SCAN_CACHE_VERSION ||
|
|
72770
|
+
value.candidateEmojisFingerprint !== candidateEmojisFingerprint ||
|
|
72771
|
+
!isPlainObject(value.files)) {
|
|
72772
|
+
return createEmptyEmojiTagScanCache(candidateEmojisFingerprint);
|
|
72261
72773
|
}
|
|
72262
72774
|
const files = {};
|
|
72263
72775
|
for (const [filePath, cachedValue] of Object.entries(value.files)) {
|
|
@@ -72283,18 +72795,33 @@ function normalizeEmojiTagScanCache(value) {
|
|
|
72283
72795
|
}
|
|
72284
72796
|
return {
|
|
72285
72797
|
version: EMOJI_TAG_SCAN_CACHE_VERSION,
|
|
72798
|
+
candidateEmojisFingerprint,
|
|
72286
72799
|
files,
|
|
72287
72800
|
};
|
|
72288
72801
|
}
|
|
72289
72802
|
/**
|
|
72290
72803
|
* Creates an empty cache payload for emoji-tag scans.
|
|
72291
72804
|
*/
|
|
72292
|
-
function createEmptyEmojiTagScanCache() {
|
|
72805
|
+
function createEmptyEmojiTagScanCache(candidateEmojisFingerprint) {
|
|
72293
72806
|
return {
|
|
72294
72807
|
version: EMOJI_TAG_SCAN_CACHE_VERSION,
|
|
72808
|
+
candidateEmojisFingerprint,
|
|
72295
72809
|
files: {},
|
|
72296
72810
|
};
|
|
72297
72811
|
}
|
|
72812
|
+
/**
|
|
72813
|
+
* Creates a stable fingerprint for the complete candidate emoji set.
|
|
72814
|
+
*
|
|
72815
|
+
* A cached scan is only valid when it was created from the exact same candidates.
|
|
72816
|
+
* Otherwise a tag that was not part of an older catalogue could be mistaken for a
|
|
72817
|
+
* fresh tag after the catalogue grows.
|
|
72818
|
+
*
|
|
72819
|
+
* @private internal utility of `scanEmojiTagUsage`
|
|
72820
|
+
*/
|
|
72821
|
+
function createCandidateEmojisFingerprint(candidateEmojis) {
|
|
72822
|
+
const normalizedCandidateEmojis = Array.from(candidateEmojis).sort().join('\u0000');
|
|
72823
|
+
return createHash('sha256').update(normalizedCandidateEmojis, 'utf-8').digest('hex');
|
|
72824
|
+
}
|
|
72298
72825
|
/**
|
|
72299
72826
|
* Converts an absolute file path into a stable cache key relative to the scanned root.
|
|
72300
72827
|
*/
|
|
@@ -72323,6 +72850,77 @@ function normalizeError(error) {
|
|
|
72323
72850
|
}
|
|
72324
72851
|
// Note: [?] Code in this file should never be published in any package
|
|
72325
72852
|
|
|
72853
|
+
/**
|
|
72854
|
+
* All possible emoji chars like "🍆", "🍡", "🍤"...
|
|
72855
|
+
* Note: this will be needed to update annually - now updated at 2022-01-19
|
|
72856
|
+
*
|
|
72857
|
+
* @see https://getemoji.com/
|
|
72858
|
+
* @deprecated Use /\p{Extended_Pictographic}/ instead
|
|
72859
|
+
*
|
|
72860
|
+
* @private within the repository
|
|
72861
|
+
*/
|
|
72862
|
+
const EMOJIS_IN_CATEGORIES = {
|
|
72863
|
+
Smileys: '😀 😃 😄 😁 😆 😅 😂 🤣 🥲 ☺️ 😊 😇 🙂 🙃 😉 😌 😍 🥰 😘 😗 😙 😚 😋 😛 😝 😜 🤪 🤨 🧐 🤓 😎 🥸 🤩 🥳 😏 😒 😞 😔 😟 😕 🙁 ☹️ 😣 😖 😫 😩 🥺 😢 😭 😤 😠 😡 🤬 🤯 😳 🥵 🥶 😱 😨 😰 😥 😓 🤗 🤔 🤭 🤫 🤥 😶 😐 😑 😬 🙄 😯 😦 😧 😮 😲 🥱 😴 🤤 😪 😵 🤐 🥴 🤢 🤮 🤧 😷 🤒 🤕 🤑 🤠 😈 👿 👹 👺 🤡 💩 👻 💀 ☠️ 👽 👾 🤖 🎃 😺 😸 😹 😻 😼 😽 🙀 😿 😾'.split(' '),
|
|
72864
|
+
'Gestures and Body Parts': '👋 🤚 🖐 ✋ 🖖 👌 🤌 🤏 ✌️ 🤞 🤟 🤘 🤙 👈 👉 👆 🖕 👇 ☝️ 👍 👎 ✊ 👊 🤛 🤜 👏 🙌 👐 🤲 🤝 🙏 ✍️ 💅 🤳 💪 🦾 🦵 🦿 🦶 👣 👂 🦻 👃 🫀 🫁 🧠 🦷 🦴 👀 👁 👅 👄 💋 🩸'.split(' '),
|
|
72865
|
+
'People and Fantasy': '👶 👧 🧒 👦 👩 🧑 👨 👩🦱 🧑🦱 👨🦱 👩🦰 🧑🦰 👨🦰 👱♀️ 👱 👱♂️ 👩🦳 🧑🦳 👨🦳 👩🦲 🧑🦲 👨🦲 🧔 👵 🧓 👴 👲 👳♀️ 👳 👳♂️ 🧕 👮♀️ 👮 👮♂️ 👷♀️ 👷 👷♂️ 💂♀️ 💂 💂♂️ 🕵️♀️ 🕵️ 🕵️♂️ 👩⚕️ 🧑⚕️ 👨⚕️ 👩🌾 🧑🌾 👨🌾 👩🍳 🧑🍳 👨🍳 👩🎓 🧑🎓 👨🎓 👩🎤 🧑🎤 👨🎤 👩🏫 🧑🏫 👨🏫 👩🏭 🧑🏭 👨🏭 👩💻 🧑💻 👨💻 👩💼 🧑💼 👨💼 👩🔧 🧑🔧 👨🔧 👩🔬 🧑🔬 👨🔬 👩🎨 🧑🎨 👨🎨 👩🚒 🧑🚒 👨🚒 👩✈️ 🧑✈️ 👨✈️ 👩🚀 🧑🚀 👨🚀 👩⚖️ 🧑⚖️ 👨⚖️ 👰♀️ 👰 👰♂️ 🤵♀️ 🤵 🤵♂️ 👸 🤴 🥷 🦸♀️ 🦸 🦸♂️ 🦹♀️ 🦹 🦹♂️ 🤶 🧑🎄 🎅 🧙♀️ 🧙 🧙♂️ 🧝♀️ 🧝 🧝♂️ 🧛♀️ 🧛 🧛♂️ 🧟♀️ 🧟 🧟♂️ 🧞♀️ 🧞 🧞♂️ 🧜♀️ 🧜 🧜♂️ 🧚♀️ 🧚 🧚♂️ 👼 🤰 🤱 👩🍼 🧑🍼 👨🍼 🙇♀️ 🙇 🙇♂️ 💁♀️ 💁 💁♂️ 🙅♀️ 🙅 🙅♂️ 🙆♀️ 🙆 🙆♂️ 🙋♀️ 🙋 🙋♂️ 🧏♀️ 🧏 🧏♂️ 🤦♀️ 🤦 🤦♂️ 🤷♀️ 🤷 🤷♂️ 🙎♀️ 🙎 🙎♂️ 🙍♀️ 🙍 🙍♂️ 💇♀️ 💇 💇♂️ 💆♀️ 💆 💆♂️ 🧖♀️ 🧖 🧖♂️ 💅 🤳 💃 🕺 👯♀️ 👯 👯♂️ 🕴 👩🦽 🧑🦽 👨🦽 👩🦼 🧑🦼 👨🦼 🚶♀️ 🚶 🚶♂️ 👩🦯 🧑🦯 👨🦯 🧎♀️ 🧎 🧎♂️ 🏃♀️ 🏃 🏃♂️ 🧍♀️ 🧍 🧍♂️ 👭 🧑🤝🧑 👬 👫 👩❤️👩 💑 👨❤️👨 👩❤️👨 👩❤️💋👩 💏 👨❤️💋👨 👩❤️💋👨 👪 👨👩👦 👨👩👧 👨👩👧👦 👨👩👦👦 👨👩👧👧 👨👨👦 👨👨👧 👨👨👧👦 👨👨👦👦 👨👨👧👧 👩👩👦 👩👩👧 👩👩👧👦 👩👩👦👦 👩👩👧👧 👨👦 👨👦👦 👨👧 👨👧👦 👨👧👧 👩👦 👩👦👦 👩👧 👩👧👦 👩👧👧 🗣 👤 👥 🫂'.split(' '),
|
|
72866
|
+
'Clothing and Accessories': '🧳 🌂 ☂️ 🧵 🪡 🪢 🧶 👓 🕶 🥽 🥼 🦺 👔 👕 👖 🧣 🧤 🧥 🧦 👗 👘 🥻 🩴 🩱 🩲 🩳 👙 👚 👛 👜 👝 🎒 👞 👟 🥾 🥿 👠 👡 🩰 👢 👑 👒 🎩 🎓 🧢 ⛑ 🪖 💄 💍 💼'.split(' '),
|
|
72867
|
+
'Pale Emojis': '👋🏻 🤚🏻 🖐🏻 ✋🏻 🖖🏻 👌🏻 🤌🏻 🤏🏻 ✌🏻 🤞🏻 🤟🏻 🤘🏻 🤙🏻 👈🏻 👉🏻 👆🏻 🖕🏻 👇🏻 ☝🏻 👍🏻 👎🏻 ✊🏻 👊🏻 🤛🏻 🤜🏻 👏🏻 🙌🏻 👐🏻 🤲🏻 🙏🏻 ✍🏻 💅🏻 🤳🏻 💪🏻 🦵🏻 🦶🏻 👂🏻 🦻🏻 👃🏻 👶🏻 👧🏻 🧒🏻 👦🏻 👩🏻 🧑🏻 👨🏻 👩🏻🦱 🧑🏻🦱 👨🏻🦱 👩🏻🦰 🧑🏻🦰 👨🏻🦰 👱🏻♀️ 👱🏻 👱🏻♂️ 👩🏻🦳 🧑🏻🦳 👨🏻🦳 👩🏻🦲 🧑🏻🦲 👨🏻🦲 🧔🏻 👵🏻 🧓🏻 👴🏻 👲🏻 👳🏻♀️ 👳🏻 👳🏻♂️ 🧕🏻 👮🏻♀️ 👮🏻 👮🏻♂️ 👷🏻♀️ 👷🏻 👷🏻♂️ 💂🏻♀️ 💂🏻 💂🏻♂️ 🕵🏻♀️ 🕵🏻 🕵🏻♂️ 👩🏻⚕️ 🧑🏻⚕️ 👨🏻⚕️ 👩🏻🌾 🧑🏻🌾 👨🏻🌾 👩🏻🍳 🧑🏻🍳 👨🏻🍳 👩🏻🎓 🧑🏻🎓 👨🏻🎓 👩🏻🎤 🧑🏻🎤 👨🏻🎤 👩🏻🏫 🧑🏻🏫 👨🏻🏫 👩🏻🏭 🧑🏻🏭 👨🏻🏭 👩🏻💻 🧑🏻💻 👨🏻💻 👩🏻💼 🧑🏻💼 👨🏻💼 👩🏻🔧 🧑🏻🔧 👨🏻🔧 👩🏻🔬 🧑🏻🔬 👨🏻🔬 👩🏻🎨 🧑🏻🎨 👨🏻🎨 👩🏻🚒 🧑🏻🚒 👨🏻🚒 👩🏻✈️ 🧑🏻✈️ 👨🏻✈️ 👩🏻🚀 🧑🏻🚀 👨🏻🚀 👩🏻⚖️ 🧑🏻⚖️ 👨🏻⚖️ 👰🏻♀️ 👰🏻 👰🏻♂️ 🤵🏻♀️ 🤵🏻 🤵🏻♂️ 👸🏻 🤴🏻 🥷🏻 🦸🏻♀️ 🦸🏻 🦸🏻♂️ 🦹🏻♀️ 🦹🏻 🦹🏻♂️ 🤶🏻 🧑🏻🎄 🎅🏻 🧙🏻♀️ 🧙🏻 🧙🏻♂️ 🧝🏻♀️ 🧝🏻 🧝🏻♂️ 🧛🏻♀️ 🧛🏻 🧛🏻♂️ 🧜🏻♀️ 🧜🏻 🧜🏻♂️ 🧚🏻♀️ 🧚🏻 🧚🏻♂️ 👼🏻 🤰🏻 🤱🏻 👩🏻🍼 🧑🏻🍼 👨🏻🍼 🙇🏻♀️ 🙇🏻 🙇🏻♂️ 💁🏻♀️ 💁🏻 💁🏻♂️ 🙅🏻♀️ 🙅🏻 🙅🏻♂️ 🙆🏻♀️ 🙆🏻 🙆🏻♂️ 🙋🏻♀️ 🙋🏻 🙋🏻♂️ 🧏🏻♀️ 🧏🏻 🧏🏻♂️ 🤦🏻♀️ 🤦🏻 🤦🏻♂️ 🤷🏻♀️ 🤷🏻 🤷🏻♂️ 🙎🏻♀️ 🙎🏻 🙎🏻♂️ 🙍🏻♀️ 🙍🏻 🙍🏻♂️ 💇🏻♀️ 💇🏻 💇🏻♂️ 💆🏻♀️ 💆🏻 💆🏻♂️ 🧖🏻♀️ 🧖🏻 🧖🏻♂️ 💃🏻 🕺🏻 🕴🏻 👩🏻🦽 🧑🏻🦽 👨🏻🦽 👩🏻🦼 🧑🏻🦼 👨🏻🦼 🚶🏻♀️ 🚶🏻 🚶🏻♂️ 👩🏻🦯 🧑🏻🦯 👨🏻🦯 🧎🏻♀️ 🧎🏻 🧎🏻♂️ 🏃🏻♀️ 🏃🏻 🏃🏻♂️ 🧍🏻♀️ 🧍🏻 🧍🏻♂️ 👭🏻 🧑🏻🤝🧑🏻 👬🏻 👫🏻 🧗🏻♀️ 🧗🏻 🧗🏻♂️ 🏇🏻 🏂🏻 🏌🏻♀️ 🏌🏻 🏌🏻♂️ 🏄🏻♀️ 🏄🏻 🏄🏻♂️ 🚣🏻♀️ 🚣🏻 🚣🏻♂️ 🏊🏻♀️ 🏊🏻 🏊🏻♂️ ⛹🏻♀️ ⛹🏻 ⛹🏻♂️ 🏋🏻♀️ 🏋🏻 🏋🏻♂️ 🚴🏻♀️ 🚴🏻 🚴🏻♂️ 🚵🏻♀️ 🚵🏻 🚵🏻♂️ 🤸🏻♀️ 🤸🏻 🤸🏻♂️ 🤽🏻♀️ 🤽🏻 🤽🏻♂️ 🤾🏻♀️ 🤾🏻 🤾🏻♂️ 🤹🏻♀️ 🤹🏻 🤹🏻♂️ 🧘🏻♀️ 🧘🏻 🧘🏻♂️ 🛀🏻 🛌'.split(' '),
|
|
72868
|
+
'Cream White Emojis': '👋🏼 🤚🏼 🖐🏼 ✋🏼 🖖🏼 👌🏼 🤌🏼 🤏🏼 ✌🏼 🤞🏼 🤟🏼 🤘🏼 🤙🏼 👈🏼 👉🏼 👆🏼 🖕🏼 👇🏼 ☝🏼 👍🏼 👎🏼 ✊🏼 👊🏼 🤛🏼 🤜🏼 👏🏼 🙌🏼 👐🏼 🤲🏼 🙏🏼 ✍🏼 💅🏼 🤳🏼 💪🏼 🦵🏼 🦶🏼 👂🏼 🦻🏼 👃🏼 👶🏼 👧🏼 🧒🏼 👦🏼 👩🏼 🧑🏼 👨🏼 👩🏼🦱 🧑🏼🦱 👨🏼🦱 👩🏼🦰 🧑🏼🦰 👨🏼🦰 👱🏼♀️ 👱🏼 👱🏼♂️ 👩🏼🦳 🧑🏼🦳 👨🏼🦳 👩🏼🦲 🧑🏼🦲 👨🏼🦲 🧔🏼 👵🏼 🧓🏼 👴🏼 👲🏼 👳🏼♀️ 👳🏼 👳🏼♂️ 🧕🏼 👮🏼♀️ 👮🏼 👮🏼♂️ 👷🏼♀️ 👷🏼 👷🏼♂️ 💂🏼♀️ 💂🏼 💂🏼♂️ 🕵🏼♀️ 🕵🏼 🕵🏼♂️ 👩🏼⚕️ 🧑🏼⚕️ 👨🏼⚕️ 👩🏼🌾 🧑🏼🌾 👨🏼🌾 👩🏼🍳 🧑🏼🍳 👨🏼🍳 👩🏼🎓 🧑🏼🎓 👨🏼🎓 👩🏼🎤 🧑🏼🎤 👨🏼🎤 👩🏼🏫 🧑🏼🏫 👨🏼🏫 👩🏼🏭 🧑🏼🏭 👨🏼🏭 👩🏼💻 🧑🏼💻 👨🏼💻 👩🏼💼 🧑🏼💼 👨🏼💼 👩🏼🔧 🧑🏼🔧 👨🏼🔧 👩🏼🔬 🧑🏼🔬 👨🏼🔬 👩🏼🎨 🧑🏼🎨 👨🏼🎨 👩🏼🚒 🧑🏼🚒 👨🏼🚒 👩🏼✈️ 🧑🏼✈️ 👨🏼✈️ 👩🏼🚀 🧑🏼🚀 👨🏼🚀 👩🏼⚖️ 🧑🏼⚖️ 👨🏼⚖️ 👰🏼♀️ 👰🏼 👰🏼♂️ 🤵🏼♀️ 🤵🏼 🤵🏼♂️ 👸🏼 🤴🏼 🥷🏼 🦸🏼♀️ 🦸🏼 🦸🏼♂️ 🦹🏼♀️ 🦹🏼 🦹🏼♂️ 🤶🏼 🧑🏼🎄 🎅🏼 🧙🏼♀️ 🧙🏼 🧙🏼♂️ 🧝🏼♀️ 🧝🏼 🧝🏼♂️ 🧛🏼♀️ 🧛🏼 🧛🏼♂️ 🧜🏼♀️ 🧜🏼 🧜🏼♂️ 🧚🏼♀️ 🧚🏼 🧚🏼♂️ 👼🏼 🤰🏼 🤱🏼 👩🏼🍼 🧑🏼🍼 👨🏼🍼 🙇🏼♀️ 🙇🏼 🙇🏼♂️ 💁🏼♀️ 💁🏼 💁🏼♂️ 🙅🏼♀️ 🙅🏼 🙅🏼♂️ 🙆🏼♀️ 🙆🏼 🙆🏼♂️ 🙋🏼♀️ 🙋🏼 🙋🏼♂️ 🧏🏼♀️ 🧏🏼 🧏🏼♂️ 🤦🏼♀️ 🤦🏼 🤦🏼♂️ 🤷🏼♀️ 🤷🏼 🤷🏼♂️ 🙎🏼♀️ 🙎🏼 🙎🏼♂️ 🙍🏼♀️ 🙍🏼 🙍🏼♂️ 💇🏼♀️ 💇🏼 💇🏼♂️ 💆🏼♀️ 💆🏼 💆🏼♂️ 🧖🏼♀️ 🧖🏼 🧖🏼♂️ 💃🏼 🕺🏼 🕴🏼 👩🏼🦽 🧑🏼🦽 👨🏼🦽 👩🏼🦼 🧑🏼🦼 👨🏼🦼 🚶🏼♀️ 🚶🏼 🚶🏼♂️ 👩🏼🦯 🧑🏼🦯 👨🏼🦯 🧎🏼♀️ 🧎🏼 🧎🏼♂️ 🏃🏼♀️ 🏃🏼 🏃🏼♂️ 🧍🏼♀️ 🧍🏼 🧍🏼♂️ 👭🏼 🧑🏼🤝🧑🏼 👬🏼 👫🏼 🧗🏼♀️ 🧗🏼 🧗🏼♂️ 🏇🏼 🏂🏼 🏌🏼♀️ 🏌🏼 🏌🏼♂️ 🏄🏼♀️ 🏄🏼 🏄🏼♂️ 🚣🏼♀️ 🚣🏼 🚣🏼♂️ 🏊🏼♀️ 🏊🏼 🏊🏼♂️ ⛹🏼♀️ ⛹🏼 ⛹🏼♂️ 🏋🏼♀️ 🏋🏼 🏋🏼♂️ 🚴🏼♀️ 🚴🏼 🚴🏼♂️ 🚵🏼♀️ 🚵🏼 🚵🏼♂️ 🤸🏼♀️ 🤸🏼 🤸🏼♂️ 🤽🏼♀️ 🤽🏼 🤽🏼♂️ 🤾🏼♀️ 🤾🏼 🤾🏼♂️ 🤹🏼♀️ 🤹🏼 🤹🏼♂️ 🧘🏼♀️ 🧘🏼 🧘🏼♂️ 🛀🏼 🛌'.split(' '),
|
|
72869
|
+
'Brown Emojis': '👋🏽 🤚🏽 🖐🏽 ✋🏽 🖖🏽 👌🏽 🤌🏽 🤏🏽 ✌🏽 🤞🏽 🤟🏽 🤘🏽 🤙🏽 👈🏽 👉🏽 👆🏽 🖕🏽 👇🏽 ☝🏽 👍🏽 👎🏽 ✊🏽 👊🏽 🤛🏽 🤜🏽 👏🏽 🙌🏽 👐🏽 🤲🏽 🙏🏽 ✍🏽 💅🏽 🤳🏽 💪🏽 🦵🏽 🦶🏽 👂🏽 🦻🏽 👃🏽 👶🏽 👧🏽 🧒🏽 👦🏽 👩🏽 🧑🏽 👨🏽 👩🏽🦱 🧑🏽🦱 👨🏽🦱 👩🏽🦰 🧑🏽🦰 👨🏽🦰 👱🏽♀️ 👱🏽 👱🏽♂️ 👩🏽🦳 🧑🏽🦳 👨🏽🦳 👩🏽🦲 🧑🏽🦲 👨🏽🦲 🧔🏽 👵🏽 🧓🏽 👴🏽 👲🏽 👳🏽♀️ 👳🏽 👳🏽♂️ 🧕🏽 👮🏽♀️ 👮🏽 👮🏽♂️ 👷🏽♀️ 👷🏽 👷🏽♂️ 💂🏽♀️ 💂🏽 💂🏽♂️ 🕵🏽♀️ 🕵🏽 🕵🏽♂️ 👩🏽⚕️ 🧑🏽⚕️ 👨🏽⚕️ 👩🏽🌾 🧑🏽🌾 👨🏽🌾 👩🏽🍳 🧑🏽🍳 👨🏽🍳 👩🏽🎓 🧑🏽🎓 👨🏽🎓 👩🏽🎤 🧑🏽🎤 👨🏽🎤 👩🏽🏫 🧑🏽🏫 👨🏽🏫 👩🏽🏭 🧑🏽🏭 👨🏽🏭 👩🏽💻 🧑🏽💻 👨🏽💻 👩🏽💼 🧑🏽💼 👨🏽💼 👩🏽🔧 🧑🏽🔧 👨🏽🔧 👩🏽🔬 🧑🏽🔬 👨🏽🔬 👩🏽🎨 🧑🏽🎨 👨🏽🎨 👩🏽🚒 🧑🏽🚒 👨🏽🚒 👩🏽✈️ 🧑🏽✈️ 👨🏽✈️ 👩🏽🚀 🧑🏽🚀 👨🏽🚀 👩🏽⚖️ 🧑🏽⚖️ 👨🏽⚖️ 👰🏽♀️ 👰🏽 👰🏽♂️ 🤵🏽♀️ 🤵🏽 🤵🏽♂️ 👸🏽 🤴🏽 🥷🏽 🦸🏽♀️ 🦸🏽 🦸🏽♂️ 🦹🏽♀️ 🦹🏽 🦹🏽♂️ 🤶🏽 🧑🏽🎄 🎅🏽 🧙🏽♀️ 🧙🏽 🧙🏽♂️ 🧝🏽♀️ 🧝🏽 🧝🏽♂️ 🧛🏽♀️ 🧛🏽 🧛🏽♂️ 🧜🏽♀️ 🧜🏽 🧜🏽♂️ 🧚🏽♀️ 🧚🏽 🧚🏽♂️ 👼🏽 🤰🏽 🤱🏽 👩🏽🍼 🧑🏽🍼 👨🏽🍼 🙇🏽♀️ 🙇🏽 🙇🏽♂️ 💁🏽♀️ 💁🏽 💁🏽♂️ 🙅🏽♀️ 🙅🏽 🙅🏽♂️ 🙆🏽♀️ 🙆🏽 🙆🏽♂️ 🙋🏽♀️ 🙋🏽 🙋🏽♂️ 🧏🏽♀️ 🧏🏽 🧏🏽♂️ 🤦🏽♀️ 🤦🏽 🤦🏽♂️ 🤷🏽♀️ 🤷🏽 🤷🏽♂️ 🙎🏽♀️ 🙎🏽 🙎🏽♂️ 🙍🏽♀️ 🙍🏽 🙍🏽♂️ 💇🏽♀️ 💇🏽 💇🏽♂️ 💆🏽♀️ 💆🏽 💆🏽♂️ 🧖🏽♀️ 🧖🏽 🧖🏽♂️ 💃🏽 🕺🏽 🕴🏽 👩🏽🦽 🧑🏽🦽 👨🏽🦽 👩🏽🦼 🧑🏽🦼 👨🏽🦼 🚶🏽♀️ 🚶🏽 🚶🏽♂️ 👩🏽🦯 🧑🏽🦯 👨🏽🦯 🧎🏽♀️ 🧎🏽 🧎🏽♂️ 🏃🏽♀️ 🏃🏽 🏃🏽♂️ 🧍🏽♀️ 🧍🏽 🧍🏽♂️ 👭🏽 🧑🏽🤝🧑🏽 👬🏽 👫🏽 🧗🏽♀️ 🧗🏽 🧗🏽♂️ 🏇🏽 🏂🏽 🏌🏽♀️ 🏌🏽 🏌🏽♂️ 🏄🏽♀️ 🏄🏽 🏄🏽♂️ 🚣🏽♀️ 🚣🏽 🚣🏽♂️ 🏊🏽♀️ 🏊🏽 🏊🏽♂️ ⛹🏽♀️ ⛹🏽 ⛹🏽♂️ 🏋🏽♀️ 🏋🏽 🏋🏽♂️ 🚴🏽♀️ 🚴🏽 🚴🏽♂️ 🚵🏽♀️ 🚵🏽 🚵🏽♂️ 🤸🏽♀️ 🤸🏽 🤸🏽♂️ 🤽🏽♀️ 🤽🏽 🤽🏽♂️ 🤾🏽♀️ 🤾🏽 🤾🏽♂️ 🤹🏽♀️ 🤹🏽 🤹🏽♂️ 🧘🏽♀️ 🧘🏽 🧘🏽♂️ 🛀🏽 🛌'.split(' '),
|
|
72870
|
+
'Dark Brown Emojis': '👋🏾 🤚🏾 🖐🏾 ✋🏾 🖖🏾 👌🏾 🤌🏾 🤏🏾 ✌🏾 🤞🏾 🤟🏾 🤘🏾 🤙🏾 👈🏾 👉🏾 👆🏾 🖕🏾 👇🏾 ☝🏾 👍🏾 👎🏾 ✊🏾 👊🏾 🤛🏾 🤜🏾 👏🏾 🙌🏾 👐🏾 🤲🏾 🙏🏾 ✍🏾 💅🏾 🤳🏾 💪🏾 🦵🏾 🦶🏾 👂🏾 🦻🏾 👃🏾 👶🏾 👧🏾 🧒🏾 👦🏾 👩🏾 🧑🏾 👨🏾 👩🏾🦱 🧑🏾🦱 👨🏾🦱 👩🏾🦰 🧑🏾🦰 👨🏾🦰 👱🏾♀️ 👱🏾 👱🏾♂️ 👩🏾🦳 🧑🏾🦳 👨🏾🦳 👩🏾🦲 🧑🏾🦲 👨🏾🦲 🧔🏾 👵🏾 🧓🏾 👴🏾 👲🏾 👳🏾♀️ 👳🏾 👳🏾♂️ 🧕🏾 👮🏾♀️ 👮🏾 👮🏾♂️ 👷🏾♀️ 👷🏾 👷🏾♂️ 💂🏾♀️ 💂🏾 💂🏾♂️ 🕵🏾♀️ 🕵🏾 🕵🏾♂️ 👩🏾⚕️ 🧑🏾⚕️ 👨🏾⚕️ 👩🏾🌾 🧑🏾🌾 👨🏾🌾 👩🏾🍳 🧑🏾🍳 👨🏾🍳 👩🏾🎓 🧑🏾🎓 👨🏾🎓 👩🏾🎤 🧑🏾🎤 👨🏾🎤 👩🏾🏫 🧑🏾🏫 👨🏾🏫 👩🏾🏭 🧑🏾🏭 👨🏾🏭 👩🏾💻 🧑🏾💻 👨🏾💻 👩🏾💼 🧑🏾💼 👨🏾💼 👩🏾🔧 🧑🏾🔧 👨🏾🔧 👩🏾🔬 🧑🏾🔬 👨🏾🔬 👩🏾🎨 🧑🏾🎨 👨🏾🎨 👩🏾🚒 🧑🏾🚒 👨🏾🚒 👩🏾✈️ 🧑🏾✈️ 👨🏾✈️ 👩🏾🚀 🧑🏾🚀 👨🏾🚀 👩🏾⚖️ 🧑🏾⚖️ 👨🏾⚖️ 👰🏾♀️ 👰🏾 👰🏾♂️ 🤵🏾♀️ 🤵🏾 🤵🏾♂️ 👸🏾 🤴🏾 🥷🏾 🦸🏾♀️ 🦸🏾 🦸🏾♂️ 🦹🏾♀️ 🦹🏾 🦹🏾♂️ 🤶🏾 🧑🏾🎄 🎅🏾 🧙🏾♀️ 🧙🏾 🧙🏾♂️ 🧝🏾♀️ 🧝🏾 🧝🏾♂️ 🧛🏾♀️ 🧛🏾 🧛🏾♂️ 🧜🏾♀️ 🧜🏾 🧜🏾♂️ 🧚🏾♀️ 🧚🏾 🧚🏾♂️ 👼🏾 🤰🏾 🤱🏾 👩🏾🍼 🧑🏾🍼 👨🏾🍼 🙇🏾♀️ 🙇🏾 🙇🏾♂️ 💁🏾♀️ 💁🏾 💁🏾♂️ 🙅🏾♀️ 🙅🏾 🙅🏾♂️ 🙆🏾♀️ 🙆🏾 🙆🏾♂️ 🙋🏾♀️ 🙋🏾 🙋🏾♂️ 🧏🏾♀️ 🧏🏾 🧏🏾♂️ 🤦🏾♀️ 🤦🏾 🤦🏾♂️ 🤷🏾♀️ 🤷🏾 🤷🏾♂️ 🙎🏾♀️ 🙎🏾 🙎🏾♂️ 🙍🏾♀️ 🙍🏾 🙍🏾♂️ 💇🏾♀️ 💇🏾 💇🏾♂️ 💆🏾♀️ 💆🏾 💆🏾♂️ 🧖🏾♀️ 🧖🏾 🧖🏾♂️ 💃🏾 🕺🏾 🕴🏿 👩🏾🦽 🧑🏾🦽 👨🏾🦽 👩🏾🦼 🧑🏾🦼 👨🏾🦼 🚶🏾♀️ 🚶🏾 🚶🏾♂️ 👩🏾🦯 🧑🏾🦯 👨🏾🦯 🧎🏾♀️ 🧎🏾 🧎🏾♂️ 🏃🏾♀️ 🏃🏾 🏃🏾♂️ 🧍🏾♀️ 🧍🏾 🧍🏾♂️ 👭🏾 🧑🏾🤝🧑🏾 👬🏾 👫🏾 🧗🏾♀️ 🧗🏾 🧗🏾♂️ 🏇🏾 🏂🏾 🏌🏾♀️ 🏌🏾 🏌🏾♂️ 🏄🏾♀️ 🏄🏾 🏄🏾♂️ 🚣🏾♀️ 🚣🏾 🚣🏾♂️ 🏊🏾♀️ 🏊🏾 🏊🏾♂️ ⛹🏾♀️ ⛹🏾 ⛹🏾♂️ 🏋🏾♀️ 🏋🏾 🏋🏾♂️ 🚴🏾♀️ 🚴🏾 🚴🏾♂️ 🚵🏾♀️ 🚵🏾 🚵🏾♂️ 🤸🏾♀️ 🤸🏾 🤸🏾♂️ 🤽🏾♀️ 🤽🏾 🤽🏾♂️ 🤾🏾♀️ 🤾🏾 🤾🏾♂️ 🤹🏾♀️ 🤹🏾 🤹🏾♂️ 🧘🏾♀️ 🧘🏾 🧘🏾♂️ 🛀🏾 🛌'.split(' '),
|
|
72871
|
+
'Black Emojis': '👋🏿 🤚🏿 🖐🏿 ✋🏿 🖖🏿 👌🏿 🤌🏿 🤏🏿 ✌🏿 🤞🏿 🤟🏿 🤘🏿 🤙🏿 👈🏿 👉🏿 👆🏿 🖕🏿 👇🏿 ☝🏿 👍🏿 👎🏿 ✊🏿 👊🏿 🤛🏿 🤜🏿 👏🏿 🙌🏿 👐🏿 🤲🏿 🙏🏿 ✍🏿 💅🏿 🤳🏿 💪🏿 🦵🏿 🦶🏿 👂🏿 🦻🏿 👃🏿 👶🏿 👧🏿 🧒🏿 👦🏿 👩🏿 🧑🏿 👨🏿 👩🏿🦱 🧑🏿🦱 👨🏿🦱 👩🏿🦰 🧑🏿🦰 👨🏿🦰 👱🏿♀️ 👱🏿 👱🏿♂️ 👩🏿🦳 🧑🏿🦳 👨🏿🦳 👩🏿🦲 🧑🏿🦲 👨🏿🦲 🧔🏿 👵🏿 🧓🏿 👴🏿 👲🏿 👳🏿♀️ 👳🏿 👳🏿♂️ 🧕🏿 👮🏿♀️ 👮🏿 👮🏿♂️ 👷🏿♀️ 👷🏿 👷🏿♂️ 💂🏿♀️ 💂🏿 💂🏿♂️ 🕵🏿♀️ 🕵🏿 🕵🏿♂️ 👩🏿⚕️ 🧑🏿⚕️ 👨🏿⚕️ 👩🏿🌾 🧑🏿🌾 👨🏿🌾 👩🏿🍳 🧑🏿🍳 👨🏿🍳 👩🏿🎓 🧑🏿🎓 👨🏿🎓 👩🏿🎤 🧑🏿🎤 👨🏿🎤 👩🏿🏫 🧑🏿🏫 👨🏿🏫 👩🏿🏭 🧑🏿🏭 👨🏿🏭 👩🏿💻 🧑🏿💻 👨🏿💻 👩🏿💼 🧑🏿💼 👨🏿💼 👩🏿🔧 🧑🏿🔧 👨🏿🔧 👩🏿🔬 🧑🏿🔬 👨🏿🔬 👩🏿🎨 🧑🏿🎨 👨🏿🎨 👩🏿🚒 🧑🏿🚒 👨🏿🚒 👩🏿✈️ 🧑🏿✈️ 👨🏿✈️ 👩🏿🚀 🧑🏿🚀 👨🏿🚀 👩🏿⚖️ 🧑🏿⚖️ 👨🏿⚖️ 👰🏿♀️ 👰🏿 👰🏿♂️ 🤵🏿♀️ 🤵🏿 🤵🏿♂️ 👸🏿 🤴🏿 🥷🏿 🦸🏿♀️ 🦸🏿 🦸🏿♂️ 🦹🏿♀️ 🦹🏿 🦹🏿♂️ 🤶🏿 🧑🏿🎄 🎅🏿 🧙🏿♀️ 🧙🏿 🧙🏿♂️ 🧝🏿♀️ 🧝🏿 🧝🏿♂️ 🧛🏿♀️ 🧛🏿 🧛🏿♂️ 🧜🏿♀️ 🧜🏿 🧜🏿♂️ 🧚🏿♀️ 🧚🏿 🧚🏿♂️ 👼🏿 🤰🏿 🤱🏿 👩🏿🍼 🧑🏿🍼 👨🏿🍼 🙇🏿♀️ 🙇🏿 🙇🏿♂️ 💁🏿♀️ 💁🏿 💁🏿♂️ 🙅🏿♀️ 🙅🏿 🙅🏿♂️ 🙆🏿♀️ 🙆🏿 🙆🏿♂️ 🙋🏿♀️ 🙋🏿 🙋🏿♂️ 🧏🏿♀️ 🧏🏿 🧏🏿♂️ 🤦🏿♀️ 🤦🏿 🤦🏿♂️ 🤷🏿♀️ 🤷🏿 🤷🏿♂️ 🙎🏿♀️ 🙎🏿 🙎🏿♂️ 🙍🏿♀️ 🙍🏿 🙍🏿♂️ 💇🏿♀️ 💇🏿 💇🏿♂️ 💆🏿♀️ 💆🏿 💆🏿♂️ 🧖🏿♀️ 🧖🏿 🧖🏿♂️ 💃🏿 🕺🏿 🕴🏿 👩🏿🦽 🧑🏿🦽 👨🏿🦽 👩🏿🦼 🧑🏿🦼 👨🏿🦼 🚶🏿♀️ 🚶🏿 🚶🏿♂️ 👩🏿🦯 🧑🏿🦯 👨🏿🦯 🧎🏿♀️ 🧎🏿 🧎🏿♂️ 🏃🏿♀️ 🏃🏿 🏃🏿♂️ 🧍🏿♀️ 🧍🏿 🧍🏿♂️ 👭🏿 🧑🏿🤝🧑🏿 👬🏿 👫🏿 🧗🏿♀️ 🧗🏿 🧗🏿♂️ 🏇🏿 🏂🏿 🏌🏿♀️ 🏌🏿 🏌🏿♂️ 🏄🏿♀️ 🏄🏿 🏄🏿♂️ 🚣🏿♀️ 🚣🏿 🚣🏿♂️ 🏊🏿♀️ 🏊🏿 🏊🏿♂️ ⛹🏿♀️ ⛹🏿 ⛹🏿♂️ 🏋🏿♀️ 🏋🏿 🏋🏿♂️ 🚴🏿♀️ 🚴🏿 🚴🏿♂️ 🚵🏿♀️ 🚵🏿 🚵🏿♂️ 🤸🏿♀️ 🤸🏿 🤸🏿♂️ 🤽🏿♀️ 🤽🏿 🤽🏿♂️ 🤾🏿♀️ 🤾🏿 🤾🏿♂️ 🤹🏿♀️ 🤹🏿 🤹🏿♂️ 🧘🏿♀️ 🧘🏿 🧘🏿♂️ 🛀🏿 🛌🏿'.split(' '),
|
|
72872
|
+
'Animals & Nature': '🐶 🐱 🐭 🐹 🐰 🦊 🐻 🐼 🐻❄️ 🐨 🐯 🦁 🐮 🐷 🐽 🐸 🐵 🙈 🙉 🙊 🐒 🐔 🐧 🐦 🐤 🐣 🐥 🦆 🦅 🦉 🦇 🐺 🐗 🐴 🦄 🐝 🪱 🐛 🦋 🐌 🐞 🐜 🪰 🪲 🪳 🦟 🦗 🕷 🕸 🦂 🐢 🐍 🦎 🦖 🦕 🐙 🦑 🦐 🦞 🦀 🐡 🐠 🐟 🐬 🐳 🐋 🦈 🐊 🐅 🐆 🦓 🦍 🦧 🦣 🐘 🦛 🦏 🐪 🐫 🦒 🦘 🦬 🐃 🐂 🐄 🐎 🐖 🐏 🐑 🦙 🐐 🦌 🐕 🐩 🦮 🐕🦺 🐈 🐈⬛ 🪶 🐓 🦃 🦤 🦚 🦜 🦢 🦩 🕊 🐇 🦝 🦨 🦡 🦫 🦦 🦥 🐁 🐀 🐿 🦔 🐾 🐉 🐲 🌵 🎄 🌲 🌳 🌴 🪵 🌱 🌿 ☘️ 🍀 🎍 🪴 🎋 🍃 🍂 🍁 🍄 🐚 🪨 🌾 💐 🌷 🌹 🥀 🌺 🌸 🌼 🌻 🌞 🌝 🌛 🌜 🌚 🌕 🌖 🌗 🌘 🌑 🌒 🌓 🌔 🌙 🌎 🌍 🌏 🪐 💫 ⭐️ 🌟 ✨ ⚡️ ☄️ 💥 🔥 🌪 🌈 ☀️ 🌤 ⛅️ 🌥 ☁️ 🌦 🌧 ⛈ 🌩 🌨 ❄️ ☃️ ⛄️ 🌬 💨 💧 💦 ☔️ ☂️ 🌊 🌫'.split(' '),
|
|
72873
|
+
'Food & Drink': '🍏 🍎 🍐 🍊 🍋 🍌 🍉 🍇 🍓 🫐 🍈 🍒 🍑 🥭 🍍 🥥 🥝 🍅 🍆 🥑 🥦 🥬 🥒 🌶 🫑 🌽 🥕 🫒 🧄 🧅 🥔 🍠 🥐 🥯 🍞 🥖 🥨 🧀 🥚 🍳 🧈 🥞 🧇 🥓 🥩 🍗 🍖 🦴 🌭 🍔 🍟 🍕 🫓 🥪 🥙 🧆 🌮 🌯 🫔 🥗 🥘 🫕 🥫 🍝 🍜 🍲 🍛 🍣 🍱 🥟 🦪 🍤 🍙 🍚 🍘 🍥 🥠 🥮 🍢 🍡 🍧 🍨 🍦 🥧 🧁 🍰 🎂 🍮 🍭 🍬 🍫 🍿 🍩 🍪 🌰 🥜 🍯 🥛 🍼 🫖 ☕️ 🍵 🧃 🥤 🧋 🍶 🍺 🍻 🥂 🍷 🥃 🍸 🍹 🧉 🍾 🧊 🥄 🍴 🍽 🥣 🥡 🥢 🧂'.split(' '),
|
|
72874
|
+
'Activity and Sports': '⚽️ 🏀 🏈 ⚾️ 🥎 🎾 🏐 🏉 🥏 🎱 🪀 🏓 🏸 🏒 🏑 🥍 🏏 🪃 🥅 ⛳️ 🪁 🏹 🎣 🤿 🥊 🥋 🎽 🛹 🛼 🛷 ⛸ 🥌 🎿 ⛷ 🏂 🪂 🏋️♀️ 🏋️ 🏋️♂️ 🤼♀️ 🤼 🤼♂️ 🤸♀️ 🤸 🤸♂️ ⛹️♀️ ⛹️ ⛹️♂️ 🤺 🤾♀️ 🤾 🤾♂️ 🏌️♀️ 🏌️ 🏌️♂️ 🏇 🧘♀️ 🧘 🧘♂️ 🏄♀️ 🏄 🏄♂️ 🏊♀️ 🏊 🏊♂️ 🤽♀️ 🤽 🤽♂️ 🚣♀️ 🚣 🚣♂️ 🧗♀️ 🧗 🧗♂️ 🚵♀️ 🚵 🚵♂️ 🚴♀️ 🚴 🚴♂️ 🏆 🥇 🥈 🥉 🏅 🎖 🏵 🎗 🎫 🎟 🎪 🤹 🤹♂️ 🤹♀️ 🎭 🩰 🎨 🎬 🎤 🎧 🎼 🎹 🥁 🪘 🎷 🎺 🪗 🎸 🪕 🎻 🎲 ♟ 🎯 🎳 🎮 🎰 🧩'.split(' '),
|
|
72875
|
+
'Travel & Places': '🚗 🚕 🚙 🚌 🚎 🏎 🚓 🚑 🚒 🚐 🛻 🚚 🚛 🚜 🦯 🦽 🦼 🛴 🚲 🛵 🏍 🛺 🚨 🚔 🚍 🚘 🚖 🚡 🚠 🚟 🚃 🚋 🚞 🚝 🚄 🚅 🚈 🚂 🚆 🚇 🚊 🚉 ✈️ 🛫 🛬 🛩 💺 🛰 🚀 🛸 🚁 🛶 ⛵️ 🚤 🛥 🛳 ⛴ 🚢 ⚓️ 🪝 ⛽️ 🚧 🚦 🚥 🚏 🗺 🗿 🗽 🗼 🏰 🏯 🏟 🎡 🎢 🎠 ⛲️ ⛱ 🏖 🏝 🏜 🌋 ⛰ 🏔 🗻 🏕 ⛺️ 🛖 🏠 🏡 🏘 🏚 🏗 🏭 🏢 🏬 🏣 🏤 🏥 🏦 🏨 🏪 🏫 🏩 💒 🏛 ⛪️ 🕌 🕍 🛕 🕋 ⛩ 🛤 🛣 🗾 🎑 🏞 🌅 🌄 🌠 🎇 🎆 🌇 🌆 🏙 🌃 🌌 🌉 🌁'.split(' '),
|
|
72876
|
+
Objects: '⌚️ 📱 📲 💻 ⌨️ 🖥 🖨 🖱 🖲 🕹 🗜 💽 💾 💿 📀 📼 📷 📸 📹 🎥 📽 🎞 📞 ☎️ 📟 📠 📺 📻 🎙 🎚 🎛 🧭 ⏱ ⏲ ⏰ 🕰 ⌛️ ⏳ 📡 🔋 🔌 💡 🔦 🕯 🪔 🧯 🛢 💸 💵 💴 💶 💷 🪙 💰 💳 💎 ⚖️ 🪜 🧰 🪛 🔧 🔨 ⚒ 🛠 ⛏ 🪚 🔩 ⚙️ 🪤 🧱 ⛓ 🧲 🔫 💣 🧨 🪓 🔪 🗡 ⚔️ 🛡 🚬 ⚰️ 🪦 ⚱️ 🏺 🔮 📿 🧿 💈 ⚗️ 🔭 🔬 🕳 🩹 🩺 💊 💉 🩸 🧬 🦠 🧫 🧪 🌡 🧹 🪠 🧺 🧻 🚽 🚰 🚿 🛁 🛀 🧼 🪥 🪒 🧽 🪣 🧴 🛎 🔑 🗝 🚪 🪑 🛋 🛏 🛌 🧸 🪆 🖼 🪞 🪟 🛍 🛒 🎁 🎈 🎏 🎀 🪄 🪅 🎊 🎉 🎎 🏮 🎐 🧧 ✉️ 📩 📨 📧 💌 📥 📤 📦 🏷 🪧 📪 📫 📬 📭 📮 📯 📜 📃 📄 📑 🧾 📊 📈 📉 🗒 🗓 📆 📅 🗑 📇 🗃 🗳 🗄 📋 📁 📂 🗂 🗞 📰 📓 📔 📒 📕 📗 📘 📙 📚 📖 🔖 🧷 🔗 📎 🖇 📐 📏 🧮 📌 📍 ✂️ 🖊 🖋 ✒️ 🖌 🖍 📝 ✏️ 🔍 🔎 🔏 🔐 🔒 🔓'.split(' '),
|
|
72877
|
+
Symbols: '❤️ 🧡 💛 💚 💙 💜 🖤 🤍 🤎 💔 ❣️ 💕 💞 💓 💗 💖 💘 💝 💟 ☮️ ✝️ ☪️ 🕉 ☸️ ✡️ 🔯 🕎 ☯️ ☦️ 🛐 ⛎ ♈️ ♉️ ♊️ ♋️ ♌️ ♍️ ♎️ ♏️ ♐️ ♑️ ♒️ ♓️ 🆔 ⚛️ 🉑 ☢️ ☣️ 📴 📳 🈶 🈚️ 🈸 🈺 🈷️ ✴️ 🆚 💮 🉐 ㊙️ ㊗️ 🈴 🈵 🈹 🈲 🅰️ 🅱️ 🆎 🆑 🅾️ 🆘 ❌ ⭕️ 🛑 ⛔️ 📛 🚫 💯 💢 ♨️ 🚷 🚯 🚳 🚱 🔞 📵 🚭 ❗️ ❕ ❓ ❔ ‼️ ⁉️ 🔅 🔆 〽️ ⚠️ 🚸 🔱 ⚜️ 🔰 ♻️ ✅ 🈯️ 💹 ❇️ ✳️ ❎ 🌐 💠 Ⓜ️ 🌀 💤 🏧 🚾 ♿️ 🅿️ 🛗 🈳 🈂️ 🛂 🛃 🛄 🛅 🚹 🚺 🚼 ⚧ 🚻 🚮 🎦 📶 🈁 🔣 ℹ️ 🔤 🔡 🔠 🆖 🆗 🆙 🆒 🆕 🆓 0️⃣ 1️⃣ 2️⃣ 3️⃣ 4️⃣ 5️⃣ 6️⃣ 7️⃣ 8️⃣ 9️⃣ 🔟 🔢 #️⃣ *️⃣ ⏏️ ▶️ ⏸ ⏯ ⏹ ⏺ ⏭ ⏮ ⏩ ⏪ ⏫ ⏬ ◀️ 🔼 🔽 ➡️ ⬅️ ⬆️ ⬇️ ↗️ ↘️ ↙️ ↖️ ↕️ ↔️ ↪️ ↩️ ⤴️ ⤵️ 🔀 🔁 🔂 🔄 🔃 🎵 🎶 ➕ ➖ ➗ ✖️ ♾ 💲 💱 ™️ ©️ ®️ 〰️ ➰ ➿ 🔚 🔙 🔛 🔝 🔜 ✔️ ☑️ 🔘 🔴 🟠 🟡 🟢 🔵 🟣 ⚫️ ⚪️ 🟤 🔺 🔻 🔸 🔹 🔶 🔷 🔳 🔲 ▪️ ▫️ ◾️ ◽️ ◼️ ◻️ 🟥 🟧 🟨 🟩 🟦 🟪 ⬛️ ⬜️ 🟫 🔈 🔇 🔉 🔊 🔔 🔕 📣 📢 👁🗨 💬 💭 🗯 ♠️ ♣️ ♥️ ♦️ 🃏 🎴 🀄️ 🕐 🕑 🕒 🕓 🕔 🕕 🕖 🕗 🕘 🕙 🕚 🕛 🕜 🕝 🕞 🕟 🕠 🕡 🕢 🕣 🕤 🕥 🕦 🕧'.split(' '),
|
|
72878
|
+
'Non-Emoji Symbols': '✢ ✣ ✤ ✥ ✦ ✧ ★ ☆ ✯ ✡︎ ✩ ✪ ✫ ✬ ✭ ✮ ✶ ✷ ✵ ✸ ✹ → ⇒ ⟹ ⇨ ⇾ ➾ ⇢ ☛ ☞ ➔ ➜ ➙ ➛ ➝ ➞ ♠︎ ♣︎ ♥︎ ♦︎ ♤ ♧ ♡ ♢ ♚ ♛ ♜ ♝ ♞ ♟ ♔ ♕ ♖ ♗ ♘ ♙ ⚀ ⚁ ⚂ ⚃ ⚄ ⚅ 🂠 ⚈ ⚉ ⚆ ⚇ 𓀀 𓀁 𓀂 𓀃 𓀄 𓀅 𓀆 𓀇 𓀈 𓀉 𓀊 𓀋 𓀌 𓀍 𓀎 𓀏 𓀐 𓀑 𓀒 𓀓 𓀔 𓀕 𓀖 𓀗 𓀘 𓀙 𓀚 𓀛 𓀜 𓀝'.split(' '),
|
|
72879
|
+
Flags: '🏳️ 🏴 🏁 🚩 🏳️🌈 🏳️⚧️ 🏴☠️'.split(' '),
|
|
72880
|
+
Countries: '🇦🇫 🇦🇽 🇦🇱 🇩🇿 🇦🇸 🇦🇩 🇦🇴 🇦🇮 🇦🇶 🇦🇬 🇦🇷 🇦🇲 🇦🇼 🇦🇺 🇦🇹 🇦🇿 🇧🇸 🇧🇭 🇧🇩 🇧🇧 🇧🇾 🇧🇪 🇧🇿 🇧🇯 🇧🇲 🇧🇹 🇧🇴 🇧🇦 🇧🇼 🇧🇷 🇮🇴 🇻🇬 🇧🇳 🇧🇬 🇧🇫 🇧🇮 🇰🇭 🇨🇲 🇨🇦 🇮🇨 🇨🇻 🇧🇶 🇰🇾 🇨🇫 🇹🇩 🇨🇱 🇨🇳 🇨🇽 🇨🇨 🇨🇴 🇰🇲 🇨🇬 🇨🇩 🇨🇰 🇨🇷 🇨🇮 🇭🇷 🇨🇺 🇨🇼 🇨🇾 🇨🇿 🇩🇰 🇩🇯 🇩🇲 🇩🇴 🇪🇨 🇪🇬 🇸🇻 🇬🇶 🇪🇷 🇪🇪 🇪🇹 🇪🇺 🇫🇰 🇫🇴 🇫🇯 🇫🇮 🇫🇷 🇬🇫 🇵🇫 🇹🇫 🇬🇦 🇬🇲 🇬🇪 🇩🇪 🇬🇭 🇬🇮 🇬🇷 🇬🇱 🇬🇩 🇬🇵 🇬🇺 🇬🇹 🇬🇬 🇬🇳 🇬🇼 🇬🇾 🇭🇹 🇭🇳 🇭🇰 🇭🇺 🇮🇸 🇮🇳 🇮🇩 🇮🇷 🇮🇶 🇮🇪 🇮🇲 🇮🇱 🇮🇹 🇯🇲 🇯🇵 🎌 🇯🇪 🇯🇴 🇰🇿 🇰🇪 🇰🇮 🇽🇰 🇰🇼 🇰🇬 🇱🇦 🇱🇻 🇱🇧 🇱🇸 🇱🇷 🇱🇾 🇱🇮 🇱🇹 🇱🇺 🇲🇴 🇲🇰 🇲🇬 🇲🇼 🇲🇾 🇲🇻 🇲🇱 🇲🇹 🇲🇭 🇲🇶 🇲🇷 🇲🇺 🇾🇹 🇲🇽 🇫🇲 🇲🇩 🇲🇨 🇲🇳 🇲🇪 🇲🇸 🇲🇦 🇲🇿 🇲🇲 🇳🇦 🇳🇷 🇳🇵 🇳🇱 🇳🇨 🇳🇿 🇳🇮 🇳🇪 🇳🇬 🇳🇺 🇳🇫 🇰🇵 🇲🇵 🇳🇴 🇴🇲 🇵🇰 🇵🇼 🇵🇸 🇵🇦 🇵🇬 🇵🇾 🇵🇪 🇵🇭 🇵🇳 🇵🇱 🇵🇹 🇵🇷 🇶🇦 🇷🇪 🇷🇴 🇷🇺 🇷🇼 🇼🇸 🇸🇲 🇸🇦 🇸🇳 🇷🇸 🇸🇨 🇸🇱 🇸🇬 🇸🇽 🇸🇰 🇸🇮 🇬🇸 🇸🇧 🇸🇴 🇿🇦 🇰🇷 🇸🇸 🇪🇸 🇱🇰 🇧🇱 🇸🇭 🇰🇳 🇱🇨 🇵🇲 🇻🇨 🇸🇩 🇸🇷 🇸🇿 🇸🇪 🇨🇭 🇸🇾 🇹🇼 🇹🇯 🇹🇿 🇹🇭 🇹🇱 🇹🇬 🇹🇰 🇹🇴 🇹🇹 🇹🇳 🇹🇷 🇹🇲 🇹🇨 🇹🇻 🇻🇮 🇺🇬 🇺🇦 🇦🇪 🇬🇧 🇺🇳 🇺🇸 🇺🇾 🇺🇿 🇻🇺 🇻🇦 🇻🇪 🇻🇳 🇼🇫 🇪🇭 🇾🇪 🇿🇲 🇿🇼'.split(' '),
|
|
72881
|
+
'New Emojis 2020': '🥲 🥸 🤌 🤌🏻 🤌🏼 🤌🏽 🤌🏾 🤌🏿 🫀 🫁 🥷 🤵♀️ 🤵🏻♀️ 🤵🏼♀️ 🤵🏽♀️ 🤵🏾♀️ 🤵🏿♀️ 🤵♂️ 🤵🏻♂️ 🤵🏼♂️ 🤵🏽♂️ 🤵🏾♂️ 🤵🏿♂️ 👰♀️ 👰🏻♀️ 👰🏼♀️ 👰🏽♀️ 👰🏾♀️ 👰🏿♀️ 👰♂️ 👰🏻♂️ 👰🏼♂️ 👰🏽♂️ 👰🏾♂️ 👰🏿♂️ 👩🍼 👩🏻🍼 👩🏼🍼 👩🏽🍼 👩🏾🍼 👩🏿🍼 🧑🍼 🧑🏻🍼 🧑🏼🍼 🧑🏽🍼 🧑🏾🍼 🧑🏿🍼 👨🍼 👨🏻🍼 👨🏼🍼 👨🏽🍼 👨🏾🍼 👨🏿🍼 🧑🎄 🧑🏻🎄 🧑🏼🎄 🧑🏽🎄 🧑🏾🎄 🧑🏿🎄 🫂 🐈⬛ 🦬 🦣 🦫 🐻❄️ 🦤 🪶 🦭 🪲 🪳 🪰 🪱 🪴 🫐 🫒 🫑 🫓 🫔 🫕 🫖 🧋 🪨 🪵 🛖 🛻 🛼 🪄 🪅 🪆 🪡 🪢 🩴 🪖 🪗 🪘 🪙 🪃 🪚 🪛 🪝 🪜 🛗 🪞 🪟 🪠 🪤 🪣 🪥 🪦 🪧 🏳️⚧️'.split(' '),
|
|
72882
|
+
'New Emoji 2021': '😮💨 😵💫 😶🌫️ ❤️🔥 ❤️🩹 🧔♀️ 🧔🏻♀️ 🧔🏼♀️ 🧔🏽♀️ 🧔🏾♀️ 🧔🏿♀️ 🧔♂️ 🧔🏻♂️ 🧔🏼♂️ 🧔🏽♂️ 🧔🏾♂️ 🧔🏿♂️ 💑🏻 💑🏼 💑🏽 💑🏾 💑🏿 💏🏻 💏🏼 💏🏽 💏🏾 💏🏿 👨🏻❤️👨🏻 👨🏻❤️👨🏼 👨🏻❤️👨🏽 👨🏻❤️👨🏾 👨🏻❤️👨🏿 👨🏼❤️👨🏻 👨🏼❤️👨🏼 👨🏼❤️👨🏽 👨🏼❤️👨🏾 👨🏼❤️👨🏿 👨🏽❤️👨🏻 👨🏽❤️👨🏼 👨🏽❤️👨🏽 👨🏽❤️👨🏾 👨🏽❤️👨🏿 👨🏾❤️👨🏻 👨🏾❤️👨🏼 👨🏾❤️👨🏽 👨🏾❤️👨🏾 👨🏾❤️👨🏿 👨🏿❤️👨🏻 👨🏿❤️👨🏼 👨🏿❤️👨🏽 👨🏿❤️👨🏾 👨🏿❤️👨🏿 👩🏻❤️👨🏻 👩🏻❤️👨🏼 👩🏻❤️👨🏽 👩🏻❤️👨🏾 👩🏻❤️👨🏿 👩🏻❤️👩🏻 👩🏻❤️👩🏼 👩🏻❤️👩🏽 👩🏻❤️👩🏾 👩🏻❤️👩🏿 👩🏼❤️👨🏻 👩🏼❤️👨🏼 👩🏼❤️👨🏽 👩🏼❤️👨🏾 👩🏼❤️👨🏿 👩🏼❤️👩🏻 👩🏼❤️👩🏼 👩🏼❤️👩🏽 👩🏼❤️👩🏾 👩🏼❤️👩🏿 👩🏽❤️👨🏻 👩🏽❤️👨🏼 👩🏽❤️👨🏽 👩🏽❤️👨🏾 👩🏽❤️👨🏿 👩🏽❤️👩🏻 👩🏽❤️👩🏼 👩🏽❤️👩🏽 👩🏽❤️👩🏾 👩🏽❤️👩🏿 👩🏾❤️👨🏻 👩🏾❤️👨🏼 👩🏾❤️👨🏽 👩🏾❤️👨🏾 👩🏾❤️👨🏿 👩🏾❤️👩🏻 👩🏾❤️👩🏼 👩🏾❤️👩🏽 👩🏾❤️👩🏾 👩🏾❤️👩🏿 👩🏿❤️👨🏻 👩🏿❤️👨🏼 👩🏿❤️👨🏽 👩🏿❤️👨🏾 👩🏿❤️👨🏿 👩🏿❤️👩🏻 👩🏿❤️👩🏼 👩🏿❤️👩🏽 👩🏿❤️👩🏾 👩🏿❤️👩🏿 🧑🏻❤️🧑🏼 🧑🏻❤️🧑🏽 🧑🏻❤️🧑🏾 🧑🏻❤️🧑🏿 🧑🏼❤️🧑🏻 🧑🏼❤️🧑🏽 🧑🏼❤️🧑🏾 🧑🏼❤️🧑🏿 🧑🏽❤️🧑🏻 🧑🏽❤️🧑🏼 🧑🏽❤️🧑🏾 🧑🏽❤️🧑🏿 🧑🏾❤️🧑🏻 🧑🏾❤️🧑🏼 🧑🏾❤️🧑🏽 🧑🏾❤️🧑🏿 🧑🏿❤️🧑🏻 🧑🏿❤️🧑🏼 🧑🏿❤️🧑🏽 🧑🏿❤️🧑🏾 👨🏻❤️💋👨🏻 👨🏻❤️💋👨🏼 👨🏻❤️💋👨🏽 👨🏻❤️💋👨🏾 👨🏻❤️💋👨🏿 👨🏼❤️💋👨🏻 👨🏼❤️💋👨🏼 👨🏼❤️💋👨🏽 👨🏼❤️💋👨🏾 👨🏼❤️💋👨🏿 👨🏽❤️💋👨🏻 👨🏽❤️💋👨🏼 👨🏽❤️💋👨🏽 👨🏽❤️💋👨🏾 👨🏽❤️💋👨🏿 👨🏾❤️💋👨🏻 👨🏾❤️💋👨🏼 👨🏾❤️💋👨🏽 👨🏾❤️💋👨🏾 👨🏾❤️💋👨🏿 👨🏿❤️💋👨🏻 👨🏿❤️💋👨🏼 👨🏿❤️💋👨🏽 👨🏿❤️💋👨🏾 👨🏿❤️💋👨🏿 👩🏻❤️💋👨🏻 👩🏻❤️💋👨🏼 👩🏻❤️💋👨🏽 👩🏻❤️💋👨🏾 👩🏻❤️💋👨🏿 👩🏻❤️💋👩🏻 👩🏻❤️💋👩🏼 👩🏻❤️💋👩🏽 👩🏻❤️💋👩🏾 👩🏻❤️💋👩🏿 👩🏼❤️💋👨🏻 👩🏼❤️💋👨🏼 👩🏼❤️💋👨🏽 👩🏼❤️💋👨🏾 👩🏼❤️💋👨🏿 👩🏼❤️💋👩🏻 👩🏼❤️💋👩🏼 👩🏼❤️💋👩🏽 👩🏼❤️💋👩🏾 👩🏼❤️💋👩🏿 👩🏽❤️💋👨🏻 👩🏽❤️💋👨🏼 👩🏽❤️💋👨🏽 👩🏽❤️💋👨🏾 👩🏽❤️💋👨🏿 👩🏽❤️💋👩🏻 👩🏽❤️💋👩🏼 👩🏽❤️💋👩🏽 👩🏽❤️💋👩🏾 👩🏽❤️💋👩🏿 👩🏾❤️💋👨🏻 👩🏾❤️💋👨🏼 👩🏾❤️💋👨🏽 👩🏾❤️💋👨🏾 👩🏾❤️💋👨🏿 👩🏾❤️💋👩🏻 👩🏾❤️💋👩🏼 👩🏾❤️💋👩🏽 👩🏾❤️💋👩🏾 👩🏾❤️💋👩🏿 👩🏿❤️💋👨🏻 👩🏿❤️💋👨🏼 👩🏿❤️💋👨🏽 👩🏿❤️💋👨🏾 👩🏿❤️💋👨🏿 👩🏿❤️💋👩🏻 👩🏿❤️💋👩🏼 👩🏿❤️💋👩🏽 👩🏿❤️💋👩🏾 👩🏿❤️💋👩🏿 🧑🏻❤️💋🧑🏼 🧑🏻❤️💋🧑🏽 🧑🏻❤️💋🧑🏾 🧑🏻❤️💋🧑🏿 🧑🏼❤️💋🧑🏻 🧑🏼❤️💋🧑🏽 🧑🏼❤️💋🧑🏾 🧑🏼❤️💋🧑🏿 🧑🏽❤️💋🧑🏻 🧑🏽❤️💋🧑🏼 🧑🏽❤️💋🧑🏾 🧑🏽❤️💋🧑🏿 🧑🏾❤️💋🧑🏻 🧑🏾❤️💋🧑🏼 🧑🏾❤️💋🧑🏽 🧑🏾❤️💋🧑🏿 🧑🏿❤️💋🧑🏻 🧑🏿❤️💋🧑🏼 🧑🏿❤️💋🧑🏽 🧑🏿❤️💋🧑🏾'.split(' '),
|
|
72883
|
+
'New Emojis (Discorered randomly)': '🖌️'.split(' '),
|
|
72884
|
+
'Emojis used in WebGPT': ['▶', '❤'],
|
|
72885
|
+
};
|
|
72886
|
+
/**
|
|
72887
|
+
* Curated repository catalogue of emoji and legacy pictographic symbols.
|
|
72888
|
+
*
|
|
72889
|
+
* Code that needs to generate a valid Unicode emoji should apply its own validation
|
|
72890
|
+
* to this catalogue rather than assume every entry is an emoji.
|
|
72891
|
+
*
|
|
72892
|
+
* @private within the repository
|
|
72893
|
+
*/
|
|
72894
|
+
const EMOJIS = new Set(Object.values(EMOJIS_IN_CATEGORIES).flat());
|
|
72895
|
+
// TODO: Mirror from Collboard or some common package
|
|
72896
|
+
// Note: [💞] Ignore a discrepancy between file name and entity name
|
|
72897
|
+
|
|
72898
|
+
/**
|
|
72899
|
+
* Matches one Unicode code point that has default emoji presentation.
|
|
72900
|
+
*
|
|
72901
|
+
* @private internal utility of generated `ptbk` emoji tags
|
|
72902
|
+
*/
|
|
72903
|
+
const SINGLE_PICTOGRAM_EMOJI_PATTERN = /^\p{Emoji_Presentation}$/u;
|
|
72904
|
+
/**
|
|
72905
|
+
* Valid single-pictogram emojis that `ptbk` can use in generated tags.
|
|
72906
|
+
*
|
|
72907
|
+
* The shared emoji catalogue also contains legacy symbols for other presentation
|
|
72908
|
+
* purposes. Restricting generated tags to Unicode emoji-presentation pictograms
|
|
72909
|
+
* prevents those symbols from being emitted as prompt identifiers.
|
|
72910
|
+
*
|
|
72911
|
+
* @private internal utility of generated `ptbk` emoji tags
|
|
72912
|
+
*/
|
|
72913
|
+
const VALID_SINGLE_PICTOGRAM_EMOJIS = new Set(Array.from(EMOJIS).filter(isValidSinglePictogramEmoji));
|
|
72914
|
+
/**
|
|
72915
|
+
* Determines whether one catalogue entry is a valid single-pictogram emoji for a generated tag.
|
|
72916
|
+
*
|
|
72917
|
+
* @private internal utility of generated `ptbk` emoji tags
|
|
72918
|
+
*/
|
|
72919
|
+
function isValidSinglePictogramEmoji(emoji) {
|
|
72920
|
+
return SINGLE_PICTOGRAM_EMOJI_PATTERN.test(emoji);
|
|
72921
|
+
}
|
|
72922
|
+
// Note: [🟡] Code for generated emoji tags should never be published outside of `@promptbook/cli`
|
|
72923
|
+
|
|
72326
72924
|
/**
|
|
72327
72925
|
* Emoji prefix used for prompt tags (e.g. `[prefix + emoji]`).
|
|
72328
72926
|
*/
|
|
@@ -72351,9 +72949,9 @@ async function getFreshPromptEmojiTags(options) {
|
|
|
72351
72949
|
includeGlobs,
|
|
72352
72950
|
ignoreGlobs,
|
|
72353
72951
|
tagPrefix,
|
|
72354
|
-
candidateEmojis:
|
|
72952
|
+
candidateEmojis: VALID_SINGLE_PICTOGRAM_EMOJIS,
|
|
72355
72953
|
});
|
|
72356
|
-
const freshEmojis = new Set(Array.from(
|
|
72954
|
+
const freshEmojis = new Set(Array.from(VALID_SINGLE_PICTOGRAM_EMOJIS).filter((emoji) => !usedEmojis.has(emoji)));
|
|
72357
72955
|
const shuffledEmojis = $shuffleItems(...Array.from(freshEmojis));
|
|
72358
72956
|
const selectedEmojis = shuffledEmojis.slice(0, count);
|
|
72359
72957
|
if (selectedEmojis.length < count) {
|
|
@@ -72403,7 +73001,7 @@ function initializeFindFreshEmojiTagRun() {
|
|
|
72403
73001
|
async function findFreshEmojiTag() {
|
|
72404
73002
|
initializeFindFreshEmojiTagRun();
|
|
72405
73003
|
console.info(`🤪 Find fresh emoji tag`);
|
|
72406
|
-
const allEmojis =
|
|
73004
|
+
const allEmojis = VALID_SINGLE_PICTOGRAM_EMOJIS;
|
|
72407
73005
|
const { usedEmojis } = await scanEmojiTagUsage({
|
|
72408
73006
|
candidateEmojis: allEmojis,
|
|
72409
73007
|
tagPrefix: '',
|
|
@@ -72421,8 +73019,6 @@ async function findFreshEmojiTag() {
|
|
|
72421
73019
|
const tag = `[${emoji}]`;
|
|
72422
73020
|
console.info(colors.bgWhite(tag));
|
|
72423
73021
|
}
|
|
72424
|
-
//========================================/
|
|
72425
|
-
// console.info(`[ Done 🤪 Find fresh emoji tag ]`);
|
|
72426
73022
|
}
|
|
72427
73023
|
// Note: [🟡] Code for CLI support script [find-fresh-emoji-tags](scripts/find-fresh-emoji-tags/find-fresh-emoji-tags.ts) should never be published outside of `@promptbook/cli`
|
|
72428
73024
|
|
|
@@ -76226,12 +76822,18 @@ function formatPromptAttemptMetadata(status, attemptCount) {
|
|
|
76226
76822
|
}
|
|
76227
76823
|
|
|
76228
76824
|
/**
|
|
76229
|
-
* Separator
|
|
76825
|
+
* Separator between consecutive harness reports in the chronological history of one continued prompt.
|
|
76826
|
+
*
|
|
76827
|
+
* It is used both for writing and reading, so each `--git-changes continue` invocation can extend the
|
|
76828
|
+
* report that the preceding invocation left in the prompt status line.
|
|
76829
|
+
*/
|
|
76830
|
+
const INTERRUPTED_CONTINUED_BY_SEPARATOR = ', interrupted, continued by ';
|
|
76831
|
+
/**
|
|
76832
|
+
* Separator written by earlier versions of `ptbk coder`, whose continuation attribution was reverse-ordered.
|
|
76230
76833
|
*
|
|
76231
|
-
*
|
|
76232
|
-
* stays parseable when yet another harness continues it later.
|
|
76834
|
+
* Existing interrupted prompts must remain resumable after the report format becomes chronological.
|
|
76233
76835
|
*/
|
|
76234
|
-
const
|
|
76836
|
+
const LEGACY_STARTED_BY_SEPARATOR = ', started by ';
|
|
76235
76837
|
/**
|
|
76236
76838
|
* Matches the attribution of one status line, which reaches from `by ` up to the step summary.
|
|
76237
76839
|
*/
|
|
@@ -76240,33 +76842,73 @@ const PROMPT_RUNNER_ATTRIBUTION_PATTERN = /(?:^|\s)by (?<attribution>.*?)(?: - |
|
|
|
76240
76842
|
* Formats who worked on one prompt for its status line.
|
|
76241
76843
|
*
|
|
76242
76844
|
* Produces `` by Claude Code `claude-opus-5` `` for a prompt implemented by a single harness and
|
|
76243
|
-
* `` by
|
|
76244
|
-
* harness started and another one continued through `--git-changes continue`.
|
|
76845
|
+
* `` by OpenAI Codex `gpt-5.6-luna`, interrupted, continued by Claude Code `claude-opus-5` `` for a
|
|
76846
|
+
* prompt which one harness started and another one continued through `--git-changes continue`.
|
|
76245
76847
|
*/
|
|
76246
76848
|
function formatPromptRunnerAttribution(options) {
|
|
76247
|
-
const { currentRunnerSignature,
|
|
76248
|
-
|
|
76849
|
+
const { currentRunnerSignature, previousRunnerSignatures } = options;
|
|
76850
|
+
const runnerSignatures = [...(previousRunnerSignatures !== null && previousRunnerSignatures !== void 0 ? previousRunnerSignatures : [])];
|
|
76851
|
+
const latestRunnerSignature = runnerSignatures.at(-1);
|
|
76852
|
+
if (latestRunnerSignature === undefined) {
|
|
76249
76853
|
return `by ${currentRunnerSignature}`;
|
|
76250
76854
|
}
|
|
76251
|
-
|
|
76855
|
+
if (isSameRunnerSignature(latestRunnerSignature, currentRunnerSignature)) {
|
|
76856
|
+
runnerSignatures[runnerSignatures.length - 1] = resolveLatestRunnerSignature(latestRunnerSignature, currentRunnerSignature);
|
|
76857
|
+
}
|
|
76858
|
+
else {
|
|
76859
|
+
runnerSignatures.push(currentRunnerSignature);
|
|
76860
|
+
}
|
|
76861
|
+
return `by ${runnerSignatures.join(INTERRUPTED_CONTINUED_BY_SEPARATOR)}`;
|
|
76252
76862
|
}
|
|
76253
76863
|
/**
|
|
76254
|
-
* Reads
|
|
76864
|
+
* Reads the chronological harness report recorded on one prompt status line.
|
|
76255
76865
|
*
|
|
76256
|
-
* A line written by a single harness
|
|
76257
|
-
*
|
|
76866
|
+
* A line written by a single harness produces one signature. A line which has already been continued produces
|
|
76867
|
+
* every signature in the order in which it worked on the prompt, so another continuation can append to it.
|
|
76258
76868
|
*/
|
|
76259
|
-
function
|
|
76869
|
+
function parsePromptRunnerAttribution(statusLine) {
|
|
76260
76870
|
var _a, _b, _c;
|
|
76261
76871
|
const attribution = (_c = (_b = (_a = statusLine.match(PROMPT_RUNNER_ATTRIBUTION_PATTERN)) === null || _a === void 0 ? void 0 : _a.groups) === null || _b === void 0 ? void 0 : _b.attribution) === null || _c === void 0 ? void 0 : _c.trim();
|
|
76262
76872
|
if (!attribution) {
|
|
76263
76873
|
return undefined;
|
|
76264
76874
|
}
|
|
76265
|
-
const
|
|
76266
|
-
if (
|
|
76267
|
-
|
|
76875
|
+
const legacyStartedBySeparatorIndex = attribution.indexOf(LEGACY_STARTED_BY_SEPARATOR);
|
|
76876
|
+
if (legacyStartedBySeparatorIndex !== -1) {
|
|
76877
|
+
const currentRunnerSignature = attribution.slice(0, legacyStartedBySeparatorIndex).trim();
|
|
76878
|
+
const startedByRunnerSignature = attribution
|
|
76879
|
+
.slice(legacyStartedBySeparatorIndex + LEGACY_STARTED_BY_SEPARATOR.length)
|
|
76880
|
+
.trim();
|
|
76881
|
+
return toPromptRunnerAttribution([startedByRunnerSignature, currentRunnerSignature]);
|
|
76882
|
+
}
|
|
76883
|
+
return toPromptRunnerAttribution(attribution.split(INTERRUPTED_CONTINUED_BY_SEPARATOR));
|
|
76884
|
+
}
|
|
76885
|
+
/**
|
|
76886
|
+
* Keeps the authentication label that is already known when the same harness resumes its own work before
|
|
76887
|
+
* reporting another label, and otherwise upgrades that last report with the newly known label.
|
|
76888
|
+
*/
|
|
76889
|
+
function resolveLatestRunnerSignature(latestRunnerSignature, currentRunnerSignature) {
|
|
76890
|
+
if (currentRunnerSignature.startsWith(`${latestRunnerSignature} (`)) {
|
|
76891
|
+
return currentRunnerSignature;
|
|
76268
76892
|
}
|
|
76269
|
-
return
|
|
76893
|
+
return latestRunnerSignature;
|
|
76894
|
+
}
|
|
76895
|
+
/**
|
|
76896
|
+
* Checks whether two report signatures describe the same harness/model/thinking configuration, allowing the
|
|
76897
|
+
* newer signature to add its authentication label.
|
|
76898
|
+
*/
|
|
76899
|
+
function isSameRunnerSignature(latestRunnerSignature, currentRunnerSignature) {
|
|
76900
|
+
return (latestRunnerSignature === currentRunnerSignature ||
|
|
76901
|
+
latestRunnerSignature.startsWith(`${currentRunnerSignature} (`) ||
|
|
76902
|
+
currentRunnerSignature.startsWith(`${latestRunnerSignature} (`));
|
|
76903
|
+
}
|
|
76904
|
+
/**
|
|
76905
|
+
* Normalizes parsed signatures and rejects malformed or empty attribution fragments.
|
|
76906
|
+
*/
|
|
76907
|
+
function toPromptRunnerAttribution(runnerSignatures) {
|
|
76908
|
+
const normalizedRunnerSignatures = runnerSignatures
|
|
76909
|
+
.map((runnerSignature) => runnerSignature.trim())
|
|
76910
|
+
.filter(Boolean);
|
|
76911
|
+
return normalizedRunnerSignatures.length === 0 ? undefined : normalizedRunnerSignatures;
|
|
76270
76912
|
}
|
|
76271
76913
|
// Note: [💞] Ignore a discrepancy between file name and exported helper names
|
|
76272
76914
|
|
|
@@ -76277,16 +76919,20 @@ function parsePromptStartedByRunnerSignature(statusLine) {
|
|
|
76277
76919
|
* ``by OpenAI Codex `gpt-5.6-luna` thinking `max` (ChatGPT account) - Implementation ~$0.2036 10 minutes``.
|
|
76278
76920
|
*/
|
|
76279
76921
|
function buildPromptStatusDetails(options) {
|
|
76280
|
-
const { steps, inProgressStepKind, runnerName, modelName,
|
|
76922
|
+
const { steps, inProgressStepKind, runnerName, modelName, previousRunnerSignatures, attemptCount, loginMethod, thinkingLevel, } = options;
|
|
76281
76923
|
const runnerSignature = formatRunnerSignature(runnerName, modelName, thinkingLevel);
|
|
76282
76924
|
const attemptMetadata = formatPromptAttemptMetadata('done', attemptCount);
|
|
76283
76925
|
const loginMethodLabel = formatCodexLoginMethod(loginMethod);
|
|
76284
76926
|
const loginMethodSuffix = loginMethodLabel ? ` (${loginMethodLabel})` : '';
|
|
76285
76927
|
const attribution = formatPromptRunnerAttribution({
|
|
76286
76928
|
currentRunnerSignature: `${runnerSignature}${loginMethodSuffix}`,
|
|
76287
|
-
|
|
76929
|
+
previousRunnerSignatures,
|
|
76288
76930
|
});
|
|
76289
|
-
|
|
76931
|
+
// Note: An interrupted prompt has step measurements from an earlier process which cannot be reconstructed
|
|
76932
|
+
// from its status line. Do not report just the continuing process as the whole prompt; keep only its
|
|
76933
|
+
// current phase while it runs, and finish with the progressive harness report.
|
|
76934
|
+
const completedStepsToReport = previousRunnerSignatures === undefined ? steps : [];
|
|
76935
|
+
const stepsSummary = formatCoderRunSteps(completedStepsToReport, inProgressStepKind);
|
|
76290
76936
|
const stepsSuffix = stepsSummary === '' ? '' : ` - ${stepsSummary}`;
|
|
76291
76937
|
return `${attemptMetadata}${attribution}${stepsSuffix}`;
|
|
76292
76938
|
}
|
|
@@ -76340,10 +76986,10 @@ function markPromptDone(options) {
|
|
|
76340
76986
|
* Marks a prompt section as failed and records runner details.
|
|
76341
76987
|
*/
|
|
76342
76988
|
function markPromptFailed(options) {
|
|
76343
|
-
const { file, section, runnerName, modelName,
|
|
76989
|
+
const { file, section, runnerName, modelName, previousRunnerSignatures, promptExecutionStartedDate, attemptCount } = options;
|
|
76344
76990
|
const attribution = formatPromptRunnerAttribution({
|
|
76345
76991
|
currentRunnerSignature: formatRunnerSignature(runnerName, modelName),
|
|
76346
|
-
|
|
76992
|
+
previousRunnerSignatures,
|
|
76347
76993
|
});
|
|
76348
76994
|
const attemptMetadata = formatPromptAttemptMetadata('failed', attemptCount);
|
|
76349
76995
|
const duration = moment().diff(promptExecutionStartedDate);
|
|
@@ -76410,6 +77056,30 @@ async function writePromptFile(file) {
|
|
|
76410
77056
|
await writeFile(file.path, content, 'utf-8');
|
|
76411
77057
|
}
|
|
76412
77058
|
|
|
77059
|
+
/**
|
|
77060
|
+
* Refreshes optional harness subscription usage in the shared coder-run UI state.
|
|
77061
|
+
*
|
|
77062
|
+
* Subscription visibility is an enhancement, not a prerequisite for running prompts. A harness which cannot expose a
|
|
77063
|
+
* snapshot, or whose account endpoint is temporarily unavailable, therefore leaves the existing dashboard unchanged.
|
|
77064
|
+
*
|
|
77065
|
+
* @private internal utility of coder run UI
|
|
77066
|
+
*/
|
|
77067
|
+
async function refreshCoderRunUiSubscriptionUsage(options) {
|
|
77068
|
+
const { runner, uiState } = options;
|
|
77069
|
+
if (!uiState || !runner.getSubscriptionUsage) {
|
|
77070
|
+
return;
|
|
77071
|
+
}
|
|
77072
|
+
try {
|
|
77073
|
+
const subscriptionUsage = await runner.getSubscriptionUsage();
|
|
77074
|
+
if (subscriptionUsage) {
|
|
77075
|
+
uiState.setSubscriptionUsage(subscriptionUsage);
|
|
77076
|
+
}
|
|
77077
|
+
}
|
|
77078
|
+
catch (_a) {
|
|
77079
|
+
// A current subscription snapshot must never interrupt the coding queue.
|
|
77080
|
+
}
|
|
77081
|
+
}
|
|
77082
|
+
|
|
76413
77083
|
/**
|
|
76414
77084
|
* Maximum number of retry attempts performed after a prompt round throws an error.
|
|
76415
77085
|
* After this many retries the round is finalized as failed.
|
|
@@ -76442,12 +77112,8 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76442
77112
|
const codexPrompt = appendCoderContext(promptWithAgent, resolvedCoderContext);
|
|
76443
77113
|
// Note: Temporary scripts and runtime logs stay in the original project so they outlive an isolated worktree
|
|
76444
77114
|
const scriptPath = buildScriptPath(nextPrompt.file, nextPrompt.section);
|
|
76445
|
-
// Note: Read before the first `[^]` rewrite of this round, which would overwrite the
|
|
76446
|
-
const
|
|
76447
|
-
nextPrompt,
|
|
76448
|
-
runnerMetadata,
|
|
76449
|
-
thinkingLevel: options.thinkingLevel,
|
|
76450
|
-
});
|
|
77115
|
+
// Note: Read before the first `[^]` rewrite of this round, which would overwrite the report recorded so far
|
|
77116
|
+
const previousRunnerSignatures = resolvePreviousRunnerSignatures(nextPrompt);
|
|
76451
77117
|
setPromptRoundRunningState({ isRichUiEnabled, promptLabel, scriptPath, uiHandle });
|
|
76452
77118
|
await waitForRequestedPause({
|
|
76453
77119
|
checkpointLabel: 'preparing the current prompt execution',
|
|
@@ -76480,18 +77146,22 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76480
77146
|
onStepStarted: (progress) => recordPromptRoundInProgress({
|
|
76481
77147
|
nextPrompt,
|
|
76482
77148
|
runnerMetadata,
|
|
76483
|
-
|
|
77149
|
+
previousRunnerSignatures,
|
|
76484
77150
|
thinkingLevel: options.thinkingLevel,
|
|
76485
77151
|
attemptCount,
|
|
76486
77152
|
progress,
|
|
76487
77153
|
}),
|
|
76488
77154
|
waitForPauseCheckpoint: waitForRequestedPause,
|
|
76489
77155
|
});
|
|
77156
|
+
await refreshCoderRunUiSubscriptionUsage({
|
|
77157
|
+
runner,
|
|
77158
|
+
uiState: uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state,
|
|
77159
|
+
});
|
|
76490
77160
|
await finalizeSuccessfulPromptRound({
|
|
76491
77161
|
options,
|
|
76492
77162
|
nextPrompt,
|
|
76493
77163
|
runnerMetadata,
|
|
76494
|
-
|
|
77164
|
+
previousRunnerSignatures,
|
|
76495
77165
|
promptExecutionStartedDate,
|
|
76496
77166
|
result,
|
|
76497
77167
|
commitMessage,
|
|
@@ -76525,7 +77195,7 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76525
77195
|
await finalizeFailedPromptRound({
|
|
76526
77196
|
nextPrompt,
|
|
76527
77197
|
runnerMetadata,
|
|
76528
|
-
|
|
77198
|
+
previousRunnerSignatures,
|
|
76529
77199
|
promptExecutionStartedDate,
|
|
76530
77200
|
attemptCount,
|
|
76531
77201
|
error: lastError,
|
|
@@ -76539,21 +77209,17 @@ async function runPromptRound({ options, runner, runnerMetadata, nextPrompt, pro
|
|
|
76539
77209
|
}, { preserveArtifactsOnSuccess: options.preserveLogs });
|
|
76540
77210
|
}
|
|
76541
77211
|
/**
|
|
76542
|
-
* Reads
|
|
77212
|
+
* Reads the chronological harness report left on the prompt before this round rewrites its in-progress status.
|
|
76543
77213
|
*
|
|
76544
77214
|
* Only a prompt resumed through `--git-changes continue` still carries the in-progress `[^]` status when its
|
|
76545
|
-
* round starts, so every other round
|
|
76546
|
-
* reported as no earlier harness either, because the status line would just name it twice.
|
|
77215
|
+
* round starts, so every other round starts a fresh report.
|
|
76547
77216
|
*/
|
|
76548
|
-
function
|
|
76549
|
-
const { nextPrompt, runnerMetadata, thinkingLevel } = options;
|
|
77217
|
+
function resolvePreviousRunnerSignatures(nextPrompt) {
|
|
76550
77218
|
if (nextPrompt.section.status !== 'in-progress') {
|
|
76551
77219
|
return undefined;
|
|
76552
77220
|
}
|
|
76553
77221
|
const { line } = resolvePromptStatusLine(nextPrompt.file, nextPrompt.section);
|
|
76554
|
-
|
|
76555
|
-
const currentRunnerSignature = formatRunnerSignature(runnerMetadata.runnerName, runnerMetadata.modelName, thinkingLevel);
|
|
76556
|
-
return startedByRunnerSignature === currentRunnerSignature ? undefined : startedByRunnerSignature;
|
|
77222
|
+
return parsePromptRunnerAttribution(line);
|
|
76557
77223
|
}
|
|
76558
77224
|
/**
|
|
76559
77225
|
* Records into the prompt file that the prompt is being implemented right now.
|
|
@@ -76563,7 +77229,7 @@ function resolveStartedByRunnerSignature(options) {
|
|
|
76563
77229
|
* crashes leaves the `[^]` status behind as the signal that this task was left in the middle.
|
|
76564
77230
|
*/
|
|
76565
77231
|
async function recordPromptRoundInProgress(options) {
|
|
76566
|
-
const { nextPrompt, runnerMetadata,
|
|
77232
|
+
const { nextPrompt, runnerMetadata, previousRunnerSignatures, thinkingLevel, attemptCount, progress } = options;
|
|
76567
77233
|
markPromptInProgress({
|
|
76568
77234
|
file: nextPrompt.file,
|
|
76569
77235
|
section: nextPrompt.section,
|
|
@@ -76571,7 +77237,7 @@ async function recordPromptRoundInProgress(options) {
|
|
|
76571
77237
|
inProgressStepKind: progress.startedStepKind,
|
|
76572
77238
|
runnerName: runnerMetadata.runnerName,
|
|
76573
77239
|
modelName: runnerMetadata.modelName,
|
|
76574
|
-
|
|
77240
|
+
previousRunnerSignatures,
|
|
76575
77241
|
attemptCount,
|
|
76576
77242
|
loginMethod: progress.loginMethod,
|
|
76577
77243
|
thinkingLevel,
|
|
@@ -76629,7 +77295,7 @@ function setPromptRoundRunningState(options) {
|
|
|
76629
77295
|
* Finalizes a successful prompt round, including prompt bookkeeping and commit flow.
|
|
76630
77296
|
*/
|
|
76631
77297
|
async function finalizeSuccessfulPromptRound(options) {
|
|
76632
|
-
const { options: runOptions, nextPrompt, runnerMetadata,
|
|
77298
|
+
const { options: runOptions, nextPrompt, runnerMetadata, previousRunnerSignatures, promptExecutionStartedDate, result, commitMessage, logPath, roundCommitScope, isRichUiEnabled, progressDisplay, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76633
77299
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
|
|
76634
77300
|
await waitForRequestedPause({
|
|
76635
77301
|
checkpointLabel: 'recording the successful prompt result',
|
|
@@ -76642,7 +77308,7 @@ async function finalizeSuccessfulPromptRound(options) {
|
|
|
76642
77308
|
steps: result.steps,
|
|
76643
77309
|
runnerName: runnerMetadata.runnerName,
|
|
76644
77310
|
modelName: runnerMetadata.modelName,
|
|
76645
|
-
|
|
77311
|
+
previousRunnerSignatures,
|
|
76646
77312
|
attemptCount: result.attemptCount,
|
|
76647
77313
|
loginMethod: result.loginMethod,
|
|
76648
77314
|
thinkingLevel: runOptions.thinkingLevel,
|
|
@@ -76697,7 +77363,7 @@ async function finalizeSuccessfulPromptRound(options) {
|
|
|
76697
77363
|
* Finalizes a failed prompt round, persisting prompt failure metadata before rethrowing.
|
|
76698
77364
|
*/
|
|
76699
77365
|
async function finalizeFailedPromptRound(options) {
|
|
76700
|
-
const { nextPrompt, runnerMetadata,
|
|
77366
|
+
const { nextPrompt, runnerMetadata, previousRunnerSignatures, promptExecutionStartedDate, attemptCount, error, options: runOptions, roundCommitScope, uiHandle, waitForRequestedPause, roundProjectPath, } = options;
|
|
76701
77367
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.stopCapturingAgentOutput();
|
|
76702
77368
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.setPhase('error');
|
|
76703
77369
|
uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state.addError(error instanceof Error ? error.message : String(error));
|
|
@@ -76711,7 +77377,7 @@ async function finalizeFailedPromptRound(options) {
|
|
|
76711
77377
|
section: nextPrompt.section,
|
|
76712
77378
|
runnerName: runnerMetadata.runnerName,
|
|
76713
77379
|
modelName: runnerMetadata.modelName,
|
|
76714
|
-
|
|
77380
|
+
previousRunnerSignatures,
|
|
76715
77381
|
promptExecutionStartedDate,
|
|
76716
77382
|
attemptCount,
|
|
76717
77383
|
});
|
|
@@ -77727,6 +78393,10 @@ async function runCodexPrompts(providedOptions) {
|
|
|
77727
78393
|
console.info(colors.green(`Running prompts with ${runner.name}`));
|
|
77728
78394
|
initializeRunUi(uiHandle, runner.name, actualRunnerModel, options);
|
|
77729
78395
|
await initializeRunUiAgentVisual(uiHandle, resolvedCoderAgent === null || resolvedCoderAgent === void 0 ? void 0 : resolvedCoderAgent.agentSource);
|
|
78396
|
+
await refreshCoderRunUiSubscriptionUsage({
|
|
78397
|
+
runner,
|
|
78398
|
+
uiState: uiHandle === null || uiHandle === void 0 ? void 0 : uiHandle.state,
|
|
78399
|
+
});
|
|
77730
78400
|
await seedCachedAveragePromptDuration({
|
|
77731
78401
|
options,
|
|
77732
78402
|
actualRunnerModel,
|
|
@@ -77953,6 +78623,13 @@ function validateRunCodexPromptOptions(options) {
|
|
|
77953
78623
|
An isolated task is implemented in a fresh temporary worktree checked out from the last commit, so the uncommitted changes of the interrupted prompt would be left behind instead of being continued.
|
|
77954
78624
|
`));
|
|
77955
78625
|
}
|
|
78626
|
+
if (options.gitChanges === 'continue' && options.testBefore === 'yes-and-fix') {
|
|
78627
|
+
throw new NotAllowed(spaceTrim$1(`
|
|
78628
|
+
Flag \`--git-changes continue\` cannot be combined with \`--test-before yes-and-fix\`.
|
|
78629
|
+
|
|
78630
|
+
An interrupted prompt already has changes in progress, so there is no unmodified project state for pre-coding verification to repair.
|
|
78631
|
+
`));
|
|
78632
|
+
}
|
|
77956
78633
|
if (options.limit !== undefined && (!Number.isInteger(options.limit) || options.limit <= 0)) {
|
|
77957
78634
|
throw new NotAllowed(spaceTrim$1(`
|
|
77958
78635
|
Flag \`--limit\` expects a positive integer.
|