@playdrop/playdrop-cli 0.5.2 → 0.5.4

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.
Files changed (118) hide show
  1. package/config/client-meta.json +4 -4
  2. package/dist/apps/build.js +49 -6
  3. package/dist/apps/index.d.ts +2 -0
  4. package/dist/apps/index.js +2 -0
  5. package/dist/apps/upload.d.ts +2 -0
  6. package/dist/apps/upload.js +126 -28
  7. package/dist/assetSpecs.d.ts +16 -0
  8. package/dist/assetSpecs.js +263 -0
  9. package/dist/assets/model-artifacts.js +3 -0
  10. package/dist/catalogue.d.ts +57 -3
  11. package/dist/catalogue.js +342 -16
  12. package/dist/commandContext.d.ts +6 -2
  13. package/dist/commandContext.js +144 -20
  14. package/dist/commands/accounts.d.ts +2 -0
  15. package/dist/commands/accounts.js +48 -0
  16. package/dist/commands/ads.d.ts +8 -0
  17. package/dist/commands/ads.js +124 -0
  18. package/dist/commands/boosts.d.ts +25 -0
  19. package/dist/commands/boosts.js +209 -0
  20. package/dist/commands/browse.d.ts +6 -1
  21. package/dist/commands/browse.js +365 -124
  22. package/dist/commands/capture.js +30 -9
  23. package/dist/commands/captureListing.d.ts +53 -0
  24. package/dist/commands/captureListing.js +815 -0
  25. package/dist/commands/create.d.ts +1 -0
  26. package/dist/commands/create.js +183 -3
  27. package/dist/commands/credits.d.ts +6 -0
  28. package/dist/commands/credits.js +47 -1
  29. package/dist/commands/detail.js +38 -4
  30. package/dist/commands/dev.js +169 -192
  31. package/dist/commands/devServer.d.ts +26 -3
  32. package/dist/commands/devServer.js +415 -72
  33. package/dist/commands/login.js +10 -2
  34. package/dist/commands/logout.d.ts +6 -1
  35. package/dist/commands/logout.js +25 -3
  36. package/dist/commands/search.d.ts +5 -0
  37. package/dist/commands/search.js +139 -17
  38. package/dist/commands/tags.d.ts +7 -0
  39. package/dist/commands/tags.js +63 -0
  40. package/dist/commands/upload-content.d.ts +13 -3
  41. package/dist/commands/upload-content.js +86 -20
  42. package/dist/commands/upload.d.ts +2 -0
  43. package/dist/commands/upload.js +187 -11
  44. package/dist/commands/validate.js +163 -2
  45. package/dist/commands/versionsBrowse.js +128 -91
  46. package/dist/commands/whoami.js +10 -2
  47. package/dist/config.d.ts +37 -0
  48. package/dist/config.js +205 -3
  49. package/dist/index.js +177 -5
  50. package/dist/refs.d.ts +2 -2
  51. package/dist/refs.js +13 -1
  52. package/dist/taskSelection.js +6 -3
  53. package/dist/taskUtils.d.ts +2 -2
  54. package/dist/taskUtils.js +1 -0
  55. package/dist/uploadLog.d.ts +1 -1
  56. package/dist/uploadLog.js +2 -2
  57. package/dist/workspaceAuth.d.ts +14 -0
  58. package/dist/workspaceAuth.js +75 -0
  59. package/node_modules/@playdrop/ai-client/package.json +1 -1
  60. package/node_modules/@playdrop/api-client/dist/client.d.ts +139 -10
  61. package/node_modules/@playdrop/api-client/dist/client.d.ts.map +1 -1
  62. package/node_modules/@playdrop/api-client/dist/client.js +6 -0
  63. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts +9 -1
  64. package/node_modules/@playdrop/api-client/dist/domains/admin.d.ts.map +1 -1
  65. package/node_modules/@playdrop/api-client/dist/domains/admin.js +45 -0
  66. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts +7 -1
  67. package/node_modules/@playdrop/api-client/dist/domains/apps.d.ts.map +1 -1
  68. package/node_modules/@playdrop/api-client/dist/domains/apps.js +58 -0
  69. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts +2 -0
  70. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.d.ts.map +1 -1
  71. package/node_modules/@playdrop/api-client/dist/domains/asset-packs.js +16 -0
  72. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts +44 -2
  73. package/node_modules/@playdrop/api-client/dist/domains/assets.d.ts.map +1 -1
  74. package/node_modules/@playdrop/api-client/dist/domains/assets.js +260 -3
  75. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts +22 -1
  76. package/node_modules/@playdrop/api-client/dist/domains/payments.d.ts.map +1 -1
  77. package/node_modules/@playdrop/api-client/dist/domains/payments.js +228 -0
  78. package/node_modules/@playdrop/api-client/dist/domains/search.d.ts.map +1 -1
  79. package/node_modules/@playdrop/api-client/dist/domains/search.js +39 -11
  80. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts +34 -0
  81. package/node_modules/@playdrop/api-client/dist/domains/tags.d.ts.map +1 -0
  82. package/node_modules/@playdrop/api-client/dist/domains/tags.js +111 -0
  83. package/node_modules/@playdrop/api-client/dist/index.d.ts +69 -1
  84. package/node_modules/@playdrop/api-client/dist/index.d.ts.map +1 -1
  85. package/node_modules/@playdrop/api-client/dist/index.js +74 -0
  86. package/node_modules/@playdrop/api-client/package.json +1 -1
  87. package/node_modules/@playdrop/boxel-core/package.json +1 -1
  88. package/node_modules/@playdrop/boxel-three/package.json +1 -1
  89. package/node_modules/@playdrop/config/client-meta.json +4 -4
  90. package/node_modules/@playdrop/config/dist/src/constants.d.ts +11 -0
  91. package/node_modules/@playdrop/config/dist/src/constants.d.ts.map +1 -1
  92. package/node_modules/@playdrop/config/dist/src/constants.js +12 -1
  93. package/node_modules/@playdrop/config/dist/tsconfig.tsbuildinfo +1 -1
  94. package/node_modules/@playdrop/config/package.json +1 -1
  95. package/node_modules/@playdrop/types/dist/api.d.ts +366 -6
  96. package/node_modules/@playdrop/types/dist/api.d.ts.map +1 -1
  97. package/node_modules/@playdrop/types/dist/api.js +52 -1
  98. package/node_modules/@playdrop/types/dist/asset-pack.d.ts +7 -1
  99. package/node_modules/@playdrop/types/dist/asset-pack.d.ts.map +1 -1
  100. package/node_modules/@playdrop/types/dist/asset-spec-contract-meta-schema.json +86 -0
  101. package/node_modules/@playdrop/types/dist/asset-spec.d.ts +163 -0
  102. package/node_modules/@playdrop/types/dist/asset-spec.d.ts.map +1 -0
  103. package/node_modules/@playdrop/types/dist/asset-spec.js +101 -0
  104. package/node_modules/@playdrop/types/dist/asset.d.ts +23 -6
  105. package/node_modules/@playdrop/types/dist/asset.d.ts.map +1 -1
  106. package/node_modules/@playdrop/types/dist/asset.js +4 -1
  107. package/node_modules/@playdrop/types/dist/graph.d.ts +4 -2
  108. package/node_modules/@playdrop/types/dist/graph.d.ts.map +1 -1
  109. package/node_modules/@playdrop/types/dist/graph.js +9 -2
  110. package/node_modules/@playdrop/types/dist/index.d.ts +1 -0
  111. package/node_modules/@playdrop/types/dist/index.d.ts.map +1 -1
  112. package/node_modules/@playdrop/types/dist/index.js +1 -0
  113. package/node_modules/@playdrop/types/dist/version.d.ts +13 -0
  114. package/node_modules/@playdrop/types/dist/version.d.ts.map +1 -1
  115. package/node_modules/@playdrop/types/dist/version.js +21 -0
  116. package/node_modules/@playdrop/types/package.json +6 -1
  117. package/node_modules/@playdrop/vox-three/package.json +1 -1
  118. package/package.json +3 -1
