@supen-ai/cli 0.1.13 → 1.3.2

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.
@@ -1766,10 +1766,6 @@ const MANAGED_CODING_CLI_INSTALL_COMMANDS = {
1766
1766
  };
1767
1767
  const CODING_CLI_NAMES = ['codex', 'gemini', 'claude'];
1768
1768
  const DETECTABLE_SPACE_APPS = [
1769
- { id: 'codex', command: 'codex', managed: true },
1770
- { id: 'gemini', command: 'gemini', managed: true },
1771
- { id: 'claude', command: 'claude', managed: false },
1772
- { id: 'acpx', command: 'acpx', managed: false },
1773
1769
  { id: 'libreoffice', command: 'libreoffice', managed: false },
1774
1770
  { id: 'dotnet', command: 'dotnet', managed: false },
1775
1771
  { id: 'tiwater-docx', command: 'tiwater-docx', managed: false },
@@ -2053,15 +2049,47 @@ function detectCodexAuthStatus(installed) {
2053
2049
  ? email || accountLine || 'Authenticated'
2054
2050
  : null;
2055
2051
  let accountId = null;
2052
+ let tokenEmail = null;
2053
+ let tokenName = null;
2056
2054
  try {
2057
2055
  const parsed = JSON.parse(fs.readFileSync(path.join(resolveCodexHome(), 'auth.json'), 'utf8'));
2058
2056
  const tokens = parsed.tokens && typeof parsed.tokens === 'object' ? parsed.tokens : {};
2059
2057
  accountId = typeof tokens.account_id === 'string' && tokens.account_id.trim() ? tokens.account_id.trim() : null;
2058
+ const identity = codexIdentityClaimsFromIdToken(tokens.id_token);
2059
+ tokenEmail = identity.email;
2060
+ tokenName = identity.name;
2060
2061
  }
2061
2062
  catch {
2062
2063
  accountId = null;
2063
2064
  }
2064
- return { authenticated, summary, account_id: accountId };
2065
+ return {
2066
+ authenticated,
2067
+ summary: authenticated ? tokenEmail || email || tokenName || accountLine || 'Authenticated' : null,
2068
+ account_id: accountId,
2069
+ email: tokenEmail || email || null,
2070
+ name: tokenName,
2071
+ };
2072
+ }
2073
+ function codexIdentityClaimsFromIdToken(idToken) {
2074
+ if (typeof idToken !== 'string' || !idToken.trim())
2075
+ return { email: null, name: null };
2076
+ const payload = idToken.split('.')[1];
2077
+ if (!payload)
2078
+ return { email: null, name: null };
2079
+ try {
2080
+ const decoded = Buffer.from(payload.replace(/-/g, '+').replace(/_/g, '/'), 'base64').toString('utf8');
2081
+ const claims = JSON.parse(decoded);
2082
+ const email = typeof claims.email === 'string' && claims.email.trim()
2083
+ ? claims.email.trim()
2084
+ : null;
2085
+ const name = typeof claims.name === 'string' && claims.name.trim()
2086
+ ? claims.name.trim()
2087
+ : null;
2088
+ return { email, name };
2089
+ }
2090
+ catch {
2091
+ return { email: null, name: null };
2092
+ }
2065
2093
  }
2066
2094
  function readCodexDefaultModel() {
2067
2095
  const codexHome = resolveCodexHome();
@@ -2277,6 +2305,23 @@ function readCodingCliStatusPayload() {
2277
2305
  codex_connect: codexConnectSnapshot(),
2278
2306
  };
2279
2307
  }
2308
+ function readInstalledAppsPayload() {
2309
+ return {
2310
+ installed_apps: DETECTABLE_SPACE_APPS.map((app) => {
2311
+ const detected = detectCliInstalled(app.command);
2312
+ return {
2313
+ id: app.id,
2314
+ installed: detected.installed,
2315
+ version: detected.version,
2316
+ managed: app.managed,
2317
+ path: detected.path,
2318
+ resolved_path: detected.resolved_path,
2319
+ install_source: detected.install_source,
2320
+ update_supported: detected.update_supported,
2321
+ };
2322
+ }),
2323
+ };
2324
+ }
2280
2325
  function readCachedCodingCliStatusPayload(options) {
2281
2326
  startCodingCliStatusCache({
2282
2327
  build: () => ({
@@ -2286,6 +2331,111 @@ function readCachedCodingCliStatusPayload(options) {
2286
2331
  });
2287
2332
  return getCodingCliStatusResponse(options);
2288
2333
  }
2334
+ function mergeQuotaWindows(...groups) {
2335
+ const byLabel = new Map();
2336
+ for (const group of groups) {
2337
+ for (const window of group) {
2338
+ const key = window.label.trim().toLowerCase();
2339
+ if (!key)
2340
+ continue;
2341
+ byLabel.set(key, window);
2342
+ }
2343
+ }
2344
+ return Array.from(byLabel.values());
2345
+ }
2346
+ function resetAtFromCodexValue(value) {
2347
+ if (typeof value === 'string' && value.trim())
2348
+ return value.trim();
2349
+ if (typeof value === 'number' && Number.isFinite(value)) {
2350
+ const millis = value > 10_000_000_000 ? value : value * 1000;
2351
+ return new Date(millis).toISOString();
2352
+ }
2353
+ return null;
2354
+ }
2355
+ function quotaLabelForCodexWindow(key, record) {
2356
+ const duration = numberValue(record.windowDurationMins);
2357
+ if (duration === 300)
2358
+ return '5-hour limit';
2359
+ if (duration === 10080)
2360
+ return 'Weekly limit';
2361
+ return key === 'primary' ? 'Primary limit' : key === 'secondary' ? 'Secondary limit' : labelForQuotaWindow(key, record);
2362
+ }
2363
+ function quotaWindowFromCodexNestedRecord(key, value) {
2364
+ if (!value || typeof value !== 'object')
2365
+ return null;
2366
+ const record = value;
2367
+ const percent = numberValue(record.usedPercent ?? record.used_percent ?? record.percent);
2368
+ const reset_at = resetAtFromCodexValue(record.resetsAt ?? record.resetAt ?? record.reset_at ?? record.reset);
2369
+ if (percent === null && !reset_at)
2370
+ return null;
2371
+ return {
2372
+ label: quotaLabelForCodexWindow(key, record),
2373
+ used: null,
2374
+ limit: null,
2375
+ remaining: null,
2376
+ percent,
2377
+ reset_at,
2378
+ };
2379
+ }
2380
+ function normalizeCodexSubscriptionQuotaWindows(subscription) {
2381
+ const rateLimits = subscription.rate_limits && typeof subscription.rate_limits === 'object'
2382
+ ? subscription.rate_limits
2383
+ : null;
2384
+ if (!rateLimits)
2385
+ return [];
2386
+ return ['primary', 'secondary']
2387
+ .map((key) => quotaWindowFromCodexNestedRecord(key, rateLimits[key]))
2388
+ .filter((entry) => Boolean(entry));
2389
+ }
2390
+ function codexSubscriptionSummary(subscription) {
2391
+ const rateLimits = subscription.rate_limits && typeof subscription.rate_limits === 'object'
2392
+ ? subscription.rate_limits
2393
+ : {};
2394
+ const credits = rateLimits.credits && typeof rateLimits.credits === 'object'
2395
+ ? rateLimits.credits
2396
+ : {};
2397
+ return {
2398
+ plan_type: typeof rateLimits.planType === 'string' && rateLimits.planType.trim() ? rateLimits.planType.trim() : null,
2399
+ credits_balance: typeof credits.balance === 'string' && credits.balance.trim() ? credits.balance.trim() : null,
2400
+ };
2401
+ }
2402
+ async function readCodexAgentStatusPayload() {
2403
+ const status = readCachedCodingCliStatusPayload();
2404
+ const quotaStatus = readLatestSpaceQuotaStatus();
2405
+ try {
2406
+ const subscription = await readCodexSubscription();
2407
+ const subscriptionWindows = mergeQuotaWindows(normalizeCodexSubscriptionQuotaWindows(subscription), normalizeQuotaWindows(subscription.rate_limits), normalizeQuotaWindows(subscription.rate_limits_by_limit_id));
2408
+ return {
2409
+ ...status,
2410
+ subscription: {
2411
+ ok: true,
2412
+ fetched_at: subscription.fetched_at,
2413
+ payload: subscription,
2414
+ error: null,
2415
+ },
2416
+ subscription_summary: codexSubscriptionSummary(subscription),
2417
+ quota_status: quotaStatus,
2418
+ quota_windows: mergeQuotaWindows(quotaStatus.windows, subscriptionWindows),
2419
+ };
2420
+ }
2421
+ catch (err) {
2422
+ return {
2423
+ ...status,
2424
+ subscription: {
2425
+ ok: false,
2426
+ fetched_at: null,
2427
+ payload: null,
2428
+ error: err?.message || 'Unable to read Codex subscription details.',
2429
+ },
2430
+ subscription_summary: {
2431
+ plan_type: null,
2432
+ credits_balance: null,
2433
+ },
2434
+ quota_status: quotaStatus,
2435
+ quota_windows: quotaStatus.windows,
2436
+ };
2437
+ }
2438
+ }
2289
2439
  function readRuntimeModelStatusPayload() {
2290
2440
  const selected_cli = process.env.SUPEN_CODING_CLI || readConfigSummary().coding_cli || 'codex';
2291
2441
  const codexTransport = normalizeCodexTransport(process.env.SUPEN_CODEX_TRANSPORT) ||
@@ -2606,7 +2756,11 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2606
2756
  return true;
2607
2757
  }
2608
2758
  if (pathname === '/api/computers/{computer_id}/apps' && method === 'GET') {
2609
- writeJson(res, 200, readCachedCodingCliStatusPayload());
2759
+ writeJson(res, 200, readInstalledAppsPayload());
2760
+ return true;
2761
+ }
2762
+ if (pathname === '/api/computers/{computer_id}/agents/codex' && method === 'GET') {
2763
+ writeJson(res, 200, await readCodexAgentStatusPayload());
2610
2764
  return true;
2611
2765
  }
2612
2766
  if (pathname === '/api/computers/{computer_id}/runtime-models' && method === 'GET') {
@@ -2634,10 +2788,10 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2634
2788
  }
2635
2789
  return true;
2636
2790
  }
2637
- const spaceCodexTransportDefaultMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/codex\/transports\/([^/]+)\/default$/);
2638
- if (spaceCodexTransportDefaultMatch && method === 'PUT') {
2791
+ const codexTransportDefaultMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/agents\/codex\/transports\/([^/]+)\/default$/);
2792
+ if (codexTransportDefaultMatch && method === 'PUT') {
2639
2793
  try {
2640
- const transport = normalizeCodexTransport(decodeURIComponent(spaceCodexTransportDefaultMatch[1] || '').trim());
2794
+ const transport = normalizeCodexTransport(decodeURIComponent(codexTransportDefaultMatch[1] || '').trim());
2641
2795
  if (!transport) {
2642
2796
  writeProtocolError(res, 400, 'validation_error', 'invalid_codex_transport', 'transport must be one of: app-server, exec, acpx');
2643
2797
  return true;
@@ -2654,15 +2808,9 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2654
2808
  }
2655
2809
  return true;
2656
2810
  }
2657
- const spaceAppDefaultMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/default$/);
2658
- if (spaceAppDefaultMatch && method === 'PUT') {
2811
+ if (pathname === '/api/computers/{computer_id}/agents/codex/default' && method === 'PUT') {
2659
2812
  try {
2660
- const cli = normalizeCliName(decodeURIComponent(spaceAppDefaultMatch[1] || '').trim());
2661
- if (!cli) {
2662
- writeProtocolError(res, 400, 'validation_error', 'invalid_cli', 'cli must be one of: codex, gemini, claude');
2663
- return true;
2664
- }
2665
- const result = setDefaultCodingCli(cli);
2813
+ const result = setDefaultCodingCli('codex');
2666
2814
  writeJson(res, 200, {
2667
2815
  ok: true,
2668
2816
  ...result,
@@ -2670,28 +2818,19 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2670
2818
  });
2671
2819
  }
2672
2820
  catch (err) {
2673
- writeProtocolError(res, 500, 'config_error', 'coding_cli_default_failed', err?.message || 'Failed to set default coding CLI');
2821
+ writeProtocolError(res, 500, 'config_error', 'coding_agent_default_failed', err?.message || 'Failed to set default coding agent');
2674
2822
  }
2675
2823
  return true;
2676
2824
  }
2677
- const spaceAppInstallMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/install$/);
2678
- if (spaceAppInstallMatch && method === 'POST') {
2825
+ if (pathname === '/api/computers/{computer_id}/agents/codex/install' && method === 'POST') {
2679
2826
  try {
2680
- const cli = normalizeCliName(decodeURIComponent(spaceAppInstallMatch[1] || '').trim());
2681
- if (!cli) {
2682
- writeProtocolError(res, 400, 'validation_error', 'invalid_cli', 'cli must be one of: codex, gemini, claude');
2683
- return true;
2684
- }
2685
- if (!isManagedCliName(cli)) {
2686
- writeProtocolError(res, 400, 'validation_error', 'install_not_supported', 'Only codex and gemini support managed install at the moment');
2687
- return true;
2688
- }
2827
+ const cli = 'codex';
2689
2828
  const current = detectCliInstalled(cli);
2690
2829
  if (current.installed && !current.update_supported) {
2691
2830
  writeProtocolError(res, 400, 'validation_error', 'coding_cli_update_not_supported', `${cli} is installed via ${current.install_source || current.resolved_path || current.path || 'an unknown source'}; update it with that installer on this computer.`);
2692
2831
  return true;
2693
2832
  }
2694
- const installSpec = MANAGED_CODING_CLI_INSTALL_COMMANDS[cli];
2833
+ const installSpec = MANAGED_CODING_CLI_INSTALL_COMMANDS.codex;
2695
2834
  const result = spawnSync(installSpec.command, installSpec.args, {
2696
2835
  encoding: 'utf8',
2697
2836
  timeout: 120_000,
@@ -2749,13 +2888,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2749
2888
  }
2750
2889
  return true;
2751
2890
  }
2752
- const spaceAppConnectMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/connect$/);
2753
- if (spaceAppConnectMatch && method === 'POST') {
2754
- const cli = normalizeCliName(decodeURIComponent(spaceAppConnectMatch[1] || '').trim());
2755
- if (cli !== 'codex') {
2756
- writeProtocolError(res, 400, 'validation_error', 'connect_not_supported', 'Only codex supports connect flow at the moment');
2757
- return true;
2758
- }
2891
+ if (pathname === '/api/computers/{computer_id}/agents/codex/connect' && method === 'POST') {
2759
2892
  const status = readCachedCodingCliStatusPayload();
2760
2893
  const codex = status.clis.find((entry) => entry.name === 'codex');
2761
2894
  if (!codex?.installed) {
@@ -2769,12 +2902,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2769
2902
  });
2770
2903
  return true;
2771
2904
  }
2772
- if (spaceAppConnectMatch && method === 'DELETE') {
2773
- const cli = normalizeCliName(decodeURIComponent(spaceAppConnectMatch[1] || '').trim());
2774
- if (cli !== 'codex') {
2775
- writeProtocolError(res, 400, 'validation_error', 'connect_not_supported', 'Only codex supports connect flow at the moment');
2776
- return true;
2777
- }
2905
+ if (pathname === '/api/computers/{computer_id}/agents/codex/connect' && method === 'DELETE') {
2778
2906
  writeJson(res, 200, {
2779
2907
  ok: true,
2780
2908
  codex_connect: cancelCodexConnectFlow(),
@@ -2782,13 +2910,7 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2782
2910
  });
2783
2911
  return true;
2784
2912
  }
2785
- const spaceAppSessionMatch = pathname.match(/^\/api\/computers\/\{computer_id\}\/apps\/([^/]+)\/session$/);
2786
- if (spaceAppSessionMatch && method === 'DELETE') {
2787
- const cli = normalizeCliName(decodeURIComponent(spaceAppSessionMatch[1] || '').trim());
2788
- if (cli !== 'codex') {
2789
- writeProtocolError(res, 400, 'validation_error', 'session_not_supported', 'Only codex supports session connect/disconnect at the moment');
2790
- return true;
2791
- }
2913
+ if (pathname === '/api/computers/{computer_id}/agents/codex/session' && method === 'DELETE') {
2792
2914
  const cmd = spawnSync('codex', ['logout'], {
2793
2915
  encoding: 'utf8',
2794
2916
  timeout: 15_000,
@@ -2832,19 +2954,6 @@ export async function handleSystemRoutes(req, res, url, pathname, method) {
2832
2954
  writeJson(res, 200, { daily: getDailyUsage() });
2833
2955
  return true;
2834
2956
  }
2835
- if (pathname === '/api/computers/{computer_id}/codex/subscription' && method === 'GET') {
2836
- try {
2837
- writeJson(res, 200, await readCodexSubscription());
2838
- }
2839
- catch (err) {
2840
- writeProtocolError(res, 503, 'service_unavailable', 'codex_subscription_unavailable', err?.message || 'Unable to read Codex subscription details.');
2841
- }
2842
- return true;
2843
- }
2844
- if (pathname === '/api/computers/{computer_id}/quota-status' && method === 'GET') {
2845
- writeJson(res, 200, readLatestSpaceQuotaStatus());
2846
- return true;
2847
- }
2848
2957
  if (pathname === '/api/computers/{computer_id}/codex/threads' && method === 'GET') {
2849
2958
  const limitRaw = coerceSingleQueryParam(url.searchParams.get('limit'));
2850
2959
  const limit = limitRaw ? Number.parseInt(limitRaw, 10) : MIRRORED_THREAD_LIMIT;