orizu 0.5.49 → 0.5.50
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/dist/account-directory.d.ts +2 -2
- package/dist/account-directory.d.ts.map +1 -1
- package/dist/account-directory.js +7 -32
- package/dist/auth-commands.d.ts.map +1 -1
- package/dist/auth-commands.js +22 -30
- package/dist/credentials.d.ts +5 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +84 -3
- package/dist/dataset-transfer-upload.d.ts.map +1 -1
- package/dist/dataset-transfer-upload.js +6 -1
- package/dist/directory-list-read.d.ts +37 -0
- package/dist/directory-list-read.d.ts.map +1 -0
- package/dist/directory-list-read.js +87 -0
- package/dist/hosted-boot.d.ts.map +1 -1
- package/dist/hosted-boot.js +59 -41
- package/dist/hosted-harness-opencode.d.ts +31 -0
- package/dist/hosted-harness-opencode.d.ts.map +1 -1
- package/dist/hosted-harness-opencode.js +45 -7
- package/dist/hosted-loop.d.ts +39 -2
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +127 -18
- package/dist/hosted-session-seed.d.ts +2 -0
- package/dist/hosted-session-seed.d.ts.map +1 -1
- package/dist/hosted-session-seed.js +27 -10
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +39 -234
- package/dist/keyboard-select.d.ts.map +1 -1
- package/dist/keyboard-select.js +23 -45
- package/dist/project-create-workflow.d.ts +18 -0
- package/dist/project-create-workflow.d.ts.map +1 -0
- package/dist/project-create-workflow.js +121 -0
- package/dist/self-update-command.d.ts +8 -0
- package/dist/self-update-command.d.ts.map +1 -0
- package/dist/self-update-command.js +390 -0
- package/dist/self-update.d.ts +29 -16
- package/dist/self-update.d.ts.map +1 -1
- package/dist/self-update.js +55 -244
- package/dist/setup-account.d.ts.map +1 -1
- package/dist/setup-account.js +4 -1
- package/dist/setup-and-skills-commands.d.ts.map +1 -1
- package/dist/setup-and-skills-commands.js +88 -102
- package/dist/skill-install-workflow.d.ts +23 -0
- package/dist/skill-install-workflow.d.ts.map +1 -0
- package/dist/skill-install-workflow.js +184 -0
- package/dist/skill-installer.d.ts +8 -3
- package/dist/skill-installer.d.ts.map +1 -1
- package/dist/skill-installer.js +26 -33
- package/dist/stored-session-requests.d.ts +27 -0
- package/dist/stored-session-requests.d.ts.map +1 -0
- package/dist/stored-session-requests.js +264 -0
- package/dist/terminal-key-session.d.ts +23 -0
- package/dist/terminal-key-session.d.ts.map +1 -0
- package/dist/terminal-key-session.js +74 -0
- package/package.json +1 -1
- package/src/account-directory.ts +8 -42
- package/src/auth-commands.ts +20 -29
- package/src/credentials.ts +83 -3
- package/src/dataset-transfer-upload.ts +7 -1
- package/src/directory-list-read.ts +123 -0
- package/src/hosted-boot.ts +65 -43
- package/src/hosted-harness-opencode.ts +65 -9
- package/src/hosted-loop.ts +152 -18
- package/src/hosted-session-seed.ts +25 -8
- package/src/http.ts +39 -317
- package/src/keyboard-select.ts +18 -55
- package/src/project-create-workflow.ts +139 -0
- package/src/self-update-command.ts +419 -0
- package/src/self-update.ts +70 -266
- package/src/setup-account.ts +4 -1
- package/src/setup-and-skills-commands.ts +89 -121
- package/src/skill-install-workflow.ts +215 -0
- package/src/skill-installer.ts +31 -38
- package/src/stored-session-requests.ts +316 -0
- package/src/terminal-key-session.ts +88 -0
|
@@ -16,6 +16,8 @@
|
|
|
16
16
|
* Credentials come only through the `-c` settings the caller passes (the
|
|
17
17
|
* boot's credential helper), never in a URL or argument.
|
|
18
18
|
*/
|
|
19
|
+
import { existsSync, readdirSync, renameSync } from 'fs';
|
|
20
|
+
import { join } from 'path';
|
|
19
21
|
export const SESSION_COPY_SEED_FAILURE = 'session copy could not be filled from the team copy';
|
|
20
22
|
function withConfig(config) {
|
|
21
23
|
return config.flatMap(setting => ['-c', setting]);
|
|
@@ -120,16 +122,31 @@ export async function cloneTeamIntoWorkTree(input) {
|
|
|
120
122
|
const pointed = input.exec('git', ['-C', input.scratchDir, 'remote', 'set-url', 'origin', input.sessionRemote]);
|
|
121
123
|
if (pointed.status !== 0)
|
|
122
124
|
throw failure('pointing the working folder at the session copy', pointed);
|
|
123
|
-
//
|
|
124
|
-
|
|
125
|
-
//
|
|
126
|
-
//
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
125
|
+
// ORI-2512: an existing folder (the runtime image may ship it empty, and
|
|
126
|
+
// the boot may have started OpenCode in it) is filled in place, never
|
|
127
|
+
// replaced: a process already started there would be left in a deleted
|
|
128
|
+
// folder. A missing folder becomes the clone.
|
|
129
|
+
if (existsSync(input.workTree)) {
|
|
130
|
+
if (readdirSync(input.workTree).length > 0)
|
|
131
|
+
throw failure('making the working folder', { status: 1, stdout: '', stderr: `${input.workTree} is not empty` });
|
|
132
|
+
try {
|
|
133
|
+
// `.git` last: a fill that stops partway leaves no repository, which
|
|
134
|
+
// the next boot refuses, rather than one missing files that a save
|
|
135
|
+
// would push as deleted.
|
|
136
|
+
const entries = readdirSync(input.scratchDir).sort((a, b) => Number(a === '.git') - Number(b === '.git'));
|
|
137
|
+
const rename = input.rename ?? renameSync;
|
|
138
|
+
for (const entry of entries)
|
|
139
|
+
rename(join(input.scratchDir, entry), join(input.workTree, entry));
|
|
140
|
+
}
|
|
141
|
+
catch (error) {
|
|
142
|
+
throw failure('making the working folder', { status: 1, stdout: '', stderr: error instanceof Error ? error.message : String(error) });
|
|
143
|
+
}
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
const moved = input.exec('mv', ['--', input.scratchDir, input.workTree]);
|
|
147
|
+
if (moved.status !== 0)
|
|
148
|
+
throw failure('making the working folder', moved);
|
|
149
|
+
}
|
|
133
150
|
}
|
|
134
151
|
finally {
|
|
135
152
|
clear();
|
package/dist/http.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAOA,OAAO,EAAsC,WAAW,EAAoB,MAAM,mBAAmB,CAAA;AAWrG,wBAAgB,sBAAsB,IAAI,MAAM,GAAG,IAAI,CAEtD;AAED,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,QAGhD;AAED,wBAAgB,cAAc,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAiBxE;AAED,UAAU,YAAY;IACpB,OAAO,EAAE,MAAM,CAAA;IACf,iBAAiB,EAAE,MAAM,CAAA;CAC1B;AAID,wBAAgB,mBAAmB,CAAC,QAAQ,CAAC,EAAE,MAAM,GAAG,YAAY,CAMnE;AAED,UAAU,WAAW;IACnB,OAAO,EAAE,MAAM,CAAA;IACf,SAAS,EAAE,OAAO,CAAA;CACnB;AAED,wBAAgB,kBAAkB,CAAC,KAAK,GAAE,WAA0B,GAAG,WAAW,CAajF;AAED,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAE7E;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAWD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,QAmBzD;AAED,wBAAgB,+BAA+B,IAAI,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAE/E;AAID,MAAM,MAAM,0BAA0B,GAAG,aAAa,GAAG,kBAAkB,GAAG,QAAQ,CAAA;AAEtF,MAAM,WAAW,2BAA2B;IAC1C,QAAQ,CAAC,OAAO,EAAE,MAAM,CAAA;IACxB,QAAQ,CAAC,MAAM,EAAE,0BAA0B,CAAA;IAC3C,sFAAsF;IACtF,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IACvF,MAAM,CAAC,MAAM,CAAC,EAAE,WAAW,GAAG,IAAI,GAAG,OAAO,CAAC,YAAY,CAAC,CAAA;CAC3D;AAED,MAAM,WAAW,YAAY;IAC3B,WAAW,EAAE,MAAM,GAAG,IAAI,CAAA;IAC1B,sBAAsB,EAAE,OAAO,CAAA;CAChC;AAMD;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAChD,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAC;IAAC,iBAAiB,CAAC,EAAE,OAAO,CAAA;CAAO,GACpE,2BAA2B,CAqE7B;AAcD,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,qBAoClG"}
|
package/dist/http.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
|
-
import {
|
|
1
|
+
import { resolveCredentialServer, getActiveBaseUrl, getServerCredentials, rememberResolvedBearer, resolveEnvBearerToken, } from './credentials.js';
|
|
2
2
|
import { canonicalServerUrl, getFlagBaseUrl, normalizeBaseUrl } from './global-flags.js';
|
|
3
|
-
import { describeLogoutHttpFailure, describeLogoutTransportFailure, } from './logout-diagnostic.js';
|
|
4
3
|
let runtimeFlags = { local: false, server: null };
|
|
5
4
|
let lastResolvedBaseUrl = null;
|
|
6
5
|
function rememberResolvedBaseUrl(baseUrl) {
|
|
@@ -78,141 +77,12 @@ export function assertSecureTokenTransport(baseUrl) {
|
|
|
78
77
|
}
|
|
79
78
|
throw new Error(`Refusing to send CLI tokens to ${baseUrl}. Use HTTPS, or --local for loopback development.`);
|
|
80
79
|
}
|
|
81
|
-
function isSessionCredentials(credentials) {
|
|
82
|
-
return 'accessToken' in credentials;
|
|
83
|
-
}
|
|
84
|
-
function getAuthorizationToken(credentials) {
|
|
85
|
-
return isSessionCredentials(credentials)
|
|
86
|
-
? rememberSessionCredentials(credentials).accessToken
|
|
87
|
-
: rememberResolvedBearer(credentials.apiKey);
|
|
88
|
-
}
|
|
89
|
-
function rememberSessionCredentials(credentials) {
|
|
90
|
-
rememberResolvedBearer(credentials.accessToken);
|
|
91
|
-
rememberResolvedBearer(credentials.refreshToken);
|
|
92
|
-
return credentials;
|
|
93
|
-
}
|
|
94
|
-
function isExpired(expiresAt) {
|
|
95
|
-
const nowUnix = Math.floor(Date.now() / 1000);
|
|
96
|
-
return expiresAt <= nowUnix + 30;
|
|
97
|
-
}
|
|
98
|
-
const DEFAULT_CREDENTIALS_REFRESH_TIMEOUT_MS = 10_000;
|
|
99
|
-
const MAX_CREDENTIALS_REFRESH_TIMEOUT_MS = 30_000;
|
|
100
|
-
const DEFAULT_LOGOUT_TIMEOUT_MS = 10_000;
|
|
101
|
-
const MAX_LOGOUT_TIMEOUT_MS = 30_000;
|
|
102
|
-
function getCredentialsRefreshTimeoutMs() {
|
|
103
|
-
const configured = Number(process.env.ORIZU_CREDENTIALS_REFRESH_TIMEOUT_MS);
|
|
104
|
-
if (!Number.isSafeInteger(configured) || configured <= 0) {
|
|
105
|
-
return DEFAULT_CREDENTIALS_REFRESH_TIMEOUT_MS;
|
|
106
|
-
}
|
|
107
|
-
return Math.min(configured, MAX_CREDENTIALS_REFRESH_TIMEOUT_MS);
|
|
108
|
-
}
|
|
109
|
-
function getLogoutTimeoutMs() {
|
|
110
|
-
const configured = Number(process.env.ORIZU_LOGOUT_TIMEOUT_MS);
|
|
111
|
-
if (!Number.isSafeInteger(configured) || configured <= 0) {
|
|
112
|
-
return DEFAULT_LOGOUT_TIMEOUT_MS;
|
|
113
|
-
}
|
|
114
|
-
return Math.min(configured, MAX_LOGOUT_TIMEOUT_MS);
|
|
115
|
-
}
|
|
116
|
-
async function withAbortDeadline(operation, callerSignal, timeoutMs, timeoutError) {
|
|
117
|
-
const controller = new AbortController();
|
|
118
|
-
const handleCallerAbort = () => controller.abort(callerSignal?.reason);
|
|
119
|
-
if (callerSignal?.aborted)
|
|
120
|
-
handleCallerAbort();
|
|
121
|
-
else
|
|
122
|
-
callerSignal?.addEventListener('abort', handleCallerAbort, { once: true });
|
|
123
|
-
const timer = setTimeout(() => controller.abort(timeoutError), timeoutMs);
|
|
124
|
-
const cancellation = new Promise((_resolve, reject) => {
|
|
125
|
-
const handleAbort = () => reject(controller.signal.reason ?? new Error('Request aborted.'));
|
|
126
|
-
if (controller.signal.aborted)
|
|
127
|
-
handleAbort();
|
|
128
|
-
else
|
|
129
|
-
controller.signal.addEventListener('abort', handleAbort, { once: true });
|
|
130
|
-
});
|
|
131
|
-
try {
|
|
132
|
-
return await Promise.race([operation(controller.signal), cancellation]);
|
|
133
|
-
}
|
|
134
|
-
finally {
|
|
135
|
-
clearTimeout(timer);
|
|
136
|
-
callerSignal?.removeEventListener('abort', handleCallerAbort);
|
|
137
|
-
}
|
|
138
|
-
}
|
|
139
|
-
async function withCredentialsRefreshDeadline(operation, callerSignal) {
|
|
140
|
-
const timeoutMs = getCredentialsRefreshTimeoutMs();
|
|
141
|
-
const timeoutError = new Error(`ORIZU_CREDENTIALS_REFRESH_TIMEOUT: session refresh exceeded ${timeoutMs}ms.`);
|
|
142
|
-
return await withAbortDeadline(operation, callerSignal, timeoutMs, timeoutError);
|
|
143
|
-
}
|
|
144
|
-
async function withLogoutDeadline(operation, callerSignal) {
|
|
145
|
-
const timeoutMs = getLogoutTimeoutMs();
|
|
146
|
-
const timeoutError = new Error(`ORIZU_LOGOUT_TIMEOUT: remote logout exceeded ${timeoutMs}ms.`);
|
|
147
|
-
timeoutError.name = 'TimeoutError';
|
|
148
|
-
return await withAbortDeadline(operation, callerSignal, timeoutMs, timeoutError);
|
|
149
|
-
}
|
|
150
80
|
export function credentialRequestRedirectPolicy() {
|
|
151
81
|
return process.env.ORIZU_CLI_AUTH_REDIRECT === 'error' ? { redirect: 'error' } : {};
|
|
152
82
|
}
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
}
|
|
157
|
-
assertSecureTokenTransport(baseUrl);
|
|
158
|
-
const refreshToken = rememberResolvedBearer(credentials.refreshToken);
|
|
159
|
-
const data = await withCredentialsRefreshDeadline(async (signal) => {
|
|
160
|
-
const response = await fetch(`${baseUrl}/api/cli/auth/refresh`, {
|
|
161
|
-
method: 'POST',
|
|
162
|
-
headers: { 'Content-Type': 'application/json' },
|
|
163
|
-
body: JSON.stringify({ refreshToken }),
|
|
164
|
-
...credentialRequestRedirectPolicy(),
|
|
165
|
-
signal,
|
|
166
|
-
});
|
|
167
|
-
if (!response.ok) {
|
|
168
|
-
throw new Error('Session expired. Run `orizu login` again.');
|
|
169
|
-
}
|
|
170
|
-
return await response.json();
|
|
171
|
-
}, callerSignal);
|
|
172
|
-
if (!data.accessToken || !data.refreshToken || !data.expiresAt) {
|
|
173
|
-
throw new Error('Server returned invalid refresh credentials. Run `orizu login` again.');
|
|
174
|
-
}
|
|
175
|
-
const refreshed = rememberSessionCredentials({
|
|
176
|
-
credentialType: 'session',
|
|
177
|
-
accessToken: data.accessToken,
|
|
178
|
-
refreshToken: data.refreshToken,
|
|
179
|
-
expiresAt: data.expiresAt,
|
|
180
|
-
});
|
|
181
|
-
if (isExpired(refreshed.expiresAt)) {
|
|
182
|
-
throw new Error('ORIZU_CREDENTIALS_REFRESH_UNUSABLE: refreshed session expires within the 30-second safety window.');
|
|
183
|
-
}
|
|
184
|
-
return refreshed;
|
|
185
|
-
}
|
|
186
|
-
async function refreshStoredCredentialsIfCurrent(baseUrl, expected, callerSignal) {
|
|
187
|
-
return await withCredentialsTransactionLockAsync(async () => {
|
|
188
|
-
const current = getServerCredentials(baseUrl);
|
|
189
|
-
if (!current || !credentialsEqual(current, expected)) {
|
|
190
|
-
return { status: 'changed', credentials: current };
|
|
191
|
-
}
|
|
192
|
-
const refreshed = await refreshCredentials(baseUrl, expected, callerSignal);
|
|
193
|
-
if (!updateServerCredentialsIfCurrent(baseUrl, expected, refreshed)) {
|
|
194
|
-
return { status: 'changed', credentials: getServerCredentials(baseUrl) };
|
|
195
|
-
}
|
|
196
|
-
return { status: 'refreshed', credentials: refreshed };
|
|
197
|
-
});
|
|
198
|
-
}
|
|
199
|
-
function requireCurrentCredentials(baseUrl, credentials) {
|
|
200
|
-
if (credentials)
|
|
201
|
-
return credentials;
|
|
202
|
-
throw new Error(`Not logged in for ${baseUrl}. Run \`orizu login --server ${baseUrl}\` (or \`--local\`) first.`);
|
|
203
|
-
}
|
|
204
|
-
async function refreshExpiredStoredCredentials(baseUrl, initial, callerSignal) {
|
|
205
|
-
if (!isSessionCredentials(initial) || !isExpired(initial.expiresAt)) {
|
|
206
|
-
return { credentials: initial, didRefresh: false };
|
|
207
|
-
}
|
|
208
|
-
const result = await refreshStoredCredentialsIfCurrent(baseUrl, initial, callerSignal);
|
|
209
|
-
if (result.status === 'changed') {
|
|
210
|
-
throw new Error('ORIZU_AUTH_CONTEXT_CHANGED: stored credentials changed before this request began; the request was not sent.');
|
|
211
|
-
}
|
|
212
|
-
return {
|
|
213
|
-
credentials: requireCurrentCredentials(baseUrl, result.credentials),
|
|
214
|
-
didRefresh: true,
|
|
215
|
-
};
|
|
83
|
+
const storedSessionRequests = () => import('./stored-session-requests.js');
|
|
84
|
+
function notLoggedIn(baseUrl) {
|
|
85
|
+
return new Error(`Not logged in for ${baseUrl}. Run \`orizu login --server ${baseUrl}\` (or \`--local\`) first.`);
|
|
216
86
|
}
|
|
217
87
|
/**
|
|
218
88
|
* Captures one authenticated server context for a multi-step operation. The
|
|
@@ -266,85 +136,41 @@ export function captureAuthenticatedRequestContext(options = {}) {
|
|
|
266
136
|
};
|
|
267
137
|
}
|
|
268
138
|
const capturedCredentials = getServerCredentials(baseUrl);
|
|
269
|
-
if (!capturedCredentials)
|
|
270
|
-
throw
|
|
271
|
-
|
|
272
|
-
void getAuthorizationToken(capturedCredentials);
|
|
139
|
+
if (!capturedCredentials)
|
|
140
|
+
throw notLoggedIn(baseUrl);
|
|
141
|
+
rememberStoredBearers(capturedCredentials);
|
|
273
142
|
let activeCredentials = capturedCredentials;
|
|
274
|
-
|
|
275
|
-
const result = await refreshStoredCredentialsIfCurrent(baseUrl, activeCredentials, callerSignal);
|
|
276
|
-
if (result.status === 'changed') {
|
|
277
|
-
throw new Error('ORIZU_CREDENTIALS_CHANGED: stored credentials changed before the captured session could refresh. Reconfirm the account and retry.');
|
|
278
|
-
}
|
|
279
|
-
activeCredentials = requireCurrentCredentials(baseUrl, result.credentials);
|
|
280
|
-
}
|
|
143
|
+
const changed = 'ORIZU_CREDENTIALS_CHANGED: stored credentials changed before the captured session could refresh. Reconfirm the account and retry.';
|
|
281
144
|
return {
|
|
282
145
|
baseUrl,
|
|
283
146
|
source: 'stored',
|
|
284
147
|
async fetch(path, init = {}, onRequestStart) {
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
...init,
|
|
295
|
-
headers: {
|
|
296
|
-
...(init.headers || {}),
|
|
297
|
-
Authorization: `Bearer ${getAuthorizationToken(activeCredentials)}`,
|
|
298
|
-
},
|
|
299
|
-
...credentialRequestRedirectPolicy(),
|
|
300
|
-
});
|
|
301
|
-
if (allowRefresh
|
|
302
|
-
&& response.status === 401
|
|
303
|
-
&& isSessionCredentials(activeCredentials)
|
|
304
|
-
&& !didRefresh) {
|
|
305
|
-
await refreshCapturedCredentials(init.signal);
|
|
306
|
-
onRequestStart?.();
|
|
307
|
-
response = await fetch(`${baseUrl}${path}`, {
|
|
308
|
-
...init,
|
|
309
|
-
headers: {
|
|
310
|
-
...(init.headers || {}),
|
|
311
|
-
Authorization: `Bearer ${getAuthorizationToken(activeCredentials)}`,
|
|
312
|
-
},
|
|
313
|
-
...credentialRequestRedirectPolicy(),
|
|
314
|
-
});
|
|
315
|
-
}
|
|
316
|
-
return response;
|
|
148
|
+
const { storedRequest } = await storedSessionRequests();
|
|
149
|
+
return await storedRequest({
|
|
150
|
+
baseUrl,
|
|
151
|
+
credentials: activeCredentials,
|
|
152
|
+
allowRefresh,
|
|
153
|
+
changedBeforeRequest: changed,
|
|
154
|
+
changedBeforeReplay: changed,
|
|
155
|
+
onRefreshed: refreshed => { activeCredentials = refreshed; },
|
|
156
|
+
}, path, init, onRequestStart);
|
|
317
157
|
},
|
|
318
158
|
async logout(callerSignal) {
|
|
319
|
-
const
|
|
320
|
-
|
|
321
|
-
? rememberResolvedBearer(activeCredentials.refreshToken)
|
|
322
|
-
: null;
|
|
323
|
-
const secrets = [authorizationToken, ...(refreshToken !== null ? [refreshToken] : [])];
|
|
324
|
-
let remoteLogoutReason = null;
|
|
325
|
-
try {
|
|
326
|
-
remoteLogoutReason = await withLogoutDeadline(async (signal) => {
|
|
327
|
-
const response = await fetch(`${baseUrl}/api/cli/auth/logout`, {
|
|
328
|
-
method: 'POST',
|
|
329
|
-
headers: {
|
|
330
|
-
'Content-Type': 'application/json',
|
|
331
|
-
Authorization: `Bearer ${authorizationToken}`,
|
|
332
|
-
},
|
|
333
|
-
body: refreshToken !== null ? JSON.stringify({ refreshToken }) : undefined,
|
|
334
|
-
...credentialRequestRedirectPolicy(),
|
|
335
|
-
signal,
|
|
336
|
-
});
|
|
337
|
-
return response.ok ? null : await describeLogoutHttpFailure(response, secrets);
|
|
338
|
-
}, callerSignal);
|
|
339
|
-
}
|
|
340
|
-
catch (error) {
|
|
341
|
-
remoteLogoutReason = describeLogoutTransportFailure(error, secrets);
|
|
342
|
-
}
|
|
343
|
-
const localCredentialCleared = clearServerCredentialsIfCurrent(baseUrl, activeCredentials);
|
|
344
|
-
return { remoteError: remoteLogoutReason, localCredentialCleared };
|
|
159
|
+
const { storedLogout } = await storedSessionRequests();
|
|
160
|
+
return await storedLogout(baseUrl, baseUrl, activeCredentials, callerSignal);
|
|
345
161
|
},
|
|
346
162
|
};
|
|
347
163
|
}
|
|
164
|
+
// Redacts a stored login from diagnostics from the moment it is read.
|
|
165
|
+
function rememberStoredBearers(credentials) {
|
|
166
|
+
if ('accessToken' in credentials) {
|
|
167
|
+
rememberResolvedBearer(credentials.accessToken);
|
|
168
|
+
rememberResolvedBearer(credentials.refreshToken);
|
|
169
|
+
}
|
|
170
|
+
else {
|
|
171
|
+
rememberResolvedBearer(credentials.apiKey);
|
|
172
|
+
}
|
|
173
|
+
}
|
|
348
174
|
// The optional observer runs immediately before a resource fetch, after auth
|
|
349
175
|
// preflight. Authentication refresh requests do not mark the resource as sent.
|
|
350
176
|
export async function authedFetch(path, init = {}, onRequestStart) {
|
|
@@ -371,35 +197,14 @@ export async function authedFetch(path, init = {}, onRequestStart) {
|
|
|
371
197
|
});
|
|
372
198
|
}
|
|
373
199
|
const credentials = getServerCredentials(baseUrl);
|
|
374
|
-
if (!credentials)
|
|
375
|
-
throw
|
|
376
|
-
}
|
|
377
|
-
|
|
378
|
-
|
|
379
|
-
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
|
|
384
|
-
},
|
|
385
|
-
...credentialRequestRedirectPolicy(),
|
|
386
|
-
});
|
|
387
|
-
if (response.status === 401
|
|
388
|
-
&& isSessionCredentials(activeCredentials)
|
|
389
|
-
&& !resolved.didRefresh) {
|
|
390
|
-
const refreshResult = await refreshStoredCredentialsIfCurrent(baseUrl, activeCredentials, init.signal);
|
|
391
|
-
if (refreshResult.status === 'changed') {
|
|
392
|
-
throw new Error('ORIZU_AUTH_CONTEXT_CHANGED: stored credentials changed after this request began; the request was not replayed.');
|
|
393
|
-
}
|
|
394
|
-
activeCredentials = requireCurrentCredentials(baseUrl, refreshResult.credentials);
|
|
395
|
-
response = await fetchResource(`${baseUrl}${path}`, {
|
|
396
|
-
...init,
|
|
397
|
-
headers: {
|
|
398
|
-
...(init.headers || {}),
|
|
399
|
-
Authorization: `Bearer ${getAuthorizationToken(activeCredentials)}`,
|
|
400
|
-
},
|
|
401
|
-
...credentialRequestRedirectPolicy(),
|
|
402
|
-
});
|
|
403
|
-
}
|
|
404
|
-
return response;
|
|
200
|
+
if (!credentials)
|
|
201
|
+
throw notLoggedIn(baseUrl);
|
|
202
|
+
const { storedRequest } = await storedSessionRequests();
|
|
203
|
+
return await storedRequest({
|
|
204
|
+
baseUrl,
|
|
205
|
+
credentials,
|
|
206
|
+
allowRefresh: true,
|
|
207
|
+
changedBeforeRequest: 'ORIZU_AUTH_CONTEXT_CHANGED: stored credentials changed before this request began; the request was not sent.',
|
|
208
|
+
changedBeforeReplay: 'ORIZU_AUTH_CONTEXT_CHANGED: stored credentials changed after this request began; the request was not replayed.',
|
|
209
|
+
}, path, init, onRequestStart);
|
|
405
210
|
}
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"keyboard-select.d.ts","sourceRoot":"","sources":["../src/keyboard-select.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"keyboard-select.d.ts","sourceRoot":"","sources":["../src/keyboard-select.ts"],"names":[],"mappings":"AAIA,UAAU,qBAAqB,CAAC,CAAC;IAC/B,QAAQ,CAAC,EAAE,MAAM,CAAC,CAAA;IAClB,WAAW,CAAC,EAAE,MAAM,CAAA;CACrB;AAED,wBAAsB,oBAAoB,CAAC,CAAC,EAC1C,KAAK,EAAE,MAAM,EACb,KAAK,EAAE,CAAC,EAAE,EACV,KAAK,EAAE,CAAC,IAAI,EAAE,CAAC,EAAE,KAAK,EAAE,MAAM,KAAK,MAAM,EACzC,OAAO,CAAC,EAAE,qBAAqB,CAAC,CAAC,CAAC,GACjC,OAAO,CAAC,CAAC,CAAC,CA6CZ"}
|
package/dist/keyboard-select.js
CHANGED
|
@@ -1,4 +1,3 @@
|
|
|
1
|
-
import { emitKeypressEvents } from 'readline';
|
|
2
1
|
import { stdin as input, stdout as output } from 'process';
|
|
3
2
|
import { sanitizeTerminalText } from './json-response.js';
|
|
4
3
|
export async function promptKeyboardSelect(title, items, label, options) {
|
|
@@ -12,9 +11,6 @@ export async function promptKeyboardSelect(title, items, label, options) {
|
|
|
12
11
|
output.write(`Use ↑/↓ to choose, Enter to confirm, or Esc to ${options?.escapeLabel || 'cancel'}.\n`);
|
|
13
12
|
let selected = 0;
|
|
14
13
|
let rendered = false;
|
|
15
|
-
const rawInput = input;
|
|
16
|
-
const wasRaw = Boolean(rawInput.isRaw);
|
|
17
|
-
emitKeypressEvents(input);
|
|
18
14
|
function render() {
|
|
19
15
|
if (rendered)
|
|
20
16
|
output.write(`\x1b[${items.length}A`);
|
|
@@ -26,47 +22,29 @@ export async function promptKeyboardSelect(title, items, label, options) {
|
|
|
26
22
|
rendered = true;
|
|
27
23
|
}
|
|
28
24
|
render();
|
|
29
|
-
|
|
30
|
-
|
|
31
|
-
|
|
32
|
-
|
|
33
|
-
|
|
34
|
-
|
|
35
|
-
|
|
36
|
-
|
|
37
|
-
|
|
38
|
-
function onKeypress(_chunk, key) {
|
|
39
|
-
if (key.ctrl && key.name === 'c') {
|
|
40
|
-
cleanup();
|
|
41
|
-
rejectPromise(new Error('Setup cancelled.'));
|
|
42
|
-
return;
|
|
43
|
-
}
|
|
44
|
-
if (key.name === 'escape') {
|
|
45
|
-
cleanup();
|
|
46
|
-
if (options?.onEscape)
|
|
47
|
-
resolvePromise(options.onEscape());
|
|
48
|
-
else
|
|
49
|
-
rejectPromise(new Error('Setup cancelled.'));
|
|
50
|
-
return;
|
|
51
|
-
}
|
|
52
|
-
if (key.name === 'up') {
|
|
53
|
-
selected = (selected - 1 + items.length) % items.length;
|
|
54
|
-
render();
|
|
55
|
-
return;
|
|
56
|
-
}
|
|
57
|
-
if (key.name === 'down') {
|
|
58
|
-
selected = (selected + 1) % items.length;
|
|
25
|
+
const onEscape = options?.onEscape;
|
|
26
|
+
const { readKeys } = await import('./terminal-key-session.js');
|
|
27
|
+
let choice;
|
|
28
|
+
try {
|
|
29
|
+
choice = await readKeys(key => {
|
|
30
|
+
if (key.name === 'escape')
|
|
31
|
+
return onEscape ? { kind: 'done', value: { escaped: true } } : { kind: 'cancel' };
|
|
32
|
+
if (key.name === 'up' || key.name === 'down') {
|
|
33
|
+
selected = (selected + (key.name === 'up' ? -1 : 1) + items.length) % items.length;
|
|
59
34
|
render();
|
|
60
|
-
return;
|
|
35
|
+
return { kind: 'wait' };
|
|
61
36
|
}
|
|
62
|
-
if (key.name === 'return' || key.name === 'enter')
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
|
|
68
|
-
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
37
|
+
if (key.name === 'return' || key.name === 'enter')
|
|
38
|
+
return { kind: 'done', value: { item: items[selected] } };
|
|
39
|
+
return { kind: 'wait' };
|
|
40
|
+
}, { hideCursor: true });
|
|
41
|
+
}
|
|
42
|
+
finally {
|
|
43
|
+
output.write('\n');
|
|
44
|
+
}
|
|
45
|
+
if ('item' in choice)
|
|
46
|
+
return choice.item;
|
|
47
|
+
if (!onEscape)
|
|
48
|
+
throw new Error('Setup cancelled.');
|
|
49
|
+
return onEscape();
|
|
72
50
|
}
|
|
@@ -0,0 +1,18 @@
|
|
|
1
|
+
/** Sends one request; calls `onRequestStart` just before anything goes out. */
|
|
2
|
+
export type ProjectCreateRequest = (path: string, init: RequestInit, onRequestStart?: () => void) => Promise<Response>;
|
|
3
|
+
export interface CreatedProject {
|
|
4
|
+
id: string;
|
|
5
|
+
name: string;
|
|
6
|
+
slug: string;
|
|
7
|
+
teamId: string;
|
|
8
|
+
teamName: string;
|
|
9
|
+
teamSlug: string;
|
|
10
|
+
role: string;
|
|
11
|
+
}
|
|
12
|
+
/**
|
|
13
|
+
* `orizu projects create` and setup's project create: one request with a time
|
|
14
|
+
* limit, never retried, since a retry after an unclear outcome could make a
|
|
15
|
+
* second project. When the outcome is unclear, the error says how to check.
|
|
16
|
+
*/
|
|
17
|
+
export declare function createProjectWithDeadline(teamSlug: string, name: string, request: ProjectCreateRequest): Promise<CreatedProject>;
|
|
18
|
+
//# sourceMappingURL=project-create-workflow.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"project-create-workflow.d.ts","sourceRoot":"","sources":["../src/project-create-workflow.ts"],"names":[],"mappings":"AASA,+EAA+E;AAC/E,MAAM,MAAM,oBAAoB,GAAG,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,EAAE,WAAW,EAAE,cAAc,CAAC,EAAE,MAAM,IAAI,KAAK,OAAO,CAAC,QAAQ,CAAC,CAAA;AActH,MAAM,WAAW,cAAc;IAC7B,EAAE,EAAE,MAAM,CAAA;IACV,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,MAAM,EAAE,MAAM,CAAA;IACd,QAAQ,EAAE,MAAM,CAAA;IAChB,QAAQ,EAAE,MAAM,CAAA;IAChB,IAAI,EAAE,MAAM,CAAA;CACb;AAmDD;;;;GAIG;AACH,wBAAsB,yBAAyB,CAAC,QAAQ,EAAE,MAAM,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,oBAAoB,GAAG,OAAO,CAAC,cAAc,CAAC,CAkDtI"}
|
|
@@ -0,0 +1,121 @@
|
|
|
1
|
+
import { Cause, Effect, Exit, Result, Schema } from 'effect';
|
|
2
|
+
import { datasetTransferTimeoutMs } from './dataset-transfer-policy.js';
|
|
3
|
+
import { extractErrorMessage } from './error-response.js';
|
|
4
|
+
import { appErrorOf, unreachedCode } from './request-outcome.js';
|
|
5
|
+
const CreateReply = Schema.Struct({
|
|
6
|
+
project: Schema.Struct({
|
|
7
|
+
id: Schema.NonEmptyString,
|
|
8
|
+
name: Schema.String,
|
|
9
|
+
slug: Schema.NonEmptyString,
|
|
10
|
+
teamSlug: Schema.NonEmptyString,
|
|
11
|
+
teamId: Schema.optional(Schema.String),
|
|
12
|
+
teamName: Schema.optional(Schema.String),
|
|
13
|
+
role: Schema.optional(Schema.String),
|
|
14
|
+
}),
|
|
15
|
+
});
|
|
16
|
+
/**
|
|
17
|
+
* Runs one request under the time limit. `run` gets the signal to pass on and
|
|
18
|
+
* the hook that marks the request as sent; the body is read inside `run`, so a
|
|
19
|
+
* reply that stalls part way is under the limit too.
|
|
20
|
+
*/
|
|
21
|
+
function withDeadline(timeoutMs, run, onFailure) {
|
|
22
|
+
return Effect.suspend(() => {
|
|
23
|
+
let sent = false;
|
|
24
|
+
let timedOut = false;
|
|
25
|
+
return Effect.tryPromise({
|
|
26
|
+
try: async (signal) => {
|
|
27
|
+
const controller = new AbortController();
|
|
28
|
+
const abort = () => controller.abort();
|
|
29
|
+
const timer = setTimeout(() => { timedOut = true; controller.abort(); }, timeoutMs);
|
|
30
|
+
if (signal.aborted)
|
|
31
|
+
abort();
|
|
32
|
+
else
|
|
33
|
+
signal.addEventListener('abort', abort, { once: true });
|
|
34
|
+
try {
|
|
35
|
+
return await run(controller.signal, () => { sent = true; });
|
|
36
|
+
}
|
|
37
|
+
finally {
|
|
38
|
+
clearTimeout(timer);
|
|
39
|
+
signal.removeEventListener('abort', abort);
|
|
40
|
+
controller.abort();
|
|
41
|
+
}
|
|
42
|
+
},
|
|
43
|
+
catch: cause => onFailure(cause, { timedOut, sent }),
|
|
44
|
+
});
|
|
45
|
+
});
|
|
46
|
+
}
|
|
47
|
+
async function runToPromise(effect) {
|
|
48
|
+
const exit = await Effect.runPromiseExit(effect);
|
|
49
|
+
if (Exit.isSuccess(exit))
|
|
50
|
+
return exit.value;
|
|
51
|
+
throw Cause.squash(exit.cause);
|
|
52
|
+
}
|
|
53
|
+
const FAILED = 'Failed to create project';
|
|
54
|
+
const projectMayBeCreated = (teamSlug, name) => `The project may already be created: run \`orizu projects list --team ${teamSlug}\` and look for "${name}" before creating it again.`;
|
|
55
|
+
class ProjectCreateFailure extends Error {
|
|
56
|
+
mayBeCreated;
|
|
57
|
+
_tag = 'ProjectCreateFailure';
|
|
58
|
+
constructor(message, mayBeCreated) {
|
|
59
|
+
super(message);
|
|
60
|
+
this.mayBeCreated = mayBeCreated;
|
|
61
|
+
}
|
|
62
|
+
}
|
|
63
|
+
/**
|
|
64
|
+
* `orizu projects create` and setup's project create: one request with a time
|
|
65
|
+
* limit, never retried, since a retry after an unclear outcome could make a
|
|
66
|
+
* second project. When the outcome is unclear, the error says how to check.
|
|
67
|
+
*/
|
|
68
|
+
export async function createProjectWithDeadline(teamSlug, name, request) {
|
|
69
|
+
const create = withDeadline(datasetTransferTimeoutMs(), async (signal, markSent) => {
|
|
70
|
+
const response = await request('/api/cli/projects', {
|
|
71
|
+
method: 'POST',
|
|
72
|
+
headers: { 'Content-Type': 'application/json' },
|
|
73
|
+
body: JSON.stringify({ teamSlug, name }),
|
|
74
|
+
signal,
|
|
75
|
+
}, markSent);
|
|
76
|
+
if (!response.ok) {
|
|
77
|
+
const appError = await appErrorOf(response.clone());
|
|
78
|
+
const detail = await extractErrorMessage(response);
|
|
79
|
+
// A 5xx that is not the app's own error may come from a gateway that gave up while the app kept working.
|
|
80
|
+
if (response.status < 400 || (response.status >= 500 && appError === null)) {
|
|
81
|
+
throw new ProjectCreateFailure(`Project create got no clear answer (status ${response.status}).`, true);
|
|
82
|
+
}
|
|
83
|
+
throw new ProjectCreateFailure(detail.startsWith(FAILED) ? detail : `${FAILED}: ${detail}`, false);
|
|
84
|
+
}
|
|
85
|
+
const body = await response.text();
|
|
86
|
+
let data;
|
|
87
|
+
try {
|
|
88
|
+
data = JSON.parse(body);
|
|
89
|
+
}
|
|
90
|
+
catch {
|
|
91
|
+
data = undefined;
|
|
92
|
+
}
|
|
93
|
+
const decoded = Schema.decodeUnknownResult(CreateReply)(data);
|
|
94
|
+
if (Result.isFailure(decoded)) {
|
|
95
|
+
throw new ProjectCreateFailure(`Project create returned an unexpected response (status ${response.status}).`, true);
|
|
96
|
+
}
|
|
97
|
+
const project = decoded.success.project;
|
|
98
|
+
return {
|
|
99
|
+
id: project.id,
|
|
100
|
+
name: project.name,
|
|
101
|
+
slug: project.slug,
|
|
102
|
+
teamId: project.teamId || '',
|
|
103
|
+
teamName: project.teamName || project.teamSlug,
|
|
104
|
+
teamSlug: project.teamSlug,
|
|
105
|
+
role: project.role || 'admin',
|
|
106
|
+
};
|
|
107
|
+
}, (cause, { timedOut, sent }) => {
|
|
108
|
+
if (cause instanceof ProjectCreateFailure)
|
|
109
|
+
return cause;
|
|
110
|
+
if (timedOut)
|
|
111
|
+
return new ProjectCreateFailure('Project create exceeded its deadline.', sent);
|
|
112
|
+
const unreached = unreachedCode(cause);
|
|
113
|
+
if (unreached)
|
|
114
|
+
return new ProjectCreateFailure(`${FAILED}: could not reach the server (${unreached}).`, false);
|
|
115
|
+
const message = cause instanceof Error ? cause.message : String(cause);
|
|
116
|
+
if (!sent || message.startsWith('ORIZU_AUTH_CONTEXT_CHANGED:'))
|
|
117
|
+
return new ProjectCreateFailure(message, false);
|
|
118
|
+
return new ProjectCreateFailure(`Project create did not finish: ${message}`, true);
|
|
119
|
+
});
|
|
120
|
+
return await runToPromise(create.pipe(Effect.mapError(failure => new Error(failure.mayBeCreated ? `${failure.message}\n${projectMayBeCreated(teamSlug, name)}` : failure.message))));
|
|
121
|
+
}
|
|
@@ -0,0 +1,8 @@
|
|
|
1
|
+
import { type GlobalInstall, type SelfUpdateIo } from './self-update.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs the update with stop handlers installed for exactly its duration. A
|
|
4
|
+
* stop interrupts it: the running child gets the same signal and the stop cap
|
|
5
|
+
* to exit, the command says what is installed now, then ends by that signal.
|
|
6
|
+
*/
|
|
7
|
+
export declare function runSelfUpdate(io: SelfUpdateIo, install: GlobalInstall): Promise<number>;
|
|
8
|
+
//# sourceMappingURL=self-update-command.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"self-update-command.d.ts","sourceRoot":"","sources":["../src/self-update-command.ts"],"names":[],"mappings":"AAYA,OAAO,EAQL,KAAK,aAAa,EAClB,KAAK,YAAY,EAClB,MAAM,kBAAkB,CAAA;AAyWzB;;;;GAIG;AACH,wBAAsB,aAAa,CAAC,EAAE,EAAE,YAAY,EAAE,OAAO,EAAE,aAAa,GAAG,OAAO,CAAC,MAAM,CAAC,CA8B7F"}
|