@moewolf/moe-icons-cli 0.0.6 → 0.0.9

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/README.md CHANGED
@@ -170,9 +170,10 @@ out keeps cached archives but blocks new unauthenticated installs/updates.
170
170
  | `MOEICONS_BOOTSTRAP_FILE` | override bootstrap completion marker path |
171
171
  | `MOEICONS_PRO_DESCRIPTOR_URL` | test-only Pro descriptor endpoint override (https or loopback http) |
172
172
  | `MOEICONS_LIBRARY_VERSIONS_URL` | test-only library versions endpoint override (https or loopback http) |
173
- | `MOEICONS_TOKEN_STORE_DIR` | 0600 file token store directory |
173
+ | `MOEICONS_TOKEN_STORE_DIR` | explicit file token store directory (with disabled keychain) |
174
+ | `MOEICONS_STATE_DIR` | session storage preference directory (default `~/.moeicons`) |
174
175
  | `MOEICONS_DISABLE_SYSTEM_KEYCHAIN` | `1` disables OS keychain storage |
175
- | `MOEICONS_AUTH0_ISSUER` / `MOEICONS_AUTH0_CLIENT_ID` | Auth0 refresh/revoke |
176
+ | `MOEICONS_AUTH0_ISSUER` / `MOEICONS_AUTH0_CLIENT_ID` | explicit Auth0 refresh/revoke overrides; official production defaults are bundled |
176
177
 
177
178
  ## Status
178
179
 
@@ -269,3 +270,28 @@ Available tools: `list_icon_groups`, `get_account`, `get_project_config`,
269
270
  explicit unsupported operation with migration guidance. Tool failures use
270
271
  `isError`; malformed protocol arguments use JSON-RPC errors. The server binds
271
272
  all operations to one project root and does not expose arbitrary shell commands.
273
+
274
+ ### Session storage and refresh
275
+
276
+ When native credential storage is unavailable and you approve a file store,
277
+ the CLI remembers that selection in `session-store.json` (no credentials).
278
+ Later CLI processes and the VS Code extension use the same selection. Credential
279
+ store errors are reported, not silently replaced by an old fallback session.
280
+ Log out before switching storage methods. Official production refresh uses the
281
+ existing public Auth0 configuration; custom/local environments must provide
282
+ matching issuer and client ID. Account and refresh requests have a deadline
283
+ covering headers and body. Revoked refresh credentials require a new login.
284
+
285
+ The VS Code extension reads credentials on its own host. For Remote SSH, WSL
286
+ or a container, run the CLI login on that host; local credentials are not copied.
287
+
288
+ ### Independent CLI release and plugin recovery
289
+
290
+ A successful canonical publish triggers `plugin-update.yml`. It verifies the
291
+ published receipt and resource pin, sends a metadata-only CLI event and waits
292
+ for Marketplace verification. `PLUGIN_DISPATCH_TOKEN` must be configured in this
293
+ repository with Contents write for `moe-icons-plugins`. A failed notification is
294
+ replayed with `publish_run_id` in that workflow (and `published_version`
295
+ for the durable Release receipt after Actions artifact expiry), without republishing the CLI or
296
+ rebuilding resources. Temporary Actions receipts have a retention period; new
297
+ releases also retain a canonical receipt on their immutable GitHub Release.
@@ -1,5 +1,6 @@
1
1
  import { createLoginState, createPkcePair } from "./pkce.js";
2
2
  import { parseCreateLoginResponse, parseExchangeLoginResponse, parsePollLoginResponse, } from "./login-schemas.js";
3
+ import { boundedResponse } from "./transport.js";
3
4
  import { CliError } from "../errors/index.js";
4
5
  function assertBrowserUrl(value, expectedOrigin) {
5
6
  const url = new URL(value);
@@ -71,7 +72,7 @@ export async function loginWithDeviceSession(config, deps, signal) {
71
72
  }
72
73
  }
