orizu 0.5.23 → 0.5.25
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 +38 -10
- package/dist/child-output-tee.d.ts +48 -0
- package/dist/child-output-tee.d.ts.map +1 -0
- package/dist/child-output-tee.js +512 -0
- package/dist/cli-auth-browser-page.d.ts +2 -0
- package/dist/cli-auth-browser-page.d.ts.map +1 -0
- package/dist/cli-auth-browser-page.js +107 -0
- package/dist/credentials.d.ts +15 -0
- package/dist/credentials.d.ts.map +1 -1
- package/dist/credentials.js +279 -59
- package/dist/github-setup.js +8 -8
- package/dist/headless-login.d.ts +17 -0
- package/dist/headless-login.d.ts.map +1 -0
- package/dist/headless-login.js +105 -0
- package/dist/help.d.ts.map +1 -1
- package/dist/help.js +33 -22
- package/dist/hosted-boot.js +3 -3
- package/dist/hosted-bootstrap.js +2 -2
- package/dist/hosted-commands.d.ts +2 -0
- package/dist/hosted-commands.d.ts.map +1 -1
- package/dist/hosted-commands.js +11 -0
- package/dist/hosted-environment.d.ts +3 -0
- package/dist/hosted-environment.d.ts.map +1 -0
- package/dist/hosted-environment.js +2 -0
- package/dist/hosted-gepa-cli.d.ts +3 -0
- package/dist/hosted-gepa-cli.d.ts.map +1 -1
- package/dist/hosted-gepa-cli.js +92 -5
- package/dist/hosted-loop.d.ts +1 -1
- package/dist/hosted-loop.d.ts.map +1 -1
- package/dist/hosted-loop.js +2 -1
- package/dist/hosted-optimization-cli.d.ts +7 -0
- package/dist/hosted-optimization-cli.d.ts.map +1 -0
- package/dist/hosted-optimization-cli.js +218 -0
- package/dist/hosted-optimization-refusals.d.ts +88 -0
- package/dist/hosted-optimization-refusals.d.ts.map +1 -0
- package/dist/hosted-optimization-refusals.js +86 -0
- package/dist/hosted-optimization.d.ts +6 -0
- package/dist/hosted-optimization.d.ts.map +1 -0
- package/dist/hosted-optimization.js +436 -0
- package/dist/hosted-provider-settings.d.ts +3 -0
- package/dist/hosted-provider-settings.d.ts.map +1 -1
- package/dist/hosted-provider-settings.js +9 -0
- package/dist/hosted-session-cli.d.ts.map +1 -1
- package/dist/hosted-session-cli.js +6 -1
- package/dist/hosted-skill-staging.d.ts +9 -9
- package/dist/hosted-skill-staging.d.ts.map +1 -1
- package/dist/hosted-skill-staging.js +13 -10
- package/dist/http.d.ts +20 -0
- package/dist/http.d.ts.map +1 -1
- package/dist/http.js +239 -19
- package/dist/index.d.ts +4 -4
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +421 -511
- package/dist/keyboard-select.d.ts +7 -0
- package/dist/keyboard-select.d.ts.map +1 -0
- package/dist/keyboard-select.js +72 -0
- package/dist/logout-diagnostic.d.ts +4 -0
- package/dist/logout-diagnostic.d.ts.map +1 -0
- package/dist/logout-diagnostic.js +26 -0
- package/dist/report-comments-cli.d.ts +1 -0
- package/dist/report-comments-cli.d.ts.map +1 -1
- package/dist/report-comments-cli.js +70 -4
- package/dist/runner-dir-verify.d.ts +5 -0
- package/dist/runner-dir-verify.d.ts.map +1 -1
- package/dist/runner-dir-verify.js +41 -9
- package/dist/runner-version-materialization.d.ts +6 -0
- package/dist/runner-version-materialization.d.ts.map +1 -0
- package/dist/runner-version-materialization.js +64 -0
- package/dist/setup-account.d.ts +11 -0
- package/dist/setup-account.d.ts.map +1 -0
- package/dist/setup-account.js +106 -0
- package/dist/setup-onboarding.d.ts +42 -0
- package/dist/setup-onboarding.d.ts.map +1 -0
- package/dist/setup-onboarding.js +143 -0
- package/dist/setup-summary.d.ts +12 -0
- package/dist/setup-summary.d.ts.map +1 -0
- package/dist/setup-summary.js +56 -0
- package/dist/skill-installer.d.ts +3 -3
- package/dist/skill-installer.d.ts.map +1 -1
- package/dist/skill-installer.js +39 -3
- package/dist/skilled-proposer-launch.d.ts +5 -1
- package/dist/skilled-proposer-launch.d.ts.map +1 -1
- package/dist/skilled-proposer-launch.js +27 -5
- package/dist/skilled-proposer-wire.d.ts +2 -0
- package/dist/skilled-proposer-wire.d.ts.map +1 -0
- package/dist/skilled-proposer-wire.js +25 -0
- package/dist/workspace.d.ts +14 -0
- package/dist/workspace.d.ts.map +1 -1
- package/dist/workspace.js +63 -4
- package/package.json +1 -1
- package/scripts/vendor-orizu-skill.mjs +3 -3
- package/src/child-output-tee.ts +587 -0
- package/src/cli-auth-browser-page.ts +108 -0
- package/src/credentials.ts +326 -57
- package/src/github-setup.ts +8 -8
- package/src/headless-login.ts +178 -0
- package/src/help.ts +35 -22
- package/src/hosted-boot.ts +3 -3
- package/src/hosted-bootstrap.ts +2 -2
- package/src/hosted-commands.ts +16 -0
- package/src/hosted-environment.ts +2 -0
- package/src/hosted-gepa-cli.ts +112 -4
- package/src/hosted-loop.ts +2 -1
- package/src/hosted-optimization-cli.ts +215 -0
- package/src/hosted-optimization-refusals.ts +101 -0
- package/src/hosted-optimization.ts +483 -0
- package/src/hosted-provider-settings.ts +12 -0
- package/src/hosted-session-cli.ts +6 -1
- package/src/hosted-skill-staging.ts +17 -14
- package/src/http.ts +325 -20
- package/src/index.ts +395 -482
- package/src/keyboard-select.ts +97 -0
- package/src/logout-diagnostic.ts +39 -0
- package/src/report-comments-cli.ts +83 -5
- package/src/runner-dir-verify.ts +74 -9
- package/src/runner-version-materialization.ts +56 -0
- package/src/setup-account.ts +129 -0
- package/src/setup-onboarding.ts +187 -0
- package/src/setup-summary.ts +77 -0
- package/src/skill-installer.ts +39 -3
- package/src/skilled-proposer-launch.ts +36 -5
- package/src/skilled-proposer-wire.ts +22 -0
- package/src/workspace.ts +65 -4
- package/vendor/orizu-gepa/src/orizu_gepa_connector/runtime.py +56 -21
- package/vendor/orizu-gepa-python/src/orizu_gepa/client.py +76 -13
- package/vendor/orizu-gepa-python/src/orizu_gepa/optimizer.py +16 -2
- package/vendor/skills/{orizu-cli → orizu}/SKILL.md +7 -3
- package/vendor/skills/orizu/agents/openai.yaml +4 -0
- package/vendor/skills/{orizu-cli → orizu}/references/assess-and-plan.md +1 -1
- package/vendor/skills/{orizu-cli → orizu}/references/building-apps.md +1 -1
- package/vendor/skills/{orizu-cli → orizu}/references/cli-reference.md +4 -4
- package/vendor/skills/{orizu-cli → orizu}/references/migrate-existing-gepa-setup.md +1 -1
- package/vendor/skills/{orizu-cli → orizu}/references/optimization-with-gepa.md +17 -3
- package/vendor/skills/orizu-cli/agents/openai.yaml +0 -4
- /package/vendor/skills/{orizu-cli → orizu}/references/building-judges.md +0 -0
- /package/vendor/skills/{orizu-cli → orizu}/references/dataset-design.md +0 -0
- /package/vendor/skills/{orizu-cli → orizu}/references/eval-strategy.md +0 -0
- /package/vendor/skills/{orizu-cli → orizu}/references/instructions-after-prompts.md +0 -0
- /package/vendor/skills/{orizu-cli → orizu}/references/optimization-reports.md +0 -0
- /package/vendor/skills/{orizu-cli → orizu}/references/primer.md +0 -0
- /package/vendor/skills/{orizu-cli → orizu}/references/prompt-control-plane.md +0 -0
- /package/vendor/skills/{orizu-cli → orizu}/scripts/test-app.mjs +0 -0
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
export function renderCliAuthBrowserPage(status) {
|
|
2
|
+
const isSuccess = status === 'success';
|
|
3
|
+
const eyebrow = isSuccess ? '// cli login complete' : '// cli login';
|
|
4
|
+
const title = isSuccess ? 'Browser authorization complete' : 'Missing authorization code';
|
|
5
|
+
const detail = isSuccess
|
|
6
|
+
? 'Your terminal will finish connecting the Orizu CLI. You can close this tab.'
|
|
7
|
+
: 'Close this tab and run orizu login again to start a fresh browser request.';
|
|
8
|
+
const accent = isSuccess ? '#E8923C' : '#C8442A';
|
|
9
|
+
return `<!doctype html>
|
|
10
|
+
<html lang="en">
|
|
11
|
+
<head>
|
|
12
|
+
<meta charset="utf-8">
|
|
13
|
+
<meta name="viewport" content="width=device-width, initial-scale=1">
|
|
14
|
+
<title>Orizu CLI Login</title>
|
|
15
|
+
<style>
|
|
16
|
+
@import url('https://fonts.googleapis.com/css2?family=Geist+Mono:wght@400;500;700&display=swap');
|
|
17
|
+
:root {
|
|
18
|
+
color-scheme: light;
|
|
19
|
+
--paper: #F4EFE3;
|
|
20
|
+
--border: #E6DFCE;
|
|
21
|
+
--ink: #353535;
|
|
22
|
+
--muted: #6B6358;
|
|
23
|
+
}
|
|
24
|
+
* { box-sizing: border-box; }
|
|
25
|
+
body {
|
|
26
|
+
margin: 0;
|
|
27
|
+
min-height: 100vh;
|
|
28
|
+
background: #FFFFFF;
|
|
29
|
+
color: #4B4B4B;
|
|
30
|
+
font-family: 'Geist Mono', Menlo, Monaco, Consolas, monospace;
|
|
31
|
+
letter-spacing: -0.025em;
|
|
32
|
+
}
|
|
33
|
+
main {
|
|
34
|
+
min-height: 100vh;
|
|
35
|
+
display: flex;
|
|
36
|
+
align-items: center;
|
|
37
|
+
justify-content: center;
|
|
38
|
+
padding: 48px 24px;
|
|
39
|
+
}
|
|
40
|
+
section {
|
|
41
|
+
width: min(100%, 520px);
|
|
42
|
+
text-align: center;
|
|
43
|
+
}
|
|
44
|
+
.brand {
|
|
45
|
+
display: flex;
|
|
46
|
+
flex-direction: column;
|
|
47
|
+
align-items: center;
|
|
48
|
+
gap: 12px;
|
|
49
|
+
margin-bottom: 28px;
|
|
50
|
+
}
|
|
51
|
+
.brand svg {
|
|
52
|
+
width: 42px;
|
|
53
|
+
height: 42px;
|
|
54
|
+
color: #202124;
|
|
55
|
+
}
|
|
56
|
+
.brand span {
|
|
57
|
+
color: #000000;
|
|
58
|
+
font-size: 16px;
|
|
59
|
+
font-weight: 700;
|
|
60
|
+
}
|
|
61
|
+
.eyebrow {
|
|
62
|
+
margin: 0 0 12px;
|
|
63
|
+
color: ${accent};
|
|
64
|
+
font-size: 12px;
|
|
65
|
+
text-transform: lowercase;
|
|
66
|
+
}
|
|
67
|
+
h1 {
|
|
68
|
+
margin: 0 0 8px;
|
|
69
|
+
color: var(--ink);
|
|
70
|
+
font-size: 24px;
|
|
71
|
+
line-height: 1.15;
|
|
72
|
+
letter-spacing: -0.05em;
|
|
73
|
+
}
|
|
74
|
+
.detail {
|
|
75
|
+
margin: 0;
|
|
76
|
+
color: var(--muted);
|
|
77
|
+
font-size: 13px;
|
|
78
|
+
line-height: 1.6;
|
|
79
|
+
}
|
|
80
|
+
</style>
|
|
81
|
+
</head>
|
|
82
|
+
<body>
|
|
83
|
+
<main>
|
|
84
|
+
<section>
|
|
85
|
+
<div class="brand" aria-hidden="true">
|
|
86
|
+
<svg viewBox="0 0 2048 2048" xmlns="http://www.w3.org/2000/svg">
|
|
87
|
+
<g fill="currentColor">
|
|
88
|
+
<polygon points="1545,879 787,1195 1189,1577"></polygon>
|
|
89
|
+
<polygon points="992,624 444,1104 688,1338"></polygon>
|
|
90
|
+
<polygon points="602,1593 1180,1594 768,1202"></polygon>
|
|
91
|
+
<polygon points="1650,1195 1398,1203 1204,1594 1229,1593"></polygon>
|
|
92
|
+
<polygon points="807,413 751,509 823,744 980,608"></polygon>
|
|
93
|
+
<polygon points="441,1127 592,1572 681,1356"></polygon>
|
|
94
|
+
<polygon points="793,399 611,457 604,471 658,602"></polygon>
|
|
95
|
+
<polygon points="593,493 498,667 636,600"></polygon>
|
|
96
|
+
</g>
|
|
97
|
+
</svg>
|
|
98
|
+
<span>orizu</span>
|
|
99
|
+
</div>
|
|
100
|
+
<p class="eyebrow">${eyebrow}</p>
|
|
101
|
+
<h1>${title}</h1>
|
|
102
|
+
<p class="detail">${detail}</p>
|
|
103
|
+
</section>
|
|
104
|
+
</main>
|
|
105
|
+
</body>
|
|
106
|
+
</html>`;
|
|
107
|
+
}
|
package/dist/credentials.d.ts
CHANGED
|
@@ -1,8 +1,22 @@
|
|
|
1
1
|
import { ServerCredentials, StoredCredentialsV2, StoredCredentialsV3 } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Runs a synchronous credentials transaction under the one process-wide and
|
|
4
|
+
* cross-process lock. Exported so real-process fixtures can hold the production
|
|
5
|
+
* lock; CLI credential mutations use this same helper.
|
|
6
|
+
*/
|
|
7
|
+
export declare function withCredentialsTransactionLock<T>(operation: () => T): T;
|
|
8
|
+
/**
|
|
9
|
+
* Holds the credentials lock until an awaited transaction settles. Nested
|
|
10
|
+
* synchronous credential writes are admitted only from this transaction's
|
|
11
|
+
* async execution context; unrelated same-process work must wait or fail.
|
|
12
|
+
*/
|
|
13
|
+
export declare function withCredentialsTransactionLockAsync<T>(operation: () => Promise<T>): Promise<T>;
|
|
2
14
|
export declare function loadCredentialsConfig(): StoredCredentialsV2 | StoredCredentialsV3 | null;
|
|
3
15
|
export declare function getServerCredentials(baseUrl: string): ServerCredentials | null;
|
|
4
16
|
export declare function saveServerCredentials(baseUrl: string, credentials: ServerCredentials): void;
|
|
5
17
|
export declare function updateServerCredentials(baseUrl: string, credentials: ServerCredentials): void;
|
|
18
|
+
export declare function credentialsEqual(left: ServerCredentials, right: ServerCredentials): boolean;
|
|
19
|
+
export declare function updateServerCredentialsIfCurrent(baseUrl: string, expected: ServerCredentials, replacement: ServerCredentials): boolean;
|
|
6
20
|
export declare function getActiveBaseUrl(): string | null;
|
|
7
21
|
/**
|
|
8
22
|
* In-sandbox bearer resolution (ALI-1044). Resolves a bearer supplied out-of-band
|
|
@@ -41,6 +55,7 @@ export declare function resolveAuthTokenForBaseUrl(baseUrl: string): string;
|
|
|
41
55
|
*/
|
|
42
56
|
export declare function hasResolvableAuth(baseUrl: string): boolean;
|
|
43
57
|
export declare function setActiveBaseUrl(baseUrl: string | null): void;
|
|
58
|
+
export declare function clearServerCredentialsIfCurrent(baseUrl: string, expected: ServerCredentials): boolean;
|
|
44
59
|
export declare function clearServerCredentials(baseUrl: string): boolean;
|
|
45
60
|
export declare function clearCredentialsFile(): void;
|
|
46
61
|
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAiBA,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,YAAY,CAAA;AAqInB;;;;GAIG;AACH,wBAAgB,8BAA8B,CAAC,CAAC,EAAE,SAAS,EAAE,MAAM,CAAC,GAAG,CAAC,CAgCvE;AAED;;;;GAIG;AACH,wBAAsB,mCAAmC,CAAC,CAAC,EACzD,SAAS,EAAE,MAAM,OAAO,CAAC,CAAC,CAAC,GAC1B,OAAO,CAAC,CAAC,CAAC,CAiDZ;AAwGD,wBAAgB,qBAAqB,IAAI,mBAAmB,GAAG,mBAAmB,GAAG,IAAI,CA8BxF;AAED,wBAAgB,oBAAoB,CAAC,OAAO,EAAE,MAAM,GAAG,iBAAiB,GAAG,IAAI,CAW9E;AAED,wBAAgB,qBAAqB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,QAepF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,QAatF;AAED,wBAAgB,gBAAgB,CAAC,IAAI,EAAE,iBAAiB,EAAE,KAAK,EAAE,iBAAiB,GAAG,OAAO,CAO3F;AAED,wBAAgB,gCAAgC,CAC9C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,iBAAiB,EAC3B,WAAW,EAAE,iBAAiB,GAC7B,OAAO,CAUT;AAED,wBAAgB,gBAAgB,IAAI,MAAM,GAAG,IAAI,CAGhD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,qBAAqB,IAAI,MAAM,GAAG,IAAI,CA0BrD;AAED;;;;;;;;GAQG;AACH,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,GAAG,MAAM,CAYlE;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAS1D;AAED,wBAAgB,gBAAgB,CAAC,OAAO,EAAE,MAAM,GAAG,IAAI,QAMtD;AAED,wBAAgB,+BAA+B,CAC7C,OAAO,EAAE,MAAM,EACf,QAAQ,EAAE,iBAAiB,GAC1B,OAAO,CAWT;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAc/D;AAED,wBAAgB,oBAAoB,SAKnC"}
|
package/dist/credentials.js
CHANGED
|
@@ -1,7 +1,8 @@
|
|
|
1
|
-
import { chmodSync, closeSync, existsSync, fsyncSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'fs';
|
|
1
|
+
import { chmodSync, closeSync, existsSync, fsyncSync, linkSync, mkdirSync, openSync, readFileSync, renameSync, rmSync, writeFileSync, } from 'fs';
|
|
2
2
|
import { join } from 'path';
|
|
3
|
-
import { homedir } from 'os';
|
|
3
|
+
import { homedir, hostname } from 'os';
|
|
4
4
|
import { randomBytes } from 'crypto';
|
|
5
|
+
import { AsyncLocalStorage } from 'async_hooks';
|
|
5
6
|
function isNodeError(error) {
|
|
6
7
|
return error instanceof Error;
|
|
7
8
|
}
|
|
@@ -14,6 +15,186 @@ function getConfigDir() {
|
|
|
14
15
|
function getCredentialsPath() {
|
|
15
16
|
return join(getConfigDir(), 'credentials.json');
|
|
16
17
|
}
|
|
18
|
+
const DEFAULT_CREDENTIALS_LOCK_TIMEOUT_MS = 2_000;
|
|
19
|
+
const MAX_CREDENTIALS_LOCK_TIMEOUT_MS = 10_000;
|
|
20
|
+
const CREDENTIALS_LOCK_RETRY_MS = 20;
|
|
21
|
+
let heldCredentialsLock = null;
|
|
22
|
+
const credentialsTransactionOwner = new AsyncLocalStorage();
|
|
23
|
+
function getCredentialsLockPath() {
|
|
24
|
+
return `${getCredentialsPath()}.lock`;
|
|
25
|
+
}
|
|
26
|
+
function readLockDuration(name, fallback, maximum = Number.MAX_SAFE_INTEGER) {
|
|
27
|
+
const raw = process.env[name];
|
|
28
|
+
if (!raw)
|
|
29
|
+
return fallback;
|
|
30
|
+
const parsed = Number(raw);
|
|
31
|
+
return Number.isSafeInteger(parsed) && parsed > 0
|
|
32
|
+
? Math.min(parsed, maximum)
|
|
33
|
+
: fallback;
|
|
34
|
+
}
|
|
35
|
+
function sleepSync(milliseconds) {
|
|
36
|
+
Atomics.wait(new Int32Array(new SharedArrayBuffer(4)), 0, 0, milliseconds);
|
|
37
|
+
}
|
|
38
|
+
function acquireCredentialsLock() {
|
|
39
|
+
const dir = getConfigDir();
|
|
40
|
+
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
41
|
+
chmodSync(dir, 0o700);
|
|
42
|
+
const lockPath = getCredentialsLockPath();
|
|
43
|
+
const timeoutMs = readLockDuration('ORIZU_CREDENTIALS_LOCK_TIMEOUT_MS', DEFAULT_CREDENTIALS_LOCK_TIMEOUT_MS, MAX_CREDENTIALS_LOCK_TIMEOUT_MS);
|
|
44
|
+
const deadline = Date.now() + timeoutMs;
|
|
45
|
+
while (true) {
|
|
46
|
+
const metadata = JSON.stringify({
|
|
47
|
+
version: 1,
|
|
48
|
+
pid: process.pid,
|
|
49
|
+
hostname: hostname(),
|
|
50
|
+
createdAt: Date.now(),
|
|
51
|
+
instanceId: randomBytes(16).toString('hex'),
|
|
52
|
+
});
|
|
53
|
+
const tempPath = join(dir, `.credentials.json.lock.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
|
|
54
|
+
try {
|
|
55
|
+
const fd = openSync(tempPath, 'wx', 0o600);
|
|
56
|
+
try {
|
|
57
|
+
writeFileSync(fd, metadata, 'utf8');
|
|
58
|
+
fsyncSync(fd);
|
|
59
|
+
chmodSync(tempPath, 0o600);
|
|
60
|
+
}
|
|
61
|
+
finally {
|
|
62
|
+
closeSync(fd);
|
|
63
|
+
}
|
|
64
|
+
try {
|
|
65
|
+
// link(2) publishes the already-complete inode without replacing an
|
|
66
|
+
// existing owner. No contender ever unlinks the canonical lock.
|
|
67
|
+
linkSync(tempPath, lockPath);
|
|
68
|
+
}
|
|
69
|
+
catch (error) {
|
|
70
|
+
if (!(isNodeError(error) && error.code === 'EEXIST'))
|
|
71
|
+
throw error;
|
|
72
|
+
if (Date.now() >= deadline) {
|
|
73
|
+
throw new Error(`ORIZU_CREDENTIALS_LOCK_TIMEOUT: credentials mutation did not acquire the lock within ${timeoutMs}ms. Verify no Orizu process is running, then remove credentials.json.lock manually.`);
|
|
74
|
+
}
|
|
75
|
+
sleepSync(Math.min(CREDENTIALS_LOCK_RETRY_MS, Math.max(1, deadline - Date.now())));
|
|
76
|
+
continue;
|
|
77
|
+
}
|
|
78
|
+
let resolveReleased = () => { };
|
|
79
|
+
const released = new Promise(resolve => { resolveReleased = resolve; });
|
|
80
|
+
return { path: lockPath, metadata, depth: 1, released, resolveReleased };
|
|
81
|
+
}
|
|
82
|
+
finally {
|
|
83
|
+
// The unique owner-only preparation link is never retained, whether
|
|
84
|
+
// publication succeeds, loses to an owner, or fails unexpectedly.
|
|
85
|
+
rmSync(tempPath, { force: true });
|
|
86
|
+
}
|
|
87
|
+
}
|
|
88
|
+
}
|
|
89
|
+
function releaseCredentialsLock(lock) {
|
|
90
|
+
let currentMetadata;
|
|
91
|
+
try {
|
|
92
|
+
currentMetadata = readFileSync(lock.path, 'utf8');
|
|
93
|
+
}
|
|
94
|
+
catch (error) {
|
|
95
|
+
if (isNodeError(error) && error.code === 'ENOENT')
|
|
96
|
+
return;
|
|
97
|
+
throw error;
|
|
98
|
+
}
|
|
99
|
+
// Preserve a changed/replaced lock rather than risking removal of another owner.
|
|
100
|
+
if (currentMetadata !== lock.metadata)
|
|
101
|
+
return;
|
|
102
|
+
rmSync(lock.path);
|
|
103
|
+
}
|
|
104
|
+
/**
|
|
105
|
+
* Runs a synchronous credentials transaction under the one process-wide and
|
|
106
|
+
* cross-process lock. Exported so real-process fixtures can hold the production
|
|
107
|
+
* lock; CLI credential mutations use this same helper.
|
|
108
|
+
*/
|
|
109
|
+
export function withCredentialsTransactionLock(operation) {
|
|
110
|
+
const lockPath = getCredentialsLockPath();
|
|
111
|
+
if (heldCredentialsLock) {
|
|
112
|
+
if (heldCredentialsLock.path !== lockPath) {
|
|
113
|
+
throw new Error('ORIZU_CREDENTIALS_LOCK_NESTED_CONFIG: credentials config changed during a mutation.');
|
|
114
|
+
}
|
|
115
|
+
if (heldCredentialsLock.asyncOwner
|
|
116
|
+
&& credentialsTransactionOwner.getStore() !== heldCredentialsLock.asyncOwner) {
|
|
117
|
+
throw new Error('ORIZU_CREDENTIALS_LOCK_REQUIRED: an asynchronous credentials transaction already owns the lock.');
|
|
118
|
+
}
|
|
119
|
+
heldCredentialsLock.depth += 1;
|
|
120
|
+
try {
|
|
121
|
+
return operation();
|
|
122
|
+
}
|
|
123
|
+
finally {
|
|
124
|
+
heldCredentialsLock.depth -= 1;
|
|
125
|
+
}
|
|
126
|
+
}
|
|
127
|
+
const lock = acquireCredentialsLock();
|
|
128
|
+
heldCredentialsLock = lock;
|
|
129
|
+
try {
|
|
130
|
+
return operation();
|
|
131
|
+
}
|
|
132
|
+
finally {
|
|
133
|
+
heldCredentialsLock = null;
|
|
134
|
+
try {
|
|
135
|
+
releaseCredentialsLock(lock);
|
|
136
|
+
}
|
|
137
|
+
finally {
|
|
138
|
+
lock.resolveReleased();
|
|
139
|
+
}
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
/**
|
|
143
|
+
* Holds the credentials lock until an awaited transaction settles. Nested
|
|
144
|
+
* synchronous credential writes are admitted only from this transaction's
|
|
145
|
+
* async execution context; unrelated same-process work must wait or fail.
|
|
146
|
+
*/
|
|
147
|
+
export async function withCredentialsTransactionLockAsync(operation) {
|
|
148
|
+
const lockPath = getCredentialsLockPath();
|
|
149
|
+
const currentOwner = credentialsTransactionOwner.getStore();
|
|
150
|
+
if (heldCredentialsLock) {
|
|
151
|
+
if (heldCredentialsLock.path !== lockPath) {
|
|
152
|
+
throw new Error('ORIZU_CREDENTIALS_LOCK_NESTED_CONFIG: credentials config changed during a mutation.');
|
|
153
|
+
}
|
|
154
|
+
if (heldCredentialsLock.asyncOwner === currentOwner && currentOwner) {
|
|
155
|
+
heldCredentialsLock.depth += 1;
|
|
156
|
+
try {
|
|
157
|
+
return await operation();
|
|
158
|
+
}
|
|
159
|
+
finally {
|
|
160
|
+
heldCredentialsLock.depth -= 1;
|
|
161
|
+
}
|
|
162
|
+
}
|
|
163
|
+
const timeoutMs = readLockDuration('ORIZU_CREDENTIALS_LOCK_TIMEOUT_MS', DEFAULT_CREDENTIALS_LOCK_TIMEOUT_MS, MAX_CREDENTIALS_LOCK_TIMEOUT_MS);
|
|
164
|
+
let timer;
|
|
165
|
+
const didRelease = await Promise.race([
|
|
166
|
+
heldCredentialsLock.released.then(() => true),
|
|
167
|
+
new Promise(resolve => { timer = setTimeout(() => resolve(false), timeoutMs); }),
|
|
168
|
+
]);
|
|
169
|
+
if (timer)
|
|
170
|
+
clearTimeout(timer);
|
|
171
|
+
if (!didRelease) {
|
|
172
|
+
throw new Error(`ORIZU_CREDENTIALS_LOCK_TIMEOUT: credentials mutation did not acquire the lock within ${timeoutMs}ms. Verify no Orizu process is running, then remove credentials.json.lock manually.`);
|
|
173
|
+
}
|
|
174
|
+
return await withCredentialsTransactionLockAsync(operation);
|
|
175
|
+
}
|
|
176
|
+
const lock = acquireCredentialsLock();
|
|
177
|
+
const owner = Symbol('credentials-transaction-owner');
|
|
178
|
+
lock.asyncOwner = owner;
|
|
179
|
+
heldCredentialsLock = lock;
|
|
180
|
+
try {
|
|
181
|
+
return await credentialsTransactionOwner.run(owner, operation);
|
|
182
|
+
}
|
|
183
|
+
finally {
|
|
184
|
+
heldCredentialsLock = null;
|
|
185
|
+
try {
|
|
186
|
+
releaseCredentialsLock(lock);
|
|
187
|
+
}
|
|
188
|
+
finally {
|
|
189
|
+
lock.resolveReleased();
|
|
190
|
+
}
|
|
191
|
+
}
|
|
192
|
+
}
|
|
193
|
+
function assertCredentialsLockHeld() {
|
|
194
|
+
if (!heldCredentialsLock || heldCredentialsLock.path !== getCredentialsLockPath()) {
|
|
195
|
+
throw new Error('ORIZU_CREDENTIALS_LOCK_REQUIRED: credentials writes require a transaction lock.');
|
|
196
|
+
}
|
|
197
|
+
}
|
|
17
198
|
function isStoredCredentialsV2(value) {
|
|
18
199
|
if (!value || typeof value !== 'object') {
|
|
19
200
|
return false;
|
|
@@ -52,35 +233,33 @@ function migrateToV2(stored) {
|
|
|
52
233
|
};
|
|
53
234
|
}
|
|
54
235
|
function writeCredentials(config) {
|
|
236
|
+
assertCredentialsLockHeld();
|
|
55
237
|
const dir = getConfigDir();
|
|
56
238
|
mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
57
239
|
chmodSync(dir, 0o700);
|
|
58
240
|
const path = getCredentialsPath();
|
|
59
241
|
const tempPath = join(dir, `.credentials.json.${process.pid}.${randomBytes(8).toString('hex')}.tmp`);
|
|
60
242
|
const payload = JSON.stringify(config, null, 2) + '\n';
|
|
61
|
-
const fd = openSync(tempPath, 'wx', 0o600);
|
|
62
243
|
try {
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
closeSync(fd);
|
|
68
|
-
}
|
|
69
|
-
chmodSync(tempPath, 0o600);
|
|
70
|
-
try {
|
|
71
|
-
renameSync(tempPath, path);
|
|
72
|
-
}
|
|
73
|
-
catch (error) {
|
|
74
|
-
if (process.platform === 'win32' && isNodeError(error) && error.code === 'EEXIST') {
|
|
75
|
-
rmSync(path, { force: true });
|
|
76
|
-
renameSync(tempPath, path);
|
|
244
|
+
const fd = openSync(tempPath, 'wx', 0o600);
|
|
245
|
+
try {
|
|
246
|
+
writeFileSync(fd, payload, 'utf-8');
|
|
247
|
+
fsyncSync(fd);
|
|
77
248
|
}
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
throw error;
|
|
249
|
+
finally {
|
|
250
|
+
closeSync(fd);
|
|
81
251
|
}
|
|
252
|
+
chmodSync(tempPath, 0o600);
|
|
253
|
+
// Never delete the destination to emulate replacement. If this platform
|
|
254
|
+
// cannot atomically replace it, preserve the old file and surface the error.
|
|
255
|
+
renameSync(tempPath, path);
|
|
256
|
+
chmodSync(path, 0o600);
|
|
257
|
+
}
|
|
258
|
+
finally {
|
|
259
|
+
// On success rename has already removed this path; on every error this
|
|
260
|
+
// removes the owner-only temporary file containing credential material.
|
|
261
|
+
rmSync(tempPath, { force: true });
|
|
82
262
|
}
|
|
83
|
-
chmodSync(path, 0o600);
|
|
84
263
|
}
|
|
85
264
|
function createEmptyCredentialsConfig() {
|
|
86
265
|
return {
|
|
@@ -134,29 +313,52 @@ export function getServerCredentials(baseUrl) {
|
|
|
134
313
|
return config.servers[baseUrl] || null;
|
|
135
314
|
}
|
|
136
315
|
export function saveServerCredentials(baseUrl, credentials) {
|
|
137
|
-
|
|
138
|
-
|
|
139
|
-
|
|
140
|
-
|
|
141
|
-
|
|
142
|
-
|
|
143
|
-
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
316
|
+
return withCredentialsTransactionLock(() => {
|
|
317
|
+
const loaded = loadCredentialsConfigForWrite();
|
|
318
|
+
const config = loaded.version === 3
|
|
319
|
+
? loaded
|
|
320
|
+
: {
|
|
321
|
+
version: 3,
|
|
322
|
+
activeBaseUrl: loaded.activeBaseUrl,
|
|
323
|
+
servers: loaded.servers,
|
|
324
|
+
};
|
|
325
|
+
config.servers[baseUrl] = credentials;
|
|
326
|
+
config.activeBaseUrl = baseUrl;
|
|
327
|
+
writeCredentials(config);
|
|
328
|
+
});
|
|
148
329
|
}
|
|
149
330
|
export function updateServerCredentials(baseUrl, credentials) {
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
331
|
+
return withCredentialsTransactionLock(() => {
|
|
332
|
+
const loaded = loadCredentialsConfigForWrite();
|
|
333
|
+
const config = loaded.version === 3
|
|
334
|
+
? loaded
|
|
335
|
+
: {
|
|
336
|
+
version: 3,
|
|
337
|
+
activeBaseUrl: loaded.activeBaseUrl,
|
|
338
|
+
servers: loaded.servers,
|
|
339
|
+
};
|
|
340
|
+
config.servers[baseUrl] = credentials;
|
|
341
|
+
writeCredentials(config);
|
|
342
|
+
});
|
|
343
|
+
}
|
|
344
|
+
export function credentialsEqual(left, right) {
|
|
345
|
+
if ('apiKey' in left || 'apiKey' in right) {
|
|
346
|
+
return 'apiKey' in left && 'apiKey' in right && left.apiKey === right.apiKey;
|
|
347
|
+
}
|
|
348
|
+
return left.accessToken === right.accessToken
|
|
349
|
+
&& left.refreshToken === right.refreshToken
|
|
350
|
+
&& left.expiresAt === right.expiresAt;
|
|
351
|
+
}
|
|
352
|
+
export function updateServerCredentialsIfCurrent(baseUrl, expected, replacement) {
|
|
353
|
+
return withCredentialsTransactionLock(() => {
|
|
354
|
+
const config = loadCredentialsConfig();
|
|
355
|
+
const current = config?.servers[baseUrl];
|
|
356
|
+
if (!config || !current || !credentialsEqual(current, expected))
|
|
357
|
+
return false;
|
|
358
|
+
config.servers[baseUrl] = replacement;
|
|
359
|
+
writeCredentials(config);
|
|
360
|
+
return true;
|
|
361
|
+
});
|
|
160
362
|
}
|
|
161
363
|
export function getActiveBaseUrl() {
|
|
162
364
|
const config = loadCredentialsConfig();
|
|
@@ -244,25 +446,43 @@ export function hasResolvableAuth(baseUrl) {
|
|
|
244
446
|
return getServerCredentials(baseUrl) !== null;
|
|
245
447
|
}
|
|
246
448
|
export function setActiveBaseUrl(baseUrl) {
|
|
247
|
-
|
|
248
|
-
|
|
249
|
-
|
|
449
|
+
return withCredentialsTransactionLock(() => {
|
|
450
|
+
const config = loadCredentialsConfigForWrite();
|
|
451
|
+
config.activeBaseUrl = baseUrl;
|
|
452
|
+
writeCredentials(config);
|
|
453
|
+
});
|
|
454
|
+
}
|
|
455
|
+
export function clearServerCredentialsIfCurrent(baseUrl, expected) {
|
|
456
|
+
return withCredentialsTransactionLock(() => {
|
|
457
|
+
const config = loadCredentialsConfig();
|
|
458
|
+
const current = config?.servers[baseUrl];
|
|
459
|
+
if (!config || !current || !credentialsEqual(current, expected))
|
|
460
|
+
return false;
|
|
461
|
+
delete config.servers[baseUrl];
|
|
462
|
+
if (config.activeBaseUrl === baseUrl)
|
|
463
|
+
config.activeBaseUrl = null;
|
|
464
|
+
writeCredentials(config);
|
|
465
|
+
return true;
|
|
466
|
+
});
|
|
250
467
|
}
|
|
251
468
|
export function clearServerCredentials(baseUrl) {
|
|
252
|
-
|
|
253
|
-
|
|
254
|
-
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
config.activeBaseUrl
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
469
|
+
return withCredentialsTransactionLock(() => {
|
|
470
|
+
const config = loadCredentialsConfig();
|
|
471
|
+
if (!config || !Object.hasOwn(config.servers, baseUrl)) {
|
|
472
|
+
return false;
|
|
473
|
+
}
|
|
474
|
+
delete config.servers[baseUrl];
|
|
475
|
+
if (config.activeBaseUrl === baseUrl) {
|
|
476
|
+
config.activeBaseUrl = null;
|
|
477
|
+
}
|
|
478
|
+
writeCredentials(config);
|
|
479
|
+
return true;
|
|
480
|
+
});
|
|
262
481
|
}
|
|
263
482
|
export function clearCredentialsFile() {
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
483
|
+
return withCredentialsTransactionLock(() => {
|
|
484
|
+
const path = getCredentialsPath();
|
|
485
|
+
if (existsSync(path))
|
|
486
|
+
rmSync(path);
|
|
487
|
+
});
|
|
268
488
|
}
|
package/dist/github-setup.js
CHANGED
|
@@ -46,8 +46,8 @@ function alreadyInstalledHintLines(teamSlug) {
|
|
|
46
46
|
' • Install the App into a DIFFERENT GitHub org for this team (each org',
|
|
47
47
|
' installation can be linked to exactly one Orizu team).',
|
|
48
48
|
' • If the org is not linked to any Orizu team, uninstall the Orizu App',
|
|
49
|
-
' from the org (GitHub → org Settings → GitHub Apps)
|
|
50
|
-
`
|
|
49
|
+
' from the org (GitHub → org Settings → GitHub Apps), then',
|
|
50
|
+
` contact Orizu support to restart GitHub linking for team ${teamSlug}.`,
|
|
51
51
|
];
|
|
52
52
|
}
|
|
53
53
|
function defaultSleep(ms) {
|
|
@@ -99,7 +99,7 @@ export async function runGithubLink(teamSlug, io) {
|
|
|
99
99
|
else {
|
|
100
100
|
io.print(' Open the URL above and install the App, then return here.');
|
|
101
101
|
}
|
|
102
|
-
io.print(
|
|
102
|
+
io.print(' Waiting for you to finish installing in the browser… (Ctrl+C to abort).');
|
|
103
103
|
const now = io.now ?? (() => Date.now());
|
|
104
104
|
const sleep = io.sleep ?? defaultSleep;
|
|
105
105
|
const interval = io.pollIntervalMs ?? DEFAULT_POLL_INTERVAL_MS;
|
|
@@ -125,7 +125,7 @@ export async function runGithubLink(teamSlug, io) {
|
|
|
125
125
|
// the server's reason and exit non-zero instead of polling until timeout.
|
|
126
126
|
if (data.status === 'unsupported_account' || data.status === 'expired') {
|
|
127
127
|
throw new Error(data.error ??
|
|
128
|
-
`GitHub link could not be completed
|
|
128
|
+
`GitHub link could not be completed for team ${teamSlug}. Contact Orizu support to restart it.`);
|
|
129
129
|
}
|
|
130
130
|
// status 'pending' → keep waiting.
|
|
131
131
|
}
|
|
@@ -135,12 +135,12 @@ export async function runGithubLink(teamSlug, io) {
|
|
|
135
135
|
throw new Error(`Timed out waiting for the GitHub App install to complete after ${Math.round(timeout / 60000)} minutes. ` +
|
|
136
136
|
'If the Orizu GitHub App is already installed in that org, GitHub never notifies Orizu when you finish the "Configure" flow — ' +
|
|
137
137
|
'install the App into a DIFFERENT GitHub org for this team (or uninstall it from the org first if no other Orizu team uses it). ' +
|
|
138
|
-
`
|
|
138
|
+
`Contact Orizu support to resume GitHub linking for team ${teamSlug} after finishing the install.`);
|
|
139
139
|
}
|
|
140
140
|
if (!printedAlreadyInstalledHint && now() - started >= ALREADY_INSTALLED_HINT_MS) {
|
|
141
141
|
for (const line of alreadyInstalledHintLines(teamSlug))
|
|
142
142
|
io.print(line);
|
|
143
|
-
io.print(
|
|
143
|
+
io.print(' Still waiting (Ctrl+C to abort)…');
|
|
144
144
|
printedAlreadyInstalledHint = true;
|
|
145
145
|
lastHeartbeat = now();
|
|
146
146
|
}
|
|
@@ -160,12 +160,12 @@ async function confirmLink(teamSlug, orgLogin, io, fetcher) {
|
|
|
160
160
|
io.print(` GitHub reported installation on org: ${orgLogin ?? '(unknown)'}`);
|
|
161
161
|
if (!io.confirm) {
|
|
162
162
|
throw new Error(`Org ${orgLogin ?? '(unknown)'} is bound but needs confirmation. ` +
|
|
163
|
-
`
|
|
163
|
+
`Contact Orizu support to confirm GitHub linking for team ${teamSlug}.`);
|
|
164
164
|
}
|
|
165
165
|
const approved = await io.confirm(orgLogin);
|
|
166
166
|
if (!approved) {
|
|
167
167
|
throw new Error(`Did not confirm org ${orgLogin ?? '(unknown)'}. ` +
|
|
168
|
-
`
|
|
168
|
+
`Contact Orizu support to link the correct GitHub organization for team ${teamSlug}.`);
|
|
169
169
|
}
|
|
170
170
|
const resp = await fetcher('/api/cli/github/link/confirm', {
|
|
171
171
|
method: 'POST',
|
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
interface HeadlessLoginDependencies {
|
|
2
|
+
parseJsonResponse: <T>(response: Response, context: string) => Promise<T>;
|
|
3
|
+
printProgress: (message: string) => void;
|
|
4
|
+
sanitizeTerminalText: (text: string) => string;
|
|
5
|
+
validateBrowserUrl: (url: string, expectedOrigin?: string) => URL;
|
|
6
|
+
}
|
|
7
|
+
export declare function shouldUseHeadlessLogin({ isForced, platform, environment, }: {
|
|
8
|
+
isForced: boolean;
|
|
9
|
+
platform?: NodeJS.Platform;
|
|
10
|
+
environment?: Record<string, string | undefined>;
|
|
11
|
+
}): boolean;
|
|
12
|
+
export declare function waitForHeadlessAuthorization({ baseUrl, codeChallenge, }: {
|
|
13
|
+
baseUrl: string;
|
|
14
|
+
codeChallenge: string;
|
|
15
|
+
}, dependencies: HeadlessLoginDependencies): Promise<string>;
|
|
16
|
+
export {};
|
|
17
|
+
//# sourceMappingURL=headless-login.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"headless-login.d.ts","sourceRoot":"","sources":["../src/headless-login.ts"],"names":[],"mappings":"AAYA,UAAU,yBAAyB;IACjC,iBAAiB,EAAE,CAAC,CAAC,EAAE,QAAQ,EAAE,QAAQ,EAAE,OAAO,EAAE,MAAM,KAAK,OAAO,CAAC,CAAC,CAAC,CAAA;IACzE,aAAa,EAAE,CAAC,OAAO,EAAE,MAAM,KAAK,IAAI,CAAA;IACxC,oBAAoB,EAAE,CAAC,IAAI,EAAE,MAAM,KAAK,MAAM,CAAA;IAC9C,kBAAkB,EAAE,CAAC,GAAG,EAAE,MAAM,EAAE,cAAc,CAAC,EAAE,MAAM,KAAK,GAAG,CAAA;CAClE;AAED,wBAAgB,sBAAsB,CAAC,EACrC,QAAQ,EACR,QAA2B,EAC3B,WAAyB,GAC1B,EAAE;IACD,QAAQ,EAAE,OAAO,CAAA;IACjB,QAAQ,CAAC,EAAE,MAAM,CAAC,QAAQ,CAAA;IAC1B,WAAW,CAAC,EAAE,MAAM,CAAC,MAAM,EAAE,MAAM,GAAG,SAAS,CAAC,CAAA;CACjD,GAAG,OAAO,CAMV;AAsCD,wBAAsB,4BAA4B,CAChD,EACE,OAAO,EACP,aAAa,GACd,EAAE;IACD,OAAO,EAAE,MAAM,CAAA;IACf,aAAa,EAAE,MAAM,CAAA;CACtB,EACD,YAAY,EAAE,yBAAyB,GACtC,OAAO,CAAC,MAAM,CAAC,CAiGjB"}
|