terminalhire 0.9.0 → 0.9.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/bin/jpi-chat-read.js +71 -32
- package/dist/bin/jpi-chat.js +71 -32
- package/dist/bin/jpi-dispatch.js +448 -198
- package/dist/bin/jpi-intro.js +72 -45
- package/dist/bin/jpi-link.js +285 -0
- package/dist/bin/jpi-trajectory.js +83 -44
- package/dist/src/chat-client.js +48 -15
- package/dist/src/intro.js +68 -45
- package/dist/src/link.js +253 -0
- package/dist/src/trajectory.js +77 -44
- package/dist/src/web-session.js +55 -0
- package/package.json +1 -1
|
@@ -3855,14 +3855,53 @@ var init_chat_keystore = __esm({
|
|
|
3855
3855
|
}
|
|
3856
3856
|
});
|
|
3857
3857
|
|
|
3858
|
-
// src/
|
|
3859
|
-
import {
|
|
3858
|
+
// src/web-session.ts
|
|
3859
|
+
import {
|
|
3860
|
+
chmodSync,
|
|
3861
|
+
existsSync as existsSync3,
|
|
3862
|
+
mkdirSync as mkdirSync3,
|
|
3863
|
+
readFileSync as readFileSync4,
|
|
3864
|
+
rmSync as rmSync3,
|
|
3865
|
+
writeFileSync as writeFileSync3
|
|
3866
|
+
} from "fs";
|
|
3860
3867
|
import { homedir as homedir3 } from "os";
|
|
3861
3868
|
import { join as join4 } from "path";
|
|
3869
|
+
function terminalhireDir() {
|
|
3870
|
+
return join4(homedir3(), ".terminalhire");
|
|
3871
|
+
}
|
|
3872
|
+
function webSessionFilePath() {
|
|
3873
|
+
return join4(terminalhireDir(), "web-session");
|
|
3874
|
+
}
|
|
3875
|
+
function readWebSessionFile() {
|
|
3876
|
+
try {
|
|
3877
|
+
const path = webSessionFilePath();
|
|
3878
|
+
if (!existsSync3(path)) return null;
|
|
3879
|
+
const v = readFileSync4(path, "utf8").trim();
|
|
3880
|
+
return v.length > 0 ? v : null;
|
|
3881
|
+
} catch {
|
|
3882
|
+
return null;
|
|
3883
|
+
}
|
|
3884
|
+
}
|
|
3885
|
+
function readWebSessionCookie() {
|
|
3886
|
+
const fromFile = readWebSessionFile();
|
|
3887
|
+
if (fromFile) return fromFile;
|
|
3888
|
+
const env = process.env["TERMINALHIRE_WEB_SESSION"];
|
|
3889
|
+
return typeof env === "string" && env.length > 0 ? env : null;
|
|
3890
|
+
}
|
|
3891
|
+
var init_web_session = __esm({
|
|
3892
|
+
"src/web-session.ts"() {
|
|
3893
|
+
"use strict";
|
|
3894
|
+
}
|
|
3895
|
+
});
|
|
3896
|
+
|
|
3897
|
+
// src/chat-client.ts
|
|
3898
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
3899
|
+
import { homedir as homedir4 } from "os";
|
|
3900
|
+
import { join as join5 } from "path";
|
|
3862
3901
|
function defaultReadPeerPins() {
|
|
3863
3902
|
try {
|
|
3864
|
-
if (!
|
|
3865
|
-
const parsed = JSON.parse(
|
|
3903
|
+
if (!existsSync4(PEERS_FILE)) return {};
|
|
3904
|
+
const parsed = JSON.parse(readFileSync5(PEERS_FILE, "utf8"));
|
|
3866
3905
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
3867
3906
|
const out = {};
|
|
3868
3907
|
for (const [login, key] of Object.entries(parsed)) {
|
|
@@ -3874,16 +3913,15 @@ function defaultReadPeerPins() {
|
|
|
3874
3913
|
}
|
|
3875
3914
|
}
|
|
3876
3915
|
function defaultWritePeerPins(pins) {
|
|
3877
|
-
|
|
3878
|
-
|
|
3916
|
+
mkdirSync4(TERMINALHIRE_DIR3, { recursive: true });
|
|
3917
|
+
writeFileSync4(PEERS_FILE, JSON.stringify(pins, null, 2), { mode: 384, encoding: "utf8" });
|
|
3879
3918
|
}
|
|
3880
3919
|
function defaultChatClientDeps() {
|
|
3881
3920
|
return {
|
|
3882
3921
|
fetchImpl: (...args) => globalThis.fetch(...args),
|
|
3883
|
-
|
|
3884
|
-
|
|
3885
|
-
|
|
3886
|
-
},
|
|
3922
|
+
// Session source priority: persisted file (`terminalhire link`) FIRST, then the
|
|
3923
|
+
// legacy TERMINALHIRE_WEB_SESSION env, then none.
|
|
3924
|
+
sessionCookie: () => readWebSessionCookie(),
|
|
3887
3925
|
loadIdentity: () => loadOrCreateIdentity(),
|
|
3888
3926
|
readPeerPins: defaultReadPeerPins,
|
|
3889
3927
|
writePeerPins: defaultWritePeerPins
|
|
@@ -4036,15 +4074,16 @@ var init_chat_client = __esm({
|
|
|
4036
4074
|
"use strict";
|
|
4037
4075
|
init_src();
|
|
4038
4076
|
init_chat_keystore();
|
|
4077
|
+
init_web_session();
|
|
4039
4078
|
CHAT_BASE = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
|
|
4040
4079
|
GH_SESSION_COOKIE = "__jpi_gh_session";
|
|
4041
|
-
TERMINALHIRE_DIR3 =
|
|
4042
|
-
PEERS_FILE =
|
|
4080
|
+
TERMINALHIRE_DIR3 = join5(homedir4(), ".terminalhire");
|
|
4081
|
+
PEERS_FILE = join5(TERMINALHIRE_DIR3, "chat-peers.json");
|
|
4043
4082
|
REQUEST_TIMEOUT_MS = 1e4;
|
|
4044
4083
|
ChatNotLinkedError = class extends Error {
|
|
4045
4084
|
constructor() {
|
|
4046
4085
|
super(
|
|
4047
|
-
|
|
4086
|
+
"No linked web session found on this machine. Run `terminalhire link` to connect this terminal to your account, then re-run."
|
|
4048
4087
|
);
|
|
4049
4088
|
this.name = "ChatNotLinkedError";
|
|
4050
4089
|
}
|
|
@@ -4052,7 +4091,7 @@ var init_chat_client = __esm({
|
|
|
4052
4091
|
ChatSessionExpiredError = class extends Error {
|
|
4053
4092
|
constructor() {
|
|
4054
4093
|
super(
|
|
4055
|
-
|
|
4094
|
+
"Your linked web session expired. Run `terminalhire link` to reconnect this terminal, then re-run."
|
|
4056
4095
|
);
|
|
4057
4096
|
this.name = "ChatSessionExpiredError";
|
|
4058
4097
|
}
|
|
@@ -4081,13 +4120,13 @@ var init_chat_client = __esm({
|
|
|
4081
4120
|
});
|
|
4082
4121
|
|
|
4083
4122
|
// src/config.ts
|
|
4084
|
-
import { readFileSync as
|
|
4085
|
-
import { join as
|
|
4086
|
-
import { homedir as
|
|
4123
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync5 } from "fs";
|
|
4124
|
+
import { join as join6 } from "path";
|
|
4125
|
+
import { homedir as homedir5 } from "os";
|
|
4087
4126
|
function readConfig() {
|
|
4088
4127
|
try {
|
|
4089
|
-
if (!
|
|
4090
|
-
const raw =
|
|
4128
|
+
if (!existsSync5(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
|
|
4129
|
+
const raw = readFileSync6(CONFIG_FILE, "utf8");
|
|
4091
4130
|
const parsed = JSON.parse(raw);
|
|
4092
4131
|
return { ...DEFAULT_CONFIG, ...parsed };
|
|
4093
4132
|
} catch {
|
|
@@ -4095,17 +4134,17 @@ function readConfig() {
|
|
|
4095
4134
|
}
|
|
4096
4135
|
}
|
|
4097
4136
|
function writeConfig(config) {
|
|
4098
|
-
|
|
4137
|
+
mkdirSync5(TERMINALHIRE_DIR4, { recursive: true });
|
|
4099
4138
|
const current = readConfig();
|
|
4100
4139
|
const merged = { ...current, ...config };
|
|
4101
|
-
|
|
4140
|
+
writeFileSync5(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4102
4141
|
}
|
|
4103
4142
|
var TERMINALHIRE_DIR4, CONFIG_FILE, DEFAULT_CONFIG;
|
|
4104
4143
|
var init_config = __esm({
|
|
4105
4144
|
"src/config.ts"() {
|
|
4106
4145
|
"use strict";
|
|
4107
|
-
TERMINALHIRE_DIR4 =
|
|
4108
|
-
CONFIG_FILE =
|
|
4146
|
+
TERMINALHIRE_DIR4 = join6(homedir5(), ".terminalhire");
|
|
4147
|
+
CONFIG_FILE = join6(TERMINALHIRE_DIR4, "config.json");
|
|
4109
4148
|
DEFAULT_CONFIG = {
|
|
4110
4149
|
nudge: "session",
|
|
4111
4150
|
peerConnect: false,
|
|
@@ -4225,13 +4264,13 @@ var init_jpi_chat = __esm({
|
|
|
4225
4264
|
});
|
|
4226
4265
|
|
|
4227
4266
|
// bin/jpi-chat-read.js
|
|
4228
|
-
import { existsSync as
|
|
4229
|
-
import { homedir as
|
|
4230
|
-
import { join as
|
|
4267
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
4268
|
+
import { homedir as homedir6 } from "os";
|
|
4269
|
+
import { join as join7 } from "path";
|
|
4231
4270
|
function readReadCursors() {
|
|
4232
4271
|
try {
|
|
4233
|
-
if (!
|
|
4234
|
-
const parsed = JSON.parse(
|
|
4272
|
+
if (!existsSync6(READS_FILE)) return {};
|
|
4273
|
+
const parsed = JSON.parse(readFileSync7(READS_FILE, "utf8"));
|
|
4235
4274
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
4236
4275
|
const out = {};
|
|
4237
4276
|
for (const [login, iso] of Object.entries(parsed)) {
|
|
@@ -4246,8 +4285,8 @@ function writeReadCursor(login, iso, deps = {}) {
|
|
|
4246
4285
|
const read = deps.readReadCursors ?? readReadCursors;
|
|
4247
4286
|
const cursors = read();
|
|
4248
4287
|
cursors[login] = iso;
|
|
4249
|
-
|
|
4250
|
-
|
|
4288
|
+
mkdirSync6(TERMINALHIRE_DIR5, { recursive: true });
|
|
4289
|
+
writeFileSync6(READS_FILE, JSON.stringify(cursors, null, 2), { mode: 384, encoding: "utf8" });
|
|
4251
4290
|
}
|
|
4252
4291
|
function formatClock(iso) {
|
|
4253
4292
|
const d = new Date(iso);
|
|
@@ -4530,8 +4569,8 @@ var init_jpi_chat_read = __esm({
|
|
|
4530
4569
|
init_chat_client();
|
|
4531
4570
|
init_jpi_chat();
|
|
4532
4571
|
CHAT_BASE3 = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
|
|
4533
|
-
TERMINALHIRE_DIR5 =
|
|
4534
|
-
READS_FILE =
|
|
4572
|
+
TERMINALHIRE_DIR5 = join7(homedir6(), ".terminalhire");
|
|
4573
|
+
READS_FILE = join7(TERMINALHIRE_DIR5, "chat-reads.json");
|
|
4535
4574
|
}
|
|
4536
4575
|
});
|
|
4537
4576
|
init_jpi_chat_read();
|
package/dist/bin/jpi-chat.js
CHANGED
|
@@ -3860,14 +3860,53 @@ var init_chat_keystore = __esm({
|
|
|
3860
3860
|
}
|
|
3861
3861
|
});
|
|
3862
3862
|
|
|
3863
|
-
// src/
|
|
3864
|
-
import {
|
|
3863
|
+
// src/web-session.ts
|
|
3864
|
+
import {
|
|
3865
|
+
chmodSync,
|
|
3866
|
+
existsSync as existsSync3,
|
|
3867
|
+
mkdirSync as mkdirSync3,
|
|
3868
|
+
readFileSync as readFileSync4,
|
|
3869
|
+
rmSync as rmSync3,
|
|
3870
|
+
writeFileSync as writeFileSync3
|
|
3871
|
+
} from "fs";
|
|
3865
3872
|
import { homedir as homedir3 } from "os";
|
|
3866
3873
|
import { join as join4 } from "path";
|
|
3874
|
+
function terminalhireDir() {
|
|
3875
|
+
return join4(homedir3(), ".terminalhire");
|
|
3876
|
+
}
|
|
3877
|
+
function webSessionFilePath() {
|
|
3878
|
+
return join4(terminalhireDir(), "web-session");
|
|
3879
|
+
}
|
|
3880
|
+
function readWebSessionFile() {
|
|
3881
|
+
try {
|
|
3882
|
+
const path = webSessionFilePath();
|
|
3883
|
+
if (!existsSync3(path)) return null;
|
|
3884
|
+
const v = readFileSync4(path, "utf8").trim();
|
|
3885
|
+
return v.length > 0 ? v : null;
|
|
3886
|
+
} catch {
|
|
3887
|
+
return null;
|
|
3888
|
+
}
|
|
3889
|
+
}
|
|
3890
|
+
function readWebSessionCookie() {
|
|
3891
|
+
const fromFile = readWebSessionFile();
|
|
3892
|
+
if (fromFile) return fromFile;
|
|
3893
|
+
const env = process.env["TERMINALHIRE_WEB_SESSION"];
|
|
3894
|
+
return typeof env === "string" && env.length > 0 ? env : null;
|
|
3895
|
+
}
|
|
3896
|
+
var init_web_session = __esm({
|
|
3897
|
+
"src/web-session.ts"() {
|
|
3898
|
+
"use strict";
|
|
3899
|
+
}
|
|
3900
|
+
});
|
|
3901
|
+
|
|
3902
|
+
// src/chat-client.ts
|
|
3903
|
+
import { existsSync as existsSync4, mkdirSync as mkdirSync4, readFileSync as readFileSync5, writeFileSync as writeFileSync4 } from "fs";
|
|
3904
|
+
import { homedir as homedir4 } from "os";
|
|
3905
|
+
import { join as join5 } from "path";
|
|
3867
3906
|
function defaultReadPeerPins() {
|
|
3868
3907
|
try {
|
|
3869
|
-
if (!
|
|
3870
|
-
const parsed = JSON.parse(
|
|
3908
|
+
if (!existsSync4(PEERS_FILE)) return {};
|
|
3909
|
+
const parsed = JSON.parse(readFileSync5(PEERS_FILE, "utf8"));
|
|
3871
3910
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
3872
3911
|
const out = {};
|
|
3873
3912
|
for (const [login, key] of Object.entries(parsed)) {
|
|
@@ -3879,16 +3918,15 @@ function defaultReadPeerPins() {
|
|
|
3879
3918
|
}
|
|
3880
3919
|
}
|
|
3881
3920
|
function defaultWritePeerPins(pins) {
|
|
3882
|
-
|
|
3883
|
-
|
|
3921
|
+
mkdirSync4(TERMINALHIRE_DIR3, { recursive: true });
|
|
3922
|
+
writeFileSync4(PEERS_FILE, JSON.stringify(pins, null, 2), { mode: 384, encoding: "utf8" });
|
|
3884
3923
|
}
|
|
3885
3924
|
function defaultChatClientDeps() {
|
|
3886
3925
|
return {
|
|
3887
3926
|
fetchImpl: (...args) => globalThis.fetch(...args),
|
|
3888
|
-
|
|
3889
|
-
|
|
3890
|
-
|
|
3891
|
-
},
|
|
3927
|
+
// Session source priority: persisted file (`terminalhire link`) FIRST, then the
|
|
3928
|
+
// legacy TERMINALHIRE_WEB_SESSION env, then none.
|
|
3929
|
+
sessionCookie: () => readWebSessionCookie(),
|
|
3892
3930
|
loadIdentity: () => loadOrCreateIdentity(),
|
|
3893
3931
|
readPeerPins: defaultReadPeerPins,
|
|
3894
3932
|
writePeerPins: defaultWritePeerPins
|
|
@@ -4041,15 +4079,16 @@ var init_chat_client = __esm({
|
|
|
4041
4079
|
"use strict";
|
|
4042
4080
|
init_src();
|
|
4043
4081
|
init_chat_keystore();
|
|
4082
|
+
init_web_session();
|
|
4044
4083
|
CHAT_BASE = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
|
|
4045
4084
|
GH_SESSION_COOKIE = "__jpi_gh_session";
|
|
4046
|
-
TERMINALHIRE_DIR3 =
|
|
4047
|
-
PEERS_FILE =
|
|
4085
|
+
TERMINALHIRE_DIR3 = join5(homedir4(), ".terminalhire");
|
|
4086
|
+
PEERS_FILE = join5(TERMINALHIRE_DIR3, "chat-peers.json");
|
|
4048
4087
|
REQUEST_TIMEOUT_MS = 1e4;
|
|
4049
4088
|
ChatNotLinkedError = class extends Error {
|
|
4050
4089
|
constructor() {
|
|
4051
4090
|
super(
|
|
4052
|
-
|
|
4091
|
+
"No linked web session found on this machine. Run `terminalhire link` to connect this terminal to your account, then re-run."
|
|
4053
4092
|
);
|
|
4054
4093
|
this.name = "ChatNotLinkedError";
|
|
4055
4094
|
}
|
|
@@ -4057,7 +4096,7 @@ var init_chat_client = __esm({
|
|
|
4057
4096
|
ChatSessionExpiredError = class extends Error {
|
|
4058
4097
|
constructor() {
|
|
4059
4098
|
super(
|
|
4060
|
-
|
|
4099
|
+
"Your linked web session expired. Run `terminalhire link` to reconnect this terminal, then re-run."
|
|
4061
4100
|
);
|
|
4062
4101
|
this.name = "ChatSessionExpiredError";
|
|
4063
4102
|
}
|
|
@@ -4086,13 +4125,13 @@ var init_chat_client = __esm({
|
|
|
4086
4125
|
});
|
|
4087
4126
|
|
|
4088
4127
|
// src/config.ts
|
|
4089
|
-
import { readFileSync as
|
|
4090
|
-
import { join as
|
|
4091
|
-
import { homedir as
|
|
4128
|
+
import { readFileSync as readFileSync6, writeFileSync as writeFileSync5, mkdirSync as mkdirSync5, existsSync as existsSync5 } from "fs";
|
|
4129
|
+
import { join as join6 } from "path";
|
|
4130
|
+
import { homedir as homedir5 } from "os";
|
|
4092
4131
|
function readConfig() {
|
|
4093
4132
|
try {
|
|
4094
|
-
if (!
|
|
4095
|
-
const raw =
|
|
4133
|
+
if (!existsSync5(CONFIG_FILE)) return { ...DEFAULT_CONFIG };
|
|
4134
|
+
const raw = readFileSync6(CONFIG_FILE, "utf8");
|
|
4096
4135
|
const parsed = JSON.parse(raw);
|
|
4097
4136
|
return { ...DEFAULT_CONFIG, ...parsed };
|
|
4098
4137
|
} catch {
|
|
@@ -4100,17 +4139,17 @@ function readConfig() {
|
|
|
4100
4139
|
}
|
|
4101
4140
|
}
|
|
4102
4141
|
function writeConfig(config) {
|
|
4103
|
-
|
|
4142
|
+
mkdirSync5(TERMINALHIRE_DIR4, { recursive: true });
|
|
4104
4143
|
const current = readConfig();
|
|
4105
4144
|
const merged = { ...current, ...config };
|
|
4106
|
-
|
|
4145
|
+
writeFileSync5(CONFIG_FILE, JSON.stringify(merged, null, 2) + "\n", "utf8");
|
|
4107
4146
|
}
|
|
4108
4147
|
var TERMINALHIRE_DIR4, CONFIG_FILE, DEFAULT_CONFIG;
|
|
4109
4148
|
var init_config = __esm({
|
|
4110
4149
|
"src/config.ts"() {
|
|
4111
4150
|
"use strict";
|
|
4112
|
-
TERMINALHIRE_DIR4 =
|
|
4113
|
-
CONFIG_FILE =
|
|
4151
|
+
TERMINALHIRE_DIR4 = join6(homedir5(), ".terminalhire");
|
|
4152
|
+
CONFIG_FILE = join6(TERMINALHIRE_DIR4, "config.json");
|
|
4114
4153
|
DEFAULT_CONFIG = {
|
|
4115
4154
|
nudge: "session",
|
|
4116
4155
|
peerConnect: false,
|
|
@@ -4134,13 +4173,13 @@ __export(jpi_chat_read_exports, {
|
|
|
4134
4173
|
runSend: () => runSend,
|
|
4135
4174
|
writeReadCursor: () => writeReadCursor
|
|
4136
4175
|
});
|
|
4137
|
-
import { existsSync as
|
|
4138
|
-
import { homedir as
|
|
4139
|
-
import { join as
|
|
4176
|
+
import { existsSync as existsSync6, mkdirSync as mkdirSync6, readFileSync as readFileSync7, writeFileSync as writeFileSync6 } from "fs";
|
|
4177
|
+
import { homedir as homedir6 } from "os";
|
|
4178
|
+
import { join as join7 } from "path";
|
|
4140
4179
|
function readReadCursors() {
|
|
4141
4180
|
try {
|
|
4142
|
-
if (!
|
|
4143
|
-
const parsed = JSON.parse(
|
|
4181
|
+
if (!existsSync6(READS_FILE)) return {};
|
|
4182
|
+
const parsed = JSON.parse(readFileSync7(READS_FILE, "utf8"));
|
|
4144
4183
|
if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
|
|
4145
4184
|
const out = {};
|
|
4146
4185
|
for (const [login, iso] of Object.entries(parsed)) {
|
|
@@ -4155,8 +4194,8 @@ function writeReadCursor(login, iso, deps = {}) {
|
|
|
4155
4194
|
const read = deps.readReadCursors ?? readReadCursors;
|
|
4156
4195
|
const cursors = read();
|
|
4157
4196
|
cursors[login] = iso;
|
|
4158
|
-
|
|
4159
|
-
|
|
4197
|
+
mkdirSync6(TERMINALHIRE_DIR5, { recursive: true });
|
|
4198
|
+
writeFileSync6(READS_FILE, JSON.stringify(cursors, null, 2), { mode: 384, encoding: "utf8" });
|
|
4160
4199
|
}
|
|
4161
4200
|
function formatClock(iso) {
|
|
4162
4201
|
const d = new Date(iso);
|
|
@@ -4440,8 +4479,8 @@ var init_jpi_chat_read = __esm({
|
|
|
4440
4479
|
init_chat_client();
|
|
4441
4480
|
init_jpi_chat();
|
|
4442
4481
|
CHAT_BASE2 = process.env["TERMINALHIRE_API_URL"] || "https://www.terminalhire.com";
|
|
4443
|
-
TERMINALHIRE_DIR5 =
|
|
4444
|
-
READS_FILE =
|
|
4482
|
+
TERMINALHIRE_DIR5 = join7(homedir6(), ".terminalhire");
|
|
4483
|
+
READS_FILE = join7(TERMINALHIRE_DIR5, "chat-reads.json");
|
|
4445
4484
|
}
|
|
4446
4485
|
});
|
|
4447
4486
|
|