@@ -57,18 +57,144 @@ function printEntry(entry) {
57
57
  }
58
58
  return;
59
59
  }
60
+ if (entry.kind === 'asset-spec') {
61
+ const currentLabel = entry.isCurrent ? ' | current' : '';
62
+ console.log(`${entry.version} | ${entry.visibility.toLowerCase()} | ${entry.status.toLowerCase()}${currentLabel} | ${(0, output_1.formatTimestamp)(entry.createdAt)}`);
63
+ console.log(` Documentation URL: ${(0, output_1.formatOptionalValue)(entry.documentationUrl)}`);
64
+ return;
65
+ }
60
66
  const currentLabel = entry.isCurrent ? ' | current' : '';
61
67
  console.log(`${entry.version} | ${entry.visibility.toLowerCase()}${currentLabel} | ${(0, output_1.formatTimestamp)(entry.createdAt)}`);
62
68
  console.log(` Download URL: ${(0, output_1.formatOptionalValue)(entry.urls.download)}`);
63
69
  console.log(` Source URL: ${entry.urls.source}`);
64
70
  }
71
+ async function loadVersionEntries(input) {
72
+ const { client, envConfig, ref, limit, offset } = input;
73
+ if (ref.kind === 'app') {
74
+ const [appDetail, response] = await Promise.all([
75
+ client.fetchAppBySlug(ref.creator, ref.name),
76
+ client.listAppVersions(ref.creator, ref.name),
77
+ ]);
78
+ const listed = response.versions ?? [];
79
+ const selected = listed.slice(offset, offset + limit);
80
+ const entries = await Promise.all(selected.map(async (version) => {
81
+ const detail = await client.getAppVersion(ref.creator, ref.name, version.version);
82
+ return {
83
+ kind: 'app',
84
+ version: version.version,
85
+ visibility: version.visibility,
86
+ createdAt: version.createdAt,
87
+ updatedAt: detail.version.updatedAt,
88
+ releaseNotes: version.releaseNotes ?? null,
89
+ isCurrent: Boolean(version.isCurrent),
90
+ urls: {
91
+ play: (0, appUrls_1.buildPlatformPlayUrl)(envConfig.webBase, {
92
+ creatorUsername: ref.creator,
93
+ appName: ref.name,
94
+ appType: appDetail.app.type,
95
+ }),
96
+ source: typeof detail.version.sourceUrl === 'string' ? detail.version.sourceUrl : null,
97
+ },
98
+ };
99
+ }));
100
+ return {
101
+ entries,
102
+ pagination: {
103
+ limit,
104
+ offset,
105
+ count: entries.length,
106
+ hasMore: listed.length > offset + limit,
107
+ },
108
+ };
109
+ }
110
+ if (ref.kind === 'asset') {
111
+ const [detail, response] = await Promise.all([
112
+ client.fetchAssetBySlug(ref.creator, ref.name),
113
+ client.listAssetVersions(ref.creator, ref.name, { limit, offset }),
114
+ ]);
115
+ const currentRevision = detail.asset.currentVersion?.revision ?? null;
116
+ return {
117
+ entries: (response.versions ?? []).map((version) => ({
118
+ kind: 'asset',
119
+ revision: version.revision,
120
+ revisionLabel: version.revisionLabel,
121
+ visibility: version.visibility,
122
+ createdAt: version.createdAt,
123
+ updatedAt: version.updatedAt,
124
+ subcategory: version.subcategory,
125
+ format: version.format,
126
+ isCurrent: currentRevision === version.revision,
127
+ urls: {
128
+ source: buildAssetSourceUrl(envConfig.apiBase, ref, version.revision),
129
+ files: assetFileUrls(version),
130
+ },
131
+ })),
132
+ pagination: response.pagination ?? {
133
+ limit,
134
+ offset,
135
+ count: (response.versions ?? []).length,
136
+ hasMore: false,
137
+ },
138
+ };
139
+ }
140
+ if (ref.kind === 'asset-pack') {
141
+ const [detail, response] = await Promise.all([
142
+ client.fetchAssetPackBySlug(ref.creator, ref.name),
143
+ client.listAssetPackVersions(ref.creator, ref.name, { limit, offset }),
144
+ ]);
145
+ const currentVersion = detail.pack.currentVersion?.version ?? null;
146
+ return {
147
+ entries: (response.versions ?? []).map((version) => ({
148
+ kind: 'asset-pack',
149
+ version: version.version,
150
+ visibility: version.visibility,
151
+ createdAt: version.createdAt,
152
+ updatedAt: version.updatedAt,
153
+ isCurrent: currentVersion === version.version,
154
+ urls: {
155
+ download: buildAssetPackDownloadUrl(envConfig.apiBase, ref, version.version),
156
+ source: buildAssetPackSourceUrl(envConfig.apiBase, ref, version.version),
157
+ },
158
+ })),
159
+ pagination: response.pagination ?? {
160
+ limit,
161
+ offset,
162
+ count: (response.versions ?? []).length,
163
+ hasMore: false,
164
+ },
165
+ };
166
+ }
167
+ const [detail, response] = await Promise.all([
168
+ client.fetchAssetSpecBySlug(ref.creator, ref.name),
169
+ client.listAssetSpecVersions(ref.creator, ref.name, { limit, offset }),
170
+ ]);
171
+ const currentVersion = detail.assetSpec.currentVersion?.version ?? null;
172
+ return {
173
+ entries: (response.versions ?? []).map((version) => ({
174
+ kind: 'asset-spec',
175
+ version: version.version,
176
+ visibility: version.visibility,
177
+ status: version.status,
178
+ createdAt: version.createdAt,
179
+ updatedAt: version.updatedAt,
180
+ isCurrent: currentVersion === version.version,
181
+ documentationUrl: version.documentationUrl ?? null,
182
+ })),
183
+ pagination: response.pagination ?? {
184
+ limit,
185
+ offset,
186
+ count: (response.versions ?? []).length,
187
+ hasMore: false,
188
+ },
189
+ };
190
+ }
65
191
  async function browseVersions(rawRef, options = {}) {
66
192
  let ref;
67
193
  try {
68
194
  ref = (0, refs_1.parseContentRef)(rawRef ?? '');
69
195
  }
70
196
  catch {
71
- (0, messages_1.printErrorWithHelp)('A canonical ref is required.', ['Use the format <creator>/<kind>/<name>.', 'Example: playdrop versions browse playdrop/app/hangingout'], { command: 'versions browse' });
197
+ (0, messages_1.printErrorWithHelp)('A canonical ref is required.', ['Use the format <creator>/<kind>/<name>.', 'Example: playdrop versions browse playdrop/app/hangingout', 'Example: playdrop versions browse playdrop/asset-spec/platformer-level'], { command: 'versions browse' });
72
198
  process.exitCode = 1;
73
199
  return;
74
200
  }
@@ -86,96 +212,7 @@ async function browseVersions(rawRef, options = {}) {
86
212
  }
87
213
  await (0, commandContext_1.withPublicEnvironment)('versions browse', async ({ client, envConfig }) => {
88
214
  try {
89
- let entries = [];
90
- let pagination;
91
- if (ref.kind === 'app') {
92
- const [appDetail, response] = await Promise.all([
93
- client.fetchAppBySlug(ref.creator, ref.name),
94
- client.listAppVersions(ref.creator, ref.name),
95
- ]);
96
- const listed = response.versions ?? [];
97
- const selected = listed.slice(offset, offset + limit);
98
- const details = await Promise.all(selected.map(async (version) => {
99
- const detail = await client.getAppVersion(ref.creator, ref.name, version.version);
100
- return {
101
- kind: 'app',
102
- version: version.version,
103
- visibility: version.visibility,
104
- createdAt: version.createdAt,
105
- updatedAt: detail.version.updatedAt,
106
- releaseNotes: version.releaseNotes ?? null,
107
- isCurrent: Boolean(version.isCurrent),
108
- urls: {
109
- play: (0, appUrls_1.buildPlatformPlayUrl)(envConfig.webBase, {
110
- creatorUsername: ref.creator,
111
- appName: ref.name,
112
- appType: appDetail.app.type,
113
- }),
114
- source: typeof detail.version.sourceUrl === 'string' ? detail.version.sourceUrl : null,
115
- },
116
- };
117
- }));
118
- entries = details;
119
- pagination = {
120
- limit,
121
- offset,
122
- count: entries.length,
123
- hasMore: listed.length > offset + limit,
124
- };
125
- }
126
- else if (ref.kind === 'asset') {
127
- const [detail, response] = await Promise.all([
128
- client.fetchAssetBySlug(ref.creator, ref.name),
129
- client.listAssetVersions(ref.creator, ref.name, { limit, offset }),
130
- ]);
131
- const currentRevision = detail.asset.currentVersion?.revision ?? null;
132
- entries = (response.versions ?? []).map((version) => ({
133
- kind: 'asset',
134
- revision: version.revision,
135
- revisionLabel: version.revisionLabel,
136
- visibility: version.visibility,
137
- createdAt: version.createdAt,
138
- updatedAt: version.updatedAt,
139
- subcategory: version.subcategory,
140
- format: version.format,
141
- isCurrent: currentRevision === version.revision,
142
- urls: {
143
- source: buildAssetSourceUrl(envConfig.apiBase, ref, version.revision),
144
- files: assetFileUrls(version),
145
- },
146
- }));
147
- pagination = response.pagination ?? {
148
- limit,
149
- offset,
150
- count: entries.length,
151
- hasMore: false,
152
- };
153
- }
154
- else {
155
- const [detail, response] = await Promise.all([
156
- client.fetchAssetPackBySlug(ref.creator, ref.name),
157
- client.listAssetPackVersions(ref.creator, ref.name, { limit, offset }),
158
- ]);
159
- const currentVersion = detail.pack.currentVersion?.version ?? null;
160
- entries = (response.versions ?? []).map((version) => ({
161
- kind: 'asset-pack',
162
- version: version.version,
163
- visibility: version.visibility,
164
- createdAt: version.createdAt,
165
- updatedAt: version.updatedAt,
166
- isCurrent: currentVersion === version.version,
167
- urls: {
168
- download: buildAssetPackDownloadUrl(envConfig.apiBase, ref, version.version),
169
- source: buildAssetPackSourceUrl(envConfig.apiBase, ref, version.version),
170
- },
171
- }));
172
- pagination = response.pagination ?? {
173
- limit,
174
- offset,
175
- count: entries.length,
176
- hasMore: false,
177
- };
178
- }
215
+ const { entries, pagination } = await loadVersionEntries({ client, envConfig, ref, limit, offset });
179
216
  if (options.json) {
180
217
  (0, output_1.printJson)({ kind: ref.kind, ref: ref.ref, entries, pagination });
181
218
  return;
@@ -9,6 +9,7 @@ const environment_1 = require("../environment");
9
9
  const messages_1 = require("../messages");
10
10
  async function whoami() {
11
11
  const cfg = (0, config_1.loadConfig)();
12
+ const currentAccount = (0, config_1.getCurrentAccountSession)(cfg);
12
13
  if (!cfg.token || !cfg.env) {
13
14
  (0, messages_1.printLoginRequired)('Checking your Playdrop account status', 'whoami');
14
15
  process.exitCode = 1;
@@ -59,7 +60,7 @@ async function whoami() {
59
60
  }
60
61
  if (!data)
61
62
  return;
62
- const username = data.user?.username;
63
+ const username = data.user?.username?.trim();
63
64
  if (!username) {
64
65
  (0, messages_1.printErrorWithHelp)('The Playdrop API returned an unexpected response.', [
65
66
  'Retry "playdrop auth whoami" in a moment.',
@@ -68,6 +69,13 @@ async function whoami() {
68
69
  process.exitCode = 1;
69
70
  return;
70
71
  }
71
- console.log(`${username} (${cfg.env})`);
72
+ if (!currentAccount) {
73
+ (0, config_1.migrateLegacySession)({
74
+ username,
75
+ env: envConfig.name,
76
+ token: cfg.token,
77
+ });
78
+ }
79
+ console.log(`${username} (${envConfig.name})`);
72
80
  console.log('Next: run "playdrop getting-started" to see the recommended workflow.');
73
81
  }
package/dist/config.d.ts CHANGED
@@ -1,6 +1,20 @@
1
+ export interface CliAccountRef {
2
+ username: string;
3
+ env: string;
4
+ }
5
+ export interface CliStoredSession {
6
+ token: string;
7
+ updatedAt: string;
8
+ }
9
+ export interface CliAccountSession extends CliAccountRef, CliStoredSession {
10
+ }
1
11
  export interface CliConfig {
12
+ version?: number;
13
+ currentAccount?: CliAccountRef | null;
14
+ accounts?: Record<string, Record<string, CliStoredSession>>;
2
15
  token?: string;
3
16
  env?: string;
17
+ currentUsername?: string;
4
18
  }
5
19
  export declare function loadConfig(): CliConfig;
6
20
  export declare function saveConfig(cfg: CliConfig): void;
@@ -8,3 +22,26 @@ export declare function clearConfig(): void;
8
22
  export declare function getConfig(): {
9
23
  path: string;
10
24
  };
25
+ export declare function listAccountSessions(cfg?: CliConfig): CliAccountSession[];
26
+ export declare function getCurrentAccountSession(cfg?: CliConfig): CliAccountSession | null;
27
+ export declare function findAccountSession(username: string, env?: string, cfg?: CliConfig): CliAccountSession | null;
28
+ export declare function listAccountSessionsForUsername(username: string, cfg?: CliConfig): CliAccountSession[];
29
+ export declare function saveAccountSession(input: {
30
+ username: string;
31
+ env: string;
32
+ token: string;
33
+ updatedAt?: string;
34
+ }): CliConfig;
35
+ export declare function migrateLegacySession(input: {
36
+ username: string;
37
+ env: string;
38
+ token: string;
39
+ }): CliConfig;
40
+ export declare function setCurrentAccount(input: {
41
+ username: string;
42
+ env?: string;
43
+ }): CliConfig;
44
+ export declare function removeAccountSession(input?: {
45
+ username?: string;
46
+ env?: string;
47
+ }): CliConfig;
package/dist/config.js CHANGED
@@ -7,29 +7,74 @@ exports.loadConfig = loadConfig;
7
7
  exports.saveConfig = saveConfig;
8
8
  exports.clearConfig = clearConfig;
9
9
  exports.getConfig = getConfig;
10
+ exports.listAccountSessions = listAccountSessions;
11
+ exports.getCurrentAccountSession = getCurrentAccountSession;
12
+ exports.findAccountSession = findAccountSession;
13
+ exports.listAccountSessionsForUsername = listAccountSessionsForUsername;
14
+ exports.saveAccountSession = saveAccountSession;
15
+ exports.migrateLegacySession = migrateLegacySession;
16
+ exports.setCurrentAccount = setCurrentAccount;
17
+ exports.removeAccountSession = removeAccountSession;
10
18
  const fs_1 = require("fs");
11
19
  const path_1 = require("path");
12
20
  const os_1 = __importDefault(require("os"));
21
+ const CONFIG_VERSION = 2;
13
22
  function getConfigPath() {
14
23
  const custom = process.env.PLAYDROP_CONFIG_PATH;
15
24
  if (custom)
16
25
  return custom;
17
26
  return (0, path_1.join)(os_1.default.homedir(), '.playdrop', 'config.json');
18
27
  }
19
- function loadConfig() {
28
+ function normalizeKey(value) {
29
+ return value.trim();
30
+ }
31
+ function clonePersistedConfig(cfg) {
32
+ return JSON.parse(JSON.stringify(cfg));
33
+ }
34
+ function buildDerivedConfig(cfg) {
35
+ const next = clonePersistedConfig(cfg);
36
+ const current = getCurrentAccountSession(next);
37
+ if (current) {
38
+ next.currentUsername = current.username;
39
+ next.env = current.env;
40
+ next.token = current.token;
41
+ }
42
+ return next;
43
+ }
44
+ function readRawConfig() {
20
45
  const file = getConfigPath();
21
46
  try {
22
47
  const data = (0, fs_1.readFileSync)(file, 'utf8');
23
- return JSON.parse(data);
48
+ const parsed = JSON.parse(data);
49
+ return parsed && typeof parsed === 'object' ? parsed : {};
24
50
  }
25
51
  catch {
26
52
  return {};
27
53
  }
28
54
  }
55
+ function stripDerivedFields(cfg) {
56
+ const next = {
57
+ version: typeof cfg.version === 'number' ? cfg.version : undefined,
58
+ currentAccount: cfg.currentAccount ?? undefined,
59
+ accounts: cfg.accounts ? clonePersistedConfig({ accounts: cfg.accounts }).accounts : undefined,
60
+ };
61
+ if (!next.accounts || Object.keys(next.accounts).length === 0) {
62
+ if (typeof cfg.token === 'string' && cfg.token.trim().length > 0) {
63
+ next.token = cfg.token.trim();
64
+ }
65
+ if (typeof cfg.env === 'string' && cfg.env.trim().length > 0) {
66
+ next.env = cfg.env.trim();
67
+ }
68
+ }
69
+ return next;
70
+ }
71
+ function loadConfig() {
72
+ return buildDerivedConfig(readRawConfig());
73
+ }
29
74
  function saveConfig(cfg) {
30
75
  const file = getConfigPath();
31
76
  (0, fs_1.mkdirSync)((0, path_1.dirname)(file), { recursive: true });
32
- (0, fs_1.writeFileSync)(file, JSON.stringify(cfg, null, 2));
77
+ (0, fs_1.writeFileSync)(file, JSON.stringify(stripDerivedFields(cfg), null, 2));
33
78
  }
34
79
  function clearConfig() {
35
80
  const file = getConfigPath();
@@ -39,3 +84,160 @@ function clearConfig() {
39
84
  function getConfig() {
40
85
  return { path: getConfigPath() };
41
86
  }
87
+ function listAccountSessions(cfg = loadConfig()) {
88
+ const accounts = cfg.accounts ?? {};
89
+ const sessions = [];
90
+ for (const [username, envMap] of Object.entries(accounts)) {
91
+ for (const [env, session] of Object.entries(envMap ?? {})) {
92
+ if (!session || typeof session.token !== 'string' || session.token.trim().length === 0) {
93
+ continue;
94
+ }
95
+ sessions.push({
96
+ username,
97
+ env,
98
+ token: session.token,
99
+ updatedAt: typeof session.updatedAt === 'string' ? session.updatedAt : '',
100
+ });
101
+ }
102
+ }
103
+ sessions.sort((left, right) => {
104
+ if (left.username === right.username) {
105
+ return left.env.localeCompare(right.env);
106
+ }
107
+ return left.username.localeCompare(right.username);
108
+ });
109
+ return sessions;
110
+ }
111
+ function getCurrentAccountSession(cfg = loadConfig()) {
112
+ const current = cfg.currentAccount;
113
+ if (!current) {
114
+ return null;
115
+ }
116
+ return findAccountSession(current.username, current.env, cfg);
117
+ }
118
+ function findAccountSession(username, env, cfg = loadConfig()) {
119
+ const normalizedUsername = normalizeKey(username);
120
+ if (!normalizedUsername) {
121
+ return null;
122
+ }
123
+ const envMap = cfg.accounts?.[normalizedUsername];
124
+ if (!envMap) {
125
+ return null;
126
+ }
127
+ if (typeof env === 'string' && env.trim().length > 0) {
128
+ const session = envMap[env.trim()];
129
+ if (!session?.token) {
130
+ return null;
131
+ }
132
+ return {
133
+ username: normalizedUsername,
134
+ env: env.trim(),
135
+ token: session.token,
136
+ updatedAt: typeof session.updatedAt === 'string' ? session.updatedAt : '',
137
+ };
138
+ }
139
+ const envs = Object.keys(envMap);
140
+ if (envs.length === 1) {
141
+ const selectedEnv = envs[0];
142
+ const session = envMap[selectedEnv];
143
+ if (!session?.token) {
144
+ return null;
145
+ }
146
+ return {
147
+ username: normalizedUsername,
148
+ env: selectedEnv,
149
+ token: session.token,
150
+ updatedAt: typeof session.updatedAt === 'string' ? session.updatedAt : '',
151
+ };
152
+ }
153
+ return null;
154
+ }
155
+ function listAccountSessionsForUsername(username, cfg = loadConfig()) {
156
+ const normalizedUsername = normalizeKey(username);
157
+ if (!normalizedUsername) {
158
+ return [];
159
+ }
160
+ return listAccountSessions(cfg).filter((session) => session.username === normalizedUsername);
161
+ }
162
+ function saveAccountSession(input) {
163
+ const cfg = loadConfig();
164
+ const username = normalizeKey(input.username);
165
+ const env = normalizeKey(input.env);
166
+ const token = input.token.trim();
167
+ if (!username || !env || !token) {
168
+ throw new Error('invalid_account_session');
169
+ }
170
+ const next = {
171
+ version: CONFIG_VERSION,
172
+ currentAccount: { username, env },
173
+ accounts: {
174
+ ...(cfg.accounts ?? {}),
175
+ [username]: {
176
+ ...(cfg.accounts?.[username] ?? {}),
177
+ [env]: {
178
+ token,
179
+ updatedAt: input.updatedAt ?? new Date().toISOString(),
180
+ },
181
+ },
182
+ },
183
+ };
184
+ saveConfig(next);
185
+ return loadConfig();
186
+ }
187
+ function migrateLegacySession(input) {
188
+ return saveAccountSession(input);
189
+ }
190
+ function setCurrentAccount(input) {
191
+ const cfg = loadConfig();
192
+ const username = normalizeKey(input.username);
193
+ const env = typeof input.env === 'string' ? normalizeKey(input.env) : '';
194
+ const session = findAccountSession(username, env || undefined, cfg);
195
+ if (!session) {
196
+ throw new Error(env ? 'account_session_not_found' : 'account_not_found');
197
+ }
198
+ const next = {
199
+ version: CONFIG_VERSION,
200
+ currentAccount: { username: session.username, env: session.env },
201
+ accounts: cfg.accounts ?? {},
202
+ };
203
+ saveConfig(next);
204
+ return loadConfig();
205
+ }
206
+ // eslint-disable-next-line complexity
207
+ function removeAccountSession(input = {}) {
208
+ const cfg = loadConfig();
209
+ const current = getCurrentAccountSession(cfg);
210
+ const username = normalizeKey(input.username ?? current?.username ?? '');
211
+ const env = normalizeKey(input.env ?? current?.env ?? '');
212
+ if (!username) {
213
+ return cfg;
214
+ }
215
+ const accounts = clonePersistedConfig({ accounts: cfg.accounts ?? {} }).accounts ?? {};
216
+ if (!accounts[username]) {
217
+ return cfg;
218
+ }
219
+ if (env) {
220
+ delete accounts[username][env];
221
+ }
222
+ else {
223
+ delete accounts[username];
224
+ }
225
+ if (accounts[username] && Object.keys(accounts[username]).length === 0) {
226
+ delete accounts[username];
227
+ }
228
+ const remainingSessions = listAccountSessions({ accounts });
229
+ const nextCurrent = remainingSessions[0]
230
+ ? { username: remainingSessions[0].username, env: remainingSessions[0].env }
231
+ : null;
232
+ const next = {
233
+ version: CONFIG_VERSION,
234
+ currentAccount: nextCurrent,
235
+ accounts,
236
+ };
237
+ if (!nextCurrent && typeof cfg.token === 'string' && typeof cfg.env === 'string' && (!cfg.accounts || Object.keys(cfg.accounts).length === 0)) {
238
+ clearConfig();
239
+ return {};
240
+ }
241
+ saveConfig(next);
242
+ return loadConfig();
243
+ }