orizu 0.5.24 → 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 CHANGED
@@ -87,9 +87,29 @@ Install the CLI globally with your package manager:
87
87
  | Bun | `bun add -g orizu` |
88
88
 
89
89
  Then run the guided setup — it signs you in, asks you to choose or create a
90
- team and project, initializes the local workspace contract, installs the shared
91
- `~/.agents/skills/orizu` skill plus selected native targets, and optionally
92
- launches Claude, Codex, or Pi:
90
+ team and project, initializes the local workspace contract, and optionally
91
+ hands off to Claude, Codex, Pi, OpenCode, and Cursor when their commands are
92
+ installed. Available commands appear in that order, with the first available
93
+ command selected by default. Other is always last. Enter launches the highlighted
94
+ agent; Esc skips agent launch and completes setup. Selecting Other prints the
95
+ complete prompt without launching anything. Explicit
96
+ `--launch claude|codex|pi|opencode|cursor` still requires an interactive terminal
97
+ and asks for confirmation unless `--yes` is supplied; non-interactive setup never
98
+ launches. Cursor uses the `agent` executable, not the Cursor editor `cursor` command.
99
+ The official [Cursor CLI overview](https://cursor.com/docs/cli/overview) and
100
+ [parameter reference](https://cursor.com/docs/cli/reference/parameters), retrieved
101
+ 2026-08-30, document direct `agent "prompt"` and `agent -p "prompt"` initial prompts;
102
+ setup uses the direct positional form without a shell.
103
+
104
+ If you accept skill installation, the picker starts with
105
+ `Universal (.agents — Codex and others) — ~/.agents/skills/orizu` and shows each
106
+ detected native target with its `~/.../orizu` destination on the same line before
107
+ any write. All visible choices start selected, and any choice—including Universal—
108
+ can be deselected. In non-interactive setup, `--agent` opts into skill installation.
109
+ Every accepted `--agent` always includes Universal, plus that agent's native
110
+ destination where needed. Duplicate destinations are removed. With no `--agent` flags, setup writes no skills.
111
+
112
+ For example:
93
113
 
94
114
  ```bash
95
115
  orizu setup
@@ -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":"AAeA,OAAO,EACL,iBAAiB,EAEjB,mBAAmB,EACnB,mBAAmB,EACpB,MAAM,YAAY,CAAA;AAkHnB,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,QAapF;AAED,wBAAgB,uBAAuB,CAAC,OAAO,EAAE,MAAM,EAAE,WAAW,EAAE,iBAAiB,QAWtF;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,QAItD;AAED,wBAAgB,sBAAsB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAY/D;AAED,wBAAgB,oBAAoB,SAKnC"}
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"}
@@ -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
- writeFileSync(fd, payload, 'utf-8');
64
- fsyncSync(fd);
65
- }
66
- finally {
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
- else {
79
- rmSync(tempPath, { force: true });
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
- const loaded = loadCredentialsConfigForWrite();
138
- const config = loaded.version === 3
139
- ? loaded
140
- : {
141
- version: 3,
142
- activeBaseUrl: loaded.activeBaseUrl,
143
- servers: loaded.servers,
144
- };
145
- config.servers[baseUrl] = credentials;
146
- config.activeBaseUrl = baseUrl;
147
- writeCredentials(config);
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
- const loaded = loadCredentialsConfigForWrite();
151
- const config = loaded.version === 3
152
- ? loaded
153
- : {
154
- version: 3,
155
- activeBaseUrl: loaded.activeBaseUrl,
156
- servers: loaded.servers,
157
- };
158
- config.servers[baseUrl] = credentials;
159
- writeCredentials(config);
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
- const config = loadCredentialsConfigForWrite();
248
- config.activeBaseUrl = baseUrl;
249
- writeCredentials(config);
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
- const config = loadCredentialsConfig();
253
- if (!config || !Object.hasOwn(config.servers, baseUrl)) {
254
- return false;
255
- }
256
- delete config.servers[baseUrl];
257
- if (config.activeBaseUrl === baseUrl) {
258
- config.activeBaseUrl = null;
259
- }
260
- writeCredentials(config);
261
- return true;
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
- const path = getCredentialsPath();
265
- if (existsSync(path)) {
266
- rmSync(path);
267
- }
483
+ return withCredentialsTransactionLock(() => {
484
+ const path = getCredentialsPath();
485
+ if (existsSync(path))
486
+ rmSync(path);
487
+ });
268
488
  }
@@ -1 +1 @@
1
- {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAAA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,CAAA;IACpB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAoCD,eAAO,MAAM,YAAY,EAAE,aAAa,EAqoCvC,CAAA;AAUD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,GAAG,SAAS,CAMxE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAWrE;AAOD,wBAAgB,cAAc,IAAI,MAAM,CA0CvC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAoBtD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA4B5D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAgBxD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;EAoB9C"}
1
+ {"version":3,"file":"help.d.ts","sourceRoot":"","sources":["../src/help.ts"],"names":[],"mappings":"AAEA,MAAM,WAAW,YAAY;IAC3B,IAAI,EAAE,MAAM,CAAA;IACZ,IAAI,EAAE,MAAM,CAAA;IACZ,QAAQ,CAAC,EAAE,OAAO,CAAA;IAClB,UAAU,CAAC,EAAE,OAAO,CAAA;IACpB,OAAO,CAAC,EAAE,MAAM,EAAE,CAAA;CACnB;AAED,MAAM,WAAW,aAAa;IAC5B,IAAI,EAAE,MAAM,EAAE,CAAA;IACd,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,EAAE,MAAM,CAAA;IACf,KAAK,EAAE,MAAM,CAAA;IACb,OAAO,CAAC,EAAE,MAAM,EAAE,EAAE,CAAA;IACpB,OAAO,CAAC,EAAE,YAAY,EAAE,CAAA;IACxB,QAAQ,CAAC,EAAE,MAAM,EAAE,CAAA;CACpB;AAuCD,eAAO,MAAM,YAAY,EAAE,aAAa,EAqoCvC,CAAA;AAUD,wBAAgB,cAAc,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,GAAG,SAAS,CAMxE;AAED,wBAAgB,qBAAqB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,aAAa,EAAE,CAWrE;AAOD,wBAAgB,cAAc,IAAI,MAAM,CA0CvC;AAED,wBAAgB,eAAe,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAoBtD;AAED,wBAAgB,iBAAiB,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,CA4B5D;AAED,wBAAgB,iBAAiB,CAAC,IAAI,EAAE,MAAM,EAAE,GAAG,MAAM,CAgBxD;AAED,wBAAgB,eAAe,CAAC,OAAO,EAAE,MAAM;;;;;;;;;;;;;;;;;;;;;EAoB9C"}
package/dist/help.js CHANGED
@@ -1,3 +1,6 @@
1
+ import { AGENT_LAUNCH_SPECS } from './setup-onboarding.js';
2
+ const SETUP_LAUNCH_AGENT_NAMES = Object.keys(AGENT_LAUNCH_SPECS);
3
+ const SETUP_LAUNCH_AGENT_USAGE = SETUP_LAUNCH_AGENT_NAMES.join('|');
1
4
  const GLOBAL_OPTIONS = [
2
5
  ['--json', 'Emit machine-readable JSON instead of human text. Available on every command, as a prefix (orizu --json <command>) or trailing flag.'],
3
6
  ['--local', 'Use http://localhost:3000.'],
@@ -219,7 +222,7 @@ export const COMMAND_DOCS = [
219
222
  },
220
223
  {
221
224
  path: ['setup'],
222
- usage: 'orizu setup [--team <slug>|--create-team <name>] [--project <slug>|--create-project <name>] [--agent <agent>]... [--workspace [path]|--no-workspace] [--validate] [--fix] [--no-symlinks] [--verbose] [--no-install] [--handoff|--no-handoff] [--launch <claude|codex|pi>] [--skip-login] [--dry-run] [--no-input|--non-interactive] [--json]',
225
+ usage: `orizu setup [--team <slug>|--create-team <name>] [--project <slug>|--create-project <name>] [--agent <agent>]... [--workspace [path]|--no-workspace] [--validate] [--fix] [--no-symlinks] [--verbose] [--no-install] [--handoff|--no-handoff] [--launch <${SETUP_LAUNCH_AGENT_USAGE}>] [--skip-login] [--dry-run] [--no-input|--non-interactive] [--json]`,
223
226
  summary: 'Guided onboarding: sign in, initialize the workbench contract, and install agent skills.',
224
227
  group: 'Agent setup',
225
228
  options: [
@@ -239,7 +242,7 @@ export const COMMAND_DOCS = [
239
242
  { name: '--no-install', help: 'Skip the coding-agent skill install step.' },
240
243
  { name: '--handoff', help: 'Print the llms.txt and selected-project handoff prompt after setup.' },
241
244
  { name: '--no-handoff', help: 'Compatibility flag; suppresses the final launch picker and explicit handoff/launch.' },
242
- { name: '--launch <agent>', help: 'Launch a detected coding agent with the selected project and llms.txt prompt (interactive terminals only).', choices: ['claude', 'codex', 'pi'] },
245
+ { name: '--launch <agent>', help: 'Launch a detected coding agent with the selected project and llms.txt prompt (interactive terminals only). The cursor selector uses the Cursor Agent `agent` executable, not the editor `cursor` command.', choices: SETUP_LAUNCH_AGENT_NAMES },
243
246
  { name: '--skip-login', help: 'Skip the authentication phase.' },
244
247
  { name: '--dry-run', help: 'Preview every planned change without writing.' },
245
248
  { name: '--no-input', help: 'Never prompt; behave as in a non-interactive terminal.' },
package/dist/http.d.ts CHANGED
@@ -5,5 +5,25 @@ export declare function resolveLoginBaseUrl(flags?: GlobalFlags): string;
5
5
  export declare function getBaseUrl(): string;
6
6
  export declare function assertSecureTokenTransport(baseUrl: string): void;
7
7
  export declare function credentialRequestRedirectPolicy(): Pick<RequestInit, 'redirect'>;
8
+ export type AuthenticatedRequestSource = 'ORIZU_TOKEN' | 'ORIZU_TOKEN_FILE' | 'stored';
9
+ export interface AuthenticatedRequestContext {
10
+ readonly baseUrl: string;
11
+ readonly source: AuthenticatedRequestSource;
12
+ fetch(path: string, init?: RequestInit): Promise<Response>;
13
+ logout(signal?: AbortSignal | null): Promise<LogoutResult>;
14
+ }
15
+ export interface LogoutResult {
16
+ remoteError: string | null;
17
+ localCredentialCleared: boolean;
18
+ }
19
+ /**
20
+ * Captures one authenticated server context for a multi-step operation. The
21
+ * returned interface intentionally exposes no credential material: its closure
22
+ * pins the selected origin and bearer/session so later global config changes
23
+ * cannot move an in-flight operation to another account.
24
+ */
25
+ export declare function captureAuthenticatedRequestContext(options?: {
26
+ allowRefresh?: boolean;
27
+ }): AuthenticatedRequestContext;
8
28
  export declare function authedFetch(path: string, init?: RequestInit): Promise<Response>;
9
29
  //# sourceMappingURL=http.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AAMA,OAAO,EAAkB,WAAW,EAAoB,MAAM,mBAAmB,CAAA;AAKjF,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,QAEhD;AAED,wBAAgB,cAAc,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAiBxE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAY7E;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;AAkCD,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,qBAqDrE"}
1
+ {"version":3,"file":"http.d.ts","sourceRoot":"","sources":["../src/http.ts"],"names":[],"mappings":"AASA,OAAO,EAAkB,WAAW,EAAoB,MAAM,mBAAmB,CAAA;AASjF,wBAAgB,cAAc,CAAC,KAAK,EAAE,WAAW,QAEhD;AAED,wBAAgB,cAAc,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAiBxE;AAED,wBAAgB,mBAAmB,CAAC,KAAK,GAAE,WAA0B,GAAG,MAAM,CAY7E;AAED,wBAAgB,UAAU,IAAI,MAAM,CAEnC;AAWD,wBAAgB,0BAA0B,CAAC,OAAO,EAAE,MAAM,QAmBzD;AAqFD,wBAAgB,+BAA+B,IAAI,IAAI,CAAC,WAAW,EAAE,UAAU,CAAC,CAE/E;AAkGD,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,KAAK,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,CAAC,EAAE,WAAW,GAAG,OAAO,CAAC,QAAQ,CAAC,CAAA;IAC1D,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;AAED;;;;;GAKG;AACH,wBAAgB,kCAAkC,CAChD,OAAO,GAAE;IAAE,YAAY,CAAC,EAAE,OAAO,CAAA;CAAO,GACvC,2BAA2B,CAgI7B;AAED,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,IAAI,GAAE,WAAgB,qBAiErE"}