@skrr-ai/cli 0.1.85 → 0.1.87
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/bin/run.js +11 -0
- package/dist/base-command.js +1 -0
- package/dist/commands/agents/chat.d.ts +58 -1
- package/dist/commands/agents/chat.js +180 -4
- package/dist/commands/agents/endpoint/enable.js +6 -0
- package/dist/commands/store/acquisitions/approve.js +3 -0
- package/dist/lib/agent-endpoints.d.ts +19 -5
- package/dist/lib/agent-endpoints.js +11 -0
- package/dist/lib/agent-home-workspace.d.ts +19 -0
- package/dist/lib/agent-home-workspace.js +27 -0
- package/dist/lib/agentic-stream.d.ts +135 -0
- package/dist/lib/agentic-stream.js +341 -6
- package/dist/lib/auth-storage.d.ts +4 -0
- package/dist/lib/auth-storage.js +33 -0
- package/dist/lib/keychain.d.ts +1 -0
- package/dist/lib/keychain.js +67 -14
- package/dist/lib/refresh.d.ts +7 -0
- package/dist/lib/refresh.js +2 -1
- package/dist/lib/store-acquisition.js +11 -3
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +5139 -5029
- package/oclif.manifest.json +42317 -42309
- package/package.json +5 -2
package/dist/lib/auth-storage.js
CHANGED
|
@@ -41,6 +41,7 @@ exports.readFromBackend = readFromBackend;
|
|
|
41
41
|
exports.describeForeignCredential = describeForeignCredential;
|
|
42
42
|
exports.writeToBackend = writeToBackend;
|
|
43
43
|
exports.clearAllBackends = clearAllBackends;
|
|
44
|
+
exports.takeResolvedKeychainRead = takeResolvedKeychainRead;
|
|
44
45
|
exports.normalizeServerOrigin = normalizeServerOrigin;
|
|
45
46
|
exports.__authFilePathForTest = __authFilePathForTest;
|
|
46
47
|
/**
|
|
@@ -504,6 +505,11 @@ function unlinkQuietly(p) {
|
|
|
504
505
|
function readFromKeychain() {
|
|
505
506
|
if (!keychain.isAvailable())
|
|
506
507
|
return null;
|
|
508
|
+
// One pass: the owner record is read once for the whole bundle, not once per
|
|
509
|
+
// secret plus once for itself (see `withKeychainReadPass`).
|
|
510
|
+
return keychain.withKeychainReadPass(readFromKeychainInPass);
|
|
511
|
+
}
|
|
512
|
+
function readFromKeychainInPass() {
|
|
507
513
|
const tokenEntry = keychain.readToken();
|
|
508
514
|
if (!tokenEntry) {
|
|
509
515
|
// Read came back empty despite "available" — don't cache a false
|
|
@@ -769,6 +775,7 @@ function describeForeignCredential(result) {
|
|
|
769
775
|
function writeToBackend(bundle, options = {}) {
|
|
770
776
|
if ((0, harness_context_1.isHarnessCliContext)())
|
|
771
777
|
throw new harness_context_1.HarnessCredentialPersistRefused();
|
|
778
|
+
lastKeychainTokenRead = null;
|
|
772
779
|
const requestedOrigin = options.serverOrigin ?? bundle.serverOrigin;
|
|
773
780
|
let persistedBundle = bundle;
|
|
774
781
|
if (requestedOrigin !== undefined) {
|
|
@@ -821,9 +828,34 @@ function writeToBackend(bundle, options = {}) {
|
|
|
821
828
|
* auth-failure cleanup). Idempotent.
|
|
822
829
|
*/
|
|
823
830
|
function clearAllBackends() {
|
|
831
|
+
lastKeychainTokenRead = null;
|
|
824
832
|
clearKeychainBackend();
|
|
825
833
|
deleteFileBackend();
|
|
826
834
|
}
|
|
835
|
+
/**
|
|
836
|
+
* The Keychain read the credential resolver just made, for the ONE caller that
|
|
837
|
+
* reads the same bundle again a moment later: `BaseCommand.init()` resolves the
|
|
838
|
+
* credential and then asks `ensureFreshCliCredential` whether it needs a
|
|
839
|
+
* refresh, which re-read every slot (another six `security` spawns, ~100ms) to
|
|
840
|
+
* learn the expiry the first read already had.
|
|
841
|
+
*
|
|
842
|
+
* Handed out at most once, only for the credential that read produced, only
|
|
843
|
+
* for the same server origin, and dropped by any write or clear in this
|
|
844
|
+
* process. It never stands in for the re-read under the auth lock: a refresh
|
|
845
|
+
* still reads the store fresh before it spends a refresh token.
|
|
846
|
+
*/
|
|
847
|
+
let lastKeychainTokenRead = null;
|
|
848
|
+
function takeResolvedKeychainRead(credential, expectedServerOrigin) {
|
|
849
|
+
const read = lastKeychainTokenRead;
|
|
850
|
+
lastKeychainTokenRead = null;
|
|
851
|
+
if (!read?.bundle || credential.source !== 'keychain')
|
|
852
|
+
return null;
|
|
853
|
+
if (!credential.token || read.bundle.token !== credential.token)
|
|
854
|
+
return null;
|
|
855
|
+
if (read.expectedServerOrigin !== normalizeServerOrigin(expectedServerOrigin))
|
|
856
|
+
return null;
|
|
857
|
+
return read;
|
|
858
|
+
}
|
|
827
859
|
/**
|
|
828
860
|
* The low-level backends, exposed for the credential resolver's
|
|
829
861
|
* `readKeychainToken` / `readFileToken` slots. Internal callers should
|
|
@@ -847,6 +879,7 @@ exports.backends = {
|
|
|
847
879
|
readKeychainToken(expectedServerOrigin) {
|
|
848
880
|
const read = readKeychainAssessed();
|
|
849
881
|
const result = assessBundleOrigin(read.bundle, 'keychain', expectedServerOrigin);
|
|
882
|
+
lastKeychainTokenRead = result.bundle && result.originStatus === 'matched' ? result : null;
|
|
850
883
|
return result.bundle?.token ?? null;
|
|
851
884
|
},
|
|
852
885
|
readFileToken(expectedServerOrigin) {
|
package/dist/lib/keychain.d.ts
CHANGED
|
@@ -38,6 +38,7 @@ export declare function isAvailable(): boolean;
|
|
|
38
38
|
* masquerade as "keychain always available" across the process lifetime.
|
|
39
39
|
*/
|
|
40
40
|
export declare function resetAvailability(): void;
|
|
41
|
+
export declare function withKeychainReadPass<T>(fn: () => T): T;
|
|
41
42
|
export interface KeychainEntry {
|
|
42
43
|
token: string;
|
|
43
44
|
expiresAt?: number;
|
package/dist/lib/keychain.js
CHANGED
|
@@ -43,6 +43,7 @@ exports.isLegacySlotCurrent = isLegacySlotCurrent;
|
|
|
43
43
|
exports.__currentServiceForTest = __currentServiceForTest;
|
|
44
44
|
exports.isAvailable = isAvailable;
|
|
45
45
|
exports.resetAvailability = resetAvailability;
|
|
46
|
+
exports.withKeychainReadPass = withKeychainReadPass;
|
|
46
47
|
exports.readToken = readToken;
|
|
47
48
|
exports.writeToken = writeToken;
|
|
48
49
|
exports.readRefreshToken = readRefreshToken;
|
|
@@ -256,17 +257,56 @@ function isSecretAccount(account) {
|
|
|
256
257
|
return account === ACCOUNT_TOKEN || account === ACCOUNT_REFRESH;
|
|
257
258
|
}
|
|
258
259
|
/**
|
|
259
|
-
*
|
|
260
|
-
*
|
|
260
|
+
* One synchronous read of a bundle, memoised per (service, account).
|
|
261
|
+
*
|
|
262
|
+
* Every `security` call is a process spawn costing ~15-20ms, and a bundle read
|
|
263
|
+
* used to make eight of them for six items: each secret's envelope decrypt
|
|
264
|
+
* re-read the owner record (`foreignOwnerRootAt`), and the bundle read it a
|
|
265
|
+
* third time for itself. Measured on `skrr agents show`, that was 17 serial
|
|
266
|
+
* spawns and ~280ms before the first request could leave.
|
|
267
|
+
*
|
|
268
|
+
* A pass is SYNCHRONOUS by construction (`execFileSync` throughout, and the
|
|
269
|
+
* callback is not awaited), so nothing else in this process can interleave a
|
|
270
|
+
* write; a write made inside the pass drops the memo for that item. Another
|
|
271
|
+
* process's write is no more or less visible than before — the eight separate
|
|
272
|
+
* reads were never one atomic snapshot either. Outside a pass nothing is
|
|
273
|
+
* memoised: a later read always reaches the Keychain, which is what the
|
|
274
|
+
* refresh path's re-read under the auth lock depends on.
|
|
261
275
|
*/
|
|
262
|
-
|
|
263
|
-
|
|
276
|
+
let readPassMemo = null;
|
|
277
|
+
function withKeychainReadPass(fn) {
|
|
278
|
+
if (readPassMemo)
|
|
279
|
+
return fn();
|
|
280
|
+
readPassMemo = new Map();
|
|
281
|
+
try {
|
|
282
|
+
return fn();
|
|
283
|
+
}
|
|
284
|
+
finally {
|
|
285
|
+
readPassMemo = null;
|
|
286
|
+
}
|
|
287
|
+
}
|
|
288
|
+
function memoKey(service, account) {
|
|
289
|
+
return `${service}\u0000${account}`;
|
|
290
|
+
}
|
|
291
|
+
function forgetSlotInPass(account, service) {
|
|
292
|
+
readPassMemo?.delete(memoKey(service, account));
|
|
293
|
+
}
|
|
294
|
+
/** The stored string exactly as `security` returns it, or null. */
|
|
295
|
+
function readRawSlot(account, service) {
|
|
296
|
+
const key = memoKey(service, account);
|
|
297
|
+
if (readPassMemo?.has(key))
|
|
298
|
+
return readPassMemo.get(key) ?? null;
|
|
299
|
+
const value = execReadSlot(account, service);
|
|
300
|
+
readPassMemo?.set(key, value);
|
|
301
|
+
return value;
|
|
302
|
+
}
|
|
303
|
+
function execReadSlot(account, service) {
|
|
264
304
|
try {
|
|
265
305
|
const out = (0, node_child_process_1.execFileSync)('security', ['find-generic-password', '-s', service, '-a', account, '-w'], { stdio: ['ignore', 'pipe', 'ignore'], encoding: 'utf-8' });
|
|
266
306
|
const trimmed = out.replace(/\n$/, '');
|
|
267
307
|
if (trimmed.length === 0)
|
|
268
308
|
return null;
|
|
269
|
-
|
|
309
|
+
return trimmed;
|
|
270
310
|
}
|
|
271
311
|
catch (err) {
|
|
272
312
|
const status = err.status;
|
|
@@ -280,6 +320,15 @@ function readSlot(account, service = currentService(), raw_ = false) {
|
|
|
280
320
|
}
|
|
281
321
|
return null;
|
|
282
322
|
}
|
|
323
|
+
}
|
|
324
|
+
/**
|
|
325
|
+
* `raw` returns the stored string exactly, with no envelope transform — for
|
|
326
|
+
* copying another config's item, which may be sealed under a key we lack.
|
|
327
|
+
*/
|
|
328
|
+
function readSlot(account, service = currentService(), raw_ = false) {
|
|
329
|
+
const raw = readRawSlot(account, service);
|
|
330
|
+
if (raw === null)
|
|
331
|
+
return null;
|
|
283
332
|
if (raw_ || !isSecretAccount(account))
|
|
284
333
|
return raw;
|
|
285
334
|
// L12 — route secret slots through the envelope.
|
|
@@ -339,6 +388,7 @@ function quoteForSecurityInteractive(v) {
|
|
|
339
388
|
return '"' + v.replace(/\\/g, '\\\\').replace(/"/g, '\\"') + '"';
|
|
340
389
|
}
|
|
341
390
|
function writeSlot(account, value, service = currentService(), raw = false) {
|
|
391
|
+
forgetSlotInPass(account, service);
|
|
342
392
|
try {
|
|
343
393
|
// L12 — wrap secret-bearing slots through the envelope when active.
|
|
344
394
|
// Empty / non-secret slots are passthrough, and so is a raw copy.
|
|
@@ -364,6 +414,7 @@ function writeSlot(account, value, service = currentService(), raw = false) {
|
|
|
364
414
|
}
|
|
365
415
|
}
|
|
366
416
|
function deleteSlot(account, service = currentService()) {
|
|
417
|
+
forgetSlotInPass(account, service);
|
|
367
418
|
try {
|
|
368
419
|
(0, node_child_process_1.execFileSync)('security', ['delete-generic-password', '-s', service, '-a', account], {
|
|
369
420
|
stdio: ['ignore', 'ignore', 'ignore'],
|
|
@@ -484,15 +535,17 @@ function readBundleAt(slot) {
|
|
|
484
535
|
if (!isAvailable())
|
|
485
536
|
return null;
|
|
486
537
|
const service = serviceForSlot(slot);
|
|
487
|
-
|
|
488
|
-
|
|
489
|
-
|
|
490
|
-
|
|
491
|
-
|
|
492
|
-
|
|
493
|
-
|
|
494
|
-
|
|
495
|
-
|
|
538
|
+
return withKeychainReadPass(() => {
|
|
539
|
+
const token = readEntryAt(service, ACCOUNT_TOKEN, ACCOUNT_EXPIRES);
|
|
540
|
+
if (!token)
|
|
541
|
+
return null;
|
|
542
|
+
return {
|
|
543
|
+
token,
|
|
544
|
+
refresh: readEntryAt(service, ACCOUNT_REFRESH, ACCOUNT_REFRESH_EXPIRES),
|
|
545
|
+
serverOrigin: readSlot(ACCOUNT_SERVER_ORIGIN, service) || null,
|
|
546
|
+
owner: readSlot(ACCOUNT_OWNER, service) || null,
|
|
547
|
+
};
|
|
548
|
+
});
|
|
496
549
|
}
|
|
497
550
|
const BUNDLE_ACCOUNTS = [
|
|
498
551
|
ACCOUNT_TOKEN,
|
package/dist/lib/refresh.d.ts
CHANGED
|
@@ -38,6 +38,13 @@ export interface EnsureFreshCliCredentialOptions {
|
|
|
38
38
|
force?: boolean;
|
|
39
39
|
/** Respect `--bare`: do not mutate persisted credentials in bare mode. */
|
|
40
40
|
bareMode?: boolean;
|
|
41
|
+
/**
|
|
42
|
+
* `credential` was produced by the resolver a moment ago; decide from the
|
|
43
|
+
* Keychain read it made rather than reading every slot again
|
|
44
|
+
* (`takeResolvedKeychainRead`). Falls back to a fresh read whenever that read
|
|
45
|
+
* does not describe exactly this credential.
|
|
46
|
+
*/
|
|
47
|
+
reuseResolvedRead?: boolean;
|
|
41
48
|
}
|
|
42
49
|
export interface EnsureFreshCliCredentialResult {
|
|
43
50
|
credential: ResolvedCredential;
|
package/dist/lib/refresh.js
CHANGED
|
@@ -133,7 +133,8 @@ async function ensureFreshCliCredential(opts = {}) {
|
|
|
133
133
|
return { credential, refreshed: false, skippedReason: 'external_source' };
|
|
134
134
|
}
|
|
135
135
|
const cfg = (0, config_1.loadConfig)();
|
|
136
|
-
const read = (0, auth_storage_1.
|
|
136
|
+
const read = (opts.reuseResolvedRead ? (0, auth_storage_1.takeResolvedKeychainRead)(credential, cfg.baseURL) : null) ??
|
|
137
|
+
(0, auth_storage_1.readFromBackend)(cfg.baseURL);
|
|
137
138
|
// If a sibling process already rotated the stored access token, adopt it
|
|
138
139
|
// instead of needlessly consuming the latest refresh token again.
|
|
139
140
|
if (read.bundle?.token && read.bundle.token !== credential.token) {
|
|
@@ -3,6 +3,16 @@ Object.defineProperty(exports, "__esModule", { value: true });
|
|
|
3
3
|
exports.describeAcquisitionRequestLines = describeAcquisitionRequestLines;
|
|
4
4
|
exports.describeAcquisitionRequestRow = describeAcquisitionRequestRow;
|
|
5
5
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
6
|
+
function describeBuyerTerms(request) {
|
|
7
|
+
if (request.buyerTerms) {
|
|
8
|
+
return `${request.buyerTerms.termsVersion} — ${request.buyerTerms.artifactUri}`;
|
|
9
|
+
}
|
|
10
|
+
if (request.invalidReason === 'buyer_terms_missing') {
|
|
11
|
+
// Stored before OSK-13520 refused to open a request without terms.
|
|
12
|
+
return 'missing — this request was recorded without buyer terms, so it can only be declined';
|
|
13
|
+
}
|
|
14
|
+
return 'not configured, so this purchase cannot be approved';
|
|
15
|
+
}
|
|
6
16
|
/**
|
|
7
17
|
* A purchase an agent asked to make, as the lines a terminal shows (OSK-12240).
|
|
8
18
|
*
|
|
@@ -22,9 +32,7 @@ function describeAcquisitionRequestLines(request) {
|
|
|
22
32
|
` Status: ${status.label}${status.detail ? ` — ${status.detail}` : ''}`,
|
|
23
33
|
` Price: ${(0, data_provider_1.formatStoreAcquisitionPrice)(request.price)} ${request.currency}`,
|
|
24
34
|
` Refunds: ${(0, data_provider_1.describeStoreAcquisitionRefundTerms)(request.refundTerms)}`,
|
|
25
|
-
` Buyer terms: ${request
|
|
26
|
-
? `${request.buyerTerms.termsVersion} — ${request.buyerTerms.artifactUri}`
|
|
27
|
-
: 'not configured, so this purchase cannot be approved'}`,
|
|
35
|
+
` Buyer terms: ${describeBuyerTerms(request)}`,
|
|
28
36
|
` Offer: ${request.offerVersionId}`,
|
|
29
37
|
` Capabilities: ${(0, data_provider_1.describeStoreAcquisitionCapabilities)(request.capabilities, request.capabilityCount)}`,
|
|
30
38
|
];
|