theclawbay 0.3.60 → 0.3.62

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.
@@ -65,7 +65,7 @@ const LEGACY_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/api/codex-auth/v1/proxy/v1";
65
65
  const CANONICAL_THECLAWBAY_OPENAI_PROXY_SUFFIX = "/v1";
66
66
  const THECLAWBAY_CANONICAL_API_HOST = "api.theclawbay.com";
67
67
  const THECLAWBAY_WEBSITE_HOSTS = new Set(["theclawbay.com", "www.theclawbay.com"]);
68
- const SUPPORTED_MODEL_IDS = new Set((0, supported_models_1.getSupportedModelIds)());
68
+ const SUPPORTED_MODEL_IDS = new Set((0, supported_models_1.getKnownTheClawBayModelIds)());
69
69
  const CONTINUE_MODEL_NAME = "The Claw Bay";
70
70
  const TRAE_PATCH_MARKER = "theclawbay-trae-patch";
71
71
  const TRAE_BUNDLE_BACKUP_SUFFIX = ".theclawbay-managed-backup";
@@ -27,7 +27,7 @@ const DEFAULT_PROVIDER_ID = "theclawbay";
27
27
  const CLI_HTTP_USER_AGENT = "theclawbay-cli";
28
28
  const SUPPORTED_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)();
29
29
  const MODEL_DISPLAY_NAMES = (0, supported_models_1.getSupportedModelDisplayNames)();
30
- const DEFAULT_CODEX_MODEL = SUPPORTED_MODEL_IDS[0] ?? "gpt-5.4";
30
+ const DEFAULT_CODEX_MODEL = SUPPORTED_MODEL_IDS.includes("gpt-5.4") ? "gpt-5.4" : (SUPPORTED_MODEL_IDS[0] ?? "gpt-5.4");
31
31
  const DEFAULT_CONTINUE_MODEL = DEFAULT_CODEX_MODEL;
32
32
  const DEFAULT_CLINE_MODEL = DEFAULT_CODEX_MODEL;
33
33
  const DEFAULT_OPENCLAW_MODEL = DEFAULT_CODEX_MODEL;
@@ -37,7 +37,7 @@ const DEFAULT_TRAE_MODEL = DEFAULT_CODEX_MODEL;
37
37
  const DEFAULT_AIDER_MODEL = DEFAULT_CODEX_MODEL;
38
38
  const DEFAULT_ZO_MODEL = DEFAULT_CODEX_MODEL;
39
39
  const DEFAULT_MODELS = [...SUPPORTED_MODEL_IDS];
40
- const PREFERRED_MODELS = [...SUPPORTED_MODEL_IDS];
40
+ const PREFERRED_MODELS = Array.from(new Set(["gpt-5.5", ...SUPPORTED_MODEL_IDS]));
41
41
  const ENV_KEY_NAME = "THECLAWBAY_API_KEY";
42
42
  const CLAUDE_ENV_API_KEY_NAME = "ANTHROPIC_API_KEY";
43
43
  const CLAUDE_ENV_BASE_URL_NAME = "ANTHROPIC_BASE_URL";
