@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.
- package/README.md +1 -1
- package/daemon/dist/channels/http-routes.d.ts.map +1 -1
- package/daemon/dist/channels/http-routes.js +2 -11
- package/daemon/dist/channels/http-routes.js.map +1 -1
- package/daemon/dist/http/routes/sessions.d.ts.map +1 -1
- package/daemon/dist/http/routes/sessions.js +0 -33
- package/daemon/dist/http/routes/sessions.js.map +1 -1
- package/daemon/dist/http/routes/system.d.ts.map +1 -1
- package/daemon/dist/http/routes/system.js +172 -63
- package/daemon/dist/http/routes/system.js.map +1 -1
- package/daemon/package.json +1 -1
- package/dist/computer.js +10 -2
- package/dist/computer.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
|
@@ -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 {
|
|
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,
|
|
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
|
|
2638
|
-
if (
|
|
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(
|
|
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
|
-
|
|
2658
|
-
if (spaceAppDefaultMatch && method === 'PUT') {
|
|
2811
|
+
if (pathname === '/api/computers/{computer_id}/agents/codex/default' && method === 'PUT') {
|
|
2659
2812
|
try {
|
|
2660
|
-
const
|
|
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', '
|
|
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
|
-
|
|
2678
|
-
if (spaceAppInstallMatch && method === 'POST') {
|
|
2825
|
+
if (pathname === '/api/computers/{computer_id}/agents/codex/install' && method === 'POST') {
|
|
2679
2826
|
try {
|
|
2680
|
-
const cli =
|
|
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
|
|
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
|
-
|
|
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 (
|
|
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
|
-
|
|
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;
|