73
74
  export async function refreshAuth0Session(config, session, deps) {
74
- const response = await deps.fetch(`${config.auth0Issuer.replace(/\/$/, "")}/oauth/token`, {
75
+ const response = await boundedResponse(deps.fetch, `${config.auth0Issuer.replace(/\/$/, "")}/oauth/token`, {
75
76
  method: "POST",
76
77
  headers: { "content-type": "application/json" },
77
78
  body: JSON.stringify({
@@ -81,10 +82,14 @@ export async function refreshAuth0Session(config, session, deps) {
81
82
  }),
82
83
  ...(deps.signal ? { signal: deps.signal } : {}),
83
84
  });
85
+ if (response.status >= 500 || response.status === 429)
86
+ throw new CliError("NETWORK_ERROR", "Auth0 refresh temporarily unavailable; retry");
84
87
  const body = (await response.json());
85
88
  if (!response.ok || typeof body.access_token !== "string") {
86
89
  throw new CliError("AUTH_ERROR", "Auth0 refresh failed");
87
90
  }
91
+ if (body.expires_in !== undefined && (typeof body.expires_in !== "number" || !Number.isSafeInteger(body.expires_in) || body.expires_in <= 0))
92
+ throw new CliError("VALIDATION_ERROR", "invalid Auth0 token lifetime");
88
93
  const updated = {
89
94
  ...session,
90
95
  accessToken: body.access_token,
@@ -92,6 +97,9 @@ export async function refreshAuth0Session(config, session, deps) {
92
97
  expiresAt: deps.now() + (typeof body.expires_in === "number" ? body.expires_in : 3600) * 1000,
93
98
  storedAt: deps.now(),
94
99
  };
100
+ const latest = deps.tokenStore.getActive();
101
+ if (!latest || latest.accessToken !== session.accessToken || latest.storedAt !== session.storedAt)
102
+ throw new CliError("AUTH_ERROR", "session changed during refresh; retry with the current account");
95
103
  deps.tokenStore.set(updated);
96
104
  return updated;
97
105
  }
@@ -7,6 +7,7 @@ export function buildAuthorizationUrl(config, attempt) {
7
7
  const params = new URLSearchParams({
8
8
  client_id: config.clientId,
9
9
  response_type: "code",
10
+ ui_locales: "en",
10
11
  redirect_uri: `http://127.0.0.1:${attempt.port}${config.redirectPath}`,
11
12
  scope: config.scope,
12
13
  audience: config.audience,
@@ -0,0 +1,25 @@
1
+ // Canonical CLI/extension session-store selection. Contains no credentials.
2
+ const {homedir} = require('node:os');
3
+ const {join, isAbsolute} = require('node:path');
4
+ const {existsSync, lstatSync, readFileSync, mkdirSync, writeFileSync, renameSync} = require('node:fs');
5
+ function stateRoot(env) { const root=env.MOEICONS_STATE_DIR || join(homedir(), '.moeicons'); if(!isAbsolute(root)) throw new Error('MOEICONS_STATE_DIR must be absolute'); return root; }
6
+ function selectedStore(env) {
7
+ if (env.MOEICONS_DISABLE_SYSTEM_KEYCHAIN === '1' && env.MOEICONS_TOKEN_STORE_DIR) { if(!isAbsolute(env.MOEICONS_TOKEN_STORE_DIR)) throw new Error('MOEICONS_TOKEN_STORE_DIR must be absolute'); return {mode:'file', rootDir:env.MOEICONS_TOKEN_STORE_DIR}; }
8
+ const path = join(stateRoot(env), 'session-store.json');
9
+ if (!existsSync(path)) return {mode:env.MOEICONS_DISABLE_SYSTEM_KEYCHAIN === '1' ? 'none' : 'system'};
10
+ const meta=lstatSync(path);
11
+ if (!meta.isFile() || meta.isSymbolicLink() || meta.size>4096 || (process.platform!=='win32' && ((meta.mode & 0o077)!==0 || meta.uid!==process.getuid()))) throw new Error('Session storage preference is not readable securely');
12
+ const value=JSON.parse(readFileSync(path,'utf8'));
13
+ if (value.schemaVersion!==1 || value.mode!=='file' || typeof value.rootDir!=='string' || !isAbsolute(value.rootDir)) throw new Error('Invalid session storage preference');
14
+ return {mode:'file',rootDir:value.rootDir};
15
+ }
16
+ function rememberFileStore(env, rootDir) {
17
+ if (!isAbsolute(rootDir)) throw new Error('Session store requires an absolute directory');
18
+ const root=stateRoot(env); mkdirSync(root,{recursive:true,mode:0o700});
19
+ const path=join(root,'session-store.json'), temp=path+'.'+process.pid+'.tmp';
20
+ writeFileSync(temp,JSON.stringify({schemaVersion:1,mode:'file',rootDir})+'\n',{mode:0o600,flag:'wx'}); renameSync(temp,path);
21
+ }
22
+ function missingCredential(platform,error) {
23
+ return platform==='darwin' ? error.code===44 || error.status===44 : platform==='linux' ? (error.code===1 || error.status===1) && !String(error.stderr || '').trim() : platform==='win32' ? error.code===44 || error.status===44 || /0x80070490|Element not found/i.test(String(error.stderr || '')) : false;
24
+ }
25
+ module.exports={selectedStore,rememberFileStore,stateRoot,missingCredential};
@@ -1,13 +1,15 @@
1
- import { existsSync, readFileSync, writeFileSync, mkdirSync, chmodSync } from "node:fs";
1
+ import { readFileSync, writeFileSync, mkdirSync, chmodSync, lstatSync } from "node:fs";
2
2
  import { homedir } from "node:os";
3
3
  import { join } from "node:path";
4
+ import { missingCredential } from "./session-policy.cjs";
5
+ import { CliError } from "../errors/index.js";
4
6
  import { execFileSync } from "node:child_process";
5
7
  function parseSession(value) {
6
8
  try {
7
9
  const session = JSON.parse(value);
8
10
  return typeof session.accountId === "string" && typeof session.accessToken === "string" &&
9
- typeof session.refreshToken === "string" && typeof session.expiresAt === "number" &&
10
- typeof session.scope === "string" && typeof session.storedAt === "number"
11
+ typeof session.refreshToken === "string" && typeof session.expiresAt === "number" && Number.isFinite(session.expiresAt) &&
12
+ typeof session.scope === "string" && typeof session.storedAt === "number" && Number.isFinite(session.storedAt)
11
13
  ? session : undefined;
12
14
  }
13
15
  catch {
@@ -22,17 +24,29 @@ export function createFileTokenStore(options = {}) {
22
24
  const root = options.rootDir ?? join(homedir(), ".moeicons");
23
25
  const file = join(root, "token-store.json");
24
26
  function readAll() {
25
- if (!existsSync(file))
26
- return {};
27
+ let meta;
27
28
  try {
29
+ meta = lstatSync(file);
30
+ }
31
+ catch (error) {
32
+ if (error.code === "ENOENT")
33
+ return {};
34
+ throw new CliError("AUTH_ERROR", "file credential storage unavailable; check permissions or repair the store");
35
+ }
36
+ try {
37
+ if (!meta.isFile() || meta.isSymbolicLink() || meta.size > 1_000_000 || (process.platform !== "win32" && ((meta.mode & 0o077) !== 0 || typeof process.getuid === "function" && meta.uid !== process.getuid())))
38
+ throw new Error("insecure store");
28
39
  const raw = JSON.parse(readFileSync(file, "utf8"));
29
- if (typeof raw === "object" && raw !== null)
30
- return raw;
40
+ if (typeof raw !== "object" || raw === null || Array.isArray(raw))
41
+ throw new Error("invalid store");
42
+ for (const item of Object.values(raw))
43
+ if (!parseSession(JSON.stringify(item)))
44
+ throw new Error("invalid session");
45
+ return raw;
31
46
  }
32
47
  catch {
33
- // corrupt store; treat as empty
48
+ throw new CliError("AUTH_ERROR", "file credential storage unavailable; check permissions or repair the store");
34
49
  }
35
- return {};
36
50
  }
37
51
  function writeAll(data) {
38
52
  mkdirSync(root, { recursive: true });
@@ -56,7 +70,7 @@ export function createFileTokenStore(options = {}) {
56
70
  delete all[accountId];
57
71
  writeAll(all);
58
72
  },
59
- clear() { writeAll({}); },
73
+ clear() { readAll(); writeAll({}); },
60
74
  };
61
75
  }
62
76
  /** Use the native credential store when the platform provides a supported CLI. */
@@ -65,7 +79,7 @@ export function createSystemTokenStore(options = {}) {
65
79
  const run = options.execFile ?? execFileSync;
66
80
  const service = "moeicons";
67
81
  const account = "active-session";
68
- const invoke = (command, args, input) => String(run(command, args, { encoding: "utf8", stdio: ["pipe", "pipe", "ignore"], ...(input ? { input } : {}) })).trim();
82
+ const invoke = (command, args, input) => String(run(command, args, { encoding: "utf8", timeout: 5000, maxBuffer: 1_000_000, stdio: ["pipe", "pipe", "pipe"], ...(input ? { input } : {}) })).trim();
69
83
  let read;
70
84
  let write;
71
85
  let remove;
@@ -87,23 +101,38 @@ export function createSystemTokenStore(options = {}) {
87
101
  }
88
102
  else if (platform === "win32") {
89
103
  const prefix = "[void][Windows.Security.Credentials.PasswordVault,Windows.Security.Credentials,ContentType=WindowsRuntime];$v=[Windows.Security.Credentials.PasswordVault]::new();";
90
- read = () => invoke("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `${prefix}$c=$v.Retrieve('${service}','${account}');$c.RetrievePassword();[Console]::Out.Write($c.Password)`]);
104
+ read = () => invoke("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `${prefix}try{$c=$v.Retrieve('${service}','${account}');$c.RetrievePassword();[Console]::Out.Write($c.Password)}catch{$e=$_.Exception;while($e){if($e.HResult -eq -2147023728){exit 44};$e=$e.InnerException};exit 45}`]);
91
105
  write = (value) => { invoke("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `${prefix}$p=[Console]::In.ReadToEnd();try{$v.Remove($v.Retrieve('${service}','${account}'))}catch{};$v.Add([Windows.Security.Credentials.PasswordCredential]::new('${service}','${account}',$p))`], value); };
92
106
  remove = () => { invoke("powershell.exe", ["-NoProfile", "-NonInteractive", "-Command", `${prefix}try{$v.Remove($v.Retrieve('${service}','${account}'))}catch{}`]); };
93
107
  }
94
108
  else {
95
109
  return undefined;
96
110
  }
97
- const active = () => { try {
98
- return parseSession(read());
99
- }
100
- catch {
101
- return undefined;
102
- } };
111
+ const active = () => {
112
+ try {
113
+ const value = read();
114
+ if (!value)
115
+ return undefined;
116
+ const session = parseSession(value);
117
+ if (!session)
118
+ throw new Error("invalid session");
119
+ return session;
120
+ }
121
+ catch (error) {
122
+ if (missingCredential(platform, error))
123
+ return undefined;
124
+ throw new CliError("AUTH_ERROR", "system credential storage unavailable; retry or check its permissions");
125
+ }
126
+ };
103
127
  return {
104
128
  get: (accountId) => { const value = active(); return value?.accountId === accountId ? value : undefined; },
105
129
  getActive: active,
106
- set: (session) => { write(JSON.stringify(session)); },
130
+ set: (session) => { try {
131
+ write(JSON.stringify(session));
132
+ }
133
+ catch {
134
+ throw new CliError("AUTH_ERROR", "cannot save system credentials; check credential store permissions");
135
+ } },
107
136
  delete: (accountId) => { if (active()?.accountId === accountId) {
108
137
  try {
109
138
  remove();
@@ -113,7 +142,10 @@ export function createSystemTokenStore(options = {}) {
113
142
  clear: () => { try {
114
143
  remove();
115
144
  }
116
- catch { /* already absent */ } },
145
+ catch (error) {
146
+ if (!missingCredential(platform, error))
147
+ throw new CliError("AUTH_ERROR", "cannot clear system credentials; check credential store permissions");
148
+ } },
117
149
  };
118
150
  }
119
151
  /** Redact a session for logging: never expose tokens. */
@@ -0,0 +1,2 @@
1
+ /** One deadline covers headers and body; no credentials or upstream bodies in errors. */
2
+ export declare function boundedResponse(request: typeof fetch, url: string, init: RequestInit, timeoutMs?: number): Promise<Response>;
@@ -0,0 +1,45 @@
1
+ import { CliError } from "../errors/index.js";
2
+ /** One deadline covers headers and body; no credentials or upstream bodies in errors. */
3
+ export async function boundedResponse(request, url, init, timeoutMs = 10_000) {
4
+ const controller = new AbortController();
5
+ const cancel = () => controller.abort(new CliError("CANCELLED", "account request cancelled"));
6
+ if (init.signal?.aborted)
7
+ cancel();
8
+ else
9
+ init.signal?.addEventListener("abort", cancel, { once: true });
10
+ const timer = setTimeout(() => controller.abort(new CliError("NETWORK_ERROR", "account request timed out; retry")), timeoutMs);
11
+ let reader;
12
+ let rejectAbort = () => { };
13
+ const interrupted = new Promise((_, reject) => { rejectAbort = reject; });
14
+ const abort = () => rejectAbort(controller.signal.reason);
15
+ controller.signal.addEventListener("abort", abort, { once: true });
16
+ try {
17
+ if (controller.signal.aborted)
18
+ throw controller.signal.reason;
19
+ const response = await Promise.race([request(url, { ...init, redirect: "error", signal: controller.signal }), interrupted]);
20
+ const chunks = [];
21
+ let size = 0;
22
+ if (response.body) {
23
+ reader = response.body.getReader();
24
+ for (;;) {
25
+ const { done, value } = await Promise.race([reader.read(), interrupted]);
26
+ if (done)
27
+ break;
28
+ size += value.byteLength;
29
+ if (size > 64_000)
30
+ throw new CliError("VALIDATION_ERROR", "account response exceeds size limit");
31
+ chunks.push(value);
32
+ }
33
+ }
34
+ return new Response([204, 205, 304].includes(response.status) ? null : Buffer.concat(chunks), { status: response.status, headers: response.headers });
35
+ }
36
+ finally {
37
+ clearTimeout(timer);
38
+ init.signal?.removeEventListener("abort", cancel);
39
+ controller.signal.removeEventListener("abort", abort);
40
+ if (reader) {
41
+ void reader.cancel().catch(() => { });
42
+ reader.releaseLock();
43
+ }
44
+ }
45
+ }
package/dist/core/auth.js CHANGED
@@ -2,20 +2,36 @@ import { loginWithDeviceSession, refreshAuth0Session } from "../auth/device-logi
2
2
  import { createFileTokenStore, createSystemTokenStore, redactSession } from "../auth/token-store.js";
3
3
  import { openBrowser } from "../auth/open-browser.js";
4
4
  import { requestJson } from "../api/client.js";
5
+ import { selectedStore, rememberFileStore, stateRoot } from "../auth/session-policy.cjs";
6
+ import { resolve } from "node:path";
7
+ import { boundedResponse } from "../auth/transport.js";
5
8
  import { CliError } from "../errors/index.js";
6
9
  const DEFAULT_API_BASE_URL = "https://api.moeicons.com";
7
10
  const DEFAULT_WEBSITE_ORIGIN = "https://moeicons.com";
8
11
  const LOOPBACK_HOSTS = new Set(["127.0.0.1", "localhost", "::1"]);
9
12
  function existingStore(context, deps) {
10
- return deps.tokenStore ?? (context.env.MOEICONS_DISABLE_SYSTEM_KEYCHAIN === "1" ? undefined : (deps.systemTokenStore ?? createSystemTokenStore)()) ??
11
- (context.env.MOEICONS_TOKEN_STORE_DIR ? (deps.fileTokenStore ?? ((rootDir) => createFileTokenStore(rootDir ? { rootDir } : {})))(context.env.MOEICONS_TOKEN_STORE_DIR) : undefined);
13
+ if (deps.tokenStore)
14
+ return deps.tokenStore;
15
+ const selection = selectedStore(context.env);
16
+ if (selection.mode === "file")
17
+ return (deps.fileTokenStore ?? ((rootDir) => createFileTokenStore({ rootDir: rootDir })))(selection.rootDir);
18
+ if (selection.mode === "none")
19
+ return undefined;
20
+ return (deps.systemTokenStore ?? createSystemTokenStore)();
12
21
  }
13
22
  /** Probe existing credentials without prompting for or creating fallback storage. */
14
23
  export async function runSessionStatusUseCase(context, deps = {}) {
15
- const store = existingStore(context, deps);
24
+ let store;
25
+ let session;
26
+ try {
27
+ store = existingStore(context, deps);
28
+ session = store?.getActive();
29
+ }
30
+ catch {
31
+ return { kind: "unknown", reason: "credential storage unavailable" };
32
+ }
16
33
  if (!store)
17
34
  return { kind: "signed-out" };
18
- const session = store.getActive();
19
35
  if (!session)
20
36
  return { kind: "signed-out" };
21
37
  if (session.expiresAt > context.now().getTime())
@@ -28,7 +44,7 @@ export async function runSessionStatusUseCase(context, deps = {}) {
28
44
  return { kind: "authenticated", account: redactSession(refreshed) };
29
45
  }
30
46
  catch (error) {
31
- if (!(error instanceof CliError) || error.code === "NETWORK_ERROR" || error.code === "CANCELLED") {
47
+ if (!(error instanceof CliError) || error.code !== "AUTH_ERROR") {
32
48
  return { kind: "unknown", reason: error instanceof Error ? error.message : "session validation unavailable" };
33
49
  }
34
50
  return { kind: "signed-out", reason: error.message };
@@ -37,6 +53,9 @@ export async function runSessionStatusUseCase(context, deps = {}) {
37
53
  async function selectStore(context, deps) {
38
54
  if (deps.tokenStore)
39
55
  return deps.tokenStore;
56
+ const selected = existingStore(context, deps);
57
+ if (selected)
58
+ return selected;
40
59
  const system = context.env.MOEICONS_DISABLE_SYSTEM_KEYCHAIN === "1" ? undefined : (deps.systemTokenStore ?? createSystemTokenStore)();
41
60
  if (system)
42
61
  return system;
@@ -52,7 +71,12 @@ async function selectStore(context, deps) {
52
71
  throw new CliError("CANCELLED", "login cancelled");
53
72
  if (!accepted)
54
73
  throw new CliError("AUTH_ERROR", "login requires secure credential storage");
55
- return (deps.fileTokenStore ?? ((rootDir) => createFileTokenStore(rootDir ? { rootDir } : {})))(context.env.MOEICONS_TOKEN_STORE_DIR);
74
+ const rootDir = resolve(context.env.MOEICONS_TOKEN_STORE_DIR ?? stateRoot(context.env));
75
+ const store = (deps.fileTokenStore ?? ((rootDir) => createFileTokenStore({ rootDir: rootDir })))(rootDir);
76
+ // Injected test stores never mutate the user's storage preference.
77
+ if (!deps.fileTokenStore)
78
+ rememberFileStore(context.env, rootDir);
79
+ return store;
56
80
  }
57
81
  function assertTrustedHttpBase(value, envName) {
58
82
  let parsed;
@@ -101,7 +125,7 @@ export function resolveAuthEnvironment(env) {
101
125
  }
102
126
  const mode = declared ?? inferred;
103
127
  // H5: the Auth0 issuer receives refresh tokens, so it must match the mode.
104
- const auth0Issuer = env.MOEICONS_AUTH0_ISSUER ?? "";
128
+ const auth0Issuer = env.MOEICONS_AUTH0_ISSUER ?? (mode === "production" && apiBaseUrl === DEFAULT_API_BASE_URL && websiteOrigin === DEFAULT_WEBSITE_ORIGIN ? "https://login.moewolf.com/" : "");
105
129
  if (auth0Issuer) {
106
130
  assertTrustedHttpBase(auth0Issuer, "MOEICONS_AUTH0_ISSUER");
107
131
  const issuerLoopback = isLoopbackHttp(auth0Issuer);
@@ -118,7 +142,7 @@ export function resolveAuthEnvironment(env) {
118
142
  apiBaseUrl,
119
143
  websiteOrigin,
120
144
  auth0Issuer,
121
- auth0ClientId: env.MOEICONS_AUTH0_CLIENT_ID ?? "",
145
+ auth0ClientId: env.MOEICONS_AUTH0_CLIENT_ID ?? (auth0Issuer === "https://login.moewolf.com/" ? "N7xIQMbIB1ao501jP6abai42SiWXyUXB" : ""),
122
146
  };
123
147
  }
124
148
  function auth0Config(context) {
@@ -191,14 +215,8 @@ export async function runAccountUseCase(context, deps = {}) {
191
215
  export async function runRemoteAccountUseCase(context, deps = {}) {
192
216
  const apiBaseUrl = auth0Config(context).apiBaseUrl;
193
217
  const doFetch = deps.fetch ?? fetch;
194
- let session;
195
- try {
196
- session = (await activeSession(context, deps)).session;
197
- }
198
- catch {
199
- throw new CliError("AUTH_ERROR", "not logged in");
200
- }
201
- const call = async (token) => doFetch(`${apiBaseUrl}/v1/cli/account`, {
218
+ const session = (await activeSession(context, deps)).session;
219
+ const call = async (token) => boundedResponse(doFetch, `${apiBaseUrl}/v1/cli/account`, {
202
220
  method: "GET",
203
221
  headers: { authorization: `Bearer ${token}` },
204
222
  signal: context.signal,
@@ -235,7 +253,7 @@ export async function runLogoutUseCase(context, deps = {}) {
235
253
  const config = auth0Config(context);
236
254
  if (config.auth0Issuer && config.auth0ClientId) {
237
255
  try {
238
- const response = await (deps.fetch ?? fetch)(`${config.auth0Issuer.replace(/\/$/, "")}/oauth/revoke`, {
256
+ const response = await boundedResponse(deps.fetch ?? fetch, `${config.auth0Issuer.replace(/\/$/, "")}/oauth/revoke`, {
239
257
  method: "POST", headers: { "content-type": "application/json" },
240
258
  body: JSON.stringify({ client_id: config.auth0ClientId, token: session.refreshToken }), signal: context.signal,
241
259
  });
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@moewolf/moe-icons-cli",
3
- "version": "0.0.6",
3
+ "version": "0.0.9",
4
4
  "description": "Moeicons CLI: install React, Vue, Vanilla, or Assets icon targets, manage releases and login, and expose an MCP server.",
5
5
  "type": "module",
6
6
  "main": "./dist/cli.js",
@@ -74,5 +74,5 @@
74
74
  "fflate": "^0.8.2",
75
75
  "jsonc-parser": "^3.3.1"
76
76
  },
77
- "payloadHash": "55b45953475de6cc2282f622cc54b1b9d3464e25811f05c96d839ee60c465861"
77
+ "payloadHash": "2c4faa8c395ce82af178bd941ababfede769b22f1721b7e6faf3c655cc003231"
78
78
  }