downcity 1.1.126 → 1.1.127
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/package.json +3 -3
- package/town/command/CityCommand.d.ts.map +1 -1
- package/town/command/CityCommand.js +9 -1
- package/town/command/CityCommand.js.map +1 -1
- package/town/shared/CityBalance.d.ts +3 -4
- package/town/shared/CityBalance.d.ts.map +1 -1
- package/town/shared/CityBalance.js +12 -53
- package/town/shared/CityBalance.js.map +1 -1
- package/town/shared/CityConnection.d.ts +3 -10
- package/town/shared/CityConnection.d.ts.map +1 -1
- package/town/shared/CityConnection.js +71 -233
- package/town/shared/CityConnection.js.map +1 -1
- package/town/shared/CityStateStore.d.ts +63 -0
- package/town/shared/CityStateStore.d.ts.map +1 -0
- package/town/shared/CityStateStore.js +267 -0
- package/town/shared/CityStateStore.js.map +1 -0
- package/town/shared/CityUserManager.d.ts +38 -0
- package/town/shared/CityUserManager.d.ts.map +1 -0
- package/town/shared/CityUserManager.js +142 -0
- package/town/shared/CityUserManager.js.map +1 -0
- package/town/town/city-model/CityAiServiceBinding.d.ts +0 -35
- package/town/town/city-model/CityAiServiceBinding.d.ts.map +1 -1
- package/town/town/city-model/CityAiServiceBinding.js +17 -66
- package/town/town/city-model/CityAiServiceBinding.js.map +1 -1
- package/town/types/TownCityUser.d.ts +114 -0
- package/town/types/TownCityUser.d.ts.map +1 -0
- package/town/types/TownCityUser.js +9 -0
- package/town/types/TownCityUser.js.map +1 -0
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "downcity",
|
|
3
|
-
"version": "1.1.
|
|
3
|
+
"version": "1.1.127",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"description": "Downcity CLI bundle — installs city and town commands",
|
|
6
6
|
"bin": {
|
|
@@ -45,8 +45,8 @@
|
|
|
45
45
|
"ws": "^8.21.0",
|
|
46
46
|
"zod": "^4.4.3",
|
|
47
47
|
"@downcity/agent": "^1.1.100",
|
|
48
|
-
"@downcity/
|
|
49
|
-
"@downcity/
|
|
48
|
+
"@downcity/plugins": "^1.0.51",
|
|
49
|
+
"@downcity/city": "^0.2.56"
|
|
50
50
|
},
|
|
51
51
|
"scripts": {
|
|
52
52
|
"build": "node scripts/copy-cli-products.mjs && node scripts/ensure-cli-executable.mjs",
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CityCommand.d.ts","sourceRoot":"","sources":["../../src/command/CityCommand.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;
|
|
1
|
+
{"version":3,"file":"CityCommand.d.ts","sourceRoot":"","sources":["../../src/command/CityCommand.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,KAAK,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAezC;;GAEG;AACH,wBAAgB,6BAA6B,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CA0FpE"}
|
|
@@ -7,7 +7,8 @@
|
|
|
7
7
|
* - `city` CLI 只负责 admin/base 管理,`town city` 只负责 user login。
|
|
8
8
|
*/
|
|
9
9
|
import { parseBoolean } from "../shared/IndexSupport.js";
|
|
10
|
-
import { DEFAULT_TOWN_ID
|
|
10
|
+
import { DEFAULT_TOWN_ID } from "../shared/CityStateStore.js";
|
|
11
|
+
import { emitCityConnectionStatus, emitCityUserWhoami, emitCityServerList, runCityConnectCommand, runCityDisconnectCommand, runCityLoginCommand, runCityLogoutCommand, runCityUseCommand, runInteractiveCityManager, } from "../shared/CityConnection.js";
|
|
11
12
|
/**
|
|
12
13
|
* 注册 `town city` 命令组。
|
|
13
14
|
*/
|
|
@@ -37,6 +38,13 @@ export function registerCityConnectionCommand(program) {
|
|
|
37
38
|
.action((options) => {
|
|
38
39
|
emitCityServerList({ as_json: options.json === true });
|
|
39
40
|
});
|
|
41
|
+
city
|
|
42
|
+
.command("whoami")
|
|
43
|
+
.description("查看 Town 当前实际使用的 City user")
|
|
44
|
+
.option("--json [enabled]", "以 JSON 输出", parseBoolean)
|
|
45
|
+
.action(async (options) => {
|
|
46
|
+
await emitCityUserWhoami({ as_json: options.json === true });
|
|
47
|
+
});
|
|
40
48
|
city
|
|
41
49
|
.command("connect [url]")
|
|
42
50
|
.description("手动添加并选择一个 City base(默认 base.downcity.ai)")
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CityCommand.js","sourceRoot":"","sources":["../../src/command/CityCommand.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAGH,OAAO,EAAE,YAAY,EAAE,MAAM,2BAA2B,CAAC;AACzD,OAAO,
|
|
1
|
+
{"version":3,"file":"CityCommand.js","sourceRoot":"","sources":["../../src/command/CityCommand.ts"],"names":[],"mappings":"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"}
|
|
@@ -7,19 +7,18 @@
|
|
|
7
7
|
* - 交互菜单只调用这里的高层函数,避免 CityConnection 模块继续膨胀。
|
|
8
8
|
*/
|
|
9
9
|
import type { TownCityBalanceAccount, TownCityRechargeInput, TownCityRechargeResult } from "../types/TownCityBalance.js";
|
|
10
|
-
import type { TownCityUserSession } from "../types/TownCitySession.js";
|
|
11
10
|
/**
|
|
12
11
|
* 读取当前 Town City user 的余额。
|
|
13
12
|
*/
|
|
14
|
-
export declare function readCurrentTownCityBalance(
|
|
13
|
+
export declare function readCurrentTownCityBalance(): Promise<TownCityBalanceAccount>;
|
|
15
14
|
/**
|
|
16
15
|
* 给当前 Town City user 发起充值。
|
|
17
16
|
*/
|
|
18
|
-
export declare function rechargeCurrentTownCityUser(
|
|
17
|
+
export declare function rechargeCurrentTownCityUser(input: TownCityRechargeInput): Promise<TownCityRechargeResult>;
|
|
19
18
|
/**
|
|
20
19
|
* 输出当前 user 余额。
|
|
21
20
|
*/
|
|
22
|
-
export declare function emitCurrentTownCityBalance(
|
|
21
|
+
export declare function emitCurrentTownCityBalance(): Promise<void>;
|
|
23
22
|
/**
|
|
24
23
|
* 输出当前 user 充值结果。
|
|
25
24
|
*/
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CityBalance.d.ts","sourceRoot":"","sources":["../../src/shared/CityBalance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EACV,sBAAsB,EAGtB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,6BAA6B,CAAC;
|
|
1
|
+
{"version":3,"file":"CityBalance.d.ts","sourceRoot":"","sources":["../../src/shared/CityBalance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAKH,OAAO,KAAK,EACV,sBAAsB,EAGtB,qBAAqB,EACrB,sBAAsB,EACvB,MAAM,6BAA6B,CAAC;AAKrC;;GAEG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,sBAAsB,CAAC,CAKlF;AAED;;GAEG;AACH,wBAAsB,2BAA2B,CAC/C,KAAK,EAAE,qBAAqB,GAC3B,OAAO,CAAC,sBAAsB,CAAC,CA0BjC;AAmBD;;GAEG;AACH,wBAAsB,0BAA0B,IAAI,OAAO,CAAC,IAAI,CAAC,CAchE;AAED;;GAEG;AACH,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,sBAAsB,GAAG,IAAI,CAoB/E"}
|
|
@@ -7,31 +7,24 @@
|
|
|
7
7
|
* - 交互菜单只调用这里的高层函数,避免 CityConnection 模块继续膨胀。
|
|
8
8
|
*/
|
|
9
9
|
import { spawnSync } from "node:child_process";
|
|
10
|
-
import { City } from "@downcity/city";
|
|
11
10
|
import { emitCliBlock } from "./CliReporter.js";
|
|
11
|
+
import { CityUserManager } from "./CityUserManager.js";
|
|
12
12
|
const DEFAULT_PAYMENT_METHOD_ID = "stripe";
|
|
13
|
+
const cityUserManager = new CityUserManager();
|
|
13
14
|
/**
|
|
14
15
|
* 读取当前 Town City user 的余额。
|
|
15
16
|
*/
|
|
16
|
-
export async function readCurrentTownCityBalance(
|
|
17
|
-
const client =
|
|
18
|
-
if (!client)
|
|
19
|
-
return null;
|
|
20
|
-
const current_user_id = await readCurrentTokenUserId(client);
|
|
21
|
-
assertSessionUserMatchesToken(session, current_user_id);
|
|
17
|
+
export async function readCurrentTownCityBalance() {
|
|
18
|
+
const { user, client } = await cityUserManager.createUserClient();
|
|
22
19
|
const account = await client.service("balance").get("me");
|
|
23
|
-
assertBalanceUserMatchesToken(account,
|
|
20
|
+
assertBalanceUserMatchesToken(account, user.user_id);
|
|
24
21
|
return account;
|
|
25
22
|
}
|
|
26
23
|
/**
|
|
27
24
|
* 给当前 Town City user 发起充值。
|
|
28
25
|
*/
|
|
29
|
-
export async function rechargeCurrentTownCityUser(
|
|
30
|
-
const client =
|
|
31
|
-
if (!client)
|
|
32
|
-
return null;
|
|
33
|
-
const current_user_id = await readCurrentTokenUserId(client);
|
|
34
|
-
assertSessionUserMatchesToken(session, current_user_id);
|
|
26
|
+
export async function rechargeCurrentTownCityUser(input) {
|
|
27
|
+
const { client } = await cityUserManager.createUserClient();
|
|
35
28
|
const amount = normalizePositiveInteger(input.amount, "amount");
|
|
36
29
|
const method_id = normalizeText(input.method_id) || DEFAULT_PAYMENT_METHOD_ID;
|
|
37
30
|
const topup = await client.service("balance").action("topups/create").invoke({
|
|
@@ -56,26 +49,10 @@ export async function rechargeCurrentTownCityUser(session, input) {
|
|
|
56
49
|
opened,
|
|
57
50
|
};
|
|
58
51
|
}
|
|
59
|
-
async function readCurrentTokenUserId(client) {
|
|
60
|
-
const result = await client.service("accounts").get("me");
|
|
61
|
-
const user_id = normalizeText(result.user?.user_id);
|
|
62
|
-
if (!user_id) {
|
|
63
|
-
throw new Error("City user token resolved without a user_id. Please run `town city login` again.");
|
|
64
|
-
}
|
|
65
|
-
return user_id;
|
|
66
|
-
}
|
|
67
|
-
function assertSessionUserMatchesToken(session, token_user_id) {
|
|
68
|
-
const session_user_id = normalizeText(session?.user_id);
|
|
69
|
-
if (session_user_id && session_user_id !== token_user_id) {
|
|
70
|
-
throw new Error([
|
|
71
|
-
"Town City session user does not match the authenticated token.",
|
|
72
|
-
`session=${session_user_id}`,
|
|
73
|
-
`token=${token_user_id}`,
|
|
74
|
-
"Run `town city logout` and then `town city login`.",
|
|
75
|
-
].join(" "));
|
|
76
|
-
}
|
|
77
|
-
}
|
|
78
52
|
function assertBalanceUserMatchesToken(account, token_user_id) {
|
|
53
|
+
if (!token_user_id) {
|
|
54
|
+
throw new Error("City user token resolved without a user_id. Run `town city login` again.");
|
|
55
|
+
}
|
|
79
56
|
if (account.user_id !== token_user_id) {
|
|
80
57
|
throw new Error([
|
|
81
58
|
"Balance account user does not match the authenticated token.",
|
|
@@ -88,10 +65,8 @@ function assertBalanceUserMatchesToken(account, token_user_id) {
|
|
|
88
65
|
/**
|
|
89
66
|
* 输出当前 user 余额。
|
|
90
67
|
*/
|
|
91
|
-
export async function emitCurrentTownCityBalance(
|
|
92
|
-
const account = await readCurrentTownCityBalance(
|
|
93
|
-
if (!account)
|
|
94
|
-
return;
|
|
68
|
+
export async function emitCurrentTownCityBalance() {
|
|
69
|
+
const account = await readCurrentTownCityBalance();
|
|
95
70
|
emitCliBlock({
|
|
96
71
|
tone: "success",
|
|
97
72
|
title: "User balance",
|
|
@@ -128,22 +103,6 @@ export function emitTownCityRechargeResult(result) {
|
|
|
128
103
|
: "Checkout URL was not returned by the payment service.",
|
|
129
104
|
});
|
|
130
105
|
}
|
|
131
|
-
function createCurrentUserCityClient(session) {
|
|
132
|
-
if (!session) {
|
|
133
|
-
emitCliBlock({
|
|
134
|
-
tone: "warning",
|
|
135
|
-
title: "City user not signed in",
|
|
136
|
-
note: "Run `town city login` first, or choose User 登录 in this manager.",
|
|
137
|
-
});
|
|
138
|
-
return null;
|
|
139
|
-
}
|
|
140
|
-
return new City({
|
|
141
|
-
role: "user",
|
|
142
|
-
city_url: session.base_url,
|
|
143
|
-
town_id: session.town_id,
|
|
144
|
-
user_token: session.user_token,
|
|
145
|
-
});
|
|
146
|
-
}
|
|
147
106
|
function normalizePositiveInteger(value, label) {
|
|
148
107
|
const amount = typeof value === "number" ? value : Number(value);
|
|
149
108
|
if (!Number.isInteger(amount) || amount <= 0) {
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CityBalance.js","sourceRoot":"","sources":["../../src/shared/CityBalance.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,SAAS,EAAE,MAAM,oBAAoB,CAAC;AAC/C,OAAO,EAAE,
|
|
1
|
+
{"version":3,"file":"CityBalance.js","sourceRoot":"","sources":["../../src/shared/CityBalance.ts"],"names":[],"mappings":"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"}
|
|
@@ -8,21 +8,14 @@
|
|
|
8
8
|
* - CLI 命令装配统一放在 `src/command/CityCommand.ts`,本模块只保留状态与登录流程。
|
|
9
9
|
*/
|
|
10
10
|
import type { TownCityConnectionState } from "../types/TownCityConnection.js";
|
|
11
|
-
import type { TownCityUserSession } from "../types/TownCitySession.js";
|
|
12
|
-
export declare const DEFAULT_CITY_URL = "https://base.downcity.ai";
|
|
13
|
-
export declare const DEFAULT_TOWN_ID = "town_downcity";
|
|
14
|
-
export declare function normalizeCityUrl(value: string): string;
|
|
15
11
|
export declare function readTownCityAdminSecretForBase(city_url: string): string | undefined;
|
|
16
|
-
export declare function readCurrentTownCitySession(): TownCityUserSession | null;
|
|
17
|
-
export declare function readTownCityUserSessionForRuntime(): {
|
|
18
|
-
city_url: string;
|
|
19
|
-
town_id: string;
|
|
20
|
-
user_token: string;
|
|
21
|
-
} | null;
|
|
22
12
|
export declare function readTownCityConnectionState(): TownCityConnectionState;
|
|
23
13
|
export declare function emitCityConnectionStatus(options?: {
|
|
24
14
|
as_json?: boolean;
|
|
25
15
|
}): void;
|
|
16
|
+
export declare function emitCityUserWhoami(options?: {
|
|
17
|
+
as_json?: boolean;
|
|
18
|
+
}): Promise<void>;
|
|
26
19
|
export declare function emitCityServerList(options?: {
|
|
27
20
|
as_json?: boolean;
|
|
28
21
|
}): void;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"CityConnection.d.ts","sourceRoot":"","sources":["../../src/shared/CityConnection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;
|
|
1
|
+
{"version":3,"file":"CityConnection.d.ts","sourceRoot":"","sources":["../../src/shared/CityConnection.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAYH,OAAO,KAAK,EACV,uBAAuB,EAExB,MAAM,gCAAgC,CAAC;AAqBxC,wBAAgB,8BAA8B,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,SAAS,CAEnF;AAcD,wBAAgB,2BAA2B,IAAI,uBAAuB,CA0BrE;AAED,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA8B9E;AAED,wBAAsB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,OAAO,CAAC,IAAI,CAAC,CAuDvF;AAED,wBAAgB,kBAAkB,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CA+BxE;AAED,wBAAsB,qBAAqB,CAAC,MAAM,EAAE;IAClD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CA2BhB;AAED,wBAAsB,iBAAiB,CAAC,MAAM,EAAE;IAC9C,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAgChB;AAiBD,wBAAsB,mBAAmB,CAAC,MAAM,EAAE;IAChD,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,OAAO,CAAC,EAAE,OAAO,CAAC;CACnB,GAAG,OAAO,CAAC,IAAI,CAAC,CAoChB;AAED,wBAAgB,oBAAoB,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAiB1E;AAED,wBAAgB,wBAAwB,CAAC,OAAO,CAAC,EAAE;IAAE,OAAO,CAAC,EAAE,OAAO,CAAA;CAAE,GAAG,IAAI,CAqB9E;AAkED,wBAAsB,yBAAyB,IAAI,OAAO,CAAC,IAAI,CAAC,CAsD/D"}
|
|
@@ -7,230 +7,20 @@
|
|
|
7
7
|
* - Town 可以只读发现 `city` CLI 已配置的 base 地址,但不依赖 city 内部模块。
|
|
8
8
|
* - CLI 命令装配统一放在 `src/command/CityCommand.ts`,本模块只保留状态与登录流程。
|
|
9
9
|
*/
|
|
10
|
-
import fs from "node:fs";
|
|
11
|
-
import os from "node:os";
|
|
12
|
-
import path from "node:path";
|
|
13
10
|
import prompts from "prompts";
|
|
14
|
-
import { PlatformStore } from "../town/store/index.js";
|
|
15
11
|
import { emitCliBlock, emitCliList } from "./CliReporter.js";
|
|
16
12
|
import { printResult } from "../utils/cli/CliOutput.js";
|
|
17
13
|
import { performTownCityUserLogin } from "./CityUserLogin.js";
|
|
18
14
|
import { emitCurrentTownCityBalance, emitTownCityRechargeResult, rechargeCurrentTownCityUser, } from "./CityBalance.js";
|
|
19
|
-
|
|
20
|
-
|
|
21
|
-
const
|
|
22
|
-
const TOWN_CITY_STATE_KEY = "town.city.state";
|
|
15
|
+
import { CityUserManager } from "./CityUserManager.js";
|
|
16
|
+
import { DEFAULT_CITY_URL, DEFAULT_TOWN_ID, listTownCityServers, normalizeCityUrl, readCityAdminSecretForUrl, readCityString, readCurrentTownCitySession, readTownCityState, resolveSelectedBaseUrl, upsertTownProfile, writeTownCityState, } from "./CityStateStore.js";
|
|
17
|
+
const cityUserManager = new CityUserManager();
|
|
23
18
|
function readString(value) {
|
|
24
|
-
return
|
|
25
|
-
}
|
|
26
|
-
function defaultProtocol(value) {
|
|
27
|
-
const host = value.split("/")[0] ?? "";
|
|
28
|
-
const clean_host = host.split(":")[0] ?? "";
|
|
29
|
-
if (clean_host === "localhost" ||
|
|
30
|
-
clean_host.includes(":") ||
|
|
31
|
-
clean_host.split(".").length === 4) {
|
|
32
|
-
return "http";
|
|
33
|
-
}
|
|
34
|
-
return "https";
|
|
35
|
-
}
|
|
36
|
-
export function normalizeCityUrl(value) {
|
|
37
|
-
const raw = String(value || "").trim();
|
|
38
|
-
if (!raw)
|
|
39
|
-
return "";
|
|
40
|
-
const has_protocol = /^[a-z][a-z\d+.-]*:\/\//iu.test(raw);
|
|
41
|
-
const with_protocol = has_protocol ? raw : `${defaultProtocol(raw)}://${raw}`;
|
|
42
|
-
const url = new URL(with_protocol);
|
|
43
|
-
if (!url.port &&
|
|
44
|
-
(url.hostname === "localhost" || /^\d+\.\d+\.\d+\.\d+$/u.test(url.hostname))) {
|
|
45
|
-
url.port = "43127";
|
|
46
|
-
}
|
|
47
|
-
return url.toString().replace(/\/+$/, "");
|
|
48
|
-
}
|
|
49
|
-
function deriveServerName(city_url) {
|
|
50
|
-
try {
|
|
51
|
-
return new URL(city_url).hostname || city_url;
|
|
52
|
-
}
|
|
53
|
-
catch {
|
|
54
|
-
return city_url;
|
|
55
|
-
}
|
|
56
|
-
}
|
|
57
|
-
function readJsonFile(file_path) {
|
|
58
|
-
try {
|
|
59
|
-
return JSON.parse(fs.readFileSync(file_path, "utf8"));
|
|
60
|
-
}
|
|
61
|
-
catch {
|
|
62
|
-
return null;
|
|
63
|
-
}
|
|
64
|
-
}
|
|
65
|
-
function normalizeLocalState(value) {
|
|
66
|
-
const selected_base_url = normalizeCityUrl(readString(value?.selected_base_url));
|
|
67
|
-
const profiles = [];
|
|
68
|
-
for (const item of Array.isArray(value?.profiles) ? value.profiles : []) {
|
|
69
|
-
const base_url = normalizeCityUrl(readString(item.base_url));
|
|
70
|
-
if (!base_url || profiles.some((profile) => profile.base_url === base_url))
|
|
71
|
-
continue;
|
|
72
|
-
profiles.push({
|
|
73
|
-
name: readString(item.name) || deriveServerName(base_url),
|
|
74
|
-
base_url,
|
|
75
|
-
});
|
|
76
|
-
}
|
|
77
|
-
const sessions = {};
|
|
78
|
-
const input_sessions = value?.sessions && typeof value.sessions === "object"
|
|
79
|
-
? value.sessions
|
|
80
|
-
: {};
|
|
81
|
-
for (const [key, session] of Object.entries(input_sessions)) {
|
|
82
|
-
const base_url = normalizeCityUrl(readString(session?.base_url) || key);
|
|
83
|
-
const user_token = readString(session?.user_token);
|
|
84
|
-
if (!base_url || !user_token)
|
|
85
|
-
continue;
|
|
86
|
-
sessions[base_url] = {
|
|
87
|
-
base_url,
|
|
88
|
-
town_id: readString(session?.town_id) || DEFAULT_TOWN_ID,
|
|
89
|
-
user_id: readString(session?.user_id) || undefined,
|
|
90
|
-
user_label: readString(session?.user_label) || undefined,
|
|
91
|
-
user_token,
|
|
92
|
-
updated_at: readString(session?.updated_at) || new Date().toISOString(),
|
|
93
|
-
};
|
|
94
|
-
}
|
|
95
|
-
return {
|
|
96
|
-
selected_base_url: selected_base_url || undefined,
|
|
97
|
-
profiles,
|
|
98
|
-
sessions,
|
|
99
|
-
};
|
|
100
|
-
}
|
|
101
|
-
function readTownCityState() {
|
|
102
|
-
const store = new PlatformStore();
|
|
103
|
-
try {
|
|
104
|
-
return normalizeLocalState(store.getSecureSettingJsonSync(TOWN_CITY_STATE_KEY));
|
|
105
|
-
}
|
|
106
|
-
finally {
|
|
107
|
-
store.close();
|
|
108
|
-
}
|
|
109
|
-
}
|
|
110
|
-
function writeTownCityState(state) {
|
|
111
|
-
const store = new PlatformStore();
|
|
112
|
-
try {
|
|
113
|
-
store.setSecureSettingJsonSync(TOWN_CITY_STATE_KEY, normalizeLocalState(state));
|
|
114
|
-
}
|
|
115
|
-
finally {
|
|
116
|
-
store.close();
|
|
117
|
-
}
|
|
118
|
-
}
|
|
119
|
-
function readCityAdminConfig() {
|
|
120
|
-
return readJsonFile(CITY_CONFIG_PATH) ?? {};
|
|
121
|
-
}
|
|
122
|
-
function readCityAdminServers() {
|
|
123
|
-
const raw = readCityAdminConfig();
|
|
124
|
-
const servers = Array.isArray(raw.servers) ? raw.servers : [];
|
|
125
|
-
const active_url = normalizeCityUrl(readString(raw.active_server_url));
|
|
126
|
-
const out = [];
|
|
127
|
-
const state = readTownCityState();
|
|
128
|
-
const selected_base_url = resolveSelectedBaseUrl(state);
|
|
129
|
-
for (const item of servers) {
|
|
130
|
-
const base_url = normalizeCityUrl(readString(item.base_url) || readString(item.url));
|
|
131
|
-
if (!base_url || out.some((server) => server.base_url === base_url))
|
|
132
|
-
continue;
|
|
133
|
-
const session = state.sessions?.[base_url];
|
|
134
|
-
out.push({
|
|
135
|
-
name: readString(item.name) || deriveServerName(base_url),
|
|
136
|
-
base_url,
|
|
137
|
-
selected: base_url === selected_base_url,
|
|
138
|
-
source: "city-admin",
|
|
139
|
-
has_admin_secret_key: Boolean(readString(item.admin_secret_key)),
|
|
140
|
-
has_user_session: Boolean(session?.user_token),
|
|
141
|
-
town_id: session?.town_id,
|
|
142
|
-
user_id: session?.user_id,
|
|
143
|
-
});
|
|
144
|
-
}
|
|
145
|
-
return out.sort((left, right) => Number(right.base_url === active_url) - Number(left.base_url === active_url)
|
|
146
|
-
|| left.name.localeCompare(right.name)
|
|
147
|
-
|| left.base_url.localeCompare(right.base_url));
|
|
148
|
-
}
|
|
149
|
-
function readCityAdminSecretForUrl(city_url) {
|
|
150
|
-
const target_url = normalizeCityUrl(city_url);
|
|
151
|
-
const raw = readCityAdminConfig();
|
|
152
|
-
const servers = Array.isArray(raw.servers) ? raw.servers : [];
|
|
153
|
-
const matched = servers.find((item) => normalizeCityUrl(readString(item.base_url) || readString(item.url)) === target_url);
|
|
154
|
-
return readString(matched?.admin_secret_key) || undefined;
|
|
19
|
+
return readCityString(value);
|
|
155
20
|
}
|
|
156
21
|
export function readTownCityAdminSecretForBase(city_url) {
|
|
157
22
|
return readCityAdminSecretForUrl(city_url);
|
|
158
23
|
}
|
|
159
|
-
function resolveSelectedBaseUrl(state = readTownCityState()) {
|
|
160
|
-
return normalizeCityUrl(readString(state.selected_base_url)) || DEFAULT_CITY_URL;
|
|
161
|
-
}
|
|
162
|
-
function upsertTownProfile(state, input) {
|
|
163
|
-
const base_url = normalizeCityUrl(input.base_url);
|
|
164
|
-
if (!base_url)
|
|
165
|
-
return state;
|
|
166
|
-
const profiles = [...(state.profiles ?? [])];
|
|
167
|
-
const index = profiles.findIndex((item) => item.base_url === base_url);
|
|
168
|
-
const profile = {
|
|
169
|
-
name: readString(input.name) || deriveServerName(base_url),
|
|
170
|
-
base_url,
|
|
171
|
-
};
|
|
172
|
-
if (index >= 0)
|
|
173
|
-
profiles[index] = profile;
|
|
174
|
-
else
|
|
175
|
-
profiles.push(profile);
|
|
176
|
-
return {
|
|
177
|
-
...state,
|
|
178
|
-
selected_base_url: base_url,
|
|
179
|
-
profiles,
|
|
180
|
-
};
|
|
181
|
-
}
|
|
182
|
-
function listTownCityServers() {
|
|
183
|
-
const state = readTownCityState();
|
|
184
|
-
const selected_base_url = resolveSelectedBaseUrl(state);
|
|
185
|
-
const admin_servers = readCityAdminServers();
|
|
186
|
-
const by_url = new Map();
|
|
187
|
-
const append = (profile) => {
|
|
188
|
-
const existing = by_url.get(profile.base_url);
|
|
189
|
-
if (!existing) {
|
|
190
|
-
by_url.set(profile.base_url, profile);
|
|
191
|
-
return;
|
|
192
|
-
}
|
|
193
|
-
by_url.set(profile.base_url, {
|
|
194
|
-
...existing,
|
|
195
|
-
selected: existing.selected || profile.selected,
|
|
196
|
-
source: existing.source === "town" ? "town" : profile.source,
|
|
197
|
-
has_admin_secret_key: existing.has_admin_secret_key || profile.has_admin_secret_key,
|
|
198
|
-
has_user_session: existing.has_user_session || profile.has_user_session,
|
|
199
|
-
town_id: existing.town_id || profile.town_id,
|
|
200
|
-
user_id: existing.user_id || profile.user_id,
|
|
201
|
-
});
|
|
202
|
-
};
|
|
203
|
-
for (const profile of state.profiles ?? []) {
|
|
204
|
-
const session = state.sessions?.[profile.base_url];
|
|
205
|
-
append({
|
|
206
|
-
name: profile.name,
|
|
207
|
-
base_url: profile.base_url,
|
|
208
|
-
selected: profile.base_url === selected_base_url,
|
|
209
|
-
source: "town",
|
|
210
|
-
has_admin_secret_key: Boolean(readCityAdminSecretForUrl(profile.base_url)),
|
|
211
|
-
has_user_session: Boolean(session?.user_token),
|
|
212
|
-
town_id: session?.town_id,
|
|
213
|
-
user_id: session?.user_id,
|
|
214
|
-
});
|
|
215
|
-
}
|
|
216
|
-
for (const server of admin_servers)
|
|
217
|
-
append(server);
|
|
218
|
-
const default_session = state.sessions?.[DEFAULT_CITY_URL];
|
|
219
|
-
append({
|
|
220
|
-
name: "Downcity Base",
|
|
221
|
-
base_url: DEFAULT_CITY_URL,
|
|
222
|
-
selected: DEFAULT_CITY_URL === selected_base_url,
|
|
223
|
-
source: "default",
|
|
224
|
-
has_admin_secret_key: Boolean(readCityAdminSecretForUrl(DEFAULT_CITY_URL)),
|
|
225
|
-
has_user_session: Boolean(default_session?.user_token),
|
|
226
|
-
town_id: default_session?.town_id,
|
|
227
|
-
user_id: default_session?.user_id,
|
|
228
|
-
});
|
|
229
|
-
return [...by_url.values()].sort((left, right) => Number(right.selected) - Number(left.selected)
|
|
230
|
-
|| Number(right.source === "default") - Number(left.source === "default")
|
|
231
|
-
|| left.name.localeCompare(right.name)
|
|
232
|
-
|| left.base_url.localeCompare(right.base_url));
|
|
233
|
-
}
|
|
234
24
|
function findCityServer(input) {
|
|
235
25
|
const query = String(input || "").trim();
|
|
236
26
|
const servers = listTownCityServers();
|
|
@@ -241,23 +31,6 @@ function findCityServer(input) {
|
|
|
241
31
|
server.base_url === normalized_query_url ||
|
|
242
32
|
server.base_url === query) ?? null;
|
|
243
33
|
}
|
|
244
|
-
export function readCurrentTownCitySession() {
|
|
245
|
-
const state = readTownCityState();
|
|
246
|
-
const base_url = resolveSelectedBaseUrl(state);
|
|
247
|
-
return state.sessions?.[base_url] ?? null;
|
|
248
|
-
}
|
|
249
|
-
export function readTownCityUserSessionForRuntime() {
|
|
250
|
-
const state = readTownCityState();
|
|
251
|
-
const city_url = resolveSelectedBaseUrl(state);
|
|
252
|
-
const session = state.sessions?.[city_url] ?? null;
|
|
253
|
-
if (!session?.user_token)
|
|
254
|
-
return null;
|
|
255
|
-
return {
|
|
256
|
-
city_url,
|
|
257
|
-
town_id: session.town_id || DEFAULT_TOWN_ID,
|
|
258
|
-
user_token: session.user_token,
|
|
259
|
-
};
|
|
260
|
-
}
|
|
261
34
|
export function readTownCityConnectionState() {
|
|
262
35
|
const state = readTownCityState();
|
|
263
36
|
const city_url = resolveSelectedBaseUrl(state);
|
|
@@ -314,6 +87,62 @@ export function emitCityConnectionStatus(options) {
|
|
|
314
87
|
: "Run `town city login` to sign in as a City user.",
|
|
315
88
|
});
|
|
316
89
|
}
|
|
90
|
+
export async function emitCityUserWhoami(options) {
|
|
91
|
+
try {
|
|
92
|
+
const user = await cityUserManager.resolveCurrentUser();
|
|
93
|
+
if (options?.as_json === true) {
|
|
94
|
+
printResult({
|
|
95
|
+
asJson: true,
|
|
96
|
+
success: true,
|
|
97
|
+
title: "city user",
|
|
98
|
+
payload: {
|
|
99
|
+
city_url: user.city_url,
|
|
100
|
+
town_id: user.town_id,
|
|
101
|
+
user_id: user.user_id,
|
|
102
|
+
user_label: user.user_label,
|
|
103
|
+
source: user.source,
|
|
104
|
+
env_overrides: user.env_overrides,
|
|
105
|
+
warnings: user.warnings,
|
|
106
|
+
},
|
|
107
|
+
});
|
|
108
|
+
return;
|
|
109
|
+
}
|
|
110
|
+
emitCliBlock({
|
|
111
|
+
tone: "success",
|
|
112
|
+
title: "City user",
|
|
113
|
+
summary: user.source,
|
|
114
|
+
facts: [
|
|
115
|
+
{ label: "url", value: user.city_url },
|
|
116
|
+
{ label: "town", value: user.town_id },
|
|
117
|
+
{ label: "user", value: user.user_id || "unknown" },
|
|
118
|
+
...(user.user_label ? [{ label: "label", value: user.user_label }] : []),
|
|
119
|
+
{ label: "source", value: user.source },
|
|
120
|
+
{ label: "env url", value: user.env_overrides.city_url ? "yes" : "no" },
|
|
121
|
+
{ label: "env town", value: user.env_overrides.town_id ? "yes" : "no" },
|
|
122
|
+
{ label: "env token", value: user.env_overrides.user_token ? "yes" : "no" },
|
|
123
|
+
],
|
|
124
|
+
note: user.warnings.join(" ") || undefined,
|
|
125
|
+
});
|
|
126
|
+
}
|
|
127
|
+
catch (error) {
|
|
128
|
+
if (options?.as_json === true) {
|
|
129
|
+
printResult({
|
|
130
|
+
asJson: true,
|
|
131
|
+
success: false,
|
|
132
|
+
title: "city user",
|
|
133
|
+
payload: {
|
|
134
|
+
error: error instanceof Error ? error.message : String(error),
|
|
135
|
+
},
|
|
136
|
+
});
|
|
137
|
+
return;
|
|
138
|
+
}
|
|
139
|
+
emitCliBlock({
|
|
140
|
+
tone: "error",
|
|
141
|
+
title: "City user unavailable",
|
|
142
|
+
note: error instanceof Error ? error.message : String(error),
|
|
143
|
+
});
|
|
144
|
+
}
|
|
145
|
+
}
|
|
317
146
|
export function emitCityServerList(options) {
|
|
318
147
|
const servers = listTownCityServers();
|
|
319
148
|
if (options?.as_json === true) {
|
|
@@ -518,6 +347,11 @@ async function promptCityManagerAction() {
|
|
|
518
347
|
description: state.has_user_token ? "重新登录当前 base" : "登录当前 base",
|
|
519
348
|
value: "login",
|
|
520
349
|
},
|
|
350
|
+
{
|
|
351
|
+
title: "查看当前 User",
|
|
352
|
+
description: "显示 Town 当前实际使用的 City user",
|
|
353
|
+
value: "whoami",
|
|
354
|
+
},
|
|
521
355
|
{
|
|
522
356
|
title: "查看 User 余额",
|
|
523
357
|
description: state.has_user_token ? "读取当前登录 user 的余额" : "需要先登录 user",
|
|
@@ -582,14 +416,18 @@ export async function runInteractiveCityManager() {
|
|
|
582
416
|
await runCityLoginCommand({});
|
|
583
417
|
continue;
|
|
584
418
|
}
|
|
419
|
+
if (action === "whoami") {
|
|
420
|
+
await emitCityUserWhoami();
|
|
421
|
+
continue;
|
|
422
|
+
}
|
|
585
423
|
if (action === "balance") {
|
|
586
|
-
await emitCurrentTownCityBalance(
|
|
424
|
+
await emitCurrentTownCityBalance();
|
|
587
425
|
continue;
|
|
588
426
|
}
|
|
589
427
|
if (action === "recharge") {
|
|
590
428
|
const input = await promptRechargeInput();
|
|
591
429
|
if (input) {
|
|
592
|
-
const result = await rechargeCurrentTownCityUser(
|
|
430
|
+
const result = await rechargeCurrentTownCityUser(input);
|
|
593
431
|
if (result)
|
|
594
432
|
emitTownCityRechargeResult(result);
|
|
595
433
|
}
|