@the-open-engine/zeroshot 6.27.0 → 6.29.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/README.md +3 -3
- package/cli/index.js +28 -21
- package/docker/zeroshot-cluster/Dockerfile +2 -3
- package/docker/zeroshot-oecp/Cargo.toml +12 -0
- package/docker/zeroshot-oecp/Dockerfile +65 -0
- package/docker/zeroshot-oecp/src/main.rs +32 -0
- package/lib/agent-cli-provider/adapters/codex.d.ts.map +1 -1
- package/lib/agent-cli-provider/adapters/codex.js +1 -0
- package/lib/agent-cli-provider/adapters/codex.js.map +1 -1
- package/lib/cluster-worker/engine-adapter.js +11 -15
- package/lib/cluster-worker/engine-input.js +14 -0
- package/lib/cluster-worker/profiles.js +42 -1
- package/lib/start-cluster.js +3 -3
- package/lib/target/bounded-json.d.ts +6 -0
- package/lib/target/bounded-json.js +43 -0
- package/lib/target/credential-lock.d.ts +1 -0
- package/lib/target/credential-lock.js +38 -0
- package/lib/target/credential-store.d.ts +27 -0
- package/lib/target/credential-store.js +113 -0
- package/lib/target/device-flow.d.ts +42 -0
- package/lib/target/device-flow.js +109 -0
- package/lib/target/discovery.d.ts +12 -0
- package/lib/target/discovery.js +97 -0
- package/lib/target/hosted-run/client.d.ts +26 -0
- package/lib/target/hosted-run/client.js +158 -0
- package/lib/target/hosted-run/commands.d.ts +4 -0
- package/lib/target/hosted-run/commands.js +113 -0
- package/lib/target/hosted-run/contracts.d.ts +49 -0
- package/lib/target/hosted-run/contracts.js +3 -0
- package/lib/target/hosted-run/input.d.ts +5 -0
- package/lib/target/hosted-run/input.js +200 -0
- package/lib/target/hosted-run.d.ts +4 -0
- package/lib/target/hosted-run.js +12 -0
- package/lib/target/index.d.ts +6 -0
- package/lib/target/index.js +38 -0
- package/lib/target/target-registry.d.ts +45 -0
- package/lib/target/target-registry.js +132 -0
- package/lib/target/target-session.d.ts +40 -0
- package/lib/target/target-session.js +163 -0
- package/package.json +25 -7
- package/scripts/audit-production-dependencies.js +150 -0
- package/scripts/opcore-agent-gate.js +159 -0
- package/scripts/opcore-agent-tool-overlays.js +154 -0
- package/scripts/opcore-introduced-check.js +290 -0
- package/src/agent-cli-provider/adapters/codex.ts +1 -0
- package/src/isolation-manager.js +16 -1
- package/src/target/bounded-json.ts +48 -0
- package/src/target/credential-lock.ts +35 -0
- package/src/target/credential-store.ts +107 -0
- package/src/target/device-flow.ts +168 -0
- package/src/target/discovery.ts +131 -0
- package/src/target/hosted-run/client.ts +198 -0
- package/src/target/hosted-run/commands.ts +140 -0
- package/src/target/hosted-run/contracts.ts +53 -0
- package/src/target/hosted-run/input.ts +199 -0
- package/src/target/hosted-run.ts +8 -0
- package/src/target/index.ts +55 -0
- package/src/target/target-registry.ts +174 -0
- package/src/target/target-session.ts +253 -0
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import type { SettingsPort } from '../target-registry.ts';
|
|
2
|
+
export interface HostedOptions {
|
|
3
|
+
readonly target?: string;
|
|
4
|
+
readonly repository?: string;
|
|
5
|
+
readonly model?: string;
|
|
6
|
+
readonly size?: string;
|
|
7
|
+
readonly submissionKey?: string;
|
|
8
|
+
readonly detach?: boolean;
|
|
9
|
+
readonly pr?: boolean;
|
|
10
|
+
readonly provider?: string;
|
|
11
|
+
readonly config?: string;
|
|
12
|
+
readonly docker?: boolean;
|
|
13
|
+
readonly worktree?: boolean;
|
|
14
|
+
readonly dockerImage?: string;
|
|
15
|
+
readonly strictSchema?: boolean;
|
|
16
|
+
readonly ship?: boolean;
|
|
17
|
+
readonly prBase?: string;
|
|
18
|
+
readonly mergeQueue?: boolean;
|
|
19
|
+
readonly closeIssue?: string;
|
|
20
|
+
readonly workers?: number;
|
|
21
|
+
readonly gitlab?: boolean;
|
|
22
|
+
readonly jira?: boolean;
|
|
23
|
+
readonly devops?: boolean;
|
|
24
|
+
readonly linear?: boolean;
|
|
25
|
+
readonly mount?: readonly string[];
|
|
26
|
+
readonly noMounts?: boolean;
|
|
27
|
+
readonly containerHome?: string;
|
|
28
|
+
}
|
|
29
|
+
export interface HostedRunIntent {
|
|
30
|
+
readonly intent_id: string;
|
|
31
|
+
readonly state: string;
|
|
32
|
+
readonly waiting_reason: string | null;
|
|
33
|
+
readonly result: Record<string, unknown> | null;
|
|
34
|
+
readonly error_code: string | null;
|
|
35
|
+
readonly [key: string]: unknown;
|
|
36
|
+
}
|
|
37
|
+
export interface HostedRunDependencies {
|
|
38
|
+
readonly settings: SettingsPort;
|
|
39
|
+
readonly environment?: NodeJS.ProcessEnv;
|
|
40
|
+
readonly fetch?: typeof globalThis.fetch;
|
|
41
|
+
readonly delay?: (milliseconds: number) => Promise<void>;
|
|
42
|
+
readonly stdout?: {
|
|
43
|
+
write(value: string): void;
|
|
44
|
+
};
|
|
45
|
+
}
|
|
46
|
+
export interface ResolvedInput {
|
|
47
|
+
readonly repository: string;
|
|
48
|
+
readonly request: Record<string, unknown>;
|
|
49
|
+
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
import type { HostedOptions, ResolvedInput } from './contracts.ts';
|
|
2
|
+
export declare function resolveHostedInput(input: string, options: HostedOptions, environment?: NodeJS.ProcessEnv): Promise<ResolvedInput>;
|
|
3
|
+
export declare function githubToken(environment: NodeJS.ProcessEnv): string;
|
|
4
|
+
export declare function providerKey(environment: NodeJS.ProcessEnv): string;
|
|
5
|
+
export declare function validateHostedOptions(options: HostedOptions): void;
|
|
@@ -0,0 +1,200 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.resolveHostedInput = resolveHostedInput;
|
|
7
|
+
exports.githubToken = githubToken;
|
|
8
|
+
exports.providerKey = providerKey;
|
|
9
|
+
exports.validateHostedOptions = validateHostedOptions;
|
|
10
|
+
const node_fs_1 = __importDefault(require("node:fs"));
|
|
11
|
+
const node_path_1 = __importDefault(require("node:path"));
|
|
12
|
+
const node_child_process_1 = require("node:child_process");
|
|
13
|
+
const SUBMISSION_KEY = /^[0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{12}$/i;
|
|
14
|
+
const CAPSULE_SIZES = new Set(['tiny', 'small', 'standard', 'large']);
|
|
15
|
+
function validRepository(value) {
|
|
16
|
+
if (!/^[A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+$/.test(value))
|
|
17
|
+
return false;
|
|
18
|
+
return value.split('/').every((segment) => segment !== '.' && segment !== '..');
|
|
19
|
+
}
|
|
20
|
+
function validModel(value) {
|
|
21
|
+
return (value.length <= 256 && /^[A-Za-z0-9][A-Za-z0-9._-]*\/[A-Za-z0-9][A-Za-z0-9._:-]*$/.test(value));
|
|
22
|
+
}
|
|
23
|
+
function repositoryFromRemote(cwd = process.cwd()) {
|
|
24
|
+
let remote;
|
|
25
|
+
try {
|
|
26
|
+
remote = (0, node_child_process_1.execFileSync)('git', ['remote', 'get-url', 'origin'], {
|
|
27
|
+
cwd,
|
|
28
|
+
encoding: 'utf8',
|
|
29
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
30
|
+
}).trim();
|
|
31
|
+
}
|
|
32
|
+
catch {
|
|
33
|
+
return null;
|
|
34
|
+
}
|
|
35
|
+
const match = remote.match(/^(?:git@github\.com:|ssh:\/\/git@github\.com\/|https?:\/\/github\.com\/)([^/]+\/[^/]+?)(?:\.git)?$/);
|
|
36
|
+
const repository = match?.[1];
|
|
37
|
+
return repository && validRepository(repository) ? repository : null;
|
|
38
|
+
}
|
|
39
|
+
function isolationProfile(options) {
|
|
40
|
+
return options.pr ? 'isolation.pr@1' : 'isolation.worktree@1';
|
|
41
|
+
}
|
|
42
|
+
function providerProfile(options) {
|
|
43
|
+
return options.pr ? 'provider.codex-openrouter-pr@1' : 'provider.codex-openrouter@1';
|
|
44
|
+
}
|
|
45
|
+
function promptRequest(prompt, options) {
|
|
46
|
+
return {
|
|
47
|
+
source: 'prompt',
|
|
48
|
+
prompt,
|
|
49
|
+
artifacts: [],
|
|
50
|
+
isolationProfile: isolationProfile(options),
|
|
51
|
+
providerProfile: providerProfile(options),
|
|
52
|
+
};
|
|
53
|
+
}
|
|
54
|
+
function issueRequest(issue, options) {
|
|
55
|
+
return {
|
|
56
|
+
source: 'issue',
|
|
57
|
+
issue,
|
|
58
|
+
artifacts: [],
|
|
59
|
+
isolationProfile: isolationProfile(options),
|
|
60
|
+
providerProfile: providerProfile(options),
|
|
61
|
+
};
|
|
62
|
+
}
|
|
63
|
+
function issueInput(value, options) {
|
|
64
|
+
const shorthand = value.match(/^([A-Za-z0-9_.-]+\/[A-Za-z0-9_.-]+)#([1-9][0-9]*)$/);
|
|
65
|
+
if (shorthand?.[1] && shorthand[2] && validRepository(shorthand[1])) {
|
|
66
|
+
const issue = `https://github.com/${shorthand[1]}/issues/${shorthand[2]}`;
|
|
67
|
+
return { repository: shorthand[1], request: issueRequest(issue, options) };
|
|
68
|
+
}
|
|
69
|
+
let url;
|
|
70
|
+
try {
|
|
71
|
+
url = new URL(value);
|
|
72
|
+
}
|
|
73
|
+
catch {
|
|
74
|
+
return null;
|
|
75
|
+
}
|
|
76
|
+
const match = url.pathname.match(/^\/([^/]+)\/([^/]+)\/issues\/([1-9][0-9]*)\/?$/);
|
|
77
|
+
const repository = match?.[1] && match[2] ? `${match[1]}/${match[2]}` : '';
|
|
78
|
+
if (url.hostname !== 'github.com' || !match || !validRepository(repository))
|
|
79
|
+
return null;
|
|
80
|
+
return { repository, request: issueRequest(url.href, options) };
|
|
81
|
+
}
|
|
82
|
+
async function readStdin() {
|
|
83
|
+
if (process.stdin.isTTY)
|
|
84
|
+
throw new Error('zeroshot run - requires piped input');
|
|
85
|
+
const chunks = [];
|
|
86
|
+
let bytes = 0;
|
|
87
|
+
for await (const chunk of process.stdin) {
|
|
88
|
+
const value = Buffer.isBuffer(chunk) ? chunk : Buffer.from(chunk);
|
|
89
|
+
bytes += value.length;
|
|
90
|
+
if (bytes > 1024 * 1024)
|
|
91
|
+
throw new Error('hosted task input exceeds 1 MiB');
|
|
92
|
+
chunks.push(value);
|
|
93
|
+
}
|
|
94
|
+
const value = Buffer.concat(chunks).toString('utf8').trim();
|
|
95
|
+
if (!value)
|
|
96
|
+
throw new Error('hosted task input is empty');
|
|
97
|
+
return value;
|
|
98
|
+
}
|
|
99
|
+
function readTaskFile(filename) {
|
|
100
|
+
const flags = node_fs_1.default.constants.O_RDONLY | (node_fs_1.default.constants.O_NOFOLLOW ?? 0) | (node_fs_1.default.constants.O_NONBLOCK ?? 0);
|
|
101
|
+
let descriptor;
|
|
102
|
+
try {
|
|
103
|
+
descriptor = node_fs_1.default.openSync(filename, flags);
|
|
104
|
+
}
|
|
105
|
+
catch (error) {
|
|
106
|
+
const code = error.code;
|
|
107
|
+
if (code === 'ENOENT' || code === 'ENOTDIR' || code === 'EISDIR')
|
|
108
|
+
return null;
|
|
109
|
+
if (code === 'ELOOP')
|
|
110
|
+
throw new Error(`hosted task file must not be a symlink: ${filename}`);
|
|
111
|
+
throw error;
|
|
112
|
+
}
|
|
113
|
+
try {
|
|
114
|
+
if (!node_fs_1.default.fstatSync(descriptor).isFile())
|
|
115
|
+
return null;
|
|
116
|
+
return node_fs_1.default.readFileSync(descriptor, 'utf8');
|
|
117
|
+
}
|
|
118
|
+
finally {
|
|
119
|
+
node_fs_1.default.closeSync(descriptor);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
async function resolveHostedInput(input, options, environment = process.env) {
|
|
123
|
+
const explicitIssue = issueInput(input, options);
|
|
124
|
+
if (explicitIssue)
|
|
125
|
+
return explicitIssue;
|
|
126
|
+
const repository = options.repository ?? environment['ZEROSHOT_REPOSITORY'] ?? repositoryFromRemote();
|
|
127
|
+
if (!validRepository(repository ?? '')) {
|
|
128
|
+
throw new Error('hosted runs need a GitHub repository; use org/repo#123, --repository owner/name, ' +
|
|
129
|
+
'ZEROSHOT_REPOSITORY, or run inside a GitHub checkout');
|
|
130
|
+
}
|
|
131
|
+
if (/^[1-9][0-9]*$/.test(input)) {
|
|
132
|
+
return { repository: repository, request: issueRequest(input, options) };
|
|
133
|
+
}
|
|
134
|
+
const prompt = input === '-' ? await readStdin() : (readTaskFile(node_path_1.default.resolve(input)) ?? input);
|
|
135
|
+
if (!prompt.trim())
|
|
136
|
+
throw new Error('hosted task input is empty');
|
|
137
|
+
return { repository: repository, request: promptRequest(prompt.trim(), options) };
|
|
138
|
+
}
|
|
139
|
+
function githubToken(environment) {
|
|
140
|
+
const configured = environment['GH_TOKEN'] ?? environment['GITHUB_TOKEN'];
|
|
141
|
+
if (configured?.trim())
|
|
142
|
+
return configured.trim();
|
|
143
|
+
try {
|
|
144
|
+
const token = (0, node_child_process_1.execFileSync)('gh', ['auth', 'token'], {
|
|
145
|
+
encoding: 'utf8',
|
|
146
|
+
stdio: ['ignore', 'pipe', 'ignore'],
|
|
147
|
+
}).trim();
|
|
148
|
+
if (token)
|
|
149
|
+
return token;
|
|
150
|
+
}
|
|
151
|
+
catch {
|
|
152
|
+
// The actionable error below covers missing and unauthenticated gh alike.
|
|
153
|
+
}
|
|
154
|
+
throw new Error('hosted runs require GH_TOKEN/GITHUB_TOKEN or an authenticated gh CLI');
|
|
155
|
+
}
|
|
156
|
+
function providerKey(environment) {
|
|
157
|
+
const key = environment['OPENROUTER_API_KEY'];
|
|
158
|
+
if (!key?.trim())
|
|
159
|
+
throw new Error('hosted Codex runs require OPENROUTER_API_KEY');
|
|
160
|
+
return key.trim();
|
|
161
|
+
}
|
|
162
|
+
function validateHostedOptions(options) {
|
|
163
|
+
const unsupported = [
|
|
164
|
+
['config', '--config'],
|
|
165
|
+
['docker', '--docker'],
|
|
166
|
+
['worktree', '--worktree'],
|
|
167
|
+
['dockerImage', '--docker-image'],
|
|
168
|
+
['strictSchema', '--strict-schema'],
|
|
169
|
+
['ship', '--ship'],
|
|
170
|
+
['prBase', '--pr-base'],
|
|
171
|
+
['mergeQueue', '--merge-queue'],
|
|
172
|
+
['closeIssue', '--close-issue'],
|
|
173
|
+
['workers', '--workers'],
|
|
174
|
+
['gitlab', '--gitlab'],
|
|
175
|
+
['jira', '--jira'],
|
|
176
|
+
['devops', '--devops'],
|
|
177
|
+
['linear', '--linear'],
|
|
178
|
+
['mount', '--mount'],
|
|
179
|
+
['noMounts', '--no-mounts'],
|
|
180
|
+
['containerHome', '--container-home'],
|
|
181
|
+
];
|
|
182
|
+
const selected = unsupported
|
|
183
|
+
.filter(([name]) => options[name] !== undefined && options[name] !== false)
|
|
184
|
+
.map(([, flag]) => flag);
|
|
185
|
+
if (options.provider && options.provider !== 'codex')
|
|
186
|
+
selected.push('--provider');
|
|
187
|
+
if (selected.length)
|
|
188
|
+
throw new Error(`hosted runs do not support ${selected.join(', ')}`);
|
|
189
|
+
if (!options.target)
|
|
190
|
+
throw new Error('hosted runs require --target');
|
|
191
|
+
if (options.model !== undefined && !validModel(options.model)) {
|
|
192
|
+
throw new Error('hosted runs require an exact provider/model slug');
|
|
193
|
+
}
|
|
194
|
+
if (!CAPSULE_SIZES.has(options.size ?? 'standard')) {
|
|
195
|
+
throw new Error('hosted runs require --size tiny, small, standard, or large');
|
|
196
|
+
}
|
|
197
|
+
if (options.submissionKey !== undefined && !SUBMISSION_KEY.test(options.submissionKey)) {
|
|
198
|
+
throw new Error('hosted runs require --submission-key to be a random UUID');
|
|
199
|
+
}
|
|
200
|
+
}
|
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export { HostedRunHttpError } from './hosted-run/client.ts';
|
|
2
|
+
export { cancelHostedRun, runHosted, statusHostedRun } from './hosted-run/commands.ts';
|
|
3
|
+
export { resolveHostedInput, validateHostedOptions } from './hosted-run/input.ts';
|
|
4
|
+
export type { HostedOptions, HostedRunDependencies, HostedRunIntent, } from './hosted-run/contracts.ts';
|
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.validateHostedOptions = exports.resolveHostedInput = exports.statusHostedRun = exports.runHosted = exports.cancelHostedRun = exports.HostedRunHttpError = void 0;
|
|
4
|
+
var client_ts_1 = require("./hosted-run/client.js");
|
|
5
|
+
Object.defineProperty(exports, "HostedRunHttpError", { enumerable: true, get: function () { return client_ts_1.HostedRunHttpError; } });
|
|
6
|
+
var commands_ts_1 = require("./hosted-run/commands.js");
|
|
7
|
+
Object.defineProperty(exports, "cancelHostedRun", { enumerable: true, get: function () { return commands_ts_1.cancelHostedRun; } });
|
|
8
|
+
Object.defineProperty(exports, "runHosted", { enumerable: true, get: function () { return commands_ts_1.runHosted; } });
|
|
9
|
+
Object.defineProperty(exports, "statusHostedRun", { enumerable: true, get: function () { return commands_ts_1.statusHostedRun; } });
|
|
10
|
+
var input_ts_1 = require("./hosted-run/input.js");
|
|
11
|
+
Object.defineProperty(exports, "resolveHostedInput", { enumerable: true, get: function () { return input_ts_1.resolveHostedInput; } });
|
|
12
|
+
Object.defineProperty(exports, "validateHostedOptions", { enumerable: true, get: function () { return input_ts_1.validateHostedOptions; } });
|
|
@@ -0,0 +1,6 @@
|
|
|
1
|
+
export { CredentialStoreUnavailableError, KeyringCredentialStore, FakeCredentialStore, targetServiceKey, TARGET_ACCOUNT, type TargetCredentialStore, } from './credential-store.ts';
|
|
2
|
+
export { acquireTargetLock } from './credential-lock.ts';
|
|
3
|
+
export { requestDeviceCode, pollForToken, DeviceFlowDeniedError, DeviceFlowExpiredError, UnboundSessionError, type DeviceCodeResponse, type TokenResponse, type HttpTransport, type Clock, } from './device-flow.ts';
|
|
4
|
+
export { addTarget, removeTarget, getTarget, listTargets, updateTargetOrganization, validateTargetName, normalizeAndValidateUrl, TargetNameInvalidError, TargetNameExistsError, TargetNotFoundError, TargetUrlInvalidError, type TargetRecord, type SettingsPort, } from './target-registry.ts';
|
|
5
|
+
export { targetLogin, refreshAccessToken, getAccessTokenProvider, revokeAndCleanup, LoginRequiredError, type BrowserOpener, type TargetSessionDeps, type TargetAccessTokenProvider, } from './target-session.ts';
|
|
6
|
+
export { discoverTargetSessionEndpoints, TargetDiscoveryError, type TargetSessionEndpoints, } from './discovery.ts';
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.TargetDiscoveryError = exports.discoverTargetSessionEndpoints = exports.LoginRequiredError = exports.revokeAndCleanup = exports.getAccessTokenProvider = exports.refreshAccessToken = exports.targetLogin = exports.TargetUrlInvalidError = exports.TargetNotFoundError = exports.TargetNameExistsError = exports.TargetNameInvalidError = exports.normalizeAndValidateUrl = exports.validateTargetName = exports.updateTargetOrganization = exports.listTargets = exports.getTarget = exports.removeTarget = exports.addTarget = exports.UnboundSessionError = exports.DeviceFlowExpiredError = exports.DeviceFlowDeniedError = exports.pollForToken = exports.requestDeviceCode = exports.acquireTargetLock = exports.TARGET_ACCOUNT = exports.targetServiceKey = exports.FakeCredentialStore = exports.KeyringCredentialStore = exports.CredentialStoreUnavailableError = void 0;
|
|
4
|
+
var credential_store_ts_1 = require("./credential-store.js");
|
|
5
|
+
Object.defineProperty(exports, "CredentialStoreUnavailableError", { enumerable: true, get: function () { return credential_store_ts_1.CredentialStoreUnavailableError; } });
|
|
6
|
+
Object.defineProperty(exports, "KeyringCredentialStore", { enumerable: true, get: function () { return credential_store_ts_1.KeyringCredentialStore; } });
|
|
7
|
+
Object.defineProperty(exports, "FakeCredentialStore", { enumerable: true, get: function () { return credential_store_ts_1.FakeCredentialStore; } });
|
|
8
|
+
Object.defineProperty(exports, "targetServiceKey", { enumerable: true, get: function () { return credential_store_ts_1.targetServiceKey; } });
|
|
9
|
+
Object.defineProperty(exports, "TARGET_ACCOUNT", { enumerable: true, get: function () { return credential_store_ts_1.TARGET_ACCOUNT; } });
|
|
10
|
+
var credential_lock_ts_1 = require("./credential-lock.js");
|
|
11
|
+
Object.defineProperty(exports, "acquireTargetLock", { enumerable: true, get: function () { return credential_lock_ts_1.acquireTargetLock; } });
|
|
12
|
+
var device_flow_ts_1 = require("./device-flow.js");
|
|
13
|
+
Object.defineProperty(exports, "requestDeviceCode", { enumerable: true, get: function () { return device_flow_ts_1.requestDeviceCode; } });
|
|
14
|
+
Object.defineProperty(exports, "pollForToken", { enumerable: true, get: function () { return device_flow_ts_1.pollForToken; } });
|
|
15
|
+
Object.defineProperty(exports, "DeviceFlowDeniedError", { enumerable: true, get: function () { return device_flow_ts_1.DeviceFlowDeniedError; } });
|
|
16
|
+
Object.defineProperty(exports, "DeviceFlowExpiredError", { enumerable: true, get: function () { return device_flow_ts_1.DeviceFlowExpiredError; } });
|
|
17
|
+
Object.defineProperty(exports, "UnboundSessionError", { enumerable: true, get: function () { return device_flow_ts_1.UnboundSessionError; } });
|
|
18
|
+
var target_registry_ts_1 = require("./target-registry.js");
|
|
19
|
+
Object.defineProperty(exports, "addTarget", { enumerable: true, get: function () { return target_registry_ts_1.addTarget; } });
|
|
20
|
+
Object.defineProperty(exports, "removeTarget", { enumerable: true, get: function () { return target_registry_ts_1.removeTarget; } });
|
|
21
|
+
Object.defineProperty(exports, "getTarget", { enumerable: true, get: function () { return target_registry_ts_1.getTarget; } });
|
|
22
|
+
Object.defineProperty(exports, "listTargets", { enumerable: true, get: function () { return target_registry_ts_1.listTargets; } });
|
|
23
|
+
Object.defineProperty(exports, "updateTargetOrganization", { enumerable: true, get: function () { return target_registry_ts_1.updateTargetOrganization; } });
|
|
24
|
+
Object.defineProperty(exports, "validateTargetName", { enumerable: true, get: function () { return target_registry_ts_1.validateTargetName; } });
|
|
25
|
+
Object.defineProperty(exports, "normalizeAndValidateUrl", { enumerable: true, get: function () { return target_registry_ts_1.normalizeAndValidateUrl; } });
|
|
26
|
+
Object.defineProperty(exports, "TargetNameInvalidError", { enumerable: true, get: function () { return target_registry_ts_1.TargetNameInvalidError; } });
|
|
27
|
+
Object.defineProperty(exports, "TargetNameExistsError", { enumerable: true, get: function () { return target_registry_ts_1.TargetNameExistsError; } });
|
|
28
|
+
Object.defineProperty(exports, "TargetNotFoundError", { enumerable: true, get: function () { return target_registry_ts_1.TargetNotFoundError; } });
|
|
29
|
+
Object.defineProperty(exports, "TargetUrlInvalidError", { enumerable: true, get: function () { return target_registry_ts_1.TargetUrlInvalidError; } });
|
|
30
|
+
var target_session_ts_1 = require("./target-session.js");
|
|
31
|
+
Object.defineProperty(exports, "targetLogin", { enumerable: true, get: function () { return target_session_ts_1.targetLogin; } });
|
|
32
|
+
Object.defineProperty(exports, "refreshAccessToken", { enumerable: true, get: function () { return target_session_ts_1.refreshAccessToken; } });
|
|
33
|
+
Object.defineProperty(exports, "getAccessTokenProvider", { enumerable: true, get: function () { return target_session_ts_1.getAccessTokenProvider; } });
|
|
34
|
+
Object.defineProperty(exports, "revokeAndCleanup", { enumerable: true, get: function () { return target_session_ts_1.revokeAndCleanup; } });
|
|
35
|
+
Object.defineProperty(exports, "LoginRequiredError", { enumerable: true, get: function () { return target_session_ts_1.LoginRequiredError; } });
|
|
36
|
+
var discovery_ts_1 = require("./discovery.js");
|
|
37
|
+
Object.defineProperty(exports, "discoverTargetSessionEndpoints", { enumerable: true, get: function () { return discovery_ts_1.discoverTargetSessionEndpoints; } });
|
|
38
|
+
Object.defineProperty(exports, "TargetDiscoveryError", { enumerable: true, get: function () { return discovery_ts_1.TargetDiscoveryError; } });
|
|
@@ -0,0 +1,45 @@
|
|
|
1
|
+
export interface TargetRecord {
|
|
2
|
+
readonly id: string;
|
|
3
|
+
readonly url: string;
|
|
4
|
+
readonly adapterVersion: string;
|
|
5
|
+
readonly deviceToken: string;
|
|
6
|
+
readonly organization?: {
|
|
7
|
+
readonly id: string;
|
|
8
|
+
readonly name: string;
|
|
9
|
+
};
|
|
10
|
+
readonly createdAt: string;
|
|
11
|
+
}
|
|
12
|
+
export declare class TargetNameInvalidError extends Error {
|
|
13
|
+
constructor(name: string);
|
|
14
|
+
}
|
|
15
|
+
export declare class TargetNameExistsError extends Error {
|
|
16
|
+
constructor(name: string);
|
|
17
|
+
}
|
|
18
|
+
export declare class TargetNotFoundError extends Error {
|
|
19
|
+
constructor(name: string);
|
|
20
|
+
}
|
|
21
|
+
export declare class TargetUrlInvalidError extends Error {
|
|
22
|
+
constructor(url: string, reason: string);
|
|
23
|
+
}
|
|
24
|
+
export declare function validateTargetName(name: string): void;
|
|
25
|
+
export declare function normalizeAndValidateUrl(rawUrl: string): string;
|
|
26
|
+
interface SettingsWithTargets {
|
|
27
|
+
_targets?: Record<string, TargetRecord>;
|
|
28
|
+
[key: string]: unknown;
|
|
29
|
+
}
|
|
30
|
+
export interface SettingsPort {
|
|
31
|
+
load(): SettingsWithTargets;
|
|
32
|
+
mutate(mutator: (settings: SettingsWithTargets) => void): void;
|
|
33
|
+
}
|
|
34
|
+
export declare function addTarget(name: string, rawUrl: string, settings: SettingsPort): TargetRecord;
|
|
35
|
+
export declare function removeTarget(name: string, settings: SettingsPort): TargetRecord;
|
|
36
|
+
export declare function getTarget(name: string, settings: SettingsPort): TargetRecord | null;
|
|
37
|
+
export declare function listTargets(settings: SettingsPort): Array<{
|
|
38
|
+
name: string;
|
|
39
|
+
record: TargetRecord;
|
|
40
|
+
}>;
|
|
41
|
+
export declare function updateTargetOrganization(name: string, organization: {
|
|
42
|
+
id: string;
|
|
43
|
+
name: string;
|
|
44
|
+
}, settings: SettingsPort): void;
|
|
45
|
+
export {};
|
|
@@ -0,0 +1,132 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
3
|
+
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
|
+
};
|
|
5
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
+
exports.TargetUrlInvalidError = exports.TargetNotFoundError = exports.TargetNameExistsError = exports.TargetNameInvalidError = void 0;
|
|
7
|
+
exports.validateTargetName = validateTargetName;
|
|
8
|
+
exports.normalizeAndValidateUrl = normalizeAndValidateUrl;
|
|
9
|
+
exports.addTarget = addTarget;
|
|
10
|
+
exports.removeTarget = removeTarget;
|
|
11
|
+
exports.getTarget = getTarget;
|
|
12
|
+
exports.listTargets = listTargets;
|
|
13
|
+
exports.updateTargetOrganization = updateTargetOrganization;
|
|
14
|
+
const node_crypto_1 = __importDefault(require("node:crypto"));
|
|
15
|
+
class TargetNameInvalidError extends Error {
|
|
16
|
+
constructor(name) {
|
|
17
|
+
super(`Invalid target name "${name}". Must be 1-64 characters, alphanumeric and hyphens only.`);
|
|
18
|
+
this.name = 'TargetNameInvalidError';
|
|
19
|
+
}
|
|
20
|
+
}
|
|
21
|
+
exports.TargetNameInvalidError = TargetNameInvalidError;
|
|
22
|
+
class TargetNameExistsError extends Error {
|
|
23
|
+
constructor(name) {
|
|
24
|
+
super(`Target "${name}" already exists. Remove it first or choose a different name.`);
|
|
25
|
+
this.name = 'TargetNameExistsError';
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
exports.TargetNameExistsError = TargetNameExistsError;
|
|
29
|
+
class TargetNotFoundError extends Error {
|
|
30
|
+
constructor(name) {
|
|
31
|
+
super(`Target "${name}" not found.`);
|
|
32
|
+
this.name = 'TargetNotFoundError';
|
|
33
|
+
}
|
|
34
|
+
}
|
|
35
|
+
exports.TargetNotFoundError = TargetNotFoundError;
|
|
36
|
+
class TargetUrlInvalidError extends Error {
|
|
37
|
+
constructor(url, reason) {
|
|
38
|
+
super(`Invalid target URL "${url}": ${reason}`);
|
|
39
|
+
this.name = 'TargetUrlInvalidError';
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.TargetUrlInvalidError = TargetUrlInvalidError;
|
|
43
|
+
const TARGET_NAME_PATTERN = /^[a-zA-Z0-9]([a-zA-Z0-9-]{0,62}[a-zA-Z0-9])?$/;
|
|
44
|
+
const LOOPBACK_HOSTS = new Set(['localhost', '127.0.0.1', '::1', '[::1]']);
|
|
45
|
+
function validateTargetName(name) {
|
|
46
|
+
if (!TARGET_NAME_PATTERN.test(name) || name.length > 64) {
|
|
47
|
+
throw new TargetNameInvalidError(name);
|
|
48
|
+
}
|
|
49
|
+
}
|
|
50
|
+
function normalizeAndValidateUrl(rawUrl) {
|
|
51
|
+
let parsed;
|
|
52
|
+
try {
|
|
53
|
+
parsed = new URL(rawUrl);
|
|
54
|
+
}
|
|
55
|
+
catch {
|
|
56
|
+
throw new TargetUrlInvalidError(rawUrl, 'not a valid URL');
|
|
57
|
+
}
|
|
58
|
+
if (parsed.username || parsed.password) {
|
|
59
|
+
throw new TargetUrlInvalidError(rawUrl, 'URL must not contain userinfo');
|
|
60
|
+
}
|
|
61
|
+
if (parsed.search || parsed.hash) {
|
|
62
|
+
throw new TargetUrlInvalidError(rawUrl, 'URL must not contain query or fragment');
|
|
63
|
+
}
|
|
64
|
+
const isLoopback = LOOPBACK_HOSTS.has(parsed.hostname);
|
|
65
|
+
if (parsed.protocol !== 'https:' && !isLoopback) {
|
|
66
|
+
throw new TargetUrlInvalidError(rawUrl, 'HTTPS required for non-loopback targets');
|
|
67
|
+
}
|
|
68
|
+
let normalized = `${parsed.protocol}//${parsed.host}${parsed.pathname}`;
|
|
69
|
+
if (normalized.endsWith('/') && normalized.length > 1) {
|
|
70
|
+
normalized = normalized.slice(0, -1);
|
|
71
|
+
}
|
|
72
|
+
return normalized;
|
|
73
|
+
}
|
|
74
|
+
function addTarget(name, rawUrl, settings) {
|
|
75
|
+
validateTargetName(name);
|
|
76
|
+
const url = normalizeAndValidateUrl(rawUrl);
|
|
77
|
+
const existing = settings.load();
|
|
78
|
+
if (existing._targets?.[name]) {
|
|
79
|
+
throw new TargetNameExistsError(name);
|
|
80
|
+
}
|
|
81
|
+
const record = {
|
|
82
|
+
id: node_crypto_1.default.randomUUID(),
|
|
83
|
+
url,
|
|
84
|
+
adapterVersion: 'v1',
|
|
85
|
+
deviceToken: node_crypto_1.default.randomUUID(),
|
|
86
|
+
createdAt: new Date().toISOString(),
|
|
87
|
+
};
|
|
88
|
+
settings.mutate((s) => {
|
|
89
|
+
if (!s._targets) {
|
|
90
|
+
s._targets = {};
|
|
91
|
+
}
|
|
92
|
+
s._targets[name] = record;
|
|
93
|
+
});
|
|
94
|
+
return record;
|
|
95
|
+
}
|
|
96
|
+
function removeTarget(name, settings) {
|
|
97
|
+
const existing = settings.load();
|
|
98
|
+
const record = existing._targets?.[name];
|
|
99
|
+
if (!record) {
|
|
100
|
+
throw new TargetNotFoundError(name);
|
|
101
|
+
}
|
|
102
|
+
settings.mutate((s) => {
|
|
103
|
+
if (s._targets) {
|
|
104
|
+
delete s._targets[name];
|
|
105
|
+
}
|
|
106
|
+
});
|
|
107
|
+
return record;
|
|
108
|
+
}
|
|
109
|
+
function getTarget(name, settings) {
|
|
110
|
+
const existing = settings.load();
|
|
111
|
+
return existing._targets?.[name] ?? null;
|
|
112
|
+
}
|
|
113
|
+
function listTargets(settings) {
|
|
114
|
+
const existing = settings.load();
|
|
115
|
+
const targets = existing._targets ?? {};
|
|
116
|
+
return Object.entries(targets).map(([name, record]) => ({ name, record }));
|
|
117
|
+
}
|
|
118
|
+
function updateTargetOrganization(name, organization, settings) {
|
|
119
|
+
const existing = settings.load();
|
|
120
|
+
if (!existing._targets?.[name]) {
|
|
121
|
+
throw new TargetNotFoundError(name);
|
|
122
|
+
}
|
|
123
|
+
settings.mutate((s) => {
|
|
124
|
+
const target = s._targets?.[name];
|
|
125
|
+
if (target) {
|
|
126
|
+
s._targets[name] = {
|
|
127
|
+
...target,
|
|
128
|
+
organization,
|
|
129
|
+
};
|
|
130
|
+
}
|
|
131
|
+
});
|
|
132
|
+
}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
import type { TargetCredentialStore } from './credential-store.ts';
|
|
2
|
+
import type { TargetRecord, SettingsPort } from './target-registry.ts';
|
|
3
|
+
import { type HttpTransport, type Clock } from './device-flow.ts';
|
|
4
|
+
export declare class LoginRequiredError extends Error {
|
|
5
|
+
readonly targetName: string;
|
|
6
|
+
constructor(targetName: string);
|
|
7
|
+
}
|
|
8
|
+
export interface BrowserOpener {
|
|
9
|
+
open(url: string): Promise<void>;
|
|
10
|
+
}
|
|
11
|
+
export interface TargetSessionDeps {
|
|
12
|
+
readonly http: HttpTransport;
|
|
13
|
+
readonly clock: Clock;
|
|
14
|
+
readonly browserOpener: BrowserOpener;
|
|
15
|
+
readonly stderr: {
|
|
16
|
+
write(s: string): void;
|
|
17
|
+
};
|
|
18
|
+
readonly discoveryEndpoints: {
|
|
19
|
+
readonly deviceAuthorizationEndpoint: string;
|
|
20
|
+
readonly tokenEndpoint: string;
|
|
21
|
+
readonly revocationEndpoint?: string;
|
|
22
|
+
readonly clientId: string;
|
|
23
|
+
readonly capsuleApiBaseUrl: string;
|
|
24
|
+
};
|
|
25
|
+
}
|
|
26
|
+
export declare function targetLogin(targetName: string, target: TargetRecord, credentialStore: TargetCredentialStore, acquireLock: () => Promise<() => Promise<void>>, settings: SettingsPort, deps: TargetSessionDeps): Promise<{
|
|
27
|
+
organization: {
|
|
28
|
+
id: string;
|
|
29
|
+
name: string;
|
|
30
|
+
};
|
|
31
|
+
}>;
|
|
32
|
+
export declare function refreshAccessToken(targetName: string, target: TargetRecord, credentialStore: TargetCredentialStore, acquireLock: () => Promise<() => Promise<void>>, deps: Pick<TargetSessionDeps, 'http' | 'discoveryEndpoints'>): Promise<{
|
|
33
|
+
accessToken: string;
|
|
34
|
+
expiresIn: number;
|
|
35
|
+
}>;
|
|
36
|
+
export interface TargetAccessTokenProvider {
|
|
37
|
+
getAccessToken(signal?: AbortSignal): Promise<string>;
|
|
38
|
+
}
|
|
39
|
+
export declare function getAccessTokenProvider(targetName: string, target: TargetRecord, credentialStore: TargetCredentialStore, acquireLock: () => Promise<() => Promise<void>>, deps: Pick<TargetSessionDeps, 'http' | 'discoveryEndpoints'>, clock?: Clock): TargetAccessTokenProvider;
|
|
40
|
+
export declare function revokeAndCleanup(target: TargetRecord, credentialStore: TargetCredentialStore, acquireLock: () => Promise<() => Promise<void>>, deps: Pick<TargetSessionDeps, 'http' | 'discoveryEndpoints'>, force: boolean): Promise<void>;
|