@moondeck/station 0.0.1 → 1.0.1
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/LICENSE +21 -0
- package/README.md +149 -2
- package/bin/uai-host.mjs +130 -0
- package/db/migrations/0000_host_tasks.sql +12 -0
- package/db/migrations/0001_host_ui.sql +11 -0
- package/db/migrations/0002_host_github_tokens.sql +8 -0
- package/db/migrations/0003_host_ssh_keys.sql +8 -0
- package/db/migrations/0004_host_owner_name.sql +1 -0
- package/db/migrations/0005_host_env_vars.sql +5 -0
- package/db/migrations/0006_host_project_env.sql +9 -0
- package/db/migrations/0007_host_github_token_kind.sql +7 -0
- package/db/migrations/0008_host_mcp_connections.sql +18 -0
- package/db/migrations/0009_host_agent_sessions.sql +15 -0
- package/db/migrations/0010_host_engine_accounts.sql +13 -0
- package/db/migrations/0011_codex_thread_id.sql +5 -0
- package/db/migrations/0012_agent_session_attach_key.sql +4 -0
- package/db/migrations/0013_github_credential_generations.sql +11 -0
- package/db/migrations/0014_host_inventory_event_index.sql +1 -0
- package/db/migrations/0015_host_settings.sql +9 -0
- package/db/migrations/0016_task_environment.sql +2 -0
- package/db/migrations/0017_engine_account_scope.sql +7 -0
- package/db/migrations/0018_engine_account_position.sql +3 -0
- package/db/migrations/0019_engine_account_store_ref.sql +3 -0
- package/db/migrations/0020_user_credential_store_ref.sql +6 -0
- package/db/migrations/0021_project_env_store_ref.sql +3 -0
- package/db/migrations/0022_task_project_ids.sql +5 -0
- package/db/migrations/0023_project_env_org.sql +4 -0
- package/db/migrations/0024_task_env_mode.sql +9 -0
- package/db/migrations/0025_project_env_authority.sql +147 -0
- package/db/migrations/0026_project_env_replica.sql +150 -0
- package/db/migrations/0027_cloud_credential_foundation.sql +51 -0
- package/db/migrations/0028_leased_engine_runtimes.sql +11 -0
- package/db/migrations/0029_task_shared_files_mode.sql +5 -0
- package/db/migrations/0030_credential_cleanup_restops.sql +12 -0
- package/db/migrations/0031_first_turn_receipts.sql +9 -0
- package/db/migrations/meta/_journal.json +230 -0
- package/db/schema.ts +871 -0
- package/images/standard/Dockerfile +347 -0
- package/images/standard/README.md +167 -0
- package/images/standard/container/code-server-settings.json +63 -0
- package/images/standard/container/corepack-version +1 -0
- package/images/standard/container/uai-init +704 -0
- package/images/standard/container/uai-materialize-runtimes +1527 -0
- package/images/standard/container/uai-task-forwarder.mjs +303 -0
- package/images/standard/tool-versions +2 -0
- package/lib/__fixtures__/skill-index/SKILL.md +10 -0
- package/lib/__fixtures__/skill-index/skills/accessibility-checklist.md +7 -0
- package/lib/__fixtures__/skill-index/skills/brand-voice.md +9 -0
- package/lib/__fixtures__/skill-index/skills/design-tokens.md +9 -0
- package/lib/agent-cli.ts +1507 -0
- package/lib/agent-errors.ts +22 -0
- package/lib/agent.ts +672 -0
- package/lib/agents/attach-compatibility.ts +51 -0
- package/lib/agents/claude.ts +918 -0
- package/lib/agents/codex-token-usage.fixture.json +130 -0
- package/lib/agents/codex.ts +1392 -0
- package/lib/agents/cursor-period-usage.ts +253 -0
- package/lib/agents/cursor.ts +562 -0
- package/lib/agents/dispatch.ts +78 -0
- package/lib/agents/durable-proc.ts +334 -0
- package/lib/agents/factory.ts +57 -0
- package/lib/agents/grok.ts +381 -0
- package/lib/agents/kimi.ts +329 -0
- package/lib/agents/machine-durable.ts +604 -0
- package/lib/agents/mock.ts +133 -0
- package/lib/agents/mode.ts +64 -0
- package/lib/agents/opencode.ts +508 -0
- package/lib/agents/output-source.ts +3 -0
- package/lib/agents/proc.ts +221 -0
- package/lib/agents/rate-limit.ts +337 -0
- package/lib/agents/registry.ts +182 -0
- package/lib/agents/session-state.ts +197 -0
- package/lib/agents/skill-index-preamble.fixture.ts +26 -0
- package/lib/agents/transport.ts +741 -0
- package/lib/agents/types.ts +254 -0
- package/lib/agents/uai.ts +106 -0
- package/lib/agents/usage.ts +44 -0
- package/lib/apple-one-off.ts +277 -0
- package/lib/apple-runtime-recycle.ts +237 -0
- package/lib/apple-uninstall-teardown.ts +224 -0
- package/lib/assignment-evidence.ts +94 -0
- package/lib/attachments.ts +48 -0
- package/lib/browser-testing.ts +1537 -0
- package/lib/capture-flow.ts +332 -0
- package/lib/cloud-credential-cleanup-adapter.ts +123 -0
- package/lib/cloud-credential-host.ts +512 -0
- package/lib/cloud-credential-leases.ts +2587 -0
- package/lib/cloud-credential-runtime.ts +1357 -0
- package/lib/cloud-state.ts +56 -0
- package/lib/codex-auth.ts +456 -0
- package/lib/command-db.ts +310 -0
- package/lib/compose-network-recovery.ts +283 -0
- package/lib/connection-secret-redaction.ts +25 -0
- package/lib/container-runtime.ts +1470 -0
- package/lib/credential-cleanup-inventory.ts +490 -0
- package/lib/credential-consumers/engine-anchor.ts +1880 -0
- package/lib/credential-consumers/engines.ts +797 -0
- package/lib/credential-consumers/files.ts +934 -0
- package/lib/credential-consumers/github.ts +702 -0
- package/lib/credential-consumers/mcp.ts +389 -0
- package/lib/credential-consumers/task-files.ts +127 -0
- package/lib/credential-consumers/task-secrets.ts +10 -0
- package/lib/credential-store-pickup.ts +149 -0
- package/lib/credential-store-scope.ts +112 -0
- package/lib/credential-store.ts +932 -0
- package/lib/credentials-activation-authority.ts +709 -0
- package/lib/credentials-purge.ts +1006 -0
- package/lib/custom-connection-capture.ts +395 -0
- package/lib/custom-connection-launch.ts +384 -0
- package/lib/custom-connection-runtime.ts +141 -0
- package/lib/db.ts +80 -0
- package/lib/disk-pressure.ts +42 -0
- package/lib/docker-exec.ts +156 -0
- package/lib/engine-accounts.ts +1970 -0
- package/lib/engine-connect.ts +321 -0
- package/lib/engine-credential-env.ts +52 -0
- package/lib/engine-login.ts +2933 -0
- package/lib/engines.ts +1342 -0
- package/lib/enrollment-state.ts +251 -0
- package/lib/env-file.ts +246 -0
- package/lib/env-schema.ts +937 -0
- package/lib/env.ts +195 -0
- package/lib/first-turn-receipts.ts +62 -0
- package/lib/fs-aliases.ts +49 -0
- package/lib/git-clone-credentials.ts +334 -0
- package/lib/git-diff.ts +530 -0
- package/lib/git-identity.ts +715 -0
- package/lib/github-git-auth.ts +207 -0
- package/lib/github-origin.ts +28 -0
- package/lib/github-tokens.ts +2439 -0
- package/lib/host-cloud-url.ts +62 -0
- package/lib/host-config.ts +279 -0
- package/lib/host-env.ts +424 -0
- package/lib/host-logs.ts +965 -0
- package/lib/internal-connections.ts +5 -0
- package/lib/keyed-promise-tail.ts +23 -0
- package/lib/launch-mode.ts +25 -0
- package/lib/lease.ts +239 -0
- package/lib/legacy-host.ts +769 -0
- package/lib/legacy-runtime-retirement.ts +424 -0
- package/lib/legacy-runtime-v1.fixture.ts +627 -0
- package/lib/machine-exec.ts +238 -0
- package/lib/machine-gateway-tunnel.ts +344 -0
- package/lib/machine-keys.ts +65 -0
- package/lib/machine-project-env-loader.ts +152 -0
- package/lib/machine-provider-aws.ts +923 -0
- package/lib/machine-provider-local.ts +338 -0
- package/lib/machine-provider.ts +90 -0
- package/lib/managed-activation-watcher.ts +72 -0
- package/lib/managed-install-owner-watcher.ts +55 -0
- package/lib/managed-operation-drain.ts +57 -0
- package/lib/managed-runtime.ts +4296 -0
- package/lib/managed-update-scheduler.ts +125 -0
- package/lib/mcp-capture.ts +865 -0
- package/lib/mcp-config-lock.ts +2 -0
- package/lib/mcp-connections.ts +959 -0
- package/lib/mcp-gateway.ts +2594 -0
- package/lib/obs.ts +512 -0
- package/lib/openrouter-budget.ts +247 -0
- package/lib/orchestrator.ts +11409 -0
- package/lib/person-capture.ts +327 -0
- package/lib/preview-ports.ts +85 -0
- package/lib/preview-sidecar.ts +331 -0
- package/lib/project-env-authority.ts +1319 -0
- package/lib/project-env-feature.ts +74 -0
- package/lib/project-env-legacy-tags.ts +130 -0
- package/lib/project-env-loader.ts +242 -0
- package/lib/project-env-replica.ts +2094 -0
- package/lib/project-env-schema-capture.ts +342 -0
- package/lib/project-env-staging.ts +148 -0
- package/lib/project-env-task-policy.ts +176 -0
- package/lib/project-env-task-replica.ts +1752 -0
- package/lib/release-feed.ts +61 -0
- package/lib/release-manifest.ts +710 -0
- package/lib/release-trust.ts +46 -0
- package/lib/repo-clone.ts +38 -0
- package/lib/routing/shadow.ts +261 -0
- package/lib/runner-boot.ts +10 -0
- package/lib/runtime-activation-tail.ts +279 -0
- package/lib/runtime-archive.ts +1216 -0
- package/lib/runtime-authority.ts +166 -0
- package/lib/runtime-guard.ts +36 -0
- package/lib/runtime-provider-state.ts +169 -0
- package/lib/runtime-reconnect.ts +86 -0
- package/lib/runtime-state.ts +1033 -0
- package/lib/safe-http.ts +364 -0
- package/lib/secrets-proxy/address.ts +95 -0
- package/lib/secrets-proxy/audit.ts +30 -0
- package/lib/secrets-proxy/ca-store.ts +200 -0
- package/lib/secrets-proxy/ca.ts +470 -0
- package/lib/secrets-proxy/container-steps.ts +138 -0
- package/lib/secrets-proxy/der.ts +273 -0
- package/lib/secrets-proxy/engine.ts +1633 -0
- package/lib/secrets-proxy/host.ts +93 -0
- package/lib/secrets-proxy/hostname.ts +57 -0
- package/lib/secrets-proxy/placeholders.ts +196 -0
- package/lib/secrets-proxy/policy.ts +150 -0
- package/lib/secrets-proxy/readiness.ts +148 -0
- package/lib/secrets-proxy/runtime.ts +582 -0
- package/lib/secrets-proxy/scrub.ts +559 -0
- package/lib/secrets-proxy/sidecar-view.ts +139 -0
- package/lib/secrets-proxy/task-scripts.ts +262 -0
- package/lib/secrets-proxy/task-trust.ts +605 -0
- package/lib/secrets-proxy/trust-watch.ts +80 -0
- package/lib/secrets.ts +71 -0
- package/lib/shared-files-s3.ts +976 -0
- package/lib/shared-files.ts +156 -0
- package/lib/sidecar-assign.ts +49 -0
- package/lib/sidecar-guard.ts +58 -0
- package/lib/sidecar-resume.ts +178 -0
- package/lib/sidecar.ts +69 -0
- package/lib/signin-box-credentials.ts +465 -0
- package/lib/signin-box.ts +114 -0
- package/lib/skill-index-example.fixture.ts +56 -0
- package/lib/skill-layout.ts +417 -0
- package/lib/skills.ts +489 -0
- package/lib/ssh.ts +401 -0
- package/lib/standard-image-cleanup.ts +117 -0
- package/lib/standard-image-pin.ts +207 -0
- package/lib/standard-image.ts +1697 -0
- package/lib/stopped-task-status-queue.ts +120 -0
- package/lib/task-container-cli.ts +269 -0
- package/lib/task-credential-cleanup.ts +752 -0
- package/lib/task-diff-sandbox.ts +464 -0
- package/lib/task-diff.ts +91 -0
- package/lib/task-env-forwarder.ts +228 -0
- package/lib/task-env-source.ts +249 -0
- package/lib/task-environment/anchor.ts +31 -0
- package/lib/task-environment/apple-container.ts +860 -0
- package/lib/task-environment/docker.ts +1344 -0
- package/lib/task-environment/index.ts +466 -0
- package/lib/task-environment/legacy-adoption.ts +467 -0
- package/lib/task-environment/machine-environment.ts +61 -0
- package/lib/task-environment/machine-task-up.ts +1227 -0
- package/lib/task-environment/machine.ts +974 -0
- package/lib/task-environment/registry.ts +59 -0
- package/lib/task-environment/self-identity.ts +20 -0
- package/lib/task-environment/self.ts +566 -0
- package/lib/task-environment/types.ts +642 -0
- package/lib/task-environment/workspace-files.ts +685 -0
- package/lib/task-environment/workspace-runtime.fixture.ts +50 -0
- package/lib/task-files-machine-retirement-outbox.ts +592 -0
- package/lib/task-files-machine-retirement.ts +116 -0
- package/lib/task-files-readiness.ts +127 -0
- package/lib/task-gateway-port.ts +22 -0
- package/lib/task-gateway-protocol.ts +190 -0
- package/lib/task-gateway-recovery.ts +47 -0
- package/lib/task-gateway-state.ts +512 -0
- package/lib/task-identity.ts +19 -0
- package/lib/task-inventory.ts +682 -0
- package/lib/task-machine-credentials-activation.ts +95 -0
- package/lib/task-machine-swap-safety.ts +729 -0
- package/lib/task-person-credentials.ts +76 -0
- package/lib/task-project-ops-host.ts +147 -0
- package/lib/task-project-ops.ts +1062 -0
- package/lib/task-secret-staging.ts +16 -0
- package/lib/task-status.ts +46 -0
- package/lib/task-token.ts +46 -0
- package/lib/token-broker-adopt.ts +344 -0
- package/lib/token-broker-lambda.ts +105 -0
- package/lib/token-broker-local.ts +392 -0
- package/lib/token-broker-revision.ts +310 -0
- package/lib/token-broker-ssm.ts +525 -0
- package/lib/token-broker.ts +218 -0
- package/lib/token-lanes/openai-oauth.ts +123 -0
- package/lib/token-lanes/registry.ts +45 -0
- package/lib/token-lanes/types.ts +163 -0
- package/lib/token-lanes/xai-oauth.ts +165 -0
- package/lib/transcript.ts +128 -0
- package/lib/tunnel-registry.ts +220 -0
- package/lib/tunnel-runtime.ts +235 -0
- package/lib/ulid.ts +16 -0
- package/lib/user-bus-env.ts +116 -0
- package/lib/user-credentials-store.ts +435 -0
- package/lib/vnode-pressure.ts +142 -0
- package/package.json +115 -8
- package/runner/runner.mjs +442 -0
- package/scripts/agent/_common.sh +633 -0
- package/scripts/agent/env-hook.sh +626 -0
- package/scripts/agent/moondeck-cli.sh +5 -0
- package/scripts/agent/task-down.sh +291 -0
- package/scripts/agent/task-status.sh +70 -0
- package/scripts/agent/task-up.sh +3761 -0
- package/scripts/agent/uai-env-exec.sh +26 -0
- package/scripts/install/darwin.ts +1048 -0
- package/scripts/install/linux.ts +958 -0
- package/scripts/install/types.ts +78 -0
- package/scripts/install/util.ts +254 -0
- package/scripts/install/win.ts +143 -0
- package/src/activation-respawn.ts +155 -0
- package/src/apple-tunnel-route.ts +104 -0
- package/src/bridge-diagnostics.ts +39 -0
- package/src/capture-wire.ts +102 -0
- package/src/cli.ts +2585 -0
- package/src/cloud-credentials.ts +2144 -0
- package/src/cloud-origins.ts +23 -0
- package/src/credential-location.ts +248 -0
- package/src/credential-wire-validation.ts +25 -0
- package/src/custom-connections.ts +237 -0
- package/src/engine-account-paths.ts +11 -0
- package/src/engine-models.ts +39 -0
- package/src/engine-usage.ts +104 -0
- package/src/enrollment-next-steps.ts +24 -0
- package/src/event-outbox.ts +185 -0
- package/src/index.ts +2907 -0
- package/src/load-env.ts +97 -0
- package/src/machine-tunnel-route.ts +156 -0
- package/src/main.ts +6363 -0
- package/src/never-proxied-domains.ts +31 -0
- package/src/outbound-frame.ts +31 -0
- package/src/paths.ts +81 -0
- package/src/project-env-latest.ts +130 -0
- package/src/project-env-policy.ts +396 -0
- package/src/project-env-wording.ts +53 -0
- package/src/protocol.ts +5550 -0
- package/src/runtime-bootstrap.ts +165 -0
- package/src/station-identity.ts +96 -0
- package/src/task-env-names.ts +28 -0
- package/src/task-secrets.ts +75 -0
- package/src/tunnel-dial.ts +102 -0
- package/src/ui/server.ts +852 -0
- package/src/ui/types.ts +173 -0
- package/src/workspace-path-policy.ts +41 -0
- package/ui/app.js +1102 -0
- package/ui/index.html +90 -0
- package/ui/style.css +685 -0
- package/ui/uai-favicon.svg +21 -0
- package/ui/uai-logo-black.svg +9 -0
- package/ui/uai-wheel.svg +9 -0
- package/bin/moondeck-station.mjs +0 -3
|
@@ -0,0 +1,253 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Cursor account room, read from the same dashboard calls the CLI's `/usage`
|
|
3
|
+
* screen uses. The headless stream does not report it.
|
|
4
|
+
*
|
|
5
|
+
* The CLI session access token (not the user API key, and not a team admin
|
|
6
|
+
* key) calls `aiserver.v1.DashboardService/GetPlanInfo` and
|
|
7
|
+
* `GetCurrentPeriodUsage` on api2.cursor.sh. A failure, a missing token, or a
|
|
8
|
+
* shape we do not recognise produces no report. Nothing here logs the token,
|
|
9
|
+
* an email, or a user id.
|
|
10
|
+
*/
|
|
11
|
+
|
|
12
|
+
import type { TaskEnvironmentAgentSessionSurface } from "../task-environment/types";
|
|
13
|
+
import type { AgentAccountUsage } from "./types";
|
|
14
|
+
|
|
15
|
+
/** Same lines Codex uses for a window that is full or close to full. */
|
|
16
|
+
export const CURSOR_PERIOD_WARNING_PERCENT = 90;
|
|
17
|
+
export const CURSOR_PERIOD_LIMITED_PERCENT = 100;
|
|
18
|
+
|
|
19
|
+
/** One probe per leased credential, including a failed one. */
|
|
20
|
+
export const CURSOR_PERIOD_PROBE_INTERVAL_MS = 5 * 60 * 1000;
|
|
21
|
+
|
|
22
|
+
/** One bound for the whole probe: reading auth.json and both dashboard calls.
|
|
23
|
+
* A hung read must not hold turn_complete after the CLI has finished. */
|
|
24
|
+
export const CURSOR_PERIOD_PROBE_TIMEOUT_MS = 4_000;
|
|
25
|
+
|
|
26
|
+
export const CURSOR_PERIOD_USAGE_LOG = "cursor period usage unavailable";
|
|
27
|
+
|
|
28
|
+
export const CURSOR_AUTH_JSON = "/home/node/.config/cursor/auth.json";
|
|
29
|
+
|
|
30
|
+
const DASHBOARD_ORIGIN = "https://api2.cursor.sh";
|
|
31
|
+
const WEEKLY_PERIOD = "INCLUDED_USAGE_PERIOD_WEEKLY";
|
|
32
|
+
const WEEK_MINUTES = 7 * 24 * 60;
|
|
33
|
+
|
|
34
|
+
const probedAt = new Map<string, number>();
|
|
35
|
+
|
|
36
|
+
/** Whether this credential may be probed at `now`. */
|
|
37
|
+
export function cursorPeriodProbeDue(credentialId: string, now: number): boolean {
|
|
38
|
+
const at = probedAt.get(credentialId);
|
|
39
|
+
return at === undefined || now - at >= CURSOR_PERIOD_PROBE_INTERVAL_MS;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Count a probe, successful or not, so the next one waits out the interval. */
|
|
43
|
+
export function markCursorPeriodProbed(credentialId: string, now: number): void {
|
|
44
|
+
probedAt.set(credentialId, now);
|
|
45
|
+
}
|
|
46
|
+
|
|
47
|
+
/** Test hook. Production never clears the gate except by process restart. */
|
|
48
|
+
export function resetCursorPeriodProbes(): void {
|
|
49
|
+
probedAt.clear();
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** The CLI session access token from `auth.json`, or null. The raw file is not returned. */
|
|
53
|
+
export function cursorAccessTokenFromAuthJson(text: string): string | null {
|
|
54
|
+
try {
|
|
55
|
+
const parsed: unknown = JSON.parse(text);
|
|
56
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) return null;
|
|
57
|
+
const token = (parsed as { accessToken?: unknown }).accessToken;
|
|
58
|
+
return typeof token === "string" && token.length > 0 ? token : null;
|
|
59
|
+
} catch {
|
|
60
|
+
return null;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
function isObj(value: unknown): value is Record<string, unknown> {
|
|
65
|
+
return typeof value === "object" && value !== null && !Array.isArray(value);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function epochMs(value: unknown): number | undefined {
|
|
69
|
+
const n =
|
|
70
|
+
typeof value === "number"
|
|
71
|
+
? value
|
|
72
|
+
: typeof value === "string" && /^[0-9]+$/.test(value)
|
|
73
|
+
? Number(value)
|
|
74
|
+
: Number.NaN;
|
|
75
|
+
return Number.isSafeInteger(n) && n > 0 ? n : undefined;
|
|
76
|
+
}
|
|
77
|
+
|
|
78
|
+
/** A 0–100 share, as the dashboard sends it. Anything else is not a window. */
|
|
79
|
+
function percent(value: unknown): number | undefined {
|
|
80
|
+
if (typeof value !== "number" || !Number.isFinite(value) || value < 0 || value > 100) {
|
|
81
|
+
return undefined;
|
|
82
|
+
}
|
|
83
|
+
return value;
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
function onDemandSpend(period: Record<string, unknown>): boolean {
|
|
87
|
+
if (!isObj(period.spendLimitUsage)) return false;
|
|
88
|
+
for (const key of ["individualUsed", "pooledUsed", "totalSpend", "overallUsed"]) {
|
|
89
|
+
const value = period.spendLimitUsage[key];
|
|
90
|
+
if (typeof value === "number" && Number.isFinite(value) && value > 0) return true;
|
|
91
|
+
}
|
|
92
|
+
return false;
|
|
93
|
+
}
|
|
94
|
+
|
|
95
|
+
/**
|
|
96
|
+
* Map a GetPlanInfo body and a GetCurrentPeriodUsage body into one account
|
|
97
|
+
* observation. Null when the percent fields are missing or not 0–100, so a
|
|
98
|
+
* schema change sends nothing.
|
|
99
|
+
*/
|
|
100
|
+
export function mapCursorPeriodUsage(
|
|
101
|
+
plan: unknown,
|
|
102
|
+
period: unknown,
|
|
103
|
+
observedAt: number,
|
|
104
|
+
): AgentAccountUsage | null {
|
|
105
|
+
if (!isObj(period) || !isObj(period.planUsage)) return null;
|
|
106
|
+
const total = percent(period.planUsage.totalPercentUsed);
|
|
107
|
+
const api = percent(period.planUsage.apiPercentUsed);
|
|
108
|
+
if (total === undefined || api === undefined) return null;
|
|
109
|
+
|
|
110
|
+
const planInfo = isObj(plan) && isObj(plan.planInfo) ? plan.planInfo : {};
|
|
111
|
+
const weekly = planInfo.includedUsagePeriod === WEEKLY_PERIOD;
|
|
112
|
+
const resetsAt = epochMs(planInfo.includedUsageResetsAt);
|
|
113
|
+
const window = (name: string, usedPercent: number) => ({
|
|
114
|
+
window: name,
|
|
115
|
+
...(weekly ? { windowMinutes: WEEK_MINUTES } : {}),
|
|
116
|
+
usedPercent,
|
|
117
|
+
...(resetsAt === undefined ? {} : { resetsAt }),
|
|
118
|
+
});
|
|
119
|
+
const windows = [window("included", total), window("api", api)];
|
|
120
|
+
const limited = windows.filter((item) => item.usedPercent >= CURSOR_PERIOD_LIMITED_PERCENT);
|
|
121
|
+
const status =
|
|
122
|
+
limited.length > 0
|
|
123
|
+
? "limited"
|
|
124
|
+
: windows.some((item) => item.usedPercent >= CURSOR_PERIOD_WARNING_PERCENT)
|
|
125
|
+
? "warning"
|
|
126
|
+
: "allowed";
|
|
127
|
+
const limitedResets = limited
|
|
128
|
+
.map((item) => item.resetsAt)
|
|
129
|
+
.filter((reset): reset is number => reset !== undefined);
|
|
130
|
+
const usage: AgentAccountUsage = {
|
|
131
|
+
status,
|
|
132
|
+
...(limitedResets.length > 0 ? { resetsAt: Math.max(...limitedResets) } : {}),
|
|
133
|
+
...(onDemandSpend(period) ? { usingOverage: true } : {}),
|
|
134
|
+
windows,
|
|
135
|
+
source: "cursor-period-usage",
|
|
136
|
+
observedAt,
|
|
137
|
+
coverage: { kind: "all-windows" },
|
|
138
|
+
};
|
|
139
|
+
return usage;
|
|
140
|
+
}
|
|
141
|
+
|
|
142
|
+
export async function readCursorAuthFromEnvironment(
|
|
143
|
+
environment: TaskEnvironmentAgentSessionSurface,
|
|
144
|
+
signal?: AbortSignal,
|
|
145
|
+
): Promise<string | null> {
|
|
146
|
+
if (signal?.aborted) return null;
|
|
147
|
+
let child: Awaited<ReturnType<TaskEnvironmentAgentSessionSurface["spawnSession"]>> | undefined;
|
|
148
|
+
const kill = (): void => {
|
|
149
|
+
if (!child) return;
|
|
150
|
+
void child.terminate("SIGKILL").catch(() => {});
|
|
151
|
+
};
|
|
152
|
+
signal?.addEventListener("abort", kill);
|
|
153
|
+
try {
|
|
154
|
+
child = await environment.spawnSession({
|
|
155
|
+
argv: ["cat", "--", CURSOR_AUTH_JSON],
|
|
156
|
+
user: "node",
|
|
157
|
+
projectEnvProfile: "task",
|
|
158
|
+
env: {},
|
|
159
|
+
maxOutputBytes: 32_768,
|
|
160
|
+
});
|
|
161
|
+
if (signal?.aborted) {
|
|
162
|
+
kill();
|
|
163
|
+
return null;
|
|
164
|
+
}
|
|
165
|
+
let text = "";
|
|
166
|
+
for await (const chunk of child.stdout) {
|
|
167
|
+
if (signal?.aborted) {
|
|
168
|
+
kill();
|
|
169
|
+
return null;
|
|
170
|
+
}
|
|
171
|
+
text += Buffer.from(chunk).toString("utf8");
|
|
172
|
+
if (text.length > 32_768) {
|
|
173
|
+
kill();
|
|
174
|
+
return null;
|
|
175
|
+
}
|
|
176
|
+
}
|
|
177
|
+
if (signal?.aborted) {
|
|
178
|
+
kill();
|
|
179
|
+
return null;
|
|
180
|
+
}
|
|
181
|
+
const exit = await child.completion;
|
|
182
|
+
return !signal?.aborted && exit.exitCode === 0 ? text : null;
|
|
183
|
+
} catch {
|
|
184
|
+
return null;
|
|
185
|
+
} finally {
|
|
186
|
+
signal?.removeEventListener("abort", kill);
|
|
187
|
+
}
|
|
188
|
+
}
|
|
189
|
+
|
|
190
|
+
async function postDashboard(
|
|
191
|
+
fetchImpl: typeof fetch,
|
|
192
|
+
token: string,
|
|
193
|
+
method: string,
|
|
194
|
+
signal: AbortSignal,
|
|
195
|
+
): Promise<unknown | null> {
|
|
196
|
+
if (signal.aborted) return null;
|
|
197
|
+
try {
|
|
198
|
+
const response = await fetchImpl(
|
|
199
|
+
`${DASHBOARD_ORIGIN}/aiserver.v1.DashboardService/${method}`,
|
|
200
|
+
{
|
|
201
|
+
method: "POST",
|
|
202
|
+
headers: {
|
|
203
|
+
Authorization: `Bearer ${token}`,
|
|
204
|
+
"Content-Type": "application/json",
|
|
205
|
+
"Connect-Protocol-Version": "1",
|
|
206
|
+
},
|
|
207
|
+
body: "{}",
|
|
208
|
+
signal,
|
|
209
|
+
},
|
|
210
|
+
);
|
|
211
|
+
if (response.status !== 200) return null;
|
|
212
|
+
return (await response.json()) as unknown;
|
|
213
|
+
} catch {
|
|
214
|
+
return null;
|
|
215
|
+
}
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
/**
|
|
219
|
+
* Read both dashboard methods and map them. Null on a non-200, a timeout, a
|
|
220
|
+
* bad body, or a shape we do not recognise. The token is sent only as the
|
|
221
|
+
* bearer for these two calls.
|
|
222
|
+
*/
|
|
223
|
+
export async function readCursorPeriodUsage(args: {
|
|
224
|
+
token: string;
|
|
225
|
+
fetchImpl?: typeof fetch;
|
|
226
|
+
timeoutMs?: number;
|
|
227
|
+
/** When set, this call does not start a second timer. The caller's deadline covers it. */
|
|
228
|
+
signal?: AbortSignal;
|
|
229
|
+
now?: number;
|
|
230
|
+
}): Promise<AgentAccountUsage | null> {
|
|
231
|
+
const fetchImpl = args.fetchImpl ?? fetch;
|
|
232
|
+
const owned = new AbortController();
|
|
233
|
+
const onParentAbort = (): void => owned.abort();
|
|
234
|
+
if (args.signal) {
|
|
235
|
+
if (args.signal.aborted) owned.abort();
|
|
236
|
+
else args.signal.addEventListener("abort", onParentAbort);
|
|
237
|
+
}
|
|
238
|
+
const timer =
|
|
239
|
+
args.signal === undefined
|
|
240
|
+
? setTimeout(() => owned.abort(), args.timeoutMs ?? CURSOR_PERIOD_PROBE_TIMEOUT_MS)
|
|
241
|
+
: undefined;
|
|
242
|
+
try {
|
|
243
|
+
const [plan, period] = await Promise.all([
|
|
244
|
+
postDashboard(fetchImpl, args.token, "GetPlanInfo", owned.signal),
|
|
245
|
+
postDashboard(fetchImpl, args.token, "GetCurrentPeriodUsage", owned.signal),
|
|
246
|
+
]);
|
|
247
|
+
if (owned.signal.aborted || plan === null || period === null) return null;
|
|
248
|
+
return mapCursorPeriodUsage(plan, period, args.now ?? Date.now());
|
|
249
|
+
} finally {
|
|
250
|
+
if (timer !== undefined) clearTimeout(timer);
|
|
251
|
+
args.signal?.removeEventListener("abort", onParentAbort);
|
|
252
|
+
}
|
|
253
|
+
}
|