wizards 0.1.9
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/LICENSE +110 -0
- package/README.md +170 -0
- package/apps/runtime/dist/agents/architect.js +453 -0
- package/apps/runtime/dist/agents/project-assistant.js +354 -0
- package/apps/runtime/dist/agents/subscription.js +206 -0
- package/apps/runtime/dist/agents/thought.js +21 -0
- package/apps/runtime/dist/agents/website.js +197 -0
- package/apps/runtime/dist/app.js +153 -0
- package/apps/runtime/dist/auth/account.js +121 -0
- package/apps/runtime/dist/auth/index.js +329 -0
- package/apps/runtime/dist/auth/keys.js +55 -0
- package/apps/runtime/dist/auth/local-ticket.js +23 -0
- package/apps/runtime/dist/authoring/example.js +73 -0
- package/apps/runtime/dist/authoring/guide.js +259 -0
- package/apps/runtime/dist/authoring/ops.js +117 -0
- package/apps/runtime/dist/cli/autostart.js +134 -0
- package/apps/runtime/dist/cli/clients.js +44 -0
- package/apps/runtime/dist/cli/environment.js +81 -0
- package/apps/runtime/dist/cli/exec.js +45 -0
- package/apps/runtime/dist/cli/home.js +74 -0
- package/apps/runtime/dist/cli/machine.js +145 -0
- package/apps/runtime/dist/cli/main.js +125 -0
- package/apps/runtime/dist/cli/setup.js +273 -0
- package/apps/runtime/dist/cli/start.js +190 -0
- package/apps/runtime/dist/cli/status.js +120 -0
- package/apps/runtime/dist/cli/ui.js +12 -0
- package/apps/runtime/dist/cli/update.js +58 -0
- package/apps/runtime/dist/connectors/builtin.js +70 -0
- package/apps/runtime/dist/connectors/external.js +400 -0
- package/apps/runtime/dist/connectors/gmail.js +107 -0
- package/apps/runtime/dist/connectors/imap.js +217 -0
- package/apps/runtime/dist/connectors/index.js +266 -0
- package/apps/runtime/dist/connectors/mail.js +72 -0
- package/apps/runtime/dist/connectors/outlook.js +90 -0
- package/apps/runtime/dist/credits/credits.js +103 -0
- package/apps/runtime/dist/credits/estimate.js +132 -0
- package/apps/runtime/dist/db/adopt.js +149 -0
- package/apps/runtime/dist/db/client.js +146 -0
- package/apps/runtime/dist/db/control-schema.js +70 -0
- package/apps/runtime/dist/db/migrations/control/0000_last_absorbing_man.sql +47 -0
- package/apps/runtime/dist/db/migrations/control/0001_gorgeous_nicolaos.sql +41 -0
- package/apps/runtime/dist/db/migrations/control/0002_secret_penance.sql +1 -0
- package/apps/runtime/dist/db/migrations/control/0003_nasty_mordo.sql +3 -0
- package/apps/runtime/dist/db/migrations/control/0004_breezy_eddie_brock.sql +6 -0
- package/apps/runtime/dist/db/migrations/control/meta/0000_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0001_snapshot.json +599 -0
- package/apps/runtime/dist/db/migrations/control/meta/0002_snapshot.json +607 -0
- package/apps/runtime/dist/db/migrations/control/meta/0003_snapshot.json +311 -0
- package/apps/runtime/dist/db/migrations/control/meta/0004_snapshot.json +350 -0
- package/apps/runtime/dist/db/migrations/control/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/migrations/tenant/0000_eminent_freak.sql +188 -0
- package/apps/runtime/dist/db/migrations/tenant/0001_salty_master_chief.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0002_cloudy_firebird.sql +1 -0
- package/apps/runtime/dist/db/migrations/tenant/0003_brown_warbound.sql +52 -0
- package/apps/runtime/dist/db/migrations/tenant/0004_romantic_nocturne.sql +6 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0000_snapshot.json +1323 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0001_snapshot.json +1330 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0002_snapshot.json +1337 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0003_snapshot.json +1605 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/0004_snapshot.json +1645 -0
- package/apps/runtime/dist/db/migrations/tenant/meta/_journal.json +41 -0
- package/apps/runtime/dist/db/schema.js +278 -0
- package/apps/runtime/dist/documents/parse.js +230 -0
- package/apps/runtime/dist/engenty/connections-external/build-connector.js +193 -0
- package/apps/runtime/dist/engenty/connections-external/errors.js +11 -0
- package/apps/runtime/dist/engenty/connections-external/import-service.js +270 -0
- package/apps/runtime/dist/engenty/connections-external/importer/classify.js +34 -0
- package/apps/runtime/dist/engenty/connections-external/importer/map-auth.js +185 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-mcp.js +63 -0
- package/apps/runtime/dist/engenty/connections-external/importer/normalize-openapi.js +130 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/http-invoker.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/invoke/mcp-client.js +143 -0
- package/apps/runtime/dist/engenty/connections-external/net/guarded-fetch.js +119 -0
- package/apps/runtime/dist/engenty/connections-external/oauth-dcr.js +88 -0
- package/apps/runtime/dist/engenty/connections-external/registry-client.js +336 -0
- package/apps/runtime/dist/engenty/connections-external/registry-source.js +82 -0
- package/apps/runtime/dist/engenty/connections-external/types.js +7 -0
- package/apps/runtime/dist/engenty/connections-github/connector.js +209 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/calendar.js +359 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/contacts.js +94 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/drive.js +290 -0
- package/apps/runtime/dist/engenty/connections-google/connectors/gmail.js +567 -0
- package/apps/runtime/dist/engenty/connections-google/definitions.js +11 -0
- package/apps/runtime/dist/engenty/connections-google/shared.js +73 -0
- package/apps/runtime/dist/engenty/connections-hubspot/connector.js +140 -0
- package/apps/runtime/dist/engenty/connections-microsoft/action.js +17 -0
- package/apps/runtime/dist/engenty/connections-microsoft/graph.js +114 -0
- package/apps/runtime/dist/engenty/connections-microsoft/onedrive.js +286 -0
- package/apps/runtime/dist/engenty/connections-microsoft/outlook.js +464 -0
- package/apps/runtime/dist/engenty/connections-s3/s3.js +328 -0
- package/apps/runtime/dist/engenty/connections-sdk/files-capability.js +86 -0
- package/apps/runtime/dist/engenty/connections-sdk/oauth2.js +150 -0
- package/apps/runtime/dist/engenty/connections-sdk/registry.js +89 -0
- package/apps/runtime/dist/engenty/connections-sdk/storage-capability.js +84 -0
- package/apps/runtime/dist/engenty/connections-sdk/types.js +30 -0
- package/apps/runtime/dist/engenty/connections-slack/connector.js +360 -0
- package/apps/runtime/dist/engenty/csv-import/csv-matrix.js +178 -0
- package/apps/runtime/dist/engenty/csv-import/import-sources.js +87 -0
- package/apps/runtime/dist/engenty/csv-import/parse-csv.js +129 -0
- package/apps/runtime/dist/engenty/csv-import/serialize-csv.js +20 -0
- package/apps/runtime/dist/engenty/csv-import/types.js +1 -0
- package/apps/runtime/dist/engenty/csv-import/xlsx-workbook.js +159 -0
- package/apps/runtime/dist/engenty/doc-converter/interface.js +9 -0
- package/apps/runtime/dist/engenty/doc-converter/page-break.js +92 -0
- package/apps/runtime/dist/engenty/doc-converter/providers/local/index.js +72 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/classifier.js +30 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/index.js +4 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/invoice.js +73 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/receipt.js +63 -0
- package/apps/runtime/dist/engenty/document-scanner/schemas/shared.js +40 -0
- package/apps/runtime/dist/engenty/search-index/chunks.js +164 -0
- package/apps/runtime/dist/engenty/search-index/contracts.js +9 -0
- package/apps/runtime/dist/engenty/shims/connections-sdk.js +39 -0
- package/apps/runtime/dist/engenty/shims/plugin-sdk.js +1 -0
- package/apps/runtime/dist/engenty/shims/web-ingest.js +3 -0
- package/apps/runtime/dist/engenty/web-ingest/ssrf.js +277 -0
- package/apps/runtime/dist/engine/asks.js +60 -0
- package/apps/runtime/dist/engine/events.js +46 -0
- package/apps/runtime/dist/engine/input.js +170 -0
- package/apps/runtime/dist/engine/prepare.js +51 -0
- package/apps/runtime/dist/engine/requirements.js +124 -0
- package/apps/runtime/dist/engine/resources.js +89 -0
- package/apps/runtime/dist/engine/runner.js +560 -0
- package/apps/runtime/dist/engine/steps.js +598 -0
- package/apps/runtime/dist/engine/template.js +183 -0
- package/apps/runtime/dist/engine/types.js +3 -0
- package/apps/runtime/dist/env.js +224 -0
- package/apps/runtime/dist/files/blobs.js +22 -0
- package/apps/runtime/dist/files/objects.js +86 -0
- package/apps/runtime/dist/files/storage.js +95 -0
- package/apps/runtime/dist/geocode.js +58 -0
- package/apps/runtime/dist/harness/claude.js +138 -0
- package/apps/runtime/dist/harness/codex.js +228 -0
- package/apps/runtime/dist/harness/cursor.js +93 -0
- package/apps/runtime/dist/harness/env.js +124 -0
- package/apps/runtime/dist/harness/gemini.js +126 -0
- package/apps/runtime/dist/harness/index.js +99 -0
- package/apps/runtime/dist/harness/model.js +267 -0
- package/apps/runtime/dist/harness/prompt.js +121 -0
- package/apps/runtime/dist/harness/terminal.js +193 -0
- package/apps/runtime/dist/harness/types.js +7 -0
- package/apps/runtime/dist/index.js +76 -0
- package/apps/runtime/dist/limits.js +57 -0
- package/apps/runtime/dist/link-preview.js +59 -0
- package/apps/runtime/dist/manage.js +151 -0
- package/apps/runtime/dist/mcp/auth.js +102 -0
- package/apps/runtime/dist/mcp/bridge.js +59 -0
- package/apps/runtime/dist/mcp/handler.js +42 -0
- package/apps/runtime/dist/mcp/scopes.js +5 -0
- package/apps/runtime/dist/mcp/tools.js +401 -0
- package/apps/runtime/dist/media/ffmpeg.js +121 -0
- package/apps/runtime/dist/media/generate.js +120 -0
- package/apps/runtime/dist/media/marking.js +121 -0
- package/apps/runtime/dist/media/transcribe.js +30 -0
- package/apps/runtime/dist/model-errors.js +3 -0
- package/apps/runtime/dist/models.js +489 -0
- package/apps/runtime/dist/plugin.js +61 -0
- package/apps/runtime/dist/render/chromium.js +85 -0
- package/apps/runtime/dist/render/convert.js +161 -0
- package/apps/runtime/dist/render/downloads.js +277 -0
- package/apps/runtime/dist/render/guard.js +25 -0
- package/apps/runtime/dist/routes/api.js +74 -0
- package/apps/runtime/dist/routes/delivery.js +52 -0
- package/apps/runtime/dist/routes/internal.js +49 -0
- package/apps/runtime/dist/routes/marketplace.js +36 -0
- package/apps/runtime/dist/routes/runs.js +805 -0
- package/apps/runtime/dist/routes/studio.js +702 -0
- package/apps/runtime/dist/routes/wizard-stream.js +30 -0
- package/apps/runtime/dist/running.js +39 -0
- package/apps/runtime/dist/sandbox/agentos.js +191 -0
- package/apps/runtime/dist/sandbox/docker.js +77 -0
- package/apps/runtime/dist/sandbox/index.js +30 -0
- package/apps/runtime/dist/sandbox/types.js +1 -0
- package/apps/runtime/dist/secrets/crypto.js +28 -0
- package/apps/runtime/dist/secrets/signing.js +34 -0
- package/apps/runtime/dist/secrets/vault.js +59 -0
- package/apps/runtime/dist/services/architect.js +92 -0
- package/apps/runtime/dist/services/brand.js +20 -0
- package/apps/runtime/dist/services/cloud.js +52 -0
- package/apps/runtime/dist/services/draft-events.js +27 -0
- package/apps/runtime/dist/services/errors.js +24 -0
- package/apps/runtime/dist/services/files.js +129 -0
- package/apps/runtime/dist/services/links.js +16 -0
- package/apps/runtime/dist/services/marketplace.js +292 -0
- package/apps/runtime/dist/services/package.js +202 -0
- package/apps/runtime/dist/services/profile.js +23 -0
- package/apps/runtime/dist/services/project-files.js +266 -0
- package/apps/runtime/dist/services/project-index.js +109 -0
- package/apps/runtime/dist/services/projects.js +181 -0
- package/apps/runtime/dist/services/runs.js +224 -0
- package/apps/runtime/dist/services/shares.js +129 -0
- package/apps/runtime/dist/services/widgets.js +34 -0
- package/apps/runtime/dist/services/wizards.js +342 -0
- package/apps/runtime/dist/settings.js +16 -0
- package/apps/runtime/dist/store/index.js +279 -0
- package/apps/runtime/dist/tenants/control.js +106 -0
- package/apps/runtime/dist/tenants/tenant.js +26 -0
- package/apps/runtime/dist/tools/browser.js +380 -0
- package/apps/runtime/dist/tools/connector.js +204 -0
- package/apps/runtime/dist/tools/index.js +234 -0
- package/apps/runtime/dist/tools/mail.js +104 -0
- package/apps/runtime/dist/tools/net-guard.js +59 -0
- package/apps/runtime/dist/tools/project.js +53 -0
- package/apps/runtime/dist/tools/shared.js +42 -0
- package/apps/runtime/dist/tools/store.js +238 -0
- package/apps/runtime/dist/update.js +118 -0
- package/apps/runtime/dist/widgets/bundle.js +99 -0
- package/apps/runtime/dist/widgets/render.js +312 -0
- package/apps/runtime/dist/widgets/runtime.js +84 -0
- package/apps/runtime/dist/widgets/script.js +30 -0
- package/apps/runtime/dist/widgets/step.js +192 -0
- package/apps/web/dist/ai-labels/ai-black.svg +20 -0
- package/apps/web/dist/ai-labels/ai-generated-black.svg +31 -0
- package/apps/web/dist/ai-labels/ai-generated-white.svg +32 -0
- package/apps/web/dist/ai-labels/ai-modified-black.svg +30 -0
- package/apps/web/dist/ai-labels/ai-modified-white.svg +31 -0
- package/apps/web/dist/ai-labels/ai-white.svg +21 -0
- package/apps/web/dist/assets/ChangelogDialog-O00vEnbm.js +1 -0
- package/apps/web/dist/assets/ChatPanel-CH9ry0Hp.js +1 -0
- package/apps/web/dist/assets/CreditsDialog-DRzecwUw.js +1 -0
- package/apps/web/dist/assets/EditorPage-BRnBf02Q.js +2 -0
- package/apps/web/dist/assets/FlowPreview-DDOj8IAH.js +1 -0
- package/apps/web/dist/assets/HomePage-CAGTTG3N.js +1 -0
- package/apps/web/dist/assets/ImportWizard-0RRrAnX3.js +1 -0
- package/apps/web/dist/assets/LocalRuntime-BrP-ENHg.css +1 -0
- package/apps/web/dist/assets/LocalRuntime-C-TBYrzs.js +16 -0
- package/apps/web/dist/assets/NewWizardPage-kI90h3_9.js +2 -0
- package/apps/web/dist/assets/PublicRunner-CKRDeIVd.js +1 -0
- package/apps/web/dist/assets/RunnerView-Rc4H8wYH.js +2 -0
- package/apps/web/dist/assets/SettingsPage-CESMuZxq.js +2 -0
- package/apps/web/dist/assets/SetupPage-D1rrYU14.js +1 -0
- package/apps/web/dist/assets/SharePage-COTAmOgh.js +1 -0
- package/apps/web/dist/assets/api-BY28cjGY.js +10 -0
- package/apps/web/dist/assets/arrow-left-UpjOBfQp.js +1 -0
- package/apps/web/dist/assets/arrow-right-CucQycEy.js +1 -0
- package/apps/web/dist/assets/changelog-p3wsaDDS.js +1 -0
- package/apps/web/dist/assets/check-Bjx41xbB.js +1 -0
- package/apps/web/dist/assets/createLucideIcon-DBu2Ovxj.js +1 -0
- package/apps/web/dist/assets/definition-cy7uvY5L.js +55 -0
- package/apps/web/dist/assets/engenty-CWIFpgKq.js +1 -0
- package/apps/web/dist/assets/esm-D1OEhjTK.js +7 -0
- package/apps/web/dist/assets/external-link-CRg6DVdO.js +1 -0
- package/apps/web/dist/assets/fluffy-engenty-CJ3wCQgT.js +456 -0
- package/apps/web/dist/assets/index-CQ_q9iMO.css +1 -0
- package/apps/web/dist/assets/index-D0gxh90t.js +10 -0
- package/apps/web/dist/assets/jsx-runtime-Dk72oS4N.js +1 -0
- package/apps/web/dist/assets/meta-BbSdtTUa.js +1 -0
- package/apps/web/dist/assets/mic-CeqQ89Dj.js +1 -0
- package/apps/web/dist/assets/oss-credits-cPuZUFHD.js +1 -0
- package/apps/web/dist/assets/outputs-D6pxH6ve.js +69 -0
- package/apps/web/dist/assets/plus-HA210wXA.js +1 -0
- package/apps/web/dist/assets/preload-helper-CNEI7N0N.js +1 -0
- package/apps/web/dist/assets/qr-scanner-worker.min-CJaXo3yu.js +98 -0
- package/apps/web/dist/assets/qr-scanner.min-C_UI0gH-.js +1 -0
- package/apps/web/dist/assets/type-BzYgVWKL.js +1 -0
- package/apps/web/dist/assets/ui-Ddwl35A6.js +1 -0
- package/apps/web/dist/assets/upload-DsgLfQ3U.js +1 -0
- package/apps/web/dist/assets/video-Dk1fuTDl.js +1 -0
- package/apps/web/dist/assets/wand-sparkles-Bf97fs8T.js +1 -0
- package/apps/web/dist/embed.js +102 -0
- package/apps/web/dist/favicon.svg +1 -0
- package/apps/web/dist/icons/apple-touch-icon.png +0 -0
- package/apps/web/dist/icons/icon-192.png +0 -0
- package/apps/web/dist/icons/icon-512.png +0 -0
- package/apps/web/dist/icons/icon-maskable-512.png +0 -0
- package/apps/web/dist/index.html +46 -0
- package/apps/web/dist/install.sh +532 -0
- package/apps/web/dist/manifest.webmanifest +20 -0
- package/apps/web/dist/sw.js +15 -0
- package/bin/wizards.mjs +13 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.d.ts +77 -0
- package/node_modules/@engenty-wizards/shared/dist/connectors.js +2 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.d.ts +1415 -0
- package/node_modules/@engenty-wizards/shared/dist/definition.js +762 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.d.ts +5 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-format.js +132 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.d.ts +11 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns-value.js +209 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.d.ts +532 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/columns.js +216 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.d.ts +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/data-tables/index.js +3 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.d.ts +14 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-lexical.js +166 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.d.ts +33 -0
- package/node_modules/@engenty-wizards/shared/dist/engenty/search-index/catalog-rank.js +99 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.d.ts +35 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-entry.js +86 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.d.ts +20 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace-search.js +133 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.d.ts +298 -0
- package/node_modules/@engenty-wizards/shared/dist/marketplace.js +211 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.d.ts +7 -0
- package/node_modules/@engenty-wizards/shared/dist/merge.js +40 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.d.ts +74 -0
- package/node_modules/@engenty-wizards/shared/dist/projects.js +96 -0
- package/node_modules/@engenty-wizards/shared/dist/run.d.ts +219 -0
- package/node_modules/@engenty-wizards/shared/dist/run.js +1 -0
- package/node_modules/@engenty-wizards/shared/dist/store.d.ts +100 -0
- package/node_modules/@engenty-wizards/shared/dist/store.js +44 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.d.ts +16 -0
- package/node_modules/@engenty-wizards/shared/dist/workspace.js +51 -0
- package/node_modules/@engenty-wizards/shared/package.json +10 -0
- package/package.json +62 -0
- package/plugin/.claude-plugin/plugin.json +9 -0
- package/plugin/.mcp.json +8 -0
- package/plugin/commands/wizard.md +8 -0
- package/plugin/skills/build-wizard/SKILL.md +27 -0
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
import { resolveEnv, run } from "./env.js";
|
|
2
|
+
import { failureOf, HarnessModel, lastJson, runClient, } from "./model.js";
|
|
3
|
+
/**
|
|
4
|
+
* Claude Code: `claude -p` answers on the person's sign-in — the client's keychain, or the key
|
|
5
|
+
* or token their login shell sets. Tools go over MCP; structured output is validated by the
|
|
6
|
+
* client (`--json-schema`); web search is its own tool.
|
|
7
|
+
*/
|
|
8
|
+
export const CLAUDE_BIN = process.env.CLAUDE_BIN?.trim() || "claude";
|
|
9
|
+
const CLASSES = {
|
|
10
|
+
classifier: "haiku",
|
|
11
|
+
standard: "sonnet",
|
|
12
|
+
high: "opus",
|
|
13
|
+
highest: "opus",
|
|
14
|
+
};
|
|
15
|
+
const spec = {
|
|
16
|
+
id: "claude",
|
|
17
|
+
passThrough: /^(ANTHROPIC_|CLAUDE_CODE_OAUTH_TOKEN$|CLAUDE_CODE_USE_|CLAUDE_CONFIG_DIR$|AWS_(PROFILE|REGION|DEFAULT_REGION|ACCESS_KEY_ID|SECRET_ACCESS_KEY|SESSION_TOKEN)$|GOOGLE_APPLICATION_CREDENTIALS$|CLOUD_ML_REGION$)/,
|
|
18
|
+
keyVars: ["ANTHROPIC_API_KEY", "ANTHROPIC_AUTH_TOKEN"],
|
|
19
|
+
async auth(env) {
|
|
20
|
+
try {
|
|
21
|
+
const { stdout } = await run(CLAUDE_BIN, ["auth", "status", "--json"], {
|
|
22
|
+
timeout: 15_000,
|
|
23
|
+
env,
|
|
24
|
+
});
|
|
25
|
+
const status = JSON.parse(stdout.trim());
|
|
26
|
+
if (!status.loggedIn) {
|
|
27
|
+
return "none";
|
|
28
|
+
}
|
|
29
|
+
return status.authMethod === "api_key" ? "api_key" : "subscription";
|
|
30
|
+
}
|
|
31
|
+
catch {
|
|
32
|
+
return "none";
|
|
33
|
+
}
|
|
34
|
+
},
|
|
35
|
+
};
|
|
36
|
+
export async function claudeEnv() {
|
|
37
|
+
return (await resolveEnv(spec)).env;
|
|
38
|
+
}
|
|
39
|
+
/** The client's result as the model call wants it: text (or the structured output as JSON) and usage. */
|
|
40
|
+
export function claudeAnswer(event) {
|
|
41
|
+
const u = event.usage ?? {};
|
|
42
|
+
const cacheRead = u.cache_read_input_tokens ?? 0;
|
|
43
|
+
const cacheWrite = u.cache_creation_input_tokens ?? 0;
|
|
44
|
+
return {
|
|
45
|
+
text: event.structured_output !== undefined
|
|
46
|
+
? JSON.stringify(event.structured_output)
|
|
47
|
+
: typeof event.result === "string"
|
|
48
|
+
? event.result
|
|
49
|
+
: JSON.stringify(event.result ?? ""),
|
|
50
|
+
usage: {
|
|
51
|
+
input: (u.input_tokens ?? 0) + cacheRead + cacheWrite,
|
|
52
|
+
cacheRead,
|
|
53
|
+
cacheWrite,
|
|
54
|
+
output: u.output_tokens ?? 0,
|
|
55
|
+
},
|
|
56
|
+
meta: { costUsd: event.total_cost_usd ?? 0, turns: event.num_turns ?? 1 },
|
|
57
|
+
sessionId: event.session_id,
|
|
58
|
+
stopReason: event.stop_reason,
|
|
59
|
+
};
|
|
60
|
+
}
|
|
61
|
+
class ClaudeCodeModel extends HarnessModel {
|
|
62
|
+
provider = "claude-code";
|
|
63
|
+
nativeSchema = true;
|
|
64
|
+
async invoke(call) {
|
|
65
|
+
const builtin = [];
|
|
66
|
+
if (call.files.length) {
|
|
67
|
+
builtin.push("Read");
|
|
68
|
+
}
|
|
69
|
+
if (call.webSearch) {
|
|
70
|
+
builtin.push("WebSearch");
|
|
71
|
+
}
|
|
72
|
+
const args = [
|
|
73
|
+
"-p",
|
|
74
|
+
"--output-format",
|
|
75
|
+
"json",
|
|
76
|
+
"--model",
|
|
77
|
+
this.modelId,
|
|
78
|
+
"--tools",
|
|
79
|
+
builtin.join(","),
|
|
80
|
+
"--strict-mcp-config",
|
|
81
|
+
"--setting-sources",
|
|
82
|
+
"",
|
|
83
|
+
"--disable-slash-commands",
|
|
84
|
+
"--no-session-persistence",
|
|
85
|
+
];
|
|
86
|
+
if (call.system) {
|
|
87
|
+
args.push("--system-prompt", call.system);
|
|
88
|
+
}
|
|
89
|
+
if (call.effort) {
|
|
90
|
+
args.push("--effort", call.effort);
|
|
91
|
+
}
|
|
92
|
+
if (call.schema) {
|
|
93
|
+
args.push("--json-schema", JSON.stringify(call.schema));
|
|
94
|
+
}
|
|
95
|
+
if (call.bridgeUrl) {
|
|
96
|
+
args.push("--mcp-config", JSON.stringify({ mcpServers: { step: { type: "http", url: call.bridgeUrl } } }));
|
|
97
|
+
}
|
|
98
|
+
const allowed = [...builtin, ...(call.bridgeUrl ? ["mcp__step"] : [])];
|
|
99
|
+
if (allowed.length) {
|
|
100
|
+
args.push("--allowedTools", allowed.join(","));
|
|
101
|
+
}
|
|
102
|
+
if (call.files.length) {
|
|
103
|
+
args.push("--add-dir", call.dir);
|
|
104
|
+
}
|
|
105
|
+
const { stdout, stderr, code } = await runClient(CLAUDE_BIN, args, {
|
|
106
|
+
cwd: call.dir,
|
|
107
|
+
env: await claudeEnv(),
|
|
108
|
+
signal: call.signal,
|
|
109
|
+
stdin: call.prompt,
|
|
110
|
+
});
|
|
111
|
+
const event = lastJson(stdout);
|
|
112
|
+
if (!event) {
|
|
113
|
+
throw failureOf(this.harness, stderr.trim() || `exit ${code}`);
|
|
114
|
+
}
|
|
115
|
+
if (event.is_error) {
|
|
116
|
+
throw failureOf(this.harness, String(event.result ?? "error"));
|
|
117
|
+
}
|
|
118
|
+
return claudeAnswer(event);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
export const claude = {
|
|
122
|
+
...spec,
|
|
123
|
+
name: "Claude Code",
|
|
124
|
+
bin: CLAUDE_BIN,
|
|
125
|
+
classes: CLASSES,
|
|
126
|
+
async version(env) {
|
|
127
|
+
try {
|
|
128
|
+
const { stdout } = await run(CLAUDE_BIN, ["--version"], { timeout: 5000, env });
|
|
129
|
+
return stdout.trim().split(/\s+/)[0] || stdout.trim();
|
|
130
|
+
}
|
|
131
|
+
catch {
|
|
132
|
+
return null;
|
|
133
|
+
}
|
|
134
|
+
},
|
|
135
|
+
login: { args: ["auth", "login"], interactive: false },
|
|
136
|
+
model: (alias, effort) => new ClaudeCodeModel(claude, alias || "sonnet", effort),
|
|
137
|
+
exhausted: "Das Anthropic-Konto, mit dem Claude Code angemeldet ist, hat kein Guthaben. Melde Claude Code mit deinem Abo an: in den Einstellungen unter „Modelle & Konto“.",
|
|
138
|
+
};
|
|
@@ -0,0 +1,228 @@
|
|
|
1
|
+
import { existsSync, readdirSync, readFileSync, rmSync, statSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { homedir } from "node:os";
|
|
3
|
+
import { join } from "node:path";
|
|
4
|
+
import { resolveEnv, run } from "./env.js";
|
|
5
|
+
import { failureOf, HarnessImageModel, HarnessModel, runClient, } from "./model.js";
|
|
6
|
+
/**
|
|
7
|
+
* Codex: `codex exec` answers on the ChatGPT sign-in kept in CODEX_HOME. The person's own
|
|
8
|
+
* config is left out so none of their MCP servers start for a call; tools go over MCP through
|
|
9
|
+
* a config override; structured output is validated by the client (`--output-schema`).
|
|
10
|
+
*/
|
|
11
|
+
export const CODEX_BIN = process.env.CODEX_BIN?.trim() || "codex";
|
|
12
|
+
/**
|
|
13
|
+
* Models a ChatGPT sign-in runs (checked 2026-10-04): Luna for small calls, Sol as the
|
|
14
|
+
* workhorse, Astra for the hardest work. A binding `codex/<model>` names another;
|
|
15
|
+
* `codex/default` = the model the client picks itself.
|
|
16
|
+
*/
|
|
17
|
+
const CLASSES = {
|
|
18
|
+
classifier: "gpt-6-luna",
|
|
19
|
+
standard: "gpt-6.1-sol",
|
|
20
|
+
high: "gpt-6.1-sol",
|
|
21
|
+
highest: "gpt-6-astra",
|
|
22
|
+
};
|
|
23
|
+
const spec = {
|
|
24
|
+
id: "codex",
|
|
25
|
+
passThrough: /^CODEX_HOME$/,
|
|
26
|
+
keyVars: [],
|
|
27
|
+
async auth(env) {
|
|
28
|
+
try {
|
|
29
|
+
const { stdout, stderr } = await run(CODEX_BIN, ["login", "status"], {
|
|
30
|
+
timeout: 15_000,
|
|
31
|
+
env,
|
|
32
|
+
});
|
|
33
|
+
const text = `${stdout}\n${stderr}`;
|
|
34
|
+
if (/not logged in/i.test(text)) {
|
|
35
|
+
return "none";
|
|
36
|
+
}
|
|
37
|
+
if (/api key/i.test(text)) {
|
|
38
|
+
return "api_key";
|
|
39
|
+
}
|
|
40
|
+
return /logged in/i.test(text) ? "subscription" : "none";
|
|
41
|
+
}
|
|
42
|
+
catch {
|
|
43
|
+
return "none";
|
|
44
|
+
}
|
|
45
|
+
},
|
|
46
|
+
};
|
|
47
|
+
/** The events of one run as the model call wants them: the last agent message and the usage. */
|
|
48
|
+
export function codexAnswer(stdout) {
|
|
49
|
+
let text = null;
|
|
50
|
+
let usage;
|
|
51
|
+
let failure = null;
|
|
52
|
+
let sessionId;
|
|
53
|
+
for (const line of stdout.split("\n")) {
|
|
54
|
+
let event;
|
|
55
|
+
try {
|
|
56
|
+
event = JSON.parse(line);
|
|
57
|
+
}
|
|
58
|
+
catch {
|
|
59
|
+
continue;
|
|
60
|
+
}
|
|
61
|
+
if (event.type === "thread.started") {
|
|
62
|
+
sessionId = event.thread_id;
|
|
63
|
+
}
|
|
64
|
+
else if (event.type === "item.completed" && event.item?.type === "agent_message") {
|
|
65
|
+
text = event.item.text ?? "";
|
|
66
|
+
}
|
|
67
|
+
else if (event.type === "turn.completed") {
|
|
68
|
+
const u = event.usage ?? {};
|
|
69
|
+
usage = {
|
|
70
|
+
input: u.input_tokens ?? 0,
|
|
71
|
+
cacheRead: u.cached_input_tokens ?? 0,
|
|
72
|
+
cacheWrite: u.cache_write_input_tokens ?? 0,
|
|
73
|
+
output: u.output_tokens ?? 0,
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
else if (event.type === "turn.failed" || event.type === "error") {
|
|
77
|
+
failure =
|
|
78
|
+
typeof event.error === "string"
|
|
79
|
+
? event.error
|
|
80
|
+
: (event.error?.message ?? event.message ?? "error");
|
|
81
|
+
}
|
|
82
|
+
}
|
|
83
|
+
return {
|
|
84
|
+
answer: text === null ? null : { text, usage, sessionId },
|
|
85
|
+
failure,
|
|
86
|
+
};
|
|
87
|
+
}
|
|
88
|
+
class CodexModel extends HarnessModel {
|
|
89
|
+
provider = "codex";
|
|
90
|
+
nativeSchema = true;
|
|
91
|
+
async invoke(call) {
|
|
92
|
+
const args = [
|
|
93
|
+
"exec",
|
|
94
|
+
"-",
|
|
95
|
+
"--json",
|
|
96
|
+
"--ephemeral",
|
|
97
|
+
"--skip-git-repo-check",
|
|
98
|
+
"--ignore-user-config",
|
|
99
|
+
"-s",
|
|
100
|
+
"read-only",
|
|
101
|
+
"-C",
|
|
102
|
+
call.dir,
|
|
103
|
+
"-c",
|
|
104
|
+
'approval_policy="never"',
|
|
105
|
+
"-c",
|
|
106
|
+
`web_search="${call.webSearch ? "live" : "disabled"}"`,
|
|
107
|
+
];
|
|
108
|
+
if (this.modelId !== "default") {
|
|
109
|
+
args.push("-m", this.modelId);
|
|
110
|
+
}
|
|
111
|
+
if (call.effort) {
|
|
112
|
+
args.push("-c", `model_reasoning_effort="${call.effort}"`);
|
|
113
|
+
}
|
|
114
|
+
if (call.system) {
|
|
115
|
+
// A JSON string is a TOML basic string: the instructions travel as one config value.
|
|
116
|
+
args.push("-c", `developer_instructions=${JSON.stringify(call.system)}`);
|
|
117
|
+
}
|
|
118
|
+
if (call.bridgeUrl) {
|
|
119
|
+
args.push("-c", `mcp_servers.step.url="${call.bridgeUrl}"`, "-c", "mcp_servers.step.startup_timeout_sec=30", "-c", "mcp_servers.step.tool_timeout_sec=600",
|
|
120
|
+
// The tools are this runtime's own: they run without the client asking anyone.
|
|
121
|
+
"-c", 'mcp_servers.step.default_tools_approval_mode="approve"');
|
|
122
|
+
}
|
|
123
|
+
if (call.schema) {
|
|
124
|
+
const file = join(call.dir, "schema.json");
|
|
125
|
+
writeFileSync(file, JSON.stringify(call.schema));
|
|
126
|
+
args.push("--output-schema", file);
|
|
127
|
+
}
|
|
128
|
+
const { stdout, stderr, code } = await runClient(CODEX_BIN, args, {
|
|
129
|
+
cwd: call.dir,
|
|
130
|
+
env: (await resolveEnv(spec)).env,
|
|
131
|
+
signal: call.signal,
|
|
132
|
+
stdin: call.prompt,
|
|
133
|
+
});
|
|
134
|
+
const { answer, failure } = codexAnswer(stdout);
|
|
135
|
+
if (failure || !answer) {
|
|
136
|
+
throw failureOf(this.harness, failure ?? stderr.trim() ?? `exit ${code}`);
|
|
137
|
+
}
|
|
138
|
+
return answer;
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
/**
|
|
142
|
+
* Images: `codex exec` with the built-in image tool, the reference photos attached with `-i`.
|
|
143
|
+
* Codex keeps what it made under CODEX_HOME/generated_images/<thread>; the file is read from
|
|
144
|
+
* there and that folder removed. `codex/image` runs the tool on the standard model.
|
|
145
|
+
*/
|
|
146
|
+
class CodexImageModel extends HarnessImageModel {
|
|
147
|
+
provider = "codex";
|
|
148
|
+
async invoke(call) {
|
|
149
|
+
const prompt = [
|
|
150
|
+
call.prompt,
|
|
151
|
+
call.files.length
|
|
152
|
+
? "Work from the attached image(s): edit them as described, keeping what is not to change."
|
|
153
|
+
: "",
|
|
154
|
+
call.aspectRatio ? `Aspect ratio: ${call.aspectRatio}.` : "",
|
|
155
|
+
"Make exactly one image with your image generation tool. Write no files yourself and run no commands. Reply only with: done",
|
|
156
|
+
]
|
|
157
|
+
.filter(Boolean)
|
|
158
|
+
.join("\n\n");
|
|
159
|
+
const args = [
|
|
160
|
+
"exec",
|
|
161
|
+
"-",
|
|
162
|
+
"--json",
|
|
163
|
+
"--ephemeral",
|
|
164
|
+
"--skip-git-repo-check",
|
|
165
|
+
"--ignore-user-config",
|
|
166
|
+
"-s",
|
|
167
|
+
"read-only",
|
|
168
|
+
"-C",
|
|
169
|
+
call.dir,
|
|
170
|
+
"-c",
|
|
171
|
+
'approval_policy="never"',
|
|
172
|
+
"-c",
|
|
173
|
+
'web_search="disabled"',
|
|
174
|
+
"-m",
|
|
175
|
+
this.modelId === "image" ? CLASSES.standard : this.modelId,
|
|
176
|
+
];
|
|
177
|
+
for (const file of call.files) {
|
|
178
|
+
args.push("-i", file);
|
|
179
|
+
}
|
|
180
|
+
const { env } = await resolveEnv(spec);
|
|
181
|
+
const { stdout, stderr, code } = await runClient(CODEX_BIN, args, {
|
|
182
|
+
cwd: call.dir,
|
|
183
|
+
env,
|
|
184
|
+
signal: call.signal,
|
|
185
|
+
stdin: prompt,
|
|
186
|
+
});
|
|
187
|
+
const { answer, failure } = codexAnswer(stdout);
|
|
188
|
+
if (failure || !answer?.sessionId) {
|
|
189
|
+
throw failureOf(this.harness, failure ?? stderr.trim() ?? `exit ${code}`);
|
|
190
|
+
}
|
|
191
|
+
const folder = join(env.CODEX_HOME?.trim() || join(homedir(), ".codex"), "generated_images", answer.sessionId);
|
|
192
|
+
try {
|
|
193
|
+
const made = existsSync(folder)
|
|
194
|
+
? readdirSync(folder)
|
|
195
|
+
.filter((f) => /\.(png|jpe?g|webp)$/i.test(f))
|
|
196
|
+
.map((f) => join(folder, f))
|
|
197
|
+
.sort((a, b) => statSync(b).mtimeMs - statSync(a).mtimeMs)
|
|
198
|
+
: [];
|
|
199
|
+
if (!made[0]) {
|
|
200
|
+
throw new Error(`Codex made no image: ${answer.text.slice(0, 300)}`);
|
|
201
|
+
}
|
|
202
|
+
return new Uint8Array(readFileSync(made[0]));
|
|
203
|
+
}
|
|
204
|
+
finally {
|
|
205
|
+
rmSync(folder, { recursive: true, force: true });
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
}
|
|
209
|
+
export const codex = {
|
|
210
|
+
...spec,
|
|
211
|
+
name: "Codex",
|
|
212
|
+
bin: CODEX_BIN,
|
|
213
|
+
classes: CLASSES,
|
|
214
|
+
async version(env) {
|
|
215
|
+
try {
|
|
216
|
+
const { stdout } = await run(CODEX_BIN, ["--version"], { timeout: 5000, env });
|
|
217
|
+
const words = stdout.trim().split(/\s+/);
|
|
218
|
+
return words[words.length - 1] || null;
|
|
219
|
+
}
|
|
220
|
+
catch {
|
|
221
|
+
return null;
|
|
222
|
+
}
|
|
223
|
+
},
|
|
224
|
+
login: { args: ["login"], interactive: false },
|
|
225
|
+
model: (alias, effort) => new CodexModel(codex, alias || "default", effort),
|
|
226
|
+
image: { alias: "image", model: (alias) => new CodexImageModel(codex, alias || "image") },
|
|
227
|
+
exhausted: "Das Kontingent des ChatGPT-Kontos, mit dem Codex angemeldet ist, ist aufgebraucht. Später noch einmal versuchen oder einen anderen Client wählen.",
|
|
228
|
+
};
|
|
@@ -0,0 +1,93 @@
|
|
|
1
|
+
import { mkdirSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { resolveEnv, run } from "./env.js";
|
|
4
|
+
import { failureOf, HarnessModel, lastJson, runClient, } from "./model.js";
|
|
5
|
+
/**
|
|
6
|
+
* Cursor Agent: `cursor-agent -p` answers on the Cursor sign-in. The call's folder is its
|
|
7
|
+
* workspace: the instructions as AGENTS.md, the tools as an MCP server in .cursor/mcp.json.
|
|
8
|
+
* The answer is asked for as JSON where a schema is wanted.
|
|
9
|
+
*/
|
|
10
|
+
export const CURSOR_BIN = process.env.CURSOR_AGENT_BIN?.trim() || "cursor-agent";
|
|
11
|
+
/** "auto" = the model the client picks itself; a binding `cursor/<model>` names one. */
|
|
12
|
+
const CLASSES = {
|
|
13
|
+
classifier: "auto",
|
|
14
|
+
standard: "auto",
|
|
15
|
+
high: "auto",
|
|
16
|
+
highest: "auto",
|
|
17
|
+
};
|
|
18
|
+
/** The escape sequences a terminal app draws with. */
|
|
19
|
+
const ANSI = new RegExp(`${String.fromCharCode(27)}\\[[0-9;]*[A-Za-z]`, "g");
|
|
20
|
+
const spec = {
|
|
21
|
+
id: "cursor",
|
|
22
|
+
passThrough: /^CURSOR_API_KEY$/,
|
|
23
|
+
keyVars: ["CURSOR_API_KEY"],
|
|
24
|
+
async auth(env) {
|
|
25
|
+
try {
|
|
26
|
+
const { stdout, stderr } = await run(CURSOR_BIN, ["status"], { timeout: 15_000, env });
|
|
27
|
+
// The client draws on the terminal even here: the control sequences are taken out.
|
|
28
|
+
const text = `${stdout}\n${stderr}`.replace(ANSI, "");
|
|
29
|
+
if (/not logged in/i.test(text) || !/logged in/i.test(text)) {
|
|
30
|
+
return "none";
|
|
31
|
+
}
|
|
32
|
+
return env.CURSOR_API_KEY ? "api_key" : "subscription";
|
|
33
|
+
}
|
|
34
|
+
catch {
|
|
35
|
+
return "none";
|
|
36
|
+
}
|
|
37
|
+
},
|
|
38
|
+
};
|
|
39
|
+
class CursorModel extends HarnessModel {
|
|
40
|
+
provider = "cursor-agent";
|
|
41
|
+
async invoke(call) {
|
|
42
|
+
if (call.system) {
|
|
43
|
+
writeFileSync(join(call.dir, "AGENTS.md"), call.system);
|
|
44
|
+
}
|
|
45
|
+
if (call.bridgeUrl) {
|
|
46
|
+
mkdirSync(join(call.dir, ".cursor"), { recursive: true });
|
|
47
|
+
writeFileSync(join(call.dir, ".cursor", "mcp.json"), JSON.stringify({ mcpServers: { step: { url: call.bridgeUrl } } }));
|
|
48
|
+
}
|
|
49
|
+
const args = ["-p", "--output-format", "json", "--workspace", call.dir, "--approve-mcps"];
|
|
50
|
+
if (this.modelId !== "auto") {
|
|
51
|
+
args.push("--model", this.modelId);
|
|
52
|
+
}
|
|
53
|
+
if (!call.bridgeUrl) {
|
|
54
|
+
// Nothing to work through: the client answers, it does not edit.
|
|
55
|
+
args.push("--mode", "ask");
|
|
56
|
+
}
|
|
57
|
+
const { stdout, stderr, code } = await runClient(CURSOR_BIN, args, {
|
|
58
|
+
cwd: call.dir,
|
|
59
|
+
env: (await resolveEnv(spec)).env,
|
|
60
|
+
signal: call.signal,
|
|
61
|
+
stdin: call.prompt,
|
|
62
|
+
});
|
|
63
|
+
const event = lastJson(stdout);
|
|
64
|
+
if (!event) {
|
|
65
|
+
throw failureOf(this.harness, stderr.trim() || `exit ${code}`);
|
|
66
|
+
}
|
|
67
|
+
if (event.is_error) {
|
|
68
|
+
throw failureOf(this.harness, String(event.result ?? "error"));
|
|
69
|
+
}
|
|
70
|
+
return {
|
|
71
|
+
text: typeof event.result === "string" ? event.result : JSON.stringify(event.result ?? ""),
|
|
72
|
+
sessionId: event.session_id,
|
|
73
|
+
meta: { durationMs: event.duration_ms ?? 0 },
|
|
74
|
+
};
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
export const cursor = {
|
|
78
|
+
...spec,
|
|
79
|
+
name: "Cursor Agent",
|
|
80
|
+
bin: CURSOR_BIN,
|
|
81
|
+
classes: CLASSES,
|
|
82
|
+
async version(env) {
|
|
83
|
+
try {
|
|
84
|
+
const { stdout } = await run(CURSOR_BIN, ["--version"], { timeout: 10_000, env });
|
|
85
|
+
return stdout.trim().split("\n").pop()?.trim() || null;
|
|
86
|
+
}
|
|
87
|
+
catch {
|
|
88
|
+
return null;
|
|
89
|
+
}
|
|
90
|
+
},
|
|
91
|
+
login: { args: ["login"], interactive: false },
|
|
92
|
+
model: (alias, effort) => new CursorModel(cursor, alias || "auto", effort),
|
|
93
|
+
};
|
|
@@ -0,0 +1,124 @@
|
|
|
1
|
+
import { execFile, spawn } from "node:child_process";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { promisify } from "node:util";
|
|
4
|
+
import { layout } from "../cli/home.js";
|
|
5
|
+
export const run = promisify(execFile);
|
|
6
|
+
let shellEnv = null;
|
|
7
|
+
/** The person's login shell, asked once: a desktop app and a bare dev server start without its variables. */
|
|
8
|
+
export function loginShellEnv() {
|
|
9
|
+
shellEnv ??= new Promise((done) => {
|
|
10
|
+
const shell = process.env.SHELL || "/bin/zsh";
|
|
11
|
+
// In a session of its own, away from the terminal `wizards` runs in: an interactive
|
|
12
|
+
// shell takes over that terminal for its job control and keeps it when it ends, and Ctrl-C
|
|
13
|
+
// then reaches nobody. (execFile cannot do this: it drops `detached`.)
|
|
14
|
+
const child = spawn(shell, ["-l", "-i", "-c", "env"], {
|
|
15
|
+
detached: true,
|
|
16
|
+
stdio: ["ignore", "pipe", "ignore"],
|
|
17
|
+
timeout: 10_000,
|
|
18
|
+
env: {
|
|
19
|
+
HOME: process.env.HOME,
|
|
20
|
+
USER: process.env.USER,
|
|
21
|
+
PATH: process.env.PATH,
|
|
22
|
+
TERM: "dumb",
|
|
23
|
+
},
|
|
24
|
+
});
|
|
25
|
+
let stdout = "";
|
|
26
|
+
child.stdout.setEncoding("utf8");
|
|
27
|
+
child.stdout.on("data", (chunk) => {
|
|
28
|
+
stdout += chunk;
|
|
29
|
+
});
|
|
30
|
+
child.on("error", () => done({}));
|
|
31
|
+
child.on("close", (code) => {
|
|
32
|
+
const out = {};
|
|
33
|
+
for (const line of code === 0 ? stdout.split("\n") : []) {
|
|
34
|
+
const at = line.indexOf("=");
|
|
35
|
+
if (at > 0) {
|
|
36
|
+
out[line.slice(0, at)] = line.slice(at + 1);
|
|
37
|
+
}
|
|
38
|
+
}
|
|
39
|
+
done(out);
|
|
40
|
+
});
|
|
41
|
+
});
|
|
42
|
+
return shellEnv;
|
|
43
|
+
}
|
|
44
|
+
/**
|
|
45
|
+
* The environment a client gets: its sign-in as the login shell sets it (else as this process
|
|
46
|
+
* has it), PATH as the shell has it so the client is found from a GUI app too, and nothing else —
|
|
47
|
+
* no key of ours, no session of a parent client.
|
|
48
|
+
*/
|
|
49
|
+
export function pickEnv(shell, own, passThrough,
|
|
50
|
+
/** Folders before the shell's PATH: the clients the setup installed. */
|
|
51
|
+
first = []) {
|
|
52
|
+
const picked = {};
|
|
53
|
+
const source = Object.keys(shell).length ? shell : own;
|
|
54
|
+
for (const [key, value] of Object.entries(source)) {
|
|
55
|
+
if (passThrough.test(key) && value) {
|
|
56
|
+
picked[key] = value;
|
|
57
|
+
}
|
|
58
|
+
}
|
|
59
|
+
const path = [...first, ...(shell.PATH ?? "").split(":"), ...(own.PATH ?? "").split(":")].filter(Boolean);
|
|
60
|
+
return {
|
|
61
|
+
...picked,
|
|
62
|
+
PATH: [...new Set(path)].join(":"),
|
|
63
|
+
HOME: own.HOME,
|
|
64
|
+
USER: own.USER,
|
|
65
|
+
LANG: "en_US.UTF-8",
|
|
66
|
+
TERM: "dumb",
|
|
67
|
+
};
|
|
68
|
+
}
|
|
69
|
+
export function stripKeys(env, keyVars) {
|
|
70
|
+
const bare = { ...env };
|
|
71
|
+
for (const key of keyVars) {
|
|
72
|
+
delete bare[key];
|
|
73
|
+
}
|
|
74
|
+
return bare;
|
|
75
|
+
}
|
|
76
|
+
/** Where the setup installs clients from npm, whoever started this runtime. */
|
|
77
|
+
const installed = () => [join(layout().clients, "bin")];
|
|
78
|
+
const resolved = new Map();
|
|
79
|
+
/**
|
|
80
|
+
* The environment a client runs in, decided once: the subscription sign-in comes first — it is
|
|
81
|
+
* what the person installed the client for — so a key the shell sets is held back while the
|
|
82
|
+
* client is signed in without it, and used only where it is the one way in.
|
|
83
|
+
*/
|
|
84
|
+
export function resolveEnv(spec) {
|
|
85
|
+
let pending = resolved.get(spec.id);
|
|
86
|
+
if (!pending) {
|
|
87
|
+
pending = (async () => {
|
|
88
|
+
const withKey = pickEnv(await loginShellEnv(), process.env, spec.passThrough, installed());
|
|
89
|
+
const bare = stripKeys(withKey, spec.keyVars);
|
|
90
|
+
const own = await spec.auth(bare);
|
|
91
|
+
if (own !== "none") {
|
|
92
|
+
return { env: bare, auth: own };
|
|
93
|
+
}
|
|
94
|
+
if (spec.keyVars.some((key) => withKey[key])) {
|
|
95
|
+
const keyed = await spec.auth(withKey);
|
|
96
|
+
if (keyed !== "none") {
|
|
97
|
+
return { env: withKey, auth: "api_key" };
|
|
98
|
+
}
|
|
99
|
+
}
|
|
100
|
+
return { env: bare, auth: "none" };
|
|
101
|
+
})();
|
|
102
|
+
resolved.set(spec.id, pending);
|
|
103
|
+
}
|
|
104
|
+
return pending;
|
|
105
|
+
}
|
|
106
|
+
/** The environment of a sign-in: the client on its own, without a key of the shell, in a real terminal. */
|
|
107
|
+
export async function loginEnv(spec) {
|
|
108
|
+
const bare = stripKeys(pickEnv(await loginShellEnv(), process.env, spec.passThrough, installed()), spec.keyVars);
|
|
109
|
+
return { ...bare, TERM: "xterm-256color", COLORTERM: "truecolor" };
|
|
110
|
+
}
|
|
111
|
+
/** The client's sign-in right now, on its own: asked again and again while the person signs in. */
|
|
112
|
+
export async function freshAuth(spec) {
|
|
113
|
+
return spec.auth(await loginEnv(spec));
|
|
114
|
+
}
|
|
115
|
+
/** Forgets what was decided: the person may just have signed in or changed their shell. */
|
|
116
|
+
export function forgetEnv(id) {
|
|
117
|
+
shellEnv = null;
|
|
118
|
+
if (id) {
|
|
119
|
+
resolved.delete(id);
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
resolved.clear();
|
|
123
|
+
}
|
|
124
|
+
}
|