@tangle-network/cli-agent-registry 0.0.0-stage → 0.14.0

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 ADDED
@@ -0,0 +1,11 @@
1
+ Copyright (c) 2025 Tangle Network
2
+
3
+ All rights reserved.
4
+
5
+ This software and associated documentation files (the "Software") are proprietary
6
+ and confidential. No part of this Software may be reproduced, distributed, or
7
+ transmitted in any form or by any means, including photocopying, recording, or
8
+ other electronic or mechanical methods, without the prior written permission of
9
+ Tangle Network.
10
+
11
+ For licensing inquiries, contact: hello@tangle.tools
package/README.md CHANGED
@@ -1,3 +1,146 @@
1
- # Temporary Holding Version
1
+ # @tangle-network/cli-agent-registry
2
2
 
3
- This version is a temporary placeholder for this package. An operational version to replace this has been submitted for review and is awaiting a staged release.
3
+ CLI harness descriptors, capabilities, model wiring, and local credential discovery.
4
+ Use the narrowest entrypoint for the runtime:
5
+
6
+ ```ts
7
+ import { getSpec } from "@tangle-network/cli-agent-registry/specs";
8
+ import {
9
+ describeLocalCliAuthExpectation,
10
+ discoverLocalCliAuthFiles,
11
+ } from "@tangle-network/cli-agent-registry/local-cli-auth";
12
+ ```
13
+
14
+ `/specs` and `/backend-types` are browser-safe. `/local-cli-auth` reads the host
15
+ filesystem and is Node-only. Workspace consumers use the same built declarations
16
+ as published consumers; build the registry before checking or bundling a consumer.
17
+
18
+ Credential discovery follows `CODEX_HOME`, `CLAUDE_CONFIG_DIR` and Claude's
19
+ `CLAUDE_SECURESTORAGE_CONFIG_DIR`; secure storage does not move settings files.
20
+ Pass `{ credentialOnly: true }` to read only the native credential file, without
21
+ settings. An explicit `homeDir` isolates discovery from ambient environment
22
+ overrides unless `env` is also supplied. Returned contents are sensitive raw files,
23
+ not proof of a valid OAuth subscription. Callers own consent and native login
24
+ validation before enrollment. This filesystem reader does not read macOS Keychain.
25
+
26
+ ## Identity, availability and implementation
27
+
28
+ Interface describes recognized identities, including historical ones. This
29
+ installation declares its backend vocabulary positively; importing it must not
30
+ require every harness subsequently recognized by Interface. `BackendType` derives
31
+ from the local declaration. Accepted IDs are unchanged. Removing an executable
32
+ must not invalidate saved identities.
33
+
34
+ `createAdapterRegistry(selectedSpecs)` owns an independent factory map and snapshots
35
+ its wiring requirements. Each selected executable descriptor requires a factory and
36
+ its native-session contract (or the existing explicit unsupported classification).
37
+ Duplicate descriptors and duplicate factory registration fail; the explicit test
38
+ override remains. Later descriptor mutation cannot weaken those requirements.
39
+
40
+ Existing module-level methods use a default registry over `CLI_AGENT_SPECS`, so
41
+ current consumers retain their requirements. Registration does not authorize execution:
42
+ callers must resolve an admitted descriptor and enforce capabilities. Unregistering a
43
+ selected factory still fails startup; it is not a way to drain live sessions. The
44
+ sidecar's all-provider import composition still needs a selected deployment manifest.
45
+
46
+ ## Adapter-owned profile semantics
47
+
48
+ `CliProviderAdapterBase` no longer guesses native profile behavior from a storage
49
+ path or owns Pi's private instruction-directory calculation. A subclass explicitly
50
+ supplies `profileHarness` and, when needed, `profileAgentDirectory`. Claude Code,
51
+ Codex, OpenCode, Kimi, NanoClaw, Pi, Hermes and OpenClaw declare their existing
52
+ profile semantics; Pi resolves its own native private directory.
53
+
54
+ Custom base subclasses using portable profile lowering must declare their identity.
55
+ Ordinary execution without a profile remains available without this declaration.
56
+ Secret concurrent-profile revisions and managed quotes are separate trusted
57
+ implementation capabilities: OpenCode explicitly opts into the existing transports.
58
+ Neither a caller's config key nor the provider name grants them. Native-session,
59
+ private-root, secret-handling and budget checks remain in place.
60
+
61
+ ### System laws
62
+
63
+ - `tests/adapter-removal.contract.test.ts`: selected descriptors require factories,
64
+ omitted descriptors do not, registries are isolated, duplicate descriptors fail,
65
+ and mutation cannot change the captured requirements.
66
+ - `../sdk-provider-cli-base/tests/profile-ownership-process.test.ts`: a real Node
67
+ child observes materialized profiles and private instruction placement; unsupported
68
+ profiles fail before spawn; ordinary execution needs no implicit profile support;
69
+ names/config keys do not grant managed quotes. Node is a process fixture, not a
70
+ claimed substitute for a native coding harness.
71
+ - Native conformance is separately bound to pinned CLI versions, declared session
72
+ contracts and adapter/shared-runner source. Stale receipts fail; never edit their
73
+ hashes to manufacture proof.
74
+
75
+ ## Executed validation
76
+
77
+ Foundation proof: Actions **38684058864**, artifact **11948704158**, commit
78
+ `64bb4f84358990dbd85d64aaf97eb73985f0a964`. Affected builds/typechecks passed;
79
+ **122 registry tests and 355 runtime-contract tests passed**.
80
+
81
+ Expanded proof: Actions **38687071248**, artifact **11950383351**, commit
82
+ `559f2550daa698ddb648ec6470aec477aff84a71`:
83
+
84
+ - The sidecar dependency closure builds.
85
+ - All provider packages and the registry pass typechecking.
86
+ - All 14 provider suites plus the shared base pass: **1,502 tests**, with 65 skipped.
87
+ - Runtime-contract tests: **355 passed**.
88
+ - Registry: **121 passed, 1 failed**. The failure correctly names four stale native
89
+ receipts after the shared-base change: Claude Code, Codex, OpenCode and Pi.
90
+
91
+ ### Native qualification: not passed
92
+
93
+ Two native attempts built the pinned CLI image, then failed connecting to the local
94
+ Sandbox shim before any harness execution. No fresh passing receipt was produced.
95
+
96
+ - Actions **38687071248**, artifact **11950938690**, source
97
+ `559f2550daa698ddb648ec6470aec477aff84a71`: fetch failure, exit 1; owned-container
98
+ cleanup passed.
99
+ - Actions **38688402352**, artifact **11951190503**, source
100
+ `d640034db5ee590c9851daa0049c32d6a3e4c160`: the same attempt with bounded transport
101
+ cause diagnostics reports `UND_ERR_CONNECT_TIMEOUT` for all four requests.
102
+ Exit 1; owned-container cleanup passed. Only test-driver diagnostics changed.
103
+ - Actions **38688973460**, artifact **11951770808**: an isolated network diagnostic
104
+ confirms that the Docker bridge gateway is not a local address in the runner's
105
+ network namespace. The runner's own loopback server answers, but the gateway
106
+ probes do not. No proxy environment is involved. A successful diagnostic job is
107
+ not a successful native qualification.
108
+
109
+ The driver assumes that its host-side shim and Docker bridge share a reachable
110
+ network context. This runner does not satisfy that assumption. Co-locate the test
111
+ services with the Docker network and expose the client endpoint explicitly, or use
112
+ an appropriate co-located runner, before repeating native qualification. Do not
113
+ weaken TLS, network isolation, receipt checks or production controls to force a pass.
114
+ All temporary branch-only proof/diagnostic workflows were removed after these runs.
115
+ No production endpoint or real model was used. Later documentation and workflow
116
+ cleanup commits are not retroactively included in the source commits named above.
117
+
118
+ ## Exact remaining work
119
+
120
+ - [ ] Repair the native proof's runner/network topology; obtain genuine passing
121
+ receipts for the changed runner; rerun the registry/invariant gate.
122
+ - [ ] Select sidecar implementation imports, API catalog admission and image/CLI
123
+ dependencies from one explicit deployment manifest. Registry omission is not
124
+ physical package or executable removal.
125
+ - [ ] Supply real start/resume availability to app admission and pickers; preserve
126
+ old IDs/history; do not pre-coerce saved or explicit requested IDs.
127
+ - [ ] Consolidate remaining per-harness profile/MCP/hooks, auth, invocation, session
128
+ and packaging facts with their owners. Replace the app's duplicated profile
129
+ support table. Common-base inference removal is done, not this entire step.
130
+ - [ ] Declare ACP's selected backing dependency, including its current Hermes
131
+ default; reject missing/retired backings before launch while preserving the
132
+ closed command allowlist and credential isolation.
133
+ - [ ] Consolidate agent-runtime's local invocation and native usage readers with
134
+ their owners; keep historical codecs usable without installed executables.
135
+ - [ ] Physically omit each adapter package/CLI in turn and prove remaining startup,
136
+ cancellation/resume, readable history and secret isolation.
137
+ - [ ] Complete sidecar typecheck and real HTTP/UI/packed-consumer coverage for the
138
+ selected catalog; update the maintained owner/roadmap indexes.
139
+ - [ ] Independent review, final-head checks, merge, package publication/cohort
140
+ adoption and authorized served verification through maintained automation.
141
+
142
+ Continue the same PRs: agent-container **#10961**, agent-sdk **#498**, agent-app
143
+ **#1116**. Their documents retain the executed evidence and remaining adoption.
144
+ Agent-runtime and Braid have no implementation PR from this attempt; change Braid
145
+ only where shared-contract adoption requires it. No production harness is retired
146
+ merely because its implementation was refactored.
@@ -0,0 +1,56 @@
1
+ import { z } from "zod";
2
+
3
+ //#region ../runtime-contracts/dist/backend-types.d.ts
4
+ /**
5
+ * This distribution's backend vocabulary, declared positively. The shared
6
+ * Interface vocabulary also contains historical and differently hosted harnesses;
7
+ * knowing one upstream must not require installing it here or make this module
8
+ * throw at import time. Keep old wire identities readable when retiring execution.
9
+ *
10
+ * Gemini is not a direct Sandbox backend; it is available only through a backing
11
+ * transport. Adding it upstream never implicitly makes it dispatchable here.
12
+ * The spec/adapter composition must still prove every backend it actually enables.
13
+ */
14
+ declare const BACKEND_TYPE: {
15
+ readonly OPENCODE: "opencode";
16
+ readonly CLAUDE_CODE: "claude-code";
17
+ readonly KIMI_CODE: "kimi-code";
18
+ readonly CODEX: "codex";
19
+ readonly AMP: "amp";
20
+ readonly FACTORY_DROIDS: "factory-droids";
21
+ readonly PI: "pi";
22
+ readonly PRIME: "prime";
23
+ readonly HERMES: "hermes";
24
+ readonly FORGE: "forge";
25
+ readonly OPENCLAW: "openclaw";
26
+ readonly NANOCLAW: "nanoclaw";
27
+ readonly ACP: "acp";
28
+ readonly CURSOR: "cursor";
29
+ readonly CLI_BASE: "cli-base";
30
+ };
31
+ /** Derived from the local declaration, not the complement of the upstream enum. */
32
+ type BackendType = (typeof BACKEND_TYPE)[keyof typeof BACKEND_TYPE];
33
+ /** Stable declaration order; registry tests check the shipped spec composition. */
34
+ declare const ALL_BACKEND_TYPES: [BackendType, ...BackendType[]];
35
+ declare const backendTypeSchema: z.ZodEnum<{
36
+ "claude-code": "claude-code";
37
+ nanoclaw: "nanoclaw";
38
+ codex: "codex";
39
+ opencode: "opencode";
40
+ "kimi-code": "kimi-code";
41
+ pi: "pi";
42
+ prime: "prime";
43
+ hermes: "hermes";
44
+ openclaw: "openclaw";
45
+ amp: "amp";
46
+ "factory-droids": "factory-droids";
47
+ forge: "forge";
48
+ cursor: "cursor";
49
+ acp: "acp";
50
+ "cli-base": "cli-base";
51
+ }>;
52
+ declare function isBackendType(value: string): value is BackendType;
53
+ declare function parseBackendType(value: string): BackendType;
54
+ //#endregion
55
+ export { isBackendType as a, backendTypeSchema as i, BACKEND_TYPE as n, parseBackendType as o, BackendType as r, ALL_BACKEND_TYPES as t };
56
+ //# sourceMappingURL=backend-types-CJH-wHS3.d.mts.map
@@ -0,0 +1,45 @@
1
+ import { z } from "zod";
2
+ //#region ../runtime-contracts/dist/backend-types.js
3
+ /**
4
+ * This distribution's backend vocabulary, declared positively. The shared
5
+ * Interface vocabulary also contains historical and differently hosted harnesses;
6
+ * knowing one upstream must not require installing it here or make this module
7
+ * throw at import time. Keep old wire identities readable when retiring execution.
8
+ *
9
+ * Gemini is not a direct Sandbox backend; it is available only through a backing
10
+ * transport. Adding it upstream never implicitly makes it dispatchable here.
11
+ * The spec/adapter composition must still prove every backend it actually enables.
12
+ */
13
+ const BACKEND_TYPE = {
14
+ OPENCODE: "opencode",
15
+ CLAUDE_CODE: "claude-code",
16
+ KIMI_CODE: "kimi-code",
17
+ CODEX: "codex",
18
+ AMP: "amp",
19
+ FACTORY_DROIDS: "factory-droids",
20
+ PI: "pi",
21
+ PRIME: "prime",
22
+ HERMES: "hermes",
23
+ FORGE: "forge",
24
+ OPENCLAW: "openclaw",
25
+ NANOCLAW: "nanoclaw",
26
+ ACP: "acp",
27
+ CURSOR: "cursor",
28
+ CLI_BASE: "cli-base"
29
+ };
30
+ /** Stable declaration order; registry tests check the shipped spec composition. */
31
+ const ALL_BACKEND_TYPES = Object.values(BACKEND_TYPE);
32
+ const backendTypeSchema = z.enum(ALL_BACKEND_TYPES);
33
+ z.string().regex(/^[A-Za-z0-9_.:@-]{1,256}$/);
34
+ const BACKEND_TYPE_SET = new Set(ALL_BACKEND_TYPES);
35
+ function isBackendType(value) {
36
+ return BACKEND_TYPE_SET.has(value);
37
+ }
38
+ function parseBackendType(value) {
39
+ if (!isBackendType(value)) throw new Error(`Unknown backend type "${value}". Expected one of: ${ALL_BACKEND_TYPES.join(", ")}`);
40
+ return value;
41
+ }
42
+ //#endregion
43
+ export { parseBackendType as a, isBackendType as i, BACKEND_TYPE as n, backendTypeSchema as r, ALL_BACKEND_TYPES as t };
44
+
45
+ //# sourceMappingURL=backend-types-CREj4iOY.mjs.map
@@ -0,0 +1,2 @@
1
+ import { a as isBackendType, i as backendTypeSchema, n as BACKEND_TYPE, o as parseBackendType, r as BackendType, t as ALL_BACKEND_TYPES } from "./backend-types-CJH-wHS3.mjs";
2
+ export { ALL_BACKEND_TYPES, BACKEND_TYPE, BackendType, backendTypeSchema, isBackendType, parseBackendType };
@@ -0,0 +1,2 @@
1
+ import { a as parseBackendType, i as isBackendType, n as BACKEND_TYPE, r as backendTypeSchema, t as ALL_BACKEND_TYPES } from "./backend-types-CREj4iOY.mjs";
2
+ export { ALL_BACKEND_TYPES, BACKEND_TYPE, backendTypeSchema, isBackendType, parseBackendType };
@@ -0,0 +1,49 @@
1
+ import { z } from "zod";
2
+
3
+ //#region src/cli-auth-bundle.d.ts
4
+ type LocalCliAuthFile = {
5
+ path: string;
6
+ content: string;
7
+ mode?: number;
8
+ };
9
+ declare const AUTH_BUNDLE_PATHS: Readonly<{
10
+ readonly codex: ".codex/auth.json";
11
+ readonly "claude-code": ".claude/.credentials.json";
12
+ }>;
13
+ /** Convert a native credential JSON bundle without reading host credentials. */
14
+ declare function materializeCliAuthBundle(backendType: string, rawBundle: string): LocalCliAuthFile[];
15
+ declare const CLI_AUTH_REFERENCE_ENV_VAR = "AGENT_INTERNAL_CLI_AUTH_REF";
16
+ declare const cliAuthReferenceSchema: z.ZodObject<{
17
+ account: z.ZodString;
18
+ secretEnv: z.ZodString;
19
+ format: z.ZodEnum<{
20
+ token: "token";
21
+ bundle: "bundle";
22
+ files: "files";
23
+ }>;
24
+ }, z.core.$strict>;
25
+ /** A supplied account binding label, not a verified native account principal. */
26
+ type CliAuthReference = z.infer<typeof cliAuthReferenceSchema>;
27
+ type CliAuthMaterial = {
28
+ reference: CliAuthReference;
29
+ authFiles: LocalCliAuthFile[];
30
+ tokenEnvironment: Record<string, string>;
31
+ };
32
+ declare const cliAuthFilesSchema: z.ZodArray<z.ZodObject<{
33
+ path: z.ZodString;
34
+ content: z.ZodString;
35
+ mode: z.ZodOptional<z.ZodNumber>;
36
+ }, z.core.$strict>>;
37
+ declare function assertCliAuthReferenceSupported(backendType: string, model: {
38
+ cliAuth?: CliAuthReference;
39
+ authMode?: string;
40
+ apiKey?: string;
41
+ apiKeyEnv?: string;
42
+ baseUrl?: string;
43
+ authFiles?: LocalCliAuthFile[];
44
+ }): CliAuthReference | undefined;
45
+ /** Resolve only the selected injected secret, without copying it into a public config. */
46
+ declare function resolveCliAuthReference(backendType: string, model: Parameters<typeof assertCliAuthReferenceSupported>[1], environment: Readonly<Record<string, string | undefined>>): CliAuthMaterial | undefined;
47
+ //#endregion
48
+ export { AUTH_BUNDLE_PATHS, CLI_AUTH_REFERENCE_ENV_VAR, CliAuthMaterial, CliAuthReference, LocalCliAuthFile, assertCliAuthReferenceSupported, cliAuthFilesSchema, cliAuthReferenceSchema, materializeCliAuthBundle, resolveCliAuthReference };
49
+ //# sourceMappingURL=cli-auth-bundle.d.mts.map
@@ -0,0 +1,92 @@
1
+ import { l as getSpec } from "./specs-Bbiay_8K.mjs";
2
+ import { z } from "zod";
3
+ //#region src/cli-auth-bundle.ts
4
+ const DEFAULT_MODE = 384;
5
+ const AUTH_BUNDLE_PATHS = Object.freeze({
6
+ codex: ".codex/auth.json",
7
+ "claude-code": ".claude/.credentials.json"
8
+ });
9
+ /** Convert a native credential JSON bundle without reading host credentials. */
10
+ function materializeCliAuthBundle(backendType, rawBundle) {
11
+ if (backendType !== "codex" && backendType !== "claude-code") throw new Error("CLI auth bundle is not supported for the selected backend");
12
+ if (typeof rawBundle !== "string") throw new Error("CLI auth bundle must be a JSON object");
13
+ let value;
14
+ try {
15
+ value = JSON.parse(rawBundle);
16
+ } catch {
17
+ throw new Error("CLI auth bundle must be a JSON object");
18
+ }
19
+ if (!value || typeof value !== "object" || Array.isArray(value)) throw new Error("CLI auth bundle must be a JSON object");
20
+ return [{
21
+ path: AUTH_BUNDLE_PATHS[backendType],
22
+ content: rawBundle,
23
+ mode: DEFAULT_MODE
24
+ }];
25
+ }
26
+ const CLI_AUTH_REFERENCE_ENV_VAR = "AGENT_INTERNAL_CLI_AUTH_REF";
27
+ const cliAuthReferenceSchema = z.object({
28
+ account: z.string().regex(/^[A-Za-z0-9][A-Za-z0-9@._+-]{0,127}$/),
29
+ secretEnv: z.string().regex(/^[A-Za-z_][A-Za-z0-9_]{0,63}$/),
30
+ format: z.enum([
31
+ "token",
32
+ "bundle",
33
+ "files"
34
+ ])
35
+ }).strict();
36
+ const cliAuthFilesSchema = z.array(z.object({
37
+ path: z.string().min(1).max(4096).refine((value) => !value.startsWith("/") && !/^[A-Za-z]:/.test(value) && !/[\\\0]/.test(value) && value.split("/").every((part) => part !== "" && part !== "." && part !== "..")),
38
+ content: z.string().max(1024 * 1024),
39
+ mode: z.number().int().min(0).max(448).refine((mode) => (mode & 63) === 0).optional()
40
+ }).strict()).min(1).max(128);
41
+ function assertCliAuthReferenceSupported(backendType, model) {
42
+ if (model.cliAuth === void 0) return void 0;
43
+ const parsedReference = cliAuthReferenceSchema.safeParse(model.cliAuth);
44
+ if (!parsedReference.success) throw new Error("Invalid native authentication reference");
45
+ const reference = parsedReference.data;
46
+ if (model.authMode === "api-key" || model.apiKey !== void 0 || model.apiKeyEnv !== void 0 || model.baseUrl !== void 0 || model.authFiles !== void 0) throw new Error("cliAuth cannot be combined with API credentials, baseUrl, or inline authFiles");
47
+ const spec = getSpec(backendType);
48
+ if (!spec?.hasCliAuth) throw new Error(`Native CLI authentication is unsupported by ${backendType}`);
49
+ if (reference.format === "token" && !spec.envVars.oauthToken) throw new Error(`Native subscription tokens are unsupported by ${backendType}`);
50
+ if (reference.format === "bundle" && backendType !== "codex" && backendType !== "claude-code") throw new Error(`Native credential bundles are unsupported by ${backendType}`);
51
+ return reference;
52
+ }
53
+ /** Resolve only the selected injected secret, without copying it into a public config. */
54
+ function resolveCliAuthReference(backendType, model, environment) {
55
+ const reference = assertCliAuthReferenceSupported(backendType, model);
56
+ if (!reference) return void 0;
57
+ const value = environment[reference.secretEnv];
58
+ if (typeof value !== "string" || value.trim().length === 0) throw new Error(`Missing native credential environment variable: ${reference.secretEnv}`);
59
+ if (reference.format === "token") {
60
+ const tokenEnv = getSpec(backendType)?.envVars.oauthToken;
61
+ if (!tokenEnv) throw new Error(`Native subscription tokens are unsupported by ${backendType}`);
62
+ if (/\s/.test(value) || value.startsWith("{") || value.startsWith("[")) throw new Error("Native token authentication requires a raw token, not a credential wrapper");
63
+ return {
64
+ reference,
65
+ authFiles: [],
66
+ tokenEnvironment: { [tokenEnv]: value }
67
+ };
68
+ }
69
+ if (reference.format === "bundle") return {
70
+ reference,
71
+ authFiles: materializeCliAuthBundle(backendType, value),
72
+ tokenEnvironment: {}
73
+ };
74
+ let parsed;
75
+ try {
76
+ parsed = JSON.parse(value);
77
+ } catch {
78
+ throw new Error("Native authFiles secret must contain a JSON array of auth files");
79
+ }
80
+ const files = cliAuthFilesSchema.safeParse(parsed);
81
+ if (!files.success) throw new Error("Native authFiles secret must contain a JSON array of auth files");
82
+ if (backendType === "codex" && (files.data.length !== 1 || files.data[0]?.path !== AUTH_BUNDLE_PATHS.codex || files.data[0]?.content.trim().length === 0)) throw new Error("Codex native auth files require only .codex/auth.json");
83
+ return {
84
+ reference,
85
+ authFiles: files.data,
86
+ tokenEnvironment: {}
87
+ };
88
+ }
89
+ //#endregion
90
+ export { AUTH_BUNDLE_PATHS, CLI_AUTH_REFERENCE_ENV_VAR, assertCliAuthReferenceSupported, cliAuthFilesSchema, cliAuthReferenceSchema, materializeCliAuthBundle, resolveCliAuthReference };
91
+
92
+ //# sourceMappingURL=cli-auth-bundle.mjs.map
@@ -0,0 +1,54 @@
1
+ import { CliAuthMaterial, CliAuthReference } from "./cli-auth-bundle.mjs";
2
+ import { z } from "zod";
3
+
4
+ //#region src/cli-auth-placeholder.d.ts
5
+ declare const metadataSchema: z.ZodObject<{
6
+ accountId: z.ZodOptional<z.ZodString>;
7
+ planType: z.ZodOptional<z.ZodUnion<readonly [z.ZodEnum<{
8
+ free: "free";
9
+ plus: "plus";
10
+ pro: "pro";
11
+ business: "business";
12
+ enterprise: "enterprise";
13
+ edu: "edu";
14
+ team: "team";
15
+ }>, z.ZodEnum<{
16
+ free: "free";
17
+ pro: "pro";
18
+ enterprise: "enterprise";
19
+ team: "team";
20
+ max: "max";
21
+ }>]>>;
22
+ accessTokenExpiresAt: z.ZodOptional<z.ZodNumber>;
23
+ hasRefreshToken: z.ZodBoolean;
24
+ }, z.core.$strict>;
25
+ /** Routing/display hints only; these fields do not verify an account principal. */
26
+ type CliAuthPlaceholderMetadata = z.infer<typeof metadataSchema>;
27
+ type PlaceholderInput = {
28
+ backendType: string;
29
+ reference: CliAuthReference; /** The existing egress owner supplies this sandbox's declared-slot token. */
30
+ placeholder: string; /** CLI compatibility timestamp, never an assertion of provider validity. */
31
+ now: Date;
32
+ metadata: CliAuthPlaceholderMetadata;
33
+ };
34
+ /** Build sandbox-safe native material without accepting any provider token. */
35
+ declare function buildCliAuthPlaceholderMaterial(input: PlaceholderInput): CliAuthMaterial;
36
+ /**
37
+ * TRUSTED OWNER ONLY. The explicit proxyAccessToken result must reach only the
38
+ * host's existing egress holder. Only nativeMaterial may enter the sandbox.
39
+ * The uploaded bundle, ID token and refresh token are never returned.
40
+ */
41
+ declare function resolveCliAuthPlaceholder(input: {
42
+ backendType: string;
43
+ reference: CliAuthReference;
44
+ secretValue: string;
45
+ placeholder: string;
46
+ now: Date;
47
+ }): {
48
+ proxyAccessToken: string;
49
+ nativeMaterial: CliAuthMaterial;
50
+ metadata: CliAuthPlaceholderMetadata;
51
+ };
52
+ //#endregion
53
+ export { CliAuthPlaceholderMetadata, buildCliAuthPlaceholderMaterial, resolveCliAuthPlaceholder };
54
+ //# sourceMappingURL=cli-auth-placeholder.d.mts.map
@@ -0,0 +1,200 @@
1
+ import { AUTH_BUNDLE_PATHS, assertCliAuthReferenceSupported, resolveCliAuthReference } from "./cli-auth-bundle.mjs";
2
+ import { i as baseProxyToken, r as declaredSecretSlotTokens } from "./harness-DIdVNSwl.mjs";
3
+ import { z } from "zod";
4
+ //#region src/cli-auth-placeholder.ts
5
+ const identity = z.string().regex(/^[A-Za-z0-9_-]{1,256}$/);
6
+ const token = z.string().min(8).max(64 * 1024).regex(/^[^\s\0]+$/);
7
+ const expiry = z.number().int().positive().max(864e13);
8
+ const codexPlan = z.enum([
9
+ "free",
10
+ "plus",
11
+ "pro",
12
+ "business",
13
+ "enterprise",
14
+ "edu",
15
+ "team"
16
+ ]);
17
+ const claudePlan = z.enum([
18
+ "free",
19
+ "pro",
20
+ "max",
21
+ "team",
22
+ "enterprise"
23
+ ]);
24
+ const metadataSchema = z.object({
25
+ accountId: identity.optional(),
26
+ planType: z.union([codexPlan, claudePlan]).optional(),
27
+ accessTokenExpiresAt: expiry.optional(),
28
+ hasRefreshToken: z.boolean()
29
+ }).strict();
30
+ const codexBundle = z.object({ tokens: z.object({
31
+ access_token: token,
32
+ refresh_token: z.union([token, z.literal("")]).optional(),
33
+ id_token: token,
34
+ account_id: identity.nullable().optional()
35
+ }) });
36
+ const codexClaims = z.object({ "https://api.openai.com/auth": z.object({
37
+ chatgpt_account_id: identity.optional(),
38
+ chatgpt_plan_type: codexPlan.optional(),
39
+ chatgpt_account_is_fedramp: z.boolean().optional()
40
+ }).optional() });
41
+ const claudeBundle = z.object({ claudeAiOauth: z.object({
42
+ accessToken: token,
43
+ refreshToken: token.nullable().optional(),
44
+ expiresAt: expiry.nullable().optional(),
45
+ subscriptionType: claudePlan.nullable().optional()
46
+ }) });
47
+ function invalidMaterial() {
48
+ throw new Error("Unsupported or invalid native credential material");
49
+ }
50
+ function parseJson(raw) {
51
+ try {
52
+ return JSON.parse(raw);
53
+ } catch {
54
+ return invalidMaterial();
55
+ }
56
+ }
57
+ function jwtClaims(raw) {
58
+ const parts = raw.split(".");
59
+ if (parts.length !== 3 || parts.some((part) => !part)) return invalidMaterial();
60
+ try {
61
+ const payload = (parts[1] ?? "").replaceAll("-", "+").replaceAll("_", "/");
62
+ const bytes = Uint8Array.from(atob(payload), (value) => value.charCodeAt(0));
63
+ return parseJson(new TextDecoder("utf-8", { fatal: true }).decode(bytes));
64
+ } catch {
65
+ return invalidMaterial();
66
+ }
67
+ }
68
+ function accessTokenExpiry(value) {
69
+ if (value.split(".").length !== 3) return void 0;
70
+ const parsed = z.object({ exp: z.number().int().positive().optional() }).safeParse(jwtClaims(value));
71
+ if (!parsed.success) return invalidMaterial();
72
+ if (parsed.data.exp === void 0) return void 0;
73
+ const result = expiry.safeParse(parsed.data.exp * 1e3);
74
+ if (!result.success) return invalidMaterial();
75
+ return result.data;
76
+ }
77
+ /** Build sandbox-safe native material without accepting any provider token. */
78
+ function buildCliAuthPlaceholderMaterial(input) {
79
+ const reference = assertCliAuthReferenceSupported(input.backendType, { cliAuth: input.reference });
80
+ const metadata = metadataSchema.safeParse(input.metadata);
81
+ const declaredTokens = Object.values(declaredSecretSlotTokens());
82
+ if (!reference || !metadata.success || !Number.isFinite(input.now.getTime()) || !declaredTokens.includes(baseProxyToken(input.placeholder)) || !/^[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/.test(input.placeholder)) return invalidMaterial();
83
+ if (input.backendType !== "codex" && input.backendType !== "claude-code") return invalidMaterial();
84
+ if (input.backendType === "codex" && reference.format === "token") return invalidMaterial();
85
+ const { accountId, planType, accessTokenExpiresAt, hasRefreshToken } = metadata.data;
86
+ let content;
87
+ if (input.backendType === "codex") {
88
+ if (planType !== void 0 && !codexPlan.safeParse(planType).success) return invalidMaterial();
89
+ const claims = {
90
+ sub: input.placeholder,
91
+ aud: "tangle-sandbox",
92
+ exp: Math.floor(input.now.getTime() / 1e3)
93
+ };
94
+ content = {
95
+ auth_mode: "chatgpt",
96
+ OPENAI_API_KEY: null,
97
+ tokens: {
98
+ id_token: `e30.${btoa(JSON.stringify(claims)).replaceAll("+", "-").replaceAll("/", "_").replace(/=+$/, "")}.tangle-placeholder`,
99
+ access_token: input.placeholder,
100
+ refresh_token: hasRefreshToken ? input.placeholder : "",
101
+ ...accountId ? { account_id: accountId } : {}
102
+ },
103
+ last_refresh: input.now.toISOString()
104
+ };
105
+ } else {
106
+ if (planType !== void 0 && !claudePlan.safeParse(planType).success) return invalidMaterial();
107
+ content = { claudeAiOauth: {
108
+ accessToken: input.placeholder,
109
+ refreshToken: hasRefreshToken ? input.placeholder : null,
110
+ expiresAt: accessTokenExpiresAt ?? null,
111
+ scopes: ["user:inference"],
112
+ subscriptionType: planType ?? null,
113
+ rateLimitTier: null
114
+ } };
115
+ }
116
+ return {
117
+ reference,
118
+ authFiles: [{
119
+ path: AUTH_BUNDLE_PATHS[input.backendType],
120
+ content: JSON.stringify(content),
121
+ mode: 384
122
+ }],
123
+ tokenEnvironment: {}
124
+ };
125
+ }
126
+ /**
127
+ * TRUSTED OWNER ONLY. The explicit proxyAccessToken result must reach only the
128
+ * host's existing egress holder. Only nativeMaterial may enter the sandbox.
129
+ * The uploaded bundle, ID token and refresh token are never returned.
130
+ */
131
+ function resolveCliAuthPlaceholder(input) {
132
+ if (input.secretValue.length > 1024 * 1024) return invalidMaterial();
133
+ const material = resolveCliAuthReference(input.backendType, { cliAuth: input.reference }, { [input.reference.secretEnv]: input.secretValue });
134
+ if (!material) return invalidMaterial();
135
+ let proxyAccessToken;
136
+ let metadata;
137
+ let credentials;
138
+ if (input.backendType === "claude-code" && material.reference.format === "token") {
139
+ const parsed = token.safeParse(Object.values(material.tokenEnvironment)[0]);
140
+ if (!parsed.success) return invalidMaterial();
141
+ proxyAccessToken = parsed.data;
142
+ credentials = [proxyAccessToken];
143
+ metadata = { hasRefreshToken: false };
144
+ } else {
145
+ const expectedPath = input.backendType === "codex" ? AUTH_BUNDLE_PATHS.codex : AUTH_BUNDLE_PATHS["claude-code"];
146
+ if (material.authFiles.length !== 1 || material.authFiles[0]?.path !== expectedPath) return invalidMaterial();
147
+ const bundle = parseJson(material.authFiles[0].content);
148
+ if (input.backendType === "codex") {
149
+ const parsed = codexBundle.safeParse(bundle);
150
+ if (!parsed.success) return invalidMaterial();
151
+ const tokens = parsed.data.tokens;
152
+ const parsedClaims = codexClaims.safeParse(jwtClaims(tokens.id_token));
153
+ if (!parsedClaims.success) return invalidMaterial();
154
+ const claims = parsedClaims.data["https://api.openai.com/auth"];
155
+ if (claims?.chatgpt_account_is_fedramp) return invalidMaterial();
156
+ if (tokens.account_id && claims?.chatgpt_account_id && tokens.account_id !== claims.chatgpt_account_id) return invalidMaterial();
157
+ proxyAccessToken = tokens.access_token;
158
+ credentials = [
159
+ proxyAccessToken,
160
+ tokens.id_token,
161
+ tokens.refresh_token ?? ""
162
+ ];
163
+ metadata = {
164
+ accountId: tokens.account_id ?? claims?.chatgpt_account_id,
165
+ planType: claims?.chatgpt_plan_type,
166
+ accessTokenExpiresAt: accessTokenExpiry(proxyAccessToken),
167
+ hasRefreshToken: Boolean(tokens.refresh_token)
168
+ };
169
+ } else if (input.backendType === "claude-code") {
170
+ const parsed = claudeBundle.safeParse(bundle);
171
+ if (!parsed.success) return invalidMaterial();
172
+ const oauth = parsed.data.claudeAiOauth;
173
+ proxyAccessToken = oauth.accessToken;
174
+ credentials = [proxyAccessToken, oauth.refreshToken ?? ""];
175
+ metadata = {
176
+ planType: oauth.subscriptionType ?? void 0,
177
+ accessTokenExpiresAt: oauth.expiresAt ?? void 0,
178
+ hasRefreshToken: Boolean(oauth.refreshToken)
179
+ };
180
+ } else return invalidMaterial();
181
+ }
182
+ const nativeMaterial = buildCliAuthPlaceholderMaterial({
183
+ ...input,
184
+ metadata
185
+ });
186
+ const publicOutput = JSON.stringify({
187
+ nativeMaterial,
188
+ metadata
189
+ });
190
+ if (credentials.some((value) => value && publicOutput.includes(value))) return invalidMaterial();
191
+ return {
192
+ proxyAccessToken,
193
+ nativeMaterial,
194
+ metadata
195
+ };
196
+ }
197
+ //#endregion
198
+ export { buildCliAuthPlaceholderMaterial, resolveCliAuthPlaceholder };
199
+
200
+ //# sourceMappingURL=cli-auth-placeholder.mjs.map