@@ -1533,7 +1533,6 @@ async function writeCodexConfig(params) {
1533
1533
  'name = "OpenAI"',
1534
1534
  `base_url = "${nativeCodexBaseUrl}"`,
1535
1535
  'wire_api = "responses"',
1536
- "requires_openai_auth = true",
1537
1536
  "supports_websockets = true",
1538
1537
  `experimental_bearer_token = "${params.apiKey}"`,
1539
1538
  MANAGED_END,
@@ -2414,10 +2413,12 @@ function objectRecordOr(value, fallback) {
2414
2413
  function modelContextLimit(modelId) {
2415
2414
  if (modelId === "gpt-5.4")
2416
2415
  return 1050000;
2416
+ if (modelId === "gpt-5.5")
2417
+ return 1000000;
2417
2418
  return 272000;
2418
2419
  }
2419
2420
  function modelOutputLimit(modelId) {
2420
- if (modelId === "gpt-5.4")
2421
+ if (modelId === "gpt-5.4" || modelId === "gpt-5.5")
2421
2422
  return 128000;
2422
2423
  return 65536;
2423
2424
  }
@@ -2984,7 +2985,6 @@ class SetupCommand extends base_command_1.BaseCommand {
2984
2985
  let restoredClaudeDesktop3pConfig = false;
2985
2986
  let claudeDesktop3pConfigPathManaged = null;
2986
2987
  let sessionMigration = null;
2987
- let authSeed = null;
2988
2988
  let authSeedCleanup = null;
2989
2989
  let stateDbMigration = null;
2990
2990
  let modelCacheMigration = null;
@@ -3090,10 +3090,6 @@ class SetupCommand extends base_command_1.BaseCommand {
3090
3090
  authSeedCleanup = await (0, codex_auth_seeding_1.cleanupSeededCodexAuth)({
3091
3091
  codexHome: paths_1.codexDir,
3092
3092
  });
3093
- authSeed = await (0, codex_auth_seeding_1.ensureCodexUsageLimitAuth)({
3094
- codexHome: paths_1.codexDir,
3095
- apiKey: authCredential,
3096
- });
3097
3093
  if (migrateCodexConversations) {
3098
3094
  stateDbMigration = await (0, codex_history_migration_1.migrateStateDbProviders)({
3099
3095
  codexHome: paths_1.codexDir,
@@ -3232,12 +3228,13 @@ class SetupCommand extends base_command_1.BaseCommand {
3232
3228
  }
3233
3229
  if (modelCacheMigration?.warning)
3234
3230
  summaryNotes.add(modelCacheMigration.warning);
3235
- if (authSeed?.warning)
3236
- summaryNotes.add(authSeed.warning);
3237
3231
  if (authSeedCleanup?.warning)
3238
3232
  summaryNotes.add(authSeedCleanup.warning);
3239
- if (authSeed?.replacedExistingAuth) {
3240
- summaryNotes.add("Codex usage visibility temporarily replaced your existing local Codex auth. `theclawbay logout` restores it.");
3233
+ if (authSeedCleanup?.action === "removed") {
3234
+ summaryNotes.add("Removed an older The Claw Bay Codex auth override that newer Codex releases now treat as invalid ChatGPT OAuth.");
3235
+ }
3236
+ else if (authSeedCleanup?.action === "restored_backup") {
3237
+ summaryNotes.add("Restored the Codex auth that existed before an older The Claw Bay auth override.");
3241
3238
  }
3242
3239
  if (selectedSetupClients.has("codex") && !migrateCodexConversations) {
3243
3240
  summaryNotes.add("Left existing Codex conversations untouched.");
@@ -3320,19 +3317,19 @@ class SetupCommand extends base_command_1.BaseCommand {
3320
3317
  this.log(`- Conversation cache: could not update local Codex history DB.${detail}`);
3321
3318
  }
3322
3319
  if (modelCacheMigration?.action === "seeded") {
3323
- this.log("- Codex model cache: added GPT-5.4 / GPT-5.4 Mini to the local picker cache.");
3320
+ this.log("- Codex model cache: added the current The Claw Bay model catalog to the local picker cache.");
3324
3321
  }
3325
3322
  else if (modelCacheMigration?.action === "created") {
3326
- this.log("- Codex model cache: created a local picker cache with GPT-5.4 / GPT-5.4 Mini.");
3323
+ this.log("- Codex model cache: created a local picker cache with the current The Claw Bay model catalog.");
3327
3324
  }
3328
3325
  else if (modelCacheMigration?.action === "refreshed") {
3329
- this.log("- Codex model cache: refreshed the local picker cache for GPT-5.4 / GPT-5.4 Mini.");
3326
+ this.log("- Codex model cache: refreshed the local picker cache for the current The Claw Bay model catalog.");
3330
3327
  }
3331
3328
  else if (modelCacheMigration?.action === "already_seeded") {
3332
- this.log("- Codex model cache: GPT-5.4 / GPT-5.4 Mini were already seeded locally.");
3329
+ this.log("- Codex model cache: the current The Claw Bay model catalog was already seeded locally.");
3333
3330
  }
3334
3331
  else if (modelCacheMigration?.action === "already_present") {
3335
- this.log("- Codex model cache: GPT-5.4 / GPT-5.4 Mini already existed locally.");
3332
+ this.log("- Codex model cache: the current The Claw Bay model catalog already existed locally.");
3336
3333
  }
3337
3334
  else if (modelCacheMigration?.warning) {
3338
3335
  this.log(`- Codex model cache: ${modelCacheMigration.warning}`);
@@ -3340,6 +3337,18 @@ class SetupCommand extends base_command_1.BaseCommand {
3340
3337
  else if (modelCacheMigration) {
3341
3338
  this.log("- Codex model cache: no change.");
3342
3339
  }
3340
+ if (authSeedCleanup?.action === "restored_backup") {
3341
+ this.log("- Codex login state: restored the auth that existed before an older The Claw Bay override.");
3342
+ }
3343
+ else if (authSeedCleanup?.action === "removed") {
3344
+ this.log("- Codex login state: removed the older The Claw Bay ChatGPT-token override for compatibility with current Codex releases.");
3345
+ }
3346
+ else if (authSeedCleanup?.warning) {
3347
+ this.log(`- Codex login state: ${authSeedCleanup.warning}`);
3348
+ }
3349
+ else {
3350
+ this.log("- Codex login state: using provider-scoped bearer auth only; no fake ChatGPT auth tokens were seeded.");
3351
+ }
3343
3352
  }
3344
3353
  else if (codexDetected) {
3345
3354
  this.log("- Codex: detected but skipped");
@@ -3468,36 +3477,6 @@ class SetupCommand extends base_command_1.BaseCommand {
3468
3477
  else {
3469
3478
  this.log("- Claude Code: not detected (skipped)");
3470
3479
  }
3471
- if (authSeed?.action === "seeded" && authSeed.mode === "chatgpt-auth-tokens") {
3472
- this.log("- Codex login state: seeded local Codex auth so remaining usage can be shown.");
3473
- }
3474
- else if (authSeed?.action === "updated" && authSeed.mode === "chatgpt-auth-tokens") {
3475
- this.log("- Codex login state: refreshed the local usage-limit auth seed.");
3476
- }
3477
- else if (authSeed?.action === "already_seeded" && authSeed.mode === "chatgpt-auth-tokens") {
3478
- this.log("- Codex login state: the local usage-limit auth seed was already present.");
3479
- }
3480
- else if (authSeed?.action === "seeded") {
3481
- this.log("- Codex login state: seeded local API-key auth.");
3482
- }
3483
- else if (authSeed?.action === "updated") {
3484
- this.log("- Codex login state: refreshed the local API-key auth seed.");
3485
- }
3486
- else if (authSeed?.action === "already_seeded") {
3487
- this.log("- Codex login state: the local API-key auth seed was already present.");
3488
- }
3489
- else if (authSeed?.action === "already_present") {
3490
- this.log("- Codex login state: preserved existing local Codex auth.");
3491
- }
3492
- else if (authSeed?.warning) {
3493
- this.log(`- Codex login state: ${authSeed.warning}`);
3494
- }
3495
- else if (authSeed) {
3496
- this.log("- Codex login state: no change.");
3497
- }
3498
- if (authSeedCleanup?.action === "restored_backup") {
3499
- this.log("- Codex login state: restored the auth that existed before an older The Claw Bay Codex auth override.");
3500
- }
3501
3480
  this.log("Next: restart Continue, Cline, Roo Code, Trae, or Zo only if they were already open during setup.");
3502
3481
  });
3503
3482
  }
@@ -240,12 +240,11 @@ async function ensureCodexApiKeyAuth(params) {
240
240
  });
241
241
  }
242
242
  async function ensureCodexUsageLimitAuth(params) {
243
- return ensureCodexAuth({
244
- codexHome: params.codexHome,
245
- apiKey: params.apiKey,
246
- mode: "chatgpt-auth-tokens",
247
- replaceExistingAuth: true,
248
- });
243
+ // Current Codex releases treat chatgptAuthTokens.access_token as a real
244
+ // ChatGPT JWT and use it against chatgpt.com. The Claw Bay credentials are
245
+ // provider-scoped bearer tokens, so the legacy fake-ChatGPT seed path is no
246
+ // longer safe to apply here.
247
+ return ensureCodexApiKeyAuth(params);
249
248
  }
250
249
  async function cleanupSeededCodexAuth(params) {
251
250
  const authPath = node_path_1.default.join(params.codexHome, AUTH_FILE);
@@ -14,13 +14,18 @@ const supported_models_1 = require("./supported-models");
14
14
  const MODELS_CACHE_FILE = "models_cache.json";
15
15
  const MODELS_CACHE_STATE_FILE = "theclawbay.models-cache.json";
16
16
  const STATE_DB_FILE_PATTERN = /^state_\d+\.sqlite$/;
17
- const TARGET_MODEL_IDS = ["gpt-5.4", "gpt-5.4-mini"];
17
+ const TARGET_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)();
18
+ const LEGACY_REMOVED_MODEL_IDS = (0, supported_models_1.getLegacyRemovedModelIds)();
18
19
  const TARGET_MODEL_ID_SET = new Set(TARGET_MODEL_IDS);
20
+ const TRACKED_MODEL_ID_SET = new Set([...TARGET_MODEL_IDS, ...LEGACY_REMOVED_MODEL_IDS]);
21
+ const LEGACY_REMOVED_MODEL_ID_SET = new Set(LEGACY_REMOVED_MODEL_IDS);
19
22
  const SEED_MARKER_KEY = "_theclawbay_seeded";
20
23
  // Older releases stored the model id as the marker value; accept those so cleanup still works.
21
24
  const SEED_MARKER_VALUE = "theclawbay";
22
- const LEGACY_SEED_MARKER_VALUES = new Set(["gpt-5.4", SEED_MARKER_VALUE, true]);
23
- const TEMPLATE_MODEL_IDS = (0, supported_models_1.getSupportedModelIds)().filter((id) => !TARGET_MODEL_ID_SET.has(id));
25
+ const LEGACY_SEED_MARKER_VALUES = new Set([...TRACKED_MODEL_ID_SET, SEED_MARKER_VALUE, true]);
26
+ const SUPPORTED_MODELS = (0, supported_models_1.getSupportedModels)();
27
+ const SUPPORTED_MODEL_CONFIG = new Map(SUPPORTED_MODELS.map((model) => [model.id, model]));
28
+ const TEMPLATE_MODEL_IDS = TARGET_MODEL_IDS;
24
29
  const DEFAULT_REASONING_LEVELS = [
25
30
  { effort: "low", description: "Fast responses with lighter reasoning" },
26
31
  { effort: "medium", description: "Balances speed and reasoning depth for everyday tasks" },
@@ -95,13 +100,13 @@ function normalizePatchFingerprintMap(state) {
95
100
  if (!state)
96
101
  return {};
97
102
  if (state.version === 1) {
98
- if (!TARGET_MODEL_ID_SET.has(state.modelId))
103
+ if (!TRACKED_MODEL_ID_SET.has(state.modelId))
99
104
  return {};
100
105
  return state.fingerprint ? { [state.modelId]: state.fingerprint } : {};
101
106
  }
102
107
  const output = {};
103
108
  for (const entry of state.models) {
104
- if (!TARGET_MODEL_ID_SET.has(entry.modelId))
109
+ if (!TRACKED_MODEL_ID_SET.has(entry.modelId))
105
110
  continue;
106
111
  if (entry.fingerprint)
107
112
  output[entry.modelId] = entry.fingerprint;
@@ -166,14 +171,13 @@ function applySeedMarker(model) {
166
171
  return next;
167
172
  }
168
173
  function seedDescription(modelId) {
169
- if (modelId === "gpt-5.4-mini")
170
- return "Smaller GPT-5.4 variant for quick iterations.";
171
- return "Latest frontier agentic coding model.";
174
+ return SUPPORTED_MODEL_CONFIG.get(modelId)?.note ?? "The Claw Bay model.";
172
175
  }
173
176
  function normalizeSeedModel(template, modelId) {
177
+ const modelConfig = SUPPORTED_MODEL_CONFIG.get(modelId);
174
178
  const seed = applySeedMarker(template ?? {});
175
179
  seed.slug = modelId;
176
- seed.display_name = modelId;
180
+ seed.display_name = modelConfig?.label ?? modelId;
177
181
  seed.description = seedDescription(modelId);
178
182
  if (typeof seed.default_reasoning_level !== "string") {
179
183
  seed.default_reasoning_level = "medium";
@@ -387,7 +391,7 @@ async function ensureCodexModelCacheHasGpt54(params) {
387
391
  await removeFileIfExists(statePath);
388
392
  return {
389
393
  action: "skipped",
390
- warning: "Codex models cache was not found, and Codex version could not be inferred for a safe GPT-5.4 / GPT-5.4 Mini seed.",
394
+ warning: "Codex models cache was not found, and Codex version could not be inferred for a safe The Claw Bay model seed.",
391
395
  };
392
396
  }
393
397
  const docModels = [];
@@ -411,7 +415,7 @@ async function ensureCodexModelCacheHasGpt54(params) {
411
415
  if (!doc) {
412
416
  return {
413
417
  action: "skipped",
414
- warning: "Codex models cache exists but is not valid JSON in the expected format; skipped GPT-5.4 / GPT-5.4 Mini seed.",
418
+ warning: "Codex models cache exists but is not valid JSON in the expected format; skipped The Claw Bay model seed.",
415
419
  };
416
420
  }
417
421
  const clientVersion = (await inferCodexClientVersion(params.codexHome)) ??
@@ -420,6 +424,20 @@ async function ensureCodexModelCacheHasGpt54(params) {
420
424
  let seeded = false;
421
425
  let stateChanged = false;
422
426
  const nextState = { ...existingState };
427
+ const legacyRemovedBefore = doc.models.length;
428
+ doc.models = doc.models.filter((entry) => {
429
+ const slug = typeof entry.slug === "string" ? entry.slug : "";
430
+ if (!slug || !LEGACY_REMOVED_MODEL_ID_SET.has(slug))
431
+ return true;
432
+ if (!hasSeedMarker(entry))
433
+ return true;
434
+ delete nextState[slug];
435
+ stateChanged = true;
436
+ return false;
437
+ });
438
+ if (doc.models.length !== legacyRemovedBefore) {
439
+ changed = true;
440
+ }
423
441
  for (const modelId of TARGET_MODEL_IDS) {
424
442
  const existingModel = findModel(doc.models, modelId);
425
443
  const trackedFingerprint = existingState[modelId];
@@ -522,14 +540,14 @@ async function cleanupSeededCodexModelCache(params) {
522
540
  if (!doc) {
523
541
  return {
524
542
  action: "none",
525
- warning: "Codex models cache exists but is not valid JSON in the expected format; left GPT-5.4 / GPT-5.4 Mini cache patch state untouched.",
543
+ warning: "Codex models cache exists but is not valid JSON in the expected format; left The Claw Bay model cache patch state untouched.",
526
544
  };
527
545
  }
528
546
  let removed = 0;
529
547
  let preserved = 0;
530
548
  doc.models = doc.models.filter((entry) => {
531
549
  const slug = typeof entry.slug === "string" ? entry.slug : "";
532
- if (!slug || !TARGET_MODEL_ID_SET.has(slug))
550
+ if (!slug || !TRACKED_MODEL_ID_SET.has(slug))
533
551
  return true;
534
552
  if (!hasSeedMarker(entry)) {
535
553
  preserved += 1;
@@ -11,3 +11,5 @@ export type SupportedModelConfig = {
11
11
  export declare function getSupportedModels(): SupportedModelConfig[];
12
12
  export declare function getSupportedModelIds(): string[];
13
13
  export declare function getSupportedModelDisplayNames(): Record<string, string>;
14
+ export declare function getLegacyRemovedModelIds(): string[];
15
+ export declare function getKnownTheClawBayModelIds(): string[];
@@ -6,11 +6,19 @@ Object.defineProperty(exports, "__esModule", { value: true });
6
6
  exports.getSupportedModels = getSupportedModels;
7
7
  exports.getSupportedModelIds = getSupportedModelIds;
8
8
  exports.getSupportedModelDisplayNames = getSupportedModelDisplayNames;
9
+ exports.getLegacyRemovedModelIds = getLegacyRemovedModelIds;
10
+ exports.getKnownTheClawBayModelIds = getKnownTheClawBayModelIds;
9
11
  const node_path_1 = __importDefault(require("node:path"));
10
12
  const node_fs_1 = require("node:fs");
11
13
  function configPath() {
12
14
  return node_path_1.default.resolve(__dirname, "..", "..", "theclawbay-supported-models.json");
13
15
  }
16
+ const LEGACY_REMOVED_MODEL_IDS = [
17
+ "gpt-image-1.5",
18
+ "gpt-5.2-codex",
19
+ "gpt-5.1-codex-max",
20
+ "gpt-5.1-codex-mini",
21
+ ];
14
22
  function parseSupportedModels(raw) {
15
23
  const parsed = JSON.parse(raw);
16
24
  if (!Array.isArray(parsed)) {
@@ -61,3 +69,9 @@ function getSupportedModelIds() {
61
69
  function getSupportedModelDisplayNames() {
62
70
  return Object.fromEntries(getSupportedModels().map((model) => [model.id, model.label]));
63
71
  }
72
+ function getLegacyRemovedModelIds() {
73
+ return [...LEGACY_REMOVED_MODEL_IDS];
74
+ }
75
+ function getKnownTheClawBayModelIds() {
76
+ return Array.from(new Set([...getSupportedModelIds(), ...getLegacyRemovedModelIds()]));
77
+ }
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "theclawbay",
3
- "version": "0.3.60",
3
+ "version": "0.3.62",
4
4
  "description": "CLI for connecting Codex, Continue, Cline, GSD, OpenClaw, OpenCode, Kilo, Roo Code, Aider, experimental Trae, and experimental Zo to The Claw Bay.",
5
5
  "license": "MIT",
6
6
  "repository": {
@@ -1,4 +1,13 @@
1
1
  [
2
+ {
3
+ "id": "gpt-5.5",
4
+ "label": "GPT-5.5",
5
+ "note": "Newest Codex model for stronger coding and agentic work.",
6
+ "tone": "coral",
7
+ "inputPer1M": 5.0,
8
+ "cachedInputPer1M": 0.5,
9
+ "outputPer1M": 30.0
10
+ },
2
11
  {
3
12
  "id": "gpt-5.4",
4
13
  "label": "GPT-5.4",
@@ -17,15 +26,6 @@
17
26
  "cachedInputPer1M": 0.125,
18
27
  "outputPer1M": 10.0
19
28
  },
20
- {
21
- "id": "gpt-image-1.5",
22
- "label": "GPT Image 1.5",
23
- "note": "Native image generation model for direct image outputs.",
24
- "tone": "coral",
25
- "inputPer1M": 8.0,
26
- "cachedInputPer1M": 2.0,
27
- "outputPer1M": 32.0
28
- },
29
29
  {
30
30
  "id": "gpt-5.3-codex",
31
31
  "label": "GPT-5.3 Codex",
@@ -35,15 +35,6 @@
35
35
  "cachedInputPer1M": 0.175,
36
36
  "outputPer1M": 14.0
37
37
  },
38
- {
39
- "id": "gpt-5.2-codex",
40
- "label": "GPT-5.2 Codex",
41
- "note": "Stable compatibility option for older Codex flows.",
42
- "tone": "sea",
43
- "inputPer1M": 1.75,
44
- "cachedInputPer1M": 0.175,
45
- "outputPer1M": 14.0
46
- },
47
38
  {
48
39
  "id": "gpt-5.2",
49
40
  "label": "GPT-5.2",
@@ -52,23 +43,5 @@
52
43
  "inputPer1M": 1.75,
53
44
  "cachedInputPer1M": 0.175,
54
45
  "outputPer1M": 14.0
55
- },
56
- {
57
- "id": "gpt-5.1-codex-max",
58
- "label": "GPT-5.1 Codex Max",
59
- "note": "Higher-throughput option for longer coding sessions.",
60
- "tone": "coral",
61
- "inputPer1M": 1.25,
62
- "cachedInputPer1M": 0.125,
63
- "outputPer1M": 10.0
64
- },
65
- {
66
- "id": "gpt-5.1-codex-mini",
67
- "label": "GPT-5.1 Codex Mini",
68
- "note": "Lower-cost Codex path for quick iterations.",
69
- "tone": "sea",
70
- "inputPer1M": 0.25,
71
- "cachedInputPer1M": 0.025,
72
- "outputPer1M": 2.0
73
46
  }
74
47
  ]