carthagent 0.0.0-stage → 1.0.7
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 +219 -2
- package/bin/ctg.mjs +736 -0
- package/docs/budgets.md +72 -0
- package/docs/compaction.md +34 -0
- package/docs/context-efficiency-plan.md +24 -0
- package/docs/context-efficiency-review.md +27 -0
- package/docs/context-efficiency.md +49 -0
- package/docs/deepseek-jev-benchmark-plan.md +70 -0
- package/docs/deepseek-jev-benchmark-repair-plan.md +20 -0
- package/docs/deepseek-jev-benchmark-restart.md +9 -0
- package/docs/deepseek-jev-benchmark-results.md +77 -0
- package/docs/deepseek-jev-benchmark-running.md +60 -0
- package/docs/deepseek-jev-benchmark-v3-results.md +41 -0
- package/docs/deepseek-jev-benchmark-v4-protocol.md +15 -0
- package/docs/deepseek-jev-benchmark-v4-results.md +119 -0
- package/docs/deepseek-jev-http400-diagnosis.md +45 -0
- package/docs/delivery-review-evidence.md +53 -0
- package/docs/delivery-timeout-repair-plan.md +51 -0
- package/docs/evaluation-readiness.md +31 -0
- package/docs/evaluation.md +70 -0
- package/docs/extending.md +354 -0
- package/docs/glm-pilot-execution.md +9 -0
- package/docs/glm-pilot-plan.md +15 -0
- package/docs/glm-pilot-results.md +71 -0
- package/docs/graph-context-optimization.md +15 -0
- package/docs/jev-repository-pilot-plan.md +25 -0
- package/docs/jev-repository-pilot-protocol.md +39 -0
- package/docs/jev-repository-pilot-restart.md +7 -0
- package/docs/jev-repository-pilot-results.md +79 -0
- package/docs/jev.md +32 -0
- package/docs/model-registry.md +25 -0
- package/docs/native-compatibility.md +117 -0
- package/docs/opencode-archive.md +9 -0
- package/docs/opencode-migration.md +115 -0
- package/docs/opencode-runtime.md +94 -0
- package/docs/opencode-workspace.md +31 -0
- package/docs/owned-runtime-workflow-registration.md +17 -0
- package/docs/plan-advisor-evaluation.md +38 -0
- package/docs/planning-startup-fix-plan.md +23 -0
- package/docs/planning-startup-fix-review.md +24 -0
- package/docs/planning.md +118 -0
- package/docs/production-merge-review.md +23 -0
- package/docs/quality.md +35 -0
- package/docs/repository-benchmark-design.md +111 -0
- package/docs/review-assurance.md +9 -0
- package/docs/review-experiment-results.md +59 -0
- package/docs/tested-planning-roadmap.md +58 -0
- package/docs/tested-planning.md +58 -0
- package/docs/tui-pickers.md +9 -0
- package/docs/webdriver.md +83 -0
- package/docs/workflow-modes.md +26 -0
- package/docs/workflow.d2 +17 -0
- package/docs/workflow.svg +149 -0
- package/docs/workspace-coordination.md +9 -0
- package/evaluate.mjs +249 -0
- package/extensions/codegraph.ts +36 -0
- package/extensions/delivery.ts +819 -0
- package/extensions/experiential.ts +21 -0
- package/extensions/subscriptions.ts +21 -0
- package/guidance/profiles/authenticated-web.json +24 -0
- package/guidance/profiles/data-visualization.json +25 -0
- package/guidance/profiles/database-change.json +24 -0
- package/guidance/profiles/external-api.json +24 -0
- package/guidance/profiles/transactional-data.json +25 -0
- package/guidance/profiles/web-application.json +26 -0
- package/lib/acceptance-review.mjs +4 -0
- package/lib/browser-bin.mjs +160 -0
- package/lib/browser-errors.mjs +54 -0
- package/lib/browser.mjs +219 -0
- package/lib/budget.mjs +37 -0
- package/lib/cdp.mjs +221 -0
- package/lib/cloud/client.mjs +275 -0
- package/lib/cloud/control-plane.mjs +350 -0
- package/lib/cloud/gateway.mjs +380 -0
- package/lib/cloud/http.mjs +135 -0
- package/lib/cloud/ledger.mjs +215 -0
- package/lib/codegraph-query.mjs +60 -0
- package/lib/codegraph.mjs +378 -0
- package/lib/context-archive.mjs +18 -0
- package/lib/delivery-context.mjs +132 -0
- package/lib/delivery-guidance.mjs +9 -0
- package/lib/delivery-review.mjs +94 -0
- package/lib/delivery.mjs +610 -0
- package/lib/engine.mjs +137 -0
- package/lib/guidance.mjs +71 -0
- package/lib/jev-advisor.mjs +47 -0
- package/lib/opencode/archive.mjs +122 -0
- package/lib/opencode/compatibility.mjs +19 -0
- package/lib/opencode/credentials.mjs +205 -0
- package/lib/opencode/evaluation-budget-plugin/index.ts +147 -0
- package/lib/opencode/evaluation.mjs +242 -0
- package/lib/opencode/file-lock.mjs +86 -0
- package/lib/opencode/headless.mjs +200 -0
- package/lib/opencode/review.mjs +213 -0
- package/lib/opencode/runtime.mjs +246 -0
- package/lib/opencode/service.mjs +249 -0
- package/lib/opencode/session-options.mjs +60 -0
- package/lib/opencode/workspace.mjs +133 -0
- package/lib/openrouter-jev.mjs +37 -0
- package/lib/outcome-contract.mjs +21 -0
- package/lib/plan-advisor.mjs +38 -0
- package/lib/planning-access.mjs +7 -0
- package/lib/planning-advice.mjs +28 -0
- package/lib/planning-discovery.mjs +14 -0
- package/lib/planning-guide.mjs +34 -0
- package/lib/planning-view.mjs +57 -0
- package/lib/planning.mjs +155 -0
- package/lib/probe-diagnostics.mjs +13 -0
- package/lib/providers/experiential.mjs +292 -0
- package/lib/providers/model-catalog.json +700 -0
- package/lib/providers/model-catalog.mjs +7 -0
- package/lib/providers/names.mjs +13 -0
- package/lib/providers/subscription-models.mjs +483 -0
- package/lib/reports.mjs +80 -0
- package/lib/review-state.mjs +49 -0
- package/lib/review.mjs +114 -0
- package/lib/scenarios.mjs +99 -0
- package/lib/ship-auth.mjs +44 -0
- package/lib/ship-jev.mjs +59 -0
- package/lib/ship-telemetry.mjs +183 -0
- package/lib/tui/ansi.mjs +255 -0
- package/lib/tui/app.mjs +2445 -0
- package/lib/tui/auth.mjs +249 -0
- package/lib/tui/carthage.mjs +109 -0
- package/lib/tui/clipboard.mjs +122 -0
- package/lib/tui/d2.mjs +431 -0
- package/lib/tui/feed.mjs +467 -0
- package/lib/tui/framing.mjs +80 -0
- package/lib/tui/glyphs.mjs +172 -0
- package/lib/tui/hash-worker.mjs +11 -0
- package/lib/tui/mock-engine.mjs +222 -0
- package/lib/tui/models.mjs +73 -0
- package/lib/tui/picker.mjs +18 -0
- package/lib/tui/theme.mjs +663 -0
- package/lib/tui/title.mjs +7 -0
- package/lib/update-channel.mjs +62 -0
- package/lib/update.mjs +230 -0
- package/lib/webdriver.mjs +82 -0
- package/lib/workflow.mjs +88 -0
- package/lib/workspace.mjs +39 -0
- package/metadata.json +8 -0
- package/package.json +94 -4
- package/plugins/carthagent/index.ts +3 -0
- package/plugins/carthagent/package.json +28 -0
- package/plugins/carthagent/src/archive-rpc.ts +46 -0
- package/plugins/carthagent/src/bootstrap.ts +38 -0
- package/plugins/carthagent/src/index.ts +62 -0
- package/plugins/carthagent/src/pull-request-rpc.ts +54 -0
- package/plugins/carthagent/src/rpc.ts +76 -0
- package/plugins/carthagent/src/server/archive-rpc.ts +327 -0
- package/plugins/carthagent/src/server/budgets.ts +21 -0
- package/plugins/carthagent/src/server/codegraph.ts +46 -0
- package/plugins/carthagent/src/server/delivery.ts +488 -0
- package/plugins/carthagent/src/server/device-auth.ts +91 -0
- package/plugins/carthagent/src/server/native-delivery.ts +26 -0
- package/plugins/carthagent/src/server/orchestration.ts +56 -0
- package/plugins/carthagent/src/server/policy.ts +24 -0
- package/plugins/carthagent/src/server/providers.ts +424 -0
- package/plugins/carthagent/src/server/pull-request-rpc.ts +387 -0
- package/plugins/carthagent/src/server/session-controls.ts +82 -0
- package/plugins/carthagent/src/server/telemetry.ts +92 -0
- package/plugins/carthagent/src/server/workspace-rpc.ts +349 -0
- package/plugins/carthagent/src/session-rpc.ts +31 -0
- package/plugins/carthagent/src/testing.ts +85 -0
- package/plugins/carthagent/src/workspace-rpc.ts +133 -0
- package/plugins/carthagent/test-plugin/index.ts +4 -0
- package/plugins/carthagent/test-plugin/package.json +1 -0
- package/plugins/carthagent/tsconfig.json +19 -0
- package/plugins/carthagent/tui.ts +1 -0
- package/prompts/delivery.md +7 -0
- package/public/index.html +983 -0
- package/scenarios/cli/grade_cli.py +69 -0
- package/scenarios/cli/scenario.json +21 -0
- package/scenarios/game/baseline/minecraft.py +193 -0
- package/scenarios/game/context.md +1 -0
- package/scenarios/game/grade_minecraft_clone.py +603 -0
- package/scenarios/game/requirements.txt +2 -0
- package/scenarios/game/scenario.json +33 -0
- package/server.mjs +341 -0
- package/skills/game-development/SKILL.md +36 -0
- package/skills/game-development/assets/ursina_starter.py +98 -0
- package/skills/game-development/scripts/verify_ursina.py +80 -0
- package/skills/software-delivery/SKILL.md +50 -0
- package/skills/typescript-delivery/SKILL.md +69 -0
- package/tests/acceptance-review.test.mjs +13 -0
- package/tests/auth.test.mjs +234 -0
- package/tests/bounded-summary.test.mjs +68 -0
- package/tests/browser-bin.test.mjs +91 -0
- package/tests/browser.test.mjs +96 -0
- package/tests/budget-engine.test.mjs +110 -0
- package/tests/budget.test.mjs +23 -0
- package/tests/cli.test.mjs +245 -0
- package/tests/clipboard.test.mjs +79 -0
- package/tests/cloud-client.test.mjs +129 -0
- package/tests/cloud-control-plane.test.mjs +144 -0
- package/tests/cloud-gateway.test.mjs +147 -0
- package/tests/cloud-http.test.mjs +102 -0
- package/tests/cloud-ledger.test.mjs +75 -0
- package/tests/codegraph-query.test.mjs +66 -0
- package/tests/codegraph.test.mjs +142 -0
- package/tests/delivery-context.test.mjs +66 -0
- package/tests/delivery-review.test.mjs +139 -0
- package/tests/delivery.test.mjs +343 -0
- package/tests/engine-api.mjs +24 -0
- package/tests/experiential-extension.test.mjs +37 -0
- package/tests/experiential.test.mjs +300 -0
- package/tests/extension.test.mjs +921 -0
- package/tests/fixtures/archive-plugin/index.ts +10 -0
- package/tests/guidance.test.mjs +78 -0
- package/tests/model-registry.test.mjs +68 -0
- package/tests/models.test.mjs +70 -0
- package/tests/openai-idempotency.test.mjs +99 -0
- package/tests/opencode-archive-rpc.test.mjs +82 -0
- package/tests/opencode-delivery.test.mjs +231 -0
- package/tests/opencode-providers.test.mjs +414 -0
- package/tests/opencode-runtime.test.mjs +455 -0
- package/tests/opencode-session-controls.test.mjs +115 -0
- package/tests/opencode-session-options.test.mjs +26 -0
- package/tests/opencode-workspace.test.mjs +339 -0
- package/tests/outcome-contract.test.mjs +17 -0
- package/tests/picker.test.mjs +34 -0
- package/tests/plan-advisor.test.mjs +88 -0
- package/tests/plan-review-loop.test.mjs +53 -0
- package/tests/planning-advice.test.mjs +27 -0
- package/tests/planning-contract.test.mjs +65 -0
- package/tests/planning-startup.test.mjs +38 -0
- package/tests/planning-view.test.mjs +33 -0
- package/tests/planning.test.mjs +223 -0
- package/tests/probe-diagnostics.test.mjs +21 -0
- package/tests/pull-request-rpc.test.mjs +366 -0
- package/tests/review-state.test.mjs +32 -0
- package/tests/review.test.mjs +127 -0
- package/tests/server.test.mjs +252 -0
- package/tests/ship-jev.test.mjs +77 -0
- package/tests/ship-telemetry.test.mjs +333 -0
- package/tests/standard-workflow.test.mjs +28 -0
- package/tests/subscription-models.test.mjs +416 -0
- package/tests/subscriptions-extension.test.mjs +27 -0
- package/tests/terminal-title.test.mjs +13 -0
- package/tests/test_grade_minecraft_clone.py +199 -0
- package/tests/test_starter.py +78 -0
- package/tests/theme.test.mjs +175 -0
- package/tests/tui-copy-paste.test.mjs +54 -0
- package/tests/tui.test.mjs +808 -0
- package/tests/update-channels.test.mjs +129 -0
- package/tests/update.test.mjs +175 -0
- package/tests/webdriver.test.mjs +31 -0
- package/tests/workflow.test.mjs +47 -0
- package/tests/workspace-rpc.test.mjs +236 -0
- package/tests/workspace.test.mjs +24 -0
- package/tools/benchmark/advisor.test.mjs +7 -0
- package/tools/benchmark/advisor.ts +27 -0
- package/tools/benchmark/campaign.mjs +45 -0
- package/tools/benchmark/diagnose-request.mjs +40 -0
- package/tools/benchmark/eval-common.mjs +32 -0
- package/tools/benchmark/eval-ledger.mjs +22 -0
- package/tools/benchmark/eval-shop.mjs +26 -0
- package/tools/benchmark/eval-voxel.mjs +20 -0
- package/tools/benchmark/evaluate.mjs +24 -0
- package/tools/benchmark/evaluators.test.mjs +13 -0
- package/tools/benchmark/fixtures/shop/server.mjs +18 -0
- package/tools/benchmark/fixtures/shop/ui.html +8 -0
- package/tools/benchmark/freeze.mjs +30 -0
- package/tools/benchmark/gateway.mjs +91 -0
- package/tools/benchmark/gateway.test.mjs +38 -0
- package/tools/benchmark/generator.mjs +11 -0
- package/tools/benchmark/generator.test.mjs +26 -0
- package/tools/benchmark/guard.test.mjs +133 -0
- package/tools/benchmark/guard.ts +136 -0
- package/tools/benchmark/native-plugin/index.ts +24 -0
- package/tools/benchmark/planning-startup.test.mjs +47 -0
- package/tools/benchmark/preflight-live.mjs +31 -0
- package/tools/benchmark/preflight.mjs +22 -0
- package/tools/benchmark/replay-diagnostic.mjs +23 -0
- package/tools/benchmark/runner.mjs +141 -0
- package/tools/benchmark/runner.test.mjs +93 -0
- package/tools/benchmark/runtime.mjs +23 -0
- package/tools/benchmark/selftest.mjs +47 -0
- package/tools/benchmark/smoke-jev.mjs +5 -0
- package/tools/benchmark/tasks.mjs +20 -0
- package/tools/benchmark/transport.mjs +65 -0
- package/tools/benchmark/transport.test.mjs +44 -0
- package/tools/benchmark/upstream-error.mjs +18 -0
- package/tools/benchmark/upstream-error.test.mjs +10 -0
- package/tools/browser-check.mjs +109 -0
- package/tools/check-syntax.mjs +27 -0
- package/tools/evaluate-plan-advisor.mjs +20 -0
- package/tools/native-fixture.mjs +194 -0
- package/tools/native-pty-smoke.py +108 -0
- package/tools/native-report-plugin/index.ts +50 -0
- package/tools/native-report-plugin/package.json +7 -0
- package/tools/patch-compaction.mjs +24 -0
- package/tools/patch-model-catalog.mjs +12 -0
- package/tools/patch-print-shutdown.mjs +39 -0
- package/tools/repo-pilot/evaluator.cjs +32 -0
- package/tools/repo-pilot/reference.mjs +43 -0
- package/tools/repo-pilot/runner.mjs +85 -0
- package/tools/repo-pilot/sandbox-entry.mjs +12 -0
- package/tools/repo-pilot/tasks.mjs +20 -0
- package/tools/repo-pilot/type-evaluator.cjs +6 -0
- package/tools/test-native-controls.mjs +84 -0
- package/tools/test-native-delivery-host.mjs +307 -0
- package/tools/test-native-evaluation.mjs +46 -0
- package/tools/test-native-host.mjs +385 -0
- package/tools/test-native-outcomes.mjs +73 -0
- package/tools/test-native-package.mjs +70 -0
- package/tools/test-native-pty.mjs +20 -0
- package/tools/test-native-report.mjs +88 -0
- package/tools/test-native-swe-host.mjs +132 -0
- package/tools/test-native-swe.mjs +122 -0
- package/tools/test-picker-pty.py +45 -0
- package/tools/test-python.mjs +29 -0
- package/tools/test-webdriver.mjs +54 -0
- package/tools/verify-release-version.mjs +37 -0
- package/tools/webdriver-check.mjs +52 -0
- package/vendor/agent/bounded-summary.mjs +88 -0
- package/vendor/agent/chunks/anthropic-messages-VWZZOSJQ.js +6 -0
- package/vendor/agent/chunks/anthropic.js +75 -0
- package/vendor/agent/chunks/azure-openai-responses-BJLAM6CD.js +2 -0
- package/vendor/agent/chunks/bedrock-converse-stream.js +74 -0
- package/vendor/agent/chunks/chord-context.js +96 -0
- package/vendor/agent/chunks/chunk-4L3WN2XY.js +2 -0
- package/vendor/agent/chunks/chunk-6CYCI3EJ.js +12 -0
- package/vendor/agent/chunks/chunk-6R7UI4IY.js +2 -0
- package/vendor/agent/chunks/chunk-7ZC4UXSL.js +2 -0
- package/vendor/agent/chunks/chunk-AXIIZGTV.js +2 -0
- package/vendor/agent/chunks/chunk-CLNPYIDP.js +42 -0
- package/vendor/agent/chunks/chunk-DRQDKNXA.js +2 -0
- package/vendor/agent/chunks/chunk-GMWCTUPB.js +2 -0
- package/vendor/agent/chunks/chunk-IDDQWTHI.js +2 -0
- package/vendor/agent/chunks/chunk-JVUZSMYM.js +1548 -0
- package/vendor/agent/chunks/chunk-L2ZPNLYD.js +2 -0
- package/vendor/agent/chunks/chunk-NUHFSC37.js +25 -0
- package/vendor/agent/chunks/chunk-OUXLLA64.js +11 -0
- package/vendor/agent/chunks/chunk-PGXDEOVV.js +2 -0
- package/vendor/agent/chunks/chunk-PJACZCIR.js +2 -0
- package/vendor/agent/chunks/chunk-PRRNXB7S.js +2 -0
- package/vendor/agent/chunks/chunk-TEPMHNKQ.js +2 -0
- package/vendor/agent/chunks/chunk-TRDNDS6A.js +4 -0
- package/vendor/agent/chunks/chunk-U6ZMQKGD.js +50 -0
- package/vendor/agent/chunks/chunk-UAQELI3K.js +2 -0
- package/vendor/agent/chunks/chunk-USJZMAIR.js +2 -0
- package/vendor/agent/chunks/chunk-XDUWOHPL.js +6 -0
- package/vendor/agent/chunks/chunk-XNGRGP62.js +2 -0
- package/vendor/agent/chunks/github-copilot.js +2 -0
- package/vendor/agent/chunks/google-generative-ai-Q2K7FBDI.js +2 -0
- package/vendor/agent/chunks/google-vertex-7UJBF4S7.js +2 -0
- package/vendor/agent/chunks/https-proxy-agent-2VXB7436.js +13 -0
- package/vendor/agent/chunks/image-resize-worker.js +2 -0
- package/vendor/agent/chunks/kimi-coding.js +2 -0
- package/vendor/agent/chunks/lib-HGQMNYVT.js +9 -0
- package/vendor/agent/chunks/mistral-conversations-4M6BZBX6.js +5 -0
- package/vendor/agent/chunks/multipart-parser-SQ6GDJL7.js +3 -0
- package/vendor/agent/chunks/node-KG362ECQ.js +16 -0
- package/vendor/agent/chunks/node-TWVVAGPO.js +2 -0
- package/vendor/agent/chunks/openai-codex-responses-R6VYZTVY.js +10 -0
- package/vendor/agent/chunks/openai-codex.js +75 -0
- package/vendor/agent/chunks/openai-completions-EKZT2IH2.js +7 -0
- package/vendor/agent/chunks/openai-responses-TFDINO6W.js +2 -0
- package/vendor/agent/chunks/openrouter-images-N4XVVFSD.js +2 -0
- package/vendor/agent/chunks/openrouter.js +75 -0
- package/vendor/agent/chunks/pi-messages-PML4SPVJ.js +8 -0
- package/vendor/agent/chunks/radius.js +75 -0
- package/vendor/agent/chunks/src-UK77SYZE.js +4 -0
- package/vendor/agent/chunks/xai.js +2 -0
- package/vendor/agent/cli.js +3 -0
- package/vendor/agent/dist/modes/interactive/theme/dark.json +91 -0
- package/vendor/agent/dist/modes/interactive/theme/light.json +90 -0
- package/vendor/agent/dist/modes/interactive/theme/theme-schema.json +356 -0
- package/vendor/agent/index.js +2 -0
- package/vendor/agent/modes/interactive/theme/dark.json +91 -0
- package/vendor/agent/modes/interactive/theme/light.json +90 -0
- package/vendor/agent/modes/interactive/theme/theme-schema.json +356 -0
- package/vendor/agent/package.json +14 -0
- package/vendor/agent/rpc-entry.js +3 -0
- package/vendor/agent/theme/dark.json +91 -0
- package/vendor/agent/theme/light.json +90 -0
- package/vendor/agent/theme/theme-schema.json +356 -0
package/lib/tui/auth.mjs
ADDED
|
@@ -0,0 +1,249 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Provider authentication for the carthagent console.
|
|
3
|
+
*
|
|
4
|
+
* The bundled engine ships a programmatic `ModelRuntime.login(providerId, type, interaction)`
|
|
5
|
+
* API (see `readStoredCredential` / `ModelRuntime` in the bundled engine).
|
|
6
|
+
* This module wraps it so the TUI can drive login as an in-console popup
|
|
7
|
+
* instead of handing the terminal over to the engine's interactive mode.
|
|
8
|
+
*
|
|
9
|
+
* The pure helpers here are unit-tested in tests/auth.test.mjs; the runtime
|
|
10
|
+
* loader imports the (heavy) bundled engine lazily, only when auth is opened.
|
|
11
|
+
*/
|
|
12
|
+
import { readFileSync } from 'node:fs';
|
|
13
|
+
import { join } from 'node:path';
|
|
14
|
+
import os from 'node:os';
|
|
15
|
+
import { CloudClient, cloudControlUrl, formatCloudAccount, formatCloudUsage } from '../cloud/client.mjs';
|
|
16
|
+
import { EXPERIENTIAL_PROVIDER_ID, registerExperientialProvider } from '../providers/experiential.mjs';
|
|
17
|
+
import { SUBSCRIPTION_PROVIDER_IDS, registerSubscriptionProviders } from '../providers/subscription-models.mjs';
|
|
18
|
+
import { CARTHAGENT_SHIP_PROVIDER_NAME } from '../providers/names.mjs';
|
|
19
|
+
|
|
20
|
+
const AGENT_DIR = process.env.CARTHAGENT_AGENT_DIR || process.env.CARTHAGENT_CODING_AGENT_DIR || join(os.homedir(), '.carthagent', 'agent');
|
|
21
|
+
|
|
22
|
+
const COMMON_API_KEY_ENVS = [
|
|
23
|
+
'ANTHROPIC_API_KEY', 'OPENAI_API_KEY', 'GEMINI_API_KEY',
|
|
24
|
+
'OPENROUTER_API_KEY', 'XAI_API_KEY', 'EXPLABS_API_KEY',
|
|
25
|
+
'GROQ_API_KEY', 'MISTRAL_API_KEY', 'DEEPSEEK_API_KEY',
|
|
26
|
+
];
|
|
27
|
+
|
|
28
|
+
function hasStoredCredential(agentDir) {
|
|
29
|
+
try {
|
|
30
|
+
const stored = JSON.parse(readFileSync(join(agentDir, 'auth.json'), 'utf8'));
|
|
31
|
+
if (!stored || typeof stored !== 'object' || Array.isArray(stored)) return false;
|
|
32
|
+
return Object.values(stored).some(credential => {
|
|
33
|
+
if (typeof credential === 'string') return !!credential.trim();
|
|
34
|
+
if (!credential || typeof credential !== 'object') return false;
|
|
35
|
+
return ['key', 'apiKey', 'access', 'refresh'].some(field => typeof credential[field] === 'string' && credential[field].trim());
|
|
36
|
+
});
|
|
37
|
+
} catch { return false; }
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
function hasConfiguredModelCredential(agentDir) {
|
|
41
|
+
try {
|
|
42
|
+
const stored = JSON.parse(readFileSync(join(agentDir, 'models.json'), 'utf8'));
|
|
43
|
+
const providers = stored?.providers;
|
|
44
|
+
if (!providers || typeof providers !== 'object' || Array.isArray(providers)) return false;
|
|
45
|
+
return Object.values(providers).some(provider => {
|
|
46
|
+
if (!provider || typeof provider !== 'object') return false;
|
|
47
|
+
return ['apiKey', 'api_key'].some(field => typeof provider[field] === 'string' && provider[field].trim());
|
|
48
|
+
});
|
|
49
|
+
} catch { return false; }
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
/** Quick lightweight check: does the user have any usable provider credential at all? */
|
|
53
|
+
export function hasConfiguredProvider(env = process.env, agentDir = AGENT_DIR) {
|
|
54
|
+
if (hasStoredCredential(agentDir) || hasConfiguredModelCredential(agentDir)) return true;
|
|
55
|
+
return COMMON_API_KEY_ENVS.some(key => env[key]?.trim());
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const PROVIDER_ORDER = new Map([
|
|
59
|
+
['experiential-labs', 0],
|
|
60
|
+
['anthropic', 10],
|
|
61
|
+
['openai-codex', 20],
|
|
62
|
+
['openai', 21],
|
|
63
|
+
['openrouter', 30],
|
|
64
|
+
]);
|
|
65
|
+
|
|
66
|
+
/** Auth methods a provider advertises, in display order (subscription first). */
|
|
67
|
+
export function authTypes(provider) {
|
|
68
|
+
const out = [];
|
|
69
|
+
if (provider?.auth?.oauth) out.push('oauth');
|
|
70
|
+
if (provider?.auth?.apiKey) out.push('api_key');
|
|
71
|
+
return out;
|
|
72
|
+
}
|
|
73
|
+
|
|
74
|
+
export function authTypeLabel(type, providerId) {
|
|
75
|
+
if (type === 'oauth' && providerId === EXPERIENTIAL_PROVIDER_ID) return 'browser sign-in';
|
|
76
|
+
return type === 'oauth' ? 'subscription (OAuth)' : 'API key';
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
/**
|
|
80
|
+
* Rows the shared provider picker can show. Ordering is product policy, not
|
|
81
|
+
* connection state: Carthagent Ship stays first, recommended providers retain
|
|
82
|
+
* fixed positions, and every other provider follows alphabetically.
|
|
83
|
+
*/
|
|
84
|
+
export function loginProviderList(runtime, { activeProviderId } = {}) {
|
|
85
|
+
const rows = [];
|
|
86
|
+
for (const provider of runtime.getProviders()) {
|
|
87
|
+
const types = authTypes(provider);
|
|
88
|
+
if (!types.length) continue;
|
|
89
|
+
let configured = false;
|
|
90
|
+
try { configured = !!runtime.getProviderAuthStatus(provider.id).configured; } catch { /* unknown */ }
|
|
91
|
+
rows.push({
|
|
92
|
+
id: provider.id,
|
|
93
|
+
name: provider.id === EXPERIENTIAL_PROVIDER_ID ? CARTHAGENT_SHIP_PROVIDER_NAME : (provider.name || provider.id),
|
|
94
|
+
types,
|
|
95
|
+
configured,
|
|
96
|
+
recommended: provider.id === 'experiential-labs',
|
|
97
|
+
priority: PROVIDER_ORDER.get(provider.id) ?? 1000,
|
|
98
|
+
});
|
|
99
|
+
}
|
|
100
|
+
rows.sort((a, b) => a.priority - b.priority || a.name.localeCompare(b.name) || a.id.localeCompare(b.id));
|
|
101
|
+
if (activeProviderId) rows.forEach(row => { row.active = row.id === activeProviderId; });
|
|
102
|
+
return rows;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
/** New installs focus the recommended row; returning users focus their active provider. */
|
|
106
|
+
export function loginProviderFocus(rows, activeProviderId) {
|
|
107
|
+
const active = activeProviderId ? rows.findIndex(row => row.id === activeProviderId) : -1;
|
|
108
|
+
if (active >= 0) return active;
|
|
109
|
+
const recommended = rows.findIndex(row => row.recommended);
|
|
110
|
+
return recommended >= 0 ? recommended : 0;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
/** Case-insensitive filter over provider id/name. */
|
|
114
|
+
export function filterLoginProviders(rows, query) {
|
|
115
|
+
const q = (query || '').trim().toLowerCase();
|
|
116
|
+
if (!q) return rows;
|
|
117
|
+
return rows.filter(r => r.id.toLowerCase().includes(q) || r.name.toLowerCase().includes(q));
|
|
118
|
+
}
|
|
119
|
+
|
|
120
|
+
/**
|
|
121
|
+
* Fold one `notify(event)` from `runtime.login` into popup-facing fields.
|
|
122
|
+
* `state` is mutated and returned for convenience. The popup owns `message`.
|
|
123
|
+
*/
|
|
124
|
+
export function applyAuthEvent(state, event) {
|
|
125
|
+
if (!event || typeof event !== 'object') return state;
|
|
126
|
+
switch (event.type) {
|
|
127
|
+
case 'auth_url':
|
|
128
|
+
state.url = event.url || '';
|
|
129
|
+
state.instructions = event.instructions || '';
|
|
130
|
+
state.message = 'Open the authorization URL in your browser';
|
|
131
|
+
break;
|
|
132
|
+
case 'device_code':
|
|
133
|
+
state.deviceCode = { userCode: event.userCode || '', verificationUri: event.verificationUri || '' };
|
|
134
|
+
state.message = 'Enter the code at the verification URL';
|
|
135
|
+
break;
|
|
136
|
+
case 'info':
|
|
137
|
+
if (event.message) state.message = event.message;
|
|
138
|
+
if (Array.isArray(event.links)) state.links = event.links;
|
|
139
|
+
break;
|
|
140
|
+
case 'progress':
|
|
141
|
+
if (event.message) state.message = event.message;
|
|
142
|
+
break;
|
|
143
|
+
default:
|
|
144
|
+
break;
|
|
145
|
+
}
|
|
146
|
+
return state;
|
|
147
|
+
}
|
|
148
|
+
|
|
149
|
+
/** Fresh popup state in the provider-picking phase. */
|
|
150
|
+
export function newAuthState() {
|
|
151
|
+
return {
|
|
152
|
+
phase: 'providers', // providers | type | prompt | waiting
|
|
153
|
+
providers: [],
|
|
154
|
+
filter: '',
|
|
155
|
+
sel: 0,
|
|
156
|
+
typeSel: 0,
|
|
157
|
+
promptSel: 0,
|
|
158
|
+
provider: null,
|
|
159
|
+
type: null,
|
|
160
|
+
prompt: null,
|
|
161
|
+
input: '',
|
|
162
|
+
message: '',
|
|
163
|
+
instructions: '',
|
|
164
|
+
url: '',
|
|
165
|
+
links: [],
|
|
166
|
+
deviceCode: null,
|
|
167
|
+
error: '',
|
|
168
|
+
busy: false,
|
|
169
|
+
};
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
let runtimePromise = null;
|
|
173
|
+
|
|
174
|
+
/** Lazily create a ModelRuntime bound to carthagent's isolated agent directory. */
|
|
175
|
+
export function loadAuthRuntime() {
|
|
176
|
+
if (!runtimePromise) {
|
|
177
|
+
runtimePromise = import('../../vendor/agent/index.js')
|
|
178
|
+
.then(async engine => {
|
|
179
|
+
const runtime = registerExperientialProvider(await engine.ModelRuntime.create({}));
|
|
180
|
+
registerSubscriptionProviders(runtime, { agentDir: AGENT_DIR });
|
|
181
|
+
// registerProvider() starts an offline refresh asynchronously. Await a
|
|
182
|
+
// provider-scoped refresh so stored OAuth credentials, dynamic Cloud
|
|
183
|
+
// models, and subscription-discovered models are present before
|
|
184
|
+
// account checks or a child engine is launched.
|
|
185
|
+
await runtime.refresh?.({ providers: [EXPERIENTIAL_PROVIDER_ID, ...SUBSCRIPTION_PROVIDER_IDS], allowNetwork: true });
|
|
186
|
+
return runtime;
|
|
187
|
+
})
|
|
188
|
+
.catch(error => { runtimePromise = null; throw error; });
|
|
189
|
+
}
|
|
190
|
+
return runtimePromise;
|
|
191
|
+
}
|
|
192
|
+
|
|
193
|
+
/** Start a provider login; `prompt`/`notify`/`signal` follow the engine's contract. */
|
|
194
|
+
export function startLogin(runtime, providerId, type, interaction) {
|
|
195
|
+
return runtime.login(providerId, type, interaction);
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/** Authenticate, then refresh that provider so dynamic model discovery is immediate. */
|
|
199
|
+
export async function loginAndRefresh(runtime, providerId, type, interaction) {
|
|
200
|
+
const result = await startLogin(runtime, providerId, type, interaction);
|
|
201
|
+
await runtime.refresh?.({ providers: [providerId], allowNetwork: true, signal: interaction?.signal });
|
|
202
|
+
return result;
|
|
203
|
+
}
|
|
204
|
+
|
|
205
|
+
async function cloudAccessToken(runtime) {
|
|
206
|
+
const status = await runtime?.checkAuth?.(EXPERIENTIAL_PROVIDER_ID);
|
|
207
|
+
if (status?.type !== 'oauth') throw new Error('cloud_login_required');
|
|
208
|
+
const resolution = await runtime.getAuth(EXPERIENTIAL_PROVIDER_ID);
|
|
209
|
+
const accessToken = resolution?.auth?.apiKey;
|
|
210
|
+
if (!accessToken) throw new Error('cloud_login_required');
|
|
211
|
+
return accessToken;
|
|
212
|
+
}
|
|
213
|
+
|
|
214
|
+
export async function loadCloudAccount(runtime, { env = process.env, fetchImpl = globalThis.fetch, signal } = {}) {
|
|
215
|
+
const accessToken = await cloudAccessToken(runtime);
|
|
216
|
+
return new CloudClient({ baseUrl: cloudControlUrl(env), fetchImpl }).account({ accessToken, signal });
|
|
217
|
+
}
|
|
218
|
+
|
|
219
|
+
export async function loadCloudUsage(runtime, { env = process.env, fetchImpl = globalThis.fetch, limit = 50, signal } = {}) {
|
|
220
|
+
const accessToken = await cloudAccessToken(runtime);
|
|
221
|
+
return new CloudClient({ baseUrl: cloudControlUrl(env), fetchImpl }).usage({ accessToken, limit, signal });
|
|
222
|
+
}
|
|
223
|
+
|
|
224
|
+
export async function loadCloudAccountUsage(runtime, options = {}) {
|
|
225
|
+
const accessToken = await cloudAccessToken(runtime);
|
|
226
|
+
const client = new CloudClient({ baseUrl: cloudControlUrl(options.env), fetchImpl: options.fetchImpl });
|
|
227
|
+
const request = { accessToken, signal: options.signal };
|
|
228
|
+
const [account, usage] = await Promise.all([
|
|
229
|
+
client.account(request),
|
|
230
|
+
client.usage({ ...request, limit: options.limit }),
|
|
231
|
+
]);
|
|
232
|
+
return { account, usage };
|
|
233
|
+
}
|
|
234
|
+
|
|
235
|
+
export async function createCloudBillingLink(runtime, action, { env = process.env, fetchImpl = globalThis.fetch, plan, signal } = {}) {
|
|
236
|
+
const accessToken = await cloudAccessToken(runtime);
|
|
237
|
+
const client = new CloudClient({ baseUrl: cloudControlUrl(env), fetchImpl });
|
|
238
|
+
const returnUrl = `${cloudControlUrl(env)}/account`;
|
|
239
|
+
if (action === 'checkout') return client.checkout({ accessToken, successUrl: returnUrl, cancelUrl: returnUrl, priceId: plan, signal });
|
|
240
|
+
if (action === 'portal') return client.portal({ accessToken, returnUrl, signal });
|
|
241
|
+
throw new Error('unknown_billing_action');
|
|
242
|
+
}
|
|
243
|
+
|
|
244
|
+
export async function revokeCloudSession(runtime, sessionId, { env = process.env, fetchImpl = globalThis.fetch, signal } = {}) {
|
|
245
|
+
const accessToken = await cloudAccessToken(runtime);
|
|
246
|
+
return new CloudClient({ baseUrl: cloudControlUrl(env), fetchImpl }).revokeSession({ accessToken, sessionId, signal });
|
|
247
|
+
}
|
|
248
|
+
|
|
249
|
+
export { formatCloudAccount, formatCloudUsage };
|
|
@@ -0,0 +1,109 @@
|
|
|
1
|
+
/** Rotating completion lines for the TUI banner — one is drawn at random when a
|
|
2
|
+
* run settles. Carthage-themed to match the Aleph mark. */
|
|
3
|
+
|
|
4
|
+
export const CARTHAGE_LINES = [
|
|
5
|
+
'Qart-Hadasht endures',
|
|
6
|
+
'The cothon is calm',
|
|
7
|
+
'The fleet is in',
|
|
8
|
+
'Byrsa stands watch',
|
|
9
|
+
'Purple on the tide',
|
|
10
|
+
'Cedar holds fast',
|
|
11
|
+
'Hannibal approves',
|
|
12
|
+
'The harbormaster nods',
|
|
13
|
+
"Elissa's city rises",
|
|
14
|
+
'Tanit smiles',
|
|
15
|
+
'The galleys rest',
|
|
16
|
+
'Salt and cedar',
|
|
17
|
+
'The Barca line holds',
|
|
18
|
+
'Silver from Tartessos',
|
|
19
|
+
'The suffetes concur',
|
|
20
|
+
'New City, new win',
|
|
21
|
+
'The murex trade thrives',
|
|
22
|
+
'Utica sends regards',
|
|
23
|
+
'Across the Middle Sea',
|
|
24
|
+
'The seawall stands',
|
|
25
|
+
'Hanno sails on',
|
|
26
|
+
'Elephants crossed the Alps',
|
|
27
|
+
'The Alps are behind you',
|
|
28
|
+
'Cannae was harder',
|
|
29
|
+
'The quinqueremes row on',
|
|
30
|
+
"Melqart's favor is yours",
|
|
31
|
+
'The Punic ports are open',
|
|
32
|
+
'A stele raised in your honor',
|
|
33
|
+
'The Gulf of Tunis glitters',
|
|
34
|
+
'The tin routes run',
|
|
35
|
+
'Motya sends tribute',
|
|
36
|
+
'Sardinia remembers',
|
|
37
|
+
'The Hundred and Four approve',
|
|
38
|
+
'Hamilcar would grin',
|
|
39
|
+
"Hasdrubal's blessing",
|
|
40
|
+
'The agora buzzes',
|
|
41
|
+
'The breakwater holds',
|
|
42
|
+
'The Punic tide runs your way',
|
|
43
|
+
"Dido's dream endures",
|
|
44
|
+
'Carthago Nova salutes',
|
|
45
|
+
"The admiral's lantern is lit",
|
|
46
|
+
'Trade winds at your back',
|
|
47
|
+
'The merchant fleet prospers',
|
|
48
|
+
'The citadel is yours',
|
|
49
|
+
'Purple sails unfurled',
|
|
50
|
+
"Mago's harvest is in",
|
|
51
|
+
'The vineyards of the Bagradas',
|
|
52
|
+
'Olive presses run full',
|
|
53
|
+
'The last amphora is sealed',
|
|
54
|
+
'The mint strikes silver',
|
|
55
|
+
"Tanit's sign on the coin",
|
|
56
|
+
'The mare and the palm',
|
|
57
|
+
'Beyond the Pillars of Hercules',
|
|
58
|
+
'Gadir holds the strait',
|
|
59
|
+
'Numidian riders rest',
|
|
60
|
+
'Baal Hammon is pleased',
|
|
61
|
+
'Libyan wheat loads fast',
|
|
62
|
+
'The customs house is quiet',
|
|
63
|
+
"Delivered under Byrsa's shadow",
|
|
64
|
+
'The Phoenician star guides you',
|
|
65
|
+
'Charted to the edge of maps',
|
|
66
|
+
'Landfall, finally',
|
|
67
|
+
'Anchored in calm water',
|
|
68
|
+
'Cargo counted, cargo safe',
|
|
69
|
+
'The manifest is clean',
|
|
70
|
+
'Every bale stamped',
|
|
71
|
+
'Sealed and shipped',
|
|
72
|
+
'The ledger balances',
|
|
73
|
+
'Harbor dues paid',
|
|
74
|
+
'The convoy made it',
|
|
75
|
+
'Full cargo, calm seas',
|
|
76
|
+
'The emporium is stocked',
|
|
77
|
+
'The tide is with us',
|
|
78
|
+
'Sunset over the great harbor',
|
|
79
|
+
'The sea gate closes softly',
|
|
80
|
+
'The watch changes',
|
|
81
|
+
'Carthage remembers',
|
|
82
|
+
'The oxhide was worth it',
|
|
83
|
+
'Byrsa, measured in oxhide',
|
|
84
|
+
'The circle of the cothon',
|
|
85
|
+
'Two hundred oars, one rhythm',
|
|
86
|
+
'The arsenal is full',
|
|
87
|
+
'Pitch and pine',
|
|
88
|
+
'Rope of esparto',
|
|
89
|
+
'The lookout calls landfall',
|
|
90
|
+
'Where the gulf meets the wall',
|
|
91
|
+
'Sea-borne and sure',
|
|
92
|
+
'The Punic way prevails',
|
|
93
|
+
"Tyre's daughter prospers",
|
|
94
|
+
'Ivory and gold dust',
|
|
95
|
+
'Amber from the north',
|
|
96
|
+
'Crimson dye for the victor',
|
|
97
|
+
'The archive keeps your name',
|
|
98
|
+
"Eshmun's blessing",
|
|
99
|
+
'Wax tablets sealed',
|
|
100
|
+
'The drum calls the stroke',
|
|
101
|
+
'Dawn patrol returns',
|
|
102
|
+
'Cap Bon rises green',
|
|
103
|
+
'The Lake of Tunis at dusk',
|
|
104
|
+
'The prow cuts true',
|
|
105
|
+
];
|
|
106
|
+
|
|
107
|
+
export function pickCarthageLine() {
|
|
108
|
+
return CARTHAGE_LINES[Math.floor(Math.random() * CARTHAGE_LINES.length)];
|
|
109
|
+
}
|
|
@@ -0,0 +1,122 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Best-effort clipboard *read* and *write* for the carthagent console.
|
|
3
|
+
*
|
|
4
|
+
* Terminals normally deliver a paste as bracketed-paste bytes, which the key
|
|
5
|
+
* parser already understands. Some setups instead bind Ctrl+V to the app
|
|
6
|
+
* (which only sees a raw 0x16 byte), so we shell out to the platform clipboard
|
|
7
|
+
* utility and fall back to a no-op when none is available.
|
|
8
|
+
*
|
|
9
|
+
* Similarly, terminal drag-selection (auto copy) emits OSC 52 escapes for
|
|
10
|
+
* compatible terminals, but macOS Terminal.app and many default configurations
|
|
11
|
+
* (e.g. iTerm2 without explicit clipboard access enabled, tmux) do not populate
|
|
12
|
+
* the system clipboard via OSC 52. We shell out to the native clipboard writer
|
|
13
|
+
* (pbcopy on macOS, wl-copy/xclip/xsel on Linux, PowerShell on Windows/WSL) so the
|
|
14
|
+
* selected text lands in the OS pasteboard directly.
|
|
15
|
+
*
|
|
16
|
+
* carthagent stays dependency-free: these are all standard OS/desktop tools (or
|
|
17
|
+
* PowerShell/clip on Windows/WSL), tried in order until one answers.
|
|
18
|
+
*/
|
|
19
|
+
import { execFile } from 'node:child_process';
|
|
20
|
+
|
|
21
|
+
const readWindows = () => ['powershell.exe', ['-NoProfile', '-Command', '[Console]::OutputEncoding = New-Object System.Text.UTF8Encoding($false); [Console]::Write((Get-Clipboard -Raw))']];
|
|
22
|
+
const writeWindows = () => [
|
|
23
|
+
['powershell.exe', ['-NoProfile', '-Command', '[Console]::InputEncoding = New-Object System.Text.UTF8Encoding($false); Set-Clipboard -Value ([Console]::In.ReadToEnd())']],
|
|
24
|
+
];
|
|
25
|
+
|
|
26
|
+
/** Clipboard read commands for a platform, in preference order. */
|
|
27
|
+
export function clipboardCommands(platform = process.platform, env = process.env) {
|
|
28
|
+
if (platform === 'darwin') return [['pbpaste', []]];
|
|
29
|
+
if (platform === 'win32') return [readWindows()];
|
|
30
|
+
if (platform !== 'linux') return [];
|
|
31
|
+
const linux = [
|
|
32
|
+
['wl-paste', ['-n']],
|
|
33
|
+
['xclip', ['-selection', 'clipboard', '-o']],
|
|
34
|
+
['xsel', ['--clipboard', '--output']],
|
|
35
|
+
];
|
|
36
|
+
if (env.WSL_DISTRO_NAME || env.WSL_INTEROP || env.WSLENV) {
|
|
37
|
+
linux.push(readWindows());
|
|
38
|
+
}
|
|
39
|
+
return linux;
|
|
40
|
+
}
|
|
41
|
+
|
|
42
|
+
/** Clipboard write commands for a platform, in preference order. */
|
|
43
|
+
export function clipboardWriteCommands(platform = process.platform, env = process.env) {
|
|
44
|
+
if (platform === 'darwin') return [['pbcopy', []]];
|
|
45
|
+
if (platform === 'win32') return writeWindows();
|
|
46
|
+
if (platform !== 'linux') return [];
|
|
47
|
+
const linux = [
|
|
48
|
+
['wl-copy', []],
|
|
49
|
+
['xclip', ['-selection', 'clipboard']],
|
|
50
|
+
['xsel', ['--clipboard', '--input']],
|
|
51
|
+
];
|
|
52
|
+
if (env.WSL_DISTRO_NAME || env.WSL_INTEROP || env.WSLENV) {
|
|
53
|
+
linux.push(...writeWindows());
|
|
54
|
+
}
|
|
55
|
+
return linux;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
/** Normalize clipboard text. Single-line fields strip all whitespace/control
|
|
59
|
+
* bytes (keys, codes, URLs); multi-line keeps newlines but drops control junk. */
|
|
60
|
+
export function sanitizePaste(text, { multiline = false } = {}) {
|
|
61
|
+
const s = String(text ?? '').replace(/\r\n?/g, '\n');
|
|
62
|
+
if (multiline) return s.replace(/[\u0000-\u0008\u000b\u000c\u000e-\u001f\u007f]/g, '');
|
|
63
|
+
return s.replace(/[\s\u0000-\u001f\u007f]+/g, '');
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
/** Read the OS clipboard, resolving to '' when no tool works. Never rejects. */
|
|
67
|
+
export function readClipboard({ platform = process.platform, env = process.env, timeoutMs = 1200, commands = null } = {}) {
|
|
68
|
+
const cmds = commands || clipboardCommands(platform, env);
|
|
69
|
+
return new Promise(resolve => {
|
|
70
|
+
let index = 0;
|
|
71
|
+
const next = () => {
|
|
72
|
+
const entry = cmds[index++];
|
|
73
|
+
if (!entry) { resolve(''); return; } // no tool answered
|
|
74
|
+
const [cmd, args] = entry;
|
|
75
|
+
try {
|
|
76
|
+
execFile(cmd, args, { timeout: timeoutMs, maxBuffer: 1024 * 1024, windowsHide: true }, (error, stdout) => {
|
|
77
|
+
if (error) next(); // missing/failed tool → try the next
|
|
78
|
+
else resolve(String(stdout || ''));
|
|
79
|
+
});
|
|
80
|
+
} catch { next(); }
|
|
81
|
+
};
|
|
82
|
+
next();
|
|
83
|
+
});
|
|
84
|
+
}
|
|
85
|
+
|
|
86
|
+
/** Write text to the OS clipboard, resolving to true on success or false when no tool works. Never rejects. */
|
|
87
|
+
export function writeClipboard(text, { platform = process.platform, env = process.env, timeoutMs = 1200, commands = null } = {}) {
|
|
88
|
+
const cmds = commands || clipboardWriteCommands(platform, env);
|
|
89
|
+
const data = String(text ?? '');
|
|
90
|
+
return new Promise(resolve => {
|
|
91
|
+
let index = 0;
|
|
92
|
+
const next = () => {
|
|
93
|
+
const entry = cmds[index++];
|
|
94
|
+
if (!entry) { resolve(false); return; } // no tool answered
|
|
95
|
+
const [cmd, args] = entry;
|
|
96
|
+
let handled = false;
|
|
97
|
+
try {
|
|
98
|
+
const child = execFile(cmd, args, { timeout: timeoutMs, windowsHide: true }, (error) => {
|
|
99
|
+
if (handled) return;
|
|
100
|
+
handled = true;
|
|
101
|
+
if (error) next();
|
|
102
|
+
else resolve(true);
|
|
103
|
+
});
|
|
104
|
+
child.on('error', () => {
|
|
105
|
+
if (handled) return;
|
|
106
|
+
handled = true;
|
|
107
|
+
next();
|
|
108
|
+
});
|
|
109
|
+
if (child.stdin) {
|
|
110
|
+
child.stdin.on('error', () => {});
|
|
111
|
+
child.stdin.end(Buffer.from(data, 'utf8'));
|
|
112
|
+
}
|
|
113
|
+
} catch {
|
|
114
|
+
if (!handled) {
|
|
115
|
+
handled = true;
|
|
116
|
+
next();
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
};
|
|
120
|
+
next();
|
|
121
|
+
});
|
|
122
|
+
}
|