@skrr-ai/cli 0.1.80 → 0.1.82
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/base-command.d.ts +20 -0
- package/dist/base-command.js +151 -7
- package/dist/commands/agents/visibility.d.ts +14 -0
- package/dist/commands/agents/visibility.js +52 -0
- package/dist/commands/create-token.d.ts +0 -1
- package/dist/commands/create-token.js +3 -1
- package/dist/commands/followups/checkin.js +1 -1
- package/dist/commands/list-tokens.d.ts +0 -1
- package/dist/commands/list-tokens.js +1 -1
- package/dist/commands/login.js +10 -0
- package/dist/commands/logout.js +4 -0
- package/dist/commands/machines/dedicated/ssh-proxy.js +1 -0
- package/dist/commands/revoke-token.d.ts +0 -1
- package/dist/commands/revoke-token.js +1 -1
- package/dist/commands/store/follow.d.ts +1 -0
- package/dist/commands/store/follow.js +7 -1
- package/dist/commands/store/follows/list.d.ts +11 -0
- package/dist/commands/store/follows/list.js +29 -0
- package/dist/commands/store/follows/remove.d.ts +16 -0
- package/dist/commands/store/follows/remove.js +37 -0
- package/dist/commands/store/installs.js +6 -1
- package/dist/commands/store/publishers/profile/draft.d.ts +17 -0
- package/dist/commands/store/publishers/profile/draft.js +54 -0
- package/dist/commands/store/publishers/profile/handle.d.ts +14 -0
- package/dist/commands/store/publishers/profile/handle.js +39 -0
- package/dist/commands/store/publishers/profile/hide.d.ts +12 -0
- package/dist/commands/store/publishers/profile/hide.js +39 -0
- package/dist/commands/store/publishers/profile/preview.d.ts +11 -0
- package/dist/commands/store/publishers/profile/preview.js +39 -0
- package/dist/commands/store/publishers/profile/publish.d.ts +12 -0
- package/dist/commands/store/publishers/profile/publish.js +41 -0
- package/dist/commands/store/publishers/profile/restore.d.ts +15 -0
- package/dist/commands/store/publishers/profile/restore.js +49 -0
- package/dist/commands/store/publishers/profile/revisions.d.ts +11 -0
- package/dist/commands/store/publishers/profile/revisions.js +30 -0
- package/dist/commands/store/publishers/profile/show.d.ts +11 -0
- package/dist/commands/store/publishers/profile/show.js +39 -0
- package/dist/commands/store/review.d.ts +26 -0
- package/dist/commands/store/review.js +93 -0
- package/dist/commands/store/reviews.d.ts +29 -0
- package/dist/commands/store/reviews.js +170 -0
- package/dist/commands/store/tip.d.ts +20 -0
- package/dist/commands/store/tip.js +57 -0
- package/dist/commands/tokens/create.d.ts +28 -0
- package/dist/commands/tokens/create.js +136 -0
- package/dist/commands/tokens/list.d.ts +11 -0
- package/dist/commands/tokens/list.js +53 -0
- package/dist/commands/tokens/revoke.d.ts +14 -0
- package/dist/commands/tokens/revoke.js +42 -0
- package/dist/lib/access-tokens.d.ts +59 -0
- package/dist/lib/access-tokens.js +50 -0
- package/dist/lib/auth-core-init.d.ts +6 -0
- package/dist/lib/auth-core-init.js +28 -6
- package/dist/lib/auth-storage.d.ts +5 -0
- package/dist/lib/auth-storage.js +8 -0
- package/dist/lib/config.d.ts +8 -0
- package/dist/lib/credential-resolver.d.ts +1 -1
- package/dist/lib/credential-resolver.js +15 -3
- package/dist/lib/daemonBroker.d.ts +48 -2
- package/dist/lib/daemonBroker.js +193 -15
- package/dist/lib/daemonBrokerRefusal.d.ts +14 -0
- package/dist/lib/daemonBrokerRefusal.js +18 -1
- package/dist/lib/dedicated-ssh.d.ts +9 -0
- package/dist/lib/dedicated-ssh.js +232 -63
- package/dist/lib/harness-context.d.ts +8 -0
- package/dist/lib/harness-context.js +55 -0
- package/dist/lib/login.js +4 -1
- package/dist/lib/node-adapter.js +2 -2
- package/dist/lib/publisher-profile-flags.d.ts +19 -0
- package/dist/lib/publisher-profile-flags.js +48 -0
- package/dist/lib/refresh.d.ts +1 -1
- package/dist/lib/refresh.js +10 -1
- package/dist/lib/ssh-ordered-bytes.d.ts +8 -0
- package/dist/lib/ssh-ordered-bytes.js +31 -0
- package/dist/lib/usd-micros.d.ts +9 -0
- package/dist/lib/usd-micros.js +21 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.d.ts +22 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.js +29 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +9 -5
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.d.ts +25 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/spawnEnv.js +35 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.d.ts +22 -3
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.js +27 -4
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +7 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +2 -2
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.d.ts +25 -0
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/spawnEnv.js +33 -0
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +4302 -4249
- package/oclif.manifest.json +41314 -39972
- package/package.json +28 -4
package/dist/base-command.d.ts
CHANGED
|
@@ -52,6 +52,10 @@ export declare abstract class BaseCommand extends Command {
|
|
|
52
52
|
* from here.
|
|
53
53
|
*/
|
|
54
54
|
protected resolvedCredential: ResolvedCredential;
|
|
55
|
+
/** Re-authorize a long-lived transport using this command's credential
|
|
56
|
+
* source. A daemon handoff is redeemed again; its refresh family is never
|
|
57
|
+
* borrowed or written into the CLI's own login. */
|
|
58
|
+
protected refreshTransportToken(): Promise<string>;
|
|
55
59
|
/**
|
|
56
60
|
* Where `resolvedCredential` really came from when its `source` slot cannot
|
|
57
61
|
* say — an in-memory daemon hand-off or Agent session token is recorded as
|
|
@@ -467,5 +471,21 @@ export declare function describeDaemonScopedCredential(bin: string, scope?: Inco
|
|
|
467
471
|
* question. Merging the two is the mistake its own comment warns about.
|
|
468
472
|
*/
|
|
469
473
|
export declare function isTypedServerRefusal(body: Record<string, unknown> | null): boolean;
|
|
474
|
+
/**
|
|
475
|
+
* Did the API itself answer this gateway-class status (502/503/504) with a
|
|
476
|
+
* typed code?
|
|
477
|
+
*
|
|
478
|
+
* Those statuses mean "outage" only when something IN FRONT of the API wrote
|
|
479
|
+
* them — the load balancer or CDN, whose bodies are HTML or empty, and which
|
|
480
|
+
* `parseErrorBody` therefore reads as null. A JSON body carrying `code` or
|
|
481
|
+
* `errorType` was written by a route or middleware that ran and chose the
|
|
482
|
+
* status: the API was up, and the request did not time out at the gateway.
|
|
483
|
+
*
|
|
484
|
+
* Checked AFTER `isTypedServerRefusal`, which handles the subset that also
|
|
485
|
+
* explains itself; this covers the coded rest — including production bodies
|
|
486
|
+
* whose message the 5xx redaction boundary replaced. A JSON body with no code
|
|
487
|
+
* stays in the outage branch: it says nothing a reader could act on.
|
|
488
|
+
*/
|
|
489
|
+
export declare function isApplicationCodedUnavailable(status: number | undefined, body: Record<string, unknown> | null): boolean;
|
|
470
490
|
export declare function isRetryableFailure(status: number | undefined, body: Record<string, unknown> | null): boolean;
|
|
471
491
|
export { Flags };
|
package/dist/base-command.js
CHANGED
|
@@ -15,6 +15,7 @@ exports.describeForbidden = describeForbidden;
|
|
|
15
15
|
exports.isDaemonScopedCredential = isDaemonScopedCredential;
|
|
16
16
|
exports.describeDaemonScopedCredential = describeDaemonScopedCredential;
|
|
17
17
|
exports.isTypedServerRefusal = isTypedServerRefusal;
|
|
18
|
+
exports.isApplicationCodedUnavailable = isApplicationCodedUnavailable;
|
|
18
19
|
exports.isRetryableFailure = isRetryableFailure;
|
|
19
20
|
const node_fs_1 = require("node:fs");
|
|
20
21
|
const core_1 = require("@oclif/core");
|
|
@@ -54,6 +55,7 @@ const edge_error_page_1 = require("./lib/edge-error-page");
|
|
|
54
55
|
const step_up_1 = require("./lib/step-up");
|
|
55
56
|
const delegated_cli_1 = require("./lib/delegated-cli");
|
|
56
57
|
const dedicated_guest_1 = require("./lib/dedicated-guest");
|
|
58
|
+
const harness_context_1 = require("./lib/harness-context");
|
|
57
59
|
const tasks_1 = require("./lib/tasks");
|
|
58
60
|
function assigneePayloadNeedsResolution(payload) {
|
|
59
61
|
const fields = [
|
|
@@ -155,6 +157,30 @@ class BaseCommand extends core_1.Command {
|
|
|
155
157
|
* from here.
|
|
156
158
|
*/
|
|
157
159
|
resolvedCredential;
|
|
160
|
+
/** Re-authorize a long-lived transport using this command's credential
|
|
161
|
+
* source. A daemon handoff is redeemed again; its refresh family is never
|
|
162
|
+
* borrowed or written into the CLI's own login. */
|
|
163
|
+
async refreshTransportToken() {
|
|
164
|
+
if (this.credentialProvenance?.kind === 'daemon-handoff') {
|
|
165
|
+
const result = await (0, daemonBroker_1.redeemBrokeredHandoffToken)(true);
|
|
166
|
+
if (!result?.accessToken)
|
|
167
|
+
throw new Error('The local daemon could not renew CLI authentication.');
|
|
168
|
+
this.resolvedCredential = {
|
|
169
|
+
token: result.accessToken,
|
|
170
|
+
source: 'env-token',
|
|
171
|
+
kind: (0, auth_core_1.classifyTokenKind)(result.accessToken),
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
else {
|
|
175
|
+
this.resolvedCredential = (await (0, refresh_1.ensureFreshCliCredential)({
|
|
176
|
+
credential: this.resolvedCredential,
|
|
177
|
+
bareMode: this.bareMode,
|
|
178
|
+
})).credential;
|
|
179
|
+
}
|
|
180
|
+
if (!this.resolvedCredential.token)
|
|
181
|
+
throw new Error('CLI authentication is unavailable.');
|
|
182
|
+
return this.resolvedCredential.token;
|
|
183
|
+
}
|
|
158
184
|
/**
|
|
159
185
|
* Where `resolvedCredential` really came from when its `source` slot cannot
|
|
160
186
|
* say — an in-memory daemon hand-off or Agent session token is recorded as
|
|
@@ -253,6 +279,21 @@ class BaseCommand extends core_1.Command {
|
|
|
253
279
|
(0, data_provider_1.setHttpAdapter)((0, node_adapter_1.createNodeAdapter)());
|
|
254
280
|
return;
|
|
255
281
|
}
|
|
282
|
+
// OSK-13254 — a process the daemon started for an agent or tool borrows
|
|
283
|
+
// from the daemon and never owns the person's stored session, so the
|
|
284
|
+
// commands that create, replace or end that session are not its to run.
|
|
285
|
+
const harnessContext = (0, harness_context_1.isHarnessCliContext)();
|
|
286
|
+
if (harnessContext && this.id && harness_context_1.HARNESS_REFUSED_AUTH_COMMANDS.has(this.id)) {
|
|
287
|
+
this.failWithCliError({
|
|
288
|
+
message: `\`${this.config.bin} ${this.id}\` is not available here: this process was started by the skrr ` +
|
|
289
|
+
'background service for an agent or tool, and skrr in it borrows short-lived tokens from ' +
|
|
290
|
+
'that service instead of holding a sign-in. Run it in your own terminal.',
|
|
291
|
+
code: 'HARNESS_CONTEXT_AUTH_COMMAND',
|
|
292
|
+
exit: 2,
|
|
293
|
+
retryable: false,
|
|
294
|
+
});
|
|
295
|
+
return;
|
|
296
|
+
}
|
|
256
297
|
// A replacement guest keeps its workspace but receives a fresh Linux
|
|
257
298
|
// machine-id. Retire the prior instance's machine-bound CLI envelopes
|
|
258
299
|
// before migration/prefetch reads them; the current daemon handoff below
|
|
@@ -261,7 +302,9 @@ class BaseCommand extends core_1.Command {
|
|
|
261
302
|
// One-shot migration: legacy token/refreshToken in cli-config.json
|
|
262
303
|
// → keychain (macOS) or cli-auth.json (elsewhere). Idempotent.
|
|
263
304
|
try {
|
|
264
|
-
|
|
305
|
+
// A borrower never writes the person's stored session (OSK-13254).
|
|
306
|
+
if (!harnessContext)
|
|
307
|
+
(0, migrate_1.migrateAuthOutOfConfig)();
|
|
265
308
|
}
|
|
266
309
|
catch {
|
|
267
310
|
// Never block a command on migration failure; the resolver still
|
|
@@ -317,9 +360,18 @@ class BaseCommand extends core_1.Command {
|
|
|
317
360
|
allowUnreadableStoredCredential: this.id === 'login' || this.id === 'logout' || this.id === 'pair',
|
|
318
361
|
});
|
|
319
362
|
let ignoredStoredCredential = null;
|
|
320
|
-
if (!this.resolvedCredential.token && !this.bareMode && !parsed.token) {
|
|
363
|
+
if (!this.resolvedCredential.token && !this.bareMode && !parsed.token && !harnessContext) {
|
|
321
364
|
ignoredStoredCredential = (0, auth_storage_1.readFromBackend)(this.cliConfig.baseURL);
|
|
322
365
|
}
|
|
366
|
+
// OSK-13254 — nothing at all is stored for this profile: a first sign-in,
|
|
367
|
+
// which may ADOPT a CLI session of its own through the daemon instead of
|
|
368
|
+
// borrowing per process. Anything stored — a refused session, another
|
|
369
|
+
// server's, another config root's — is left alone and the daemon lends.
|
|
370
|
+
const nothingStored = !harnessContext &&
|
|
371
|
+
!!ignoredStoredCredential &&
|
|
372
|
+
!ignoredStoredCredential.bundle &&
|
|
373
|
+
ignoredStoredCredential.ownerStatus !== 'foreign' &&
|
|
374
|
+
(ignoredStoredCredential.originStatus ?? 'none') === 'none';
|
|
323
375
|
// Daemon-as-broker auto-mint. When the resolver came up empty AND
|
|
324
376
|
// a local `oversky` daemon is already authenticated for this user,
|
|
325
377
|
// silently mint a sibling cli-scope refresh family via the daemon's
|
|
@@ -347,8 +399,14 @@ class BaseCommand extends core_1.Command {
|
|
|
347
399
|
cliConfig: this.cliConfig,
|
|
348
400
|
bareMode: this.bareMode,
|
|
349
401
|
commandId: this.id,
|
|
402
|
+
adopt: nothingStored,
|
|
350
403
|
}));
|
|
351
|
-
if (autoResult.
|
|
404
|
+
if (autoResult.adoptedBySibling) {
|
|
405
|
+
// Another `skrr` adopted this profile's session while this one waited
|
|
406
|
+
// for the adoption lock: read it like any stored session.
|
|
407
|
+
this.resolvedCredential = await (0, credential_resolver_1.resolveCredential)({ bareMode: this.bareMode });
|
|
408
|
+
}
|
|
409
|
+
else if (autoResult.triggered && autoResult.outcome) {
|
|
352
410
|
if (autoResult.outcome.ok) {
|
|
353
411
|
// Persist the cliId now so a sibling skrr process spawned
|
|
354
412
|
// immediately after sees the same identity.
|
|
@@ -396,6 +454,14 @@ class BaseCommand extends core_1.Command {
|
|
|
396
454
|
: {}),
|
|
397
455
|
}
|
|
398
456
|
: null;
|
|
457
|
+
if (!autoResult.outcome.brokered && nothingStored) {
|
|
458
|
+
// Said once, on stderr, the moment it happens: a session now exists
|
|
459
|
+
// that the person did not type a command to create (OSK-13254).
|
|
460
|
+
process.stderr.write(`[skrr] Signed this CLI in through the skrr background service on this computer. ` +
|
|
461
|
+
`The session is saved to ${(0, credential_resolver_1.describeCredentialSource)(this.resolvedCredential.source)} ` +
|
|
462
|
+
`and renews itself, so skrr keeps working while the service restarts. ` +
|
|
463
|
+
`It is listed under Active Sessions; \`${this.config.bin} logout\` ends it.\n`);
|
|
464
|
+
}
|
|
399
465
|
}
|
|
400
466
|
else if ((this.brokerRefusal =
|
|
401
467
|
(0, daemonBrokerRefusal_1.describeBrokerRefusal)(autoResult.outcome, this.config.bin) ??
|
|
@@ -1176,11 +1242,15 @@ class BaseCommand extends core_1.Command {
|
|
|
1176
1242
|
// `login` sends a CI job to do the one thing it cannot, and hides the
|
|
1177
1243
|
// real mistake (dogfood OSK-4693). Name the credential instead.
|
|
1178
1244
|
const presentedCiToken = Boolean(this.resolvedCredential?.token?.startsWith('osk_ci_'));
|
|
1245
|
+
// A personal access token (OSK-13208) has no session behind it to renew
|
|
1246
|
+
// and no daemon to ask: the server's own reason (expired, revoked) IS
|
|
1247
|
+
// the answer, and the repair is a new token, not `login`.
|
|
1248
|
+
const presentedPat = Boolean(this.resolvedCredential?.token?.startsWith('osk_pat_'));
|
|
1179
1249
|
// OSK-12116 — the 401 is usually the SECOND failure: the token expired
|
|
1180
1250
|
// (routine) and renewing it was refused (the fault). The server's code
|
|
1181
1251
|
// for the retry is `TOKEN_EXPIRED`, which names the routine half; the
|
|
1182
1252
|
// latched refresh refusal names the half the reader can act on.
|
|
1183
|
-
const refreshRefusal = presentedCiToken
|
|
1253
|
+
const refreshRefusal = presentedCiToken || presentedPat
|
|
1184
1254
|
? null
|
|
1185
1255
|
: (0, auth_failure_1.describeRefreshRefusal)(readRefreshRefusal(), {
|
|
1186
1256
|
bin: this.config.bin,
|
|
@@ -1190,9 +1260,9 @@ class BaseCommand extends core_1.Command {
|
|
|
1190
1260
|
// refused refresh, and the daemon refused too. Its reason decides the
|
|
1191
1261
|
// repair (OSK-12149, OSK-12151), and on a Dedicated guest there is no
|
|
1192
1262
|
// login to run, so its remedy replaces the generic "Run login".
|
|
1193
|
-
const brokerRefusal = presentedCiToken || !e.brokerFailure
|
|
1263
|
+
const brokerRefusal = presentedCiToken || presentedPat || !e.brokerFailure
|
|
1194
1264
|
? null
|
|
1195
|
-
: (0, daemonBrokerRefusal_1.
|
|
1265
|
+
: (0, daemonBrokerRefusal_1.describeBrokerFailure)(e.brokerFailure, this.config.bin);
|
|
1196
1266
|
const renewal = refreshRefusal
|
|
1197
1267
|
? refreshRefusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
|
|
1198
1268
|
: "This CLI's own sign-in could not be renewed.";
|
|
@@ -1209,6 +1279,12 @@ class BaseCommand extends core_1.Command {
|
|
|
1209
1279
|
`daemon (\`OVERSKY_TOKEN=<token> skrrd start\`). ${this.config.bin} commands do ` +
|
|
1210
1280
|
`not accept it — sign in with \`${this.config.bin} login\` instead.`;
|
|
1211
1281
|
}
|
|
1282
|
+
else if (presentedPat) {
|
|
1283
|
+
authMessage =
|
|
1284
|
+
`Authentication failed: ${bodyMessage || 'the personal access token was refused.'} ` +
|
|
1285
|
+
`Create a new token with \`${this.config.bin} tokens create\` from a signed-in terminal ` +
|
|
1286
|
+
`and update the secret that sets SKRR_TOKEN.`;
|
|
1287
|
+
}
|
|
1212
1288
|
else if (brokerRefusal) {
|
|
1213
1289
|
authMessage = `Authentication failed. ${renewal} ${brokerRefusal.headline}\n→ ${brokerRefusal.remedy}`;
|
|
1214
1290
|
}
|
|
@@ -1332,6 +1408,45 @@ class BaseCommand extends core_1.Command {
|
|
|
1332
1408
|
requestId: stringProp(body, 'requestId') || e.requestId,
|
|
1333
1409
|
});
|
|
1334
1410
|
}
|
|
1411
|
+
else if (isApplicationCodedUnavailable(e.status, body)) {
|
|
1412
|
+
// A 502/503/504 the API ITSELF wrote, with a typed code — not a page
|
|
1413
|
+
// from the load balancer or CDN. The outage branch below would say the
|
|
1414
|
+
// platform is down, absolve this command, and (for a write) call the
|
|
1415
|
+
// outcome unknown because "the gateway may have timed out after the
|
|
1416
|
+
// server committed it". None of that holds when the API answered.
|
|
1417
|
+
//
|
|
1418
|
+
// Observed 2026-10-01: `skrr agents chat --daemon <id>` printed exactly
|
|
1419
|
+
// that outage paragraph for `POST /api/agentic/chat` → 503
|
|
1420
|
+
// `preferred_daemon_unavailable` — "The selected local daemon is not
|
|
1421
|
+
// connected" — a synchronous refusal before anything was enqueued,
|
|
1422
|
+
// while skrr.ai served every other request. The reader went looking
|
|
1423
|
+
// for a platform outage.
|
|
1424
|
+
//
|
|
1425
|
+
// No incident is recorded: one route refusing in its own words is not
|
|
1426
|
+
// evidence the control plane is failing. `isOutageFailure` is left
|
|
1427
|
+
// alone because it also decides whether the outbox queues a write.
|
|
1428
|
+
const retryable = isRetryableFailure(e.status, body);
|
|
1429
|
+
const authored = bodyMessage && !isRedactedServerMessage(bodyMessage) ? bodyMessage : '';
|
|
1430
|
+
const requestId = stringProp(body, 'requestId') || e.requestId;
|
|
1431
|
+
this.failWithCliError({
|
|
1432
|
+
message: say(`${this.cliConfig.baseURL} refused this request (HTTP ${e.status}, ${bodyCode})` +
|
|
1433
|
+
`${authored ? `: ${authored}` : '.'} ` +
|
|
1434
|
+
'The API answered this itself, so the request did not time out at the gateway. ' +
|
|
1435
|
+
(authored ? '' : 'It did not say why in this response. ') +
|
|
1436
|
+
(retryable
|
|
1437
|
+
? 'Retrying may succeed once that condition clears.'
|
|
1438
|
+
: 'Retrying the same request will not help until that condition changes.') +
|
|
1439
|
+
(requestId && !authored
|
|
1440
|
+
? ` Request id ${requestId} — quote it when reporting this.`
|
|
1441
|
+
: '')),
|
|
1442
|
+
code: bodyCode,
|
|
1443
|
+
status: e.status,
|
|
1444
|
+
exit: retryable ? 5 : 1,
|
|
1445
|
+
retryable,
|
|
1446
|
+
details: body ?? undefined,
|
|
1447
|
+
...(requestId ? { requestId } : {}),
|
|
1448
|
+
});
|
|
1449
|
+
}
|
|
1335
1450
|
else if ((0, outbox_1.isOutageFailure)(e)) {
|
|
1336
1451
|
// A gateway/unavailable status is an OUTAGE, and saying so is the whole
|
|
1337
1452
|
// point. During the OSK-2806 incident these surfaced as bare
|
|
@@ -1512,7 +1627,7 @@ class BaseCommand extends core_1.Command {
|
|
|
1512
1627
|
cliId: this.cliConfig ? (0, cli_id_1.resolveCliId)(this.cliConfig) : undefined,
|
|
1513
1628
|
});
|
|
1514
1629
|
const brokerRefusal = e.brokerFailure
|
|
1515
|
-
? (0, daemonBrokerRefusal_1.
|
|
1630
|
+
? (0, daemonBrokerRefusal_1.describeBrokerFailure)(e.brokerFailure, this.config.bin)
|
|
1516
1631
|
: null;
|
|
1517
1632
|
const why = (refusal
|
|
1518
1633
|
? refusal.message.replace(/\s*Run `[^`]+` to sign in again\.$/, '')
|
|
@@ -2030,6 +2145,35 @@ function isTypedServerRefusal(body) {
|
|
|
2030
2145
|
return false;
|
|
2031
2146
|
return (typeof body.cause === 'string' || typeof body.remedy === 'string' || body.retryable === false);
|
|
2032
2147
|
}
|
|
2148
|
+
/**
|
|
2149
|
+
* Did the API itself answer this gateway-class status (502/503/504) with a
|
|
2150
|
+
* typed code?
|
|
2151
|
+
*
|
|
2152
|
+
* Those statuses mean "outage" only when something IN FRONT of the API wrote
|
|
2153
|
+
* them — the load balancer or CDN, whose bodies are HTML or empty, and which
|
|
2154
|
+
* `parseErrorBody` therefore reads as null. A JSON body carrying `code` or
|
|
2155
|
+
* `errorType` was written by a route or middleware that ran and chose the
|
|
2156
|
+
* status: the API was up, and the request did not time out at the gateway.
|
|
2157
|
+
*
|
|
2158
|
+
* Checked AFTER `isTypedServerRefusal`, which handles the subset that also
|
|
2159
|
+
* explains itself; this covers the coded rest — including production bodies
|
|
2160
|
+
* whose message the 5xx redaction boundary replaced. A JSON body with no code
|
|
2161
|
+
* stays in the outage branch: it says nothing a reader could act on.
|
|
2162
|
+
*/
|
|
2163
|
+
function isApplicationCodedUnavailable(status, body) {
|
|
2164
|
+
if (status !== 502 && status !== 503 && status !== 504)
|
|
2165
|
+
return false;
|
|
2166
|
+
return Boolean(stringProp(body, 'code') || stringProp(body, 'errorType'));
|
|
2167
|
+
}
|
|
2168
|
+
/**
|
|
2169
|
+
* The sentence `redactServerErrorResponses` substitutes for a 5xx message in
|
|
2170
|
+
* production. It is the boundary's, not the route's, so it is never relayed as
|
|
2171
|
+
* if it explained anything.
|
|
2172
|
+
*/
|
|
2173
|
+
const REDACTED_SERVER_MESSAGE = 'An unexpected error occurred.';
|
|
2174
|
+
function isRedactedServerMessage(message) {
|
|
2175
|
+
return message.trim() === REDACTED_SERVER_MESSAGE;
|
|
2176
|
+
}
|
|
2033
2177
|
function isRetryableFailure(status, body) {
|
|
2034
2178
|
const declared = body?.retryable;
|
|
2035
2179
|
if (typeof declared === 'boolean')
|
|
@@ -1,3 +1,4 @@
|
|
|
1
|
+
import type { StoreReleaseLicense } from '@skrr-ai/data-provider';
|
|
1
2
|
import { BaseCommand } from '../../base-command';
|
|
2
3
|
/**
|
|
3
4
|
* `--capability-rationale <permission-key>=<note>` → the publisher notes a
|
|
@@ -18,6 +19,16 @@ export declare function parseReleaseDependencies(entries: readonly string[] | un
|
|
|
18
19
|
}>;
|
|
19
20
|
error?: string;
|
|
20
21
|
};
|
|
22
|
+
/**
|
|
23
|
+
* `--license` / `--remix` → the `license` declaration a public release carries
|
|
24
|
+
* (SUP11). Values are already constrained by the flags' `options`; this only
|
|
25
|
+
* enforces the pairing the flags cannot express: a remix grant is part of a
|
|
26
|
+
* license declaration, so it is meaningless without one.
|
|
27
|
+
*/
|
|
28
|
+
export declare function buildReleaseLicense(licenseId: string | undefined, remix: string | undefined): {
|
|
29
|
+
license?: StoreReleaseLicense;
|
|
30
|
+
error?: string;
|
|
31
|
+
};
|
|
21
32
|
export default class AgentsVisibility extends BaseCommand {
|
|
22
33
|
static description: string;
|
|
23
34
|
static examples: string[];
|
|
@@ -36,6 +47,9 @@ export default class AgentsVisibility extends BaseCommand {
|
|
|
36
47
|
'capability-rationale': import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
37
48
|
language: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
38
49
|
dependency: import("@oclif/core/lib/interfaces").OptionFlag<string[] | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
50
|
+
license: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
51
|
+
remix: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
52
|
+
'source-visibility': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
39
53
|
};
|
|
40
54
|
run(): Promise<void>;
|
|
41
55
|
}
|
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
3
|
exports.parseCapabilityRationales = parseCapabilityRationales;
|
|
4
4
|
exports.parseReleaseDependencies = parseReleaseDependencies;
|
|
5
|
+
exports.buildReleaseLicense = buildReleaseLicense;
|
|
5
6
|
const core_1 = require("@oclif/core");
|
|
6
7
|
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
7
8
|
const base_command_1 = require("../../base-command");
|
|
@@ -48,6 +49,25 @@ function parseReleaseDependencies(entries) {
|
|
|
48
49
|
}
|
|
49
50
|
return { dependencies };
|
|
50
51
|
}
|
|
52
|
+
/**
|
|
53
|
+
* `--license` / `--remix` → the `license` declaration a public release carries
|
|
54
|
+
* (SUP11). Values are already constrained by the flags' `options`; this only
|
|
55
|
+
* enforces the pairing the flags cannot express: a remix grant is part of a
|
|
56
|
+
* license declaration, so it is meaningless without one.
|
|
57
|
+
*/
|
|
58
|
+
function buildReleaseLicense(licenseId, remix) {
|
|
59
|
+
if (!licenseId) {
|
|
60
|
+
return remix
|
|
61
|
+
? { error: '--remix is part of a license declaration; pass it with --license <id>.' }
|
|
62
|
+
: {};
|
|
63
|
+
}
|
|
64
|
+
return {
|
|
65
|
+
license: {
|
|
66
|
+
id: licenseId,
|
|
67
|
+
...(remix ? { remix: remix } : {}),
|
|
68
|
+
},
|
|
69
|
+
};
|
|
70
|
+
}
|
|
51
71
|
class AgentsVisibility extends base_command_1.BaseCommand {
|
|
52
72
|
static description = 'Set the visibility of an agent';
|
|
53
73
|
static examples = [
|
|
@@ -57,6 +77,8 @@ class AgentsVisibility extends base_command_1.BaseCommand {
|
|
|
57
77
|
'<%= config.bin %> agents visibility <agent-id> --public --capability-rationale "network:*=Looks up flight prices on the web"',
|
|
58
78
|
'<%= config.bin %> agents visibility <agent-id> --public --language en --language zh-Hans',
|
|
59
79
|
'<%= config.bin %> agents visibility <agent-id> --public --dependency skill:<skill-id>@<store-release-id>',
|
|
80
|
+
'<%= config.bin %> agents visibility <agent-id> --public --license mit --remix attributed --source-visibility public',
|
|
81
|
+
'<%= config.bin %> agents visibility <agent-id> --public --license proprietary --source-visibility sealed',
|
|
60
82
|
];
|
|
61
83
|
static args = {
|
|
62
84
|
id: core_1.Args.string({ description: 'Agent ID', required: true, ignoreStdin: true }),
|
|
@@ -96,6 +118,18 @@ class AgentsVisibility extends base_command_1.BaseCommand {
|
|
|
96
118
|
description: 'Pin an attached Skill or Action to its immutable Store release: skill:<id>@<store-release-id> (repeatable)',
|
|
97
119
|
multiple: true,
|
|
98
120
|
}),
|
|
121
|
+
license: core_1.Flags.string({
|
|
122
|
+
description: 'The license the public release is offered under. Recorded on the release and shown on the listing; proprietary grants no reuse',
|
|
123
|
+
options: [...data_provider_1.STORE_RELEASE_LICENSE_IDS],
|
|
124
|
+
}),
|
|
125
|
+
remix: core_1.Flags.string({
|
|
126
|
+
description: "Whether a buyer's installed copy may be published as a derived listing: attributed (the fork credits this release) or none. Needs --license; defaults to none",
|
|
127
|
+
options: [...data_provider_1.STORE_RELEASE_REMIX_GRANTS],
|
|
128
|
+
}),
|
|
129
|
+
'source-visibility': core_1.Flags.string({
|
|
130
|
+
description: "Who may read the release's source, independent of price: licensed (license holders), public (anyone who sees the listing) or sealed (you only). Omit to keep the price-derived rule",
|
|
131
|
+
options: [...data_provider_1.STORE_RELEASE_SOURCE_VISIBILITIES],
|
|
132
|
+
}),
|
|
99
133
|
};
|
|
100
134
|
async run() {
|
|
101
135
|
this.requireAuth();
|
|
@@ -128,6 +162,15 @@ class AgentsVisibility extends base_command_1.BaseCommand {
|
|
|
128
162
|
exit: 1,
|
|
129
163
|
});
|
|
130
164
|
}
|
|
165
|
+
const { license, error: licenseError } = buildReleaseLicense(flags.license, flags.remix);
|
|
166
|
+
if (licenseError)
|
|
167
|
+
this.error(licenseError, { exit: 1 });
|
|
168
|
+
const sourceVisibility = flags['source-visibility'];
|
|
169
|
+
// The server refuses both declarations with isPublic: false — they are
|
|
170
|
+
// terms of a public release, and an unpublish creates none.
|
|
171
|
+
if ((license || sourceVisibility) && !isPublic) {
|
|
172
|
+
this.error('--license, --remix and --source-visibility are terms of a public release; pass them with --public.', { exit: 1 });
|
|
173
|
+
}
|
|
131
174
|
let result;
|
|
132
175
|
try {
|
|
133
176
|
result = await data_provider_1.dataService.setAgentVisibility({
|
|
@@ -141,6 +184,8 @@ class AgentsVisibility extends base_command_1.BaseCommand {
|
|
|
141
184
|
capabilityRationales,
|
|
142
185
|
supportedLanguages: flags.language,
|
|
143
186
|
dependencies,
|
|
187
|
+
license,
|
|
188
|
+
sourceVisibility,
|
|
144
189
|
});
|
|
145
190
|
}
|
|
146
191
|
catch (err) {
|
|
@@ -152,6 +197,13 @@ class AgentsVisibility extends base_command_1.BaseCommand {
|
|
|
152
197
|
}
|
|
153
198
|
const label = isPublic ? 'public' : 'private';
|
|
154
199
|
this.log(`Set agent ${args.id} visibility: ${label}`);
|
|
200
|
+
const recorded = result;
|
|
201
|
+
if (recorded?.license) {
|
|
202
|
+
this.log(`License: ${recorded.license.id} (remix: ${recorded.license.remix})`);
|
|
203
|
+
}
|
|
204
|
+
if (recorded?.sourceVisibility) {
|
|
205
|
+
this.log(`Source visibility: ${recorded.sourceVisibility}`);
|
|
206
|
+
}
|
|
155
207
|
}
|
|
156
208
|
}
|
|
157
209
|
exports.default = AgentsVisibility;
|
|
@@ -43,7 +43,6 @@ import { BaseCommand } from '../base-command';
|
|
|
43
43
|
*/
|
|
44
44
|
export default class CreateToken extends BaseCommand {
|
|
45
45
|
static description: string;
|
|
46
|
-
static aliases: string[];
|
|
47
46
|
static examples: string[];
|
|
48
47
|
static flags: {
|
|
49
48
|
name: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
@@ -49,7 +49,9 @@ const api_fetch_1 = require("../lib/api-fetch");
|
|
|
49
49
|
class CreateToken extends base_command_1.BaseCommand {
|
|
50
50
|
static description = 'Create a long-lived token for bootstrapping a daemon on a CI/headless machine. ' +
|
|
51
51
|
'Printed exactly once. Not accepted by `skrr` commands themselves.';
|
|
52
|
-
|
|
52
|
+
// `tokens:create` used to alias this command. It now names personal access
|
|
53
|
+
// tokens (OSK-13208), the credential `skrr` itself accepts; this one only
|
|
54
|
+
// bootstraps a daemon, and sharing the name is what sent CI here.
|
|
53
55
|
static examples = [
|
|
54
56
|
'<%= config.bin %> create-token --name "github-actions-prod" --ttl 1y',
|
|
55
57
|
'<%= config.bin %> create-token --name "local-ci" --ttl 30d',
|
|
@@ -49,7 +49,7 @@ class FollowupsCheckin extends base_command_1.BaseCommand {
|
|
|
49
49
|
catch (err) {
|
|
50
50
|
this.failWithCliError({
|
|
51
51
|
message: err instanceof Error ? err.message : String(err),
|
|
52
|
-
code: '
|
|
52
|
+
code: 'FOLLOWUP_INVALID_FIELD',
|
|
53
53
|
exit: 2,
|
|
54
54
|
retryable: false,
|
|
55
55
|
});
|
|
@@ -8,7 +8,6 @@ import { BaseCommand } from '../base-command';
|
|
|
8
8
|
*/
|
|
9
9
|
export default class ListTokens extends BaseCommand {
|
|
10
10
|
static description: string;
|
|
11
|
-
static aliases: string[];
|
|
12
11
|
static flags: {
|
|
13
12
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
14
13
|
all: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
@@ -13,7 +13,7 @@ const api_fetch_1 = require("../lib/api-fetch");
|
|
|
13
13
|
*/
|
|
14
14
|
class ListTokens extends base_command_1.BaseCommand {
|
|
15
15
|
static description = 'List your long-lived CI tokens';
|
|
16
|
-
|
|
16
|
+
// No `tokens:list` alias: `skrr tokens` is personal access tokens (OSK-13208).
|
|
17
17
|
static flags = {
|
|
18
18
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
19
19
|
all: core_1.Flags.boolean({
|
package/dist/commands/login.js
CHANGED
|
@@ -126,6 +126,16 @@ class Login extends base_command_1.BaseCommand {
|
|
|
126
126
|
`(${explicitBaseUrl}). Pass one of them.`, { exit: 2 });
|
|
127
127
|
}
|
|
128
128
|
const requestedBaseUrl = envBaseUrl ?? explicitBaseUrl;
|
|
129
|
+
// OSK-13254 — an explicit sign-in lifts what `skrr logout` set: from here a
|
|
130
|
+
// command that finds no stored session may adopt one through the daemon
|
|
131
|
+
// again. Cleared up front, because asking to sign in is the decision.
|
|
132
|
+
{
|
|
133
|
+
const current = (0, config_1.loadConfig)();
|
|
134
|
+
if (current.adoptDaemonSession === false) {
|
|
135
|
+
const { adoptDaemonSession: _cleared, ...rest } = current;
|
|
136
|
+
(0, config_1.saveConfig)(rest);
|
|
137
|
+
}
|
|
138
|
+
}
|
|
129
139
|
/**
|
|
130
140
|
* Undo the base-URL write if this login does not complete.
|
|
131
141
|
*
|
package/dist/commands/logout.js
CHANGED
|
@@ -260,6 +260,10 @@ class Logout extends base_command_1.BaseCommand {
|
|
|
260
260
|
...(agentSetting.value ? { [first_party_harness_managed_1.AGENT_CONFIG_KEY]: agentSetting.value } : {}),
|
|
261
261
|
...(cfg.credentials ? { credentials: cfg.credentials } : {}),
|
|
262
262
|
migrated: { ...(cfg.migrated ?? {}), authOutOfConfig: true },
|
|
263
|
+
// OSK-13254 — signed out means no durable session reappears behind this
|
|
264
|
+
// command. The daemon may still LEND this CLI short-lived tokens (said
|
|
265
|
+
// below); `skrr login` clears the flag.
|
|
266
|
+
adoptDaemonSession: false,
|
|
263
267
|
});
|
|
264
268
|
const finalMessage = flags['all-devices']
|
|
265
269
|
? allDevicesError
|
|
@@ -44,6 +44,7 @@ class DedicatedMachinesSshProxy extends dedicated_lease_command_1.DedicatedLease
|
|
|
44
44
|
code = await (0, dedicated_ssh_1.runSshProxy)({
|
|
45
45
|
baseURL: this.cliConfig.baseURL,
|
|
46
46
|
token: this.resolvedCredential.token,
|
|
47
|
+
getToken: () => this.refreshTransportToken(),
|
|
47
48
|
leaseId: lease,
|
|
48
49
|
input: process.stdin,
|
|
49
50
|
output: process.stdout,
|
|
@@ -11,7 +11,6 @@ import { BaseCommand } from '../base-command';
|
|
|
11
11
|
*/
|
|
12
12
|
export default class RevokeToken extends BaseCommand {
|
|
13
13
|
static description: string;
|
|
14
|
-
static aliases: string[];
|
|
15
14
|
static args: {
|
|
16
15
|
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
17
16
|
};
|
|
@@ -15,7 +15,7 @@ const api_fetch_1 = require("../lib/api-fetch");
|
|
|
15
15
|
*/
|
|
16
16
|
class RevokeToken extends base_command_1.BaseCommand {
|
|
17
17
|
static description = 'Revoke a long-lived CI token by ID or unique prefix';
|
|
18
|
-
|
|
18
|
+
// No `tokens:revoke` alias: `skrr tokens` is personal access tokens (OSK-13208).
|
|
19
19
|
static args = {
|
|
20
20
|
id: core_1.Args.string({
|
|
21
21
|
name: 'id',
|
|
@@ -8,6 +8,7 @@ export default class StoreFollow extends BaseCommand {
|
|
|
8
8
|
};
|
|
9
9
|
static flags: {
|
|
10
10
|
undo: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
status: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
12
|
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
13
|
};
|
|
13
14
|
run(): Promise<void>;
|
|
@@ -18,13 +18,19 @@ class StoreFollow extends base_command_1.BaseCommand {
|
|
|
18
18
|
}),
|
|
19
19
|
};
|
|
20
20
|
static flags = {
|
|
21
|
-
undo: core_1.Flags.boolean({ description: 'Unfollow the publisher' }),
|
|
21
|
+
undo: core_1.Flags.boolean({ description: 'Unfollow the publisher', exclusive: ['status'] }),
|
|
22
|
+
status: core_1.Flags.boolean({ description: 'Show whether you follow the publisher; change nothing' }),
|
|
22
23
|
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
23
24
|
};
|
|
24
25
|
async run() {
|
|
25
26
|
this.requireAuth();
|
|
26
27
|
const { args, flags } = await this.parse(StoreFollow);
|
|
27
28
|
try {
|
|
29
|
+
if (flags.status) {
|
|
30
|
+
const overlay = await data_provider_1.dataService.getStorePublisherFollowOverlay(args.handle);
|
|
31
|
+
this.log(JSON.stringify(overlay, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
28
34
|
const result = flags.undo
|
|
29
35
|
? await data_provider_1.dataService.unfollowStorePublisherByHandle(args.handle)
|
|
30
36
|
: await data_provider_1.dataService.followStorePublisherByHandle(args.handle);
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base-command';
|
|
2
|
+
/** The publishers you follow, newest first, one page at a time. */
|
|
3
|
+
export default class StoreFollowsList extends BaseCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
cursor: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
|
|
8
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,29 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const data_provider_1 = require("@skrr-ai/data-provider");
|
|
5
|
+
const base_command_1 = require("../../../base-command");
|
|
6
|
+
/** The publishers you follow, newest first, one page at a time. */
|
|
7
|
+
class StoreFollowsList extends base_command_1.BaseCommand {
|
|
8
|
+
static description = 'List the store publishers you follow';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> store follows list',
|
|
11
|
+
'<%= config.bin %> store follows list --cursor <next>',
|
|
12
|
+
];
|
|
13
|
+
static flags = {
|
|
14
|
+
cursor: core_1.Flags.string({ description: 'Continue from a previous page' }),
|
|
15
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
16
|
+
};
|
|
17
|
+
async run() {
|
|
18
|
+
this.requireAuth();
|
|
19
|
+
const { flags } = await this.parse(StoreFollowsList);
|
|
20
|
+
try {
|
|
21
|
+
const result = await data_provider_1.dataService.getStorePublisherFollows(flags.cursor);
|
|
22
|
+
this.log(JSON.stringify(result, null, 2));
|
|
23
|
+
}
|
|
24
|
+
catch (err) {
|
|
25
|
+
this.handleApiError(err);
|
|
26
|
+
}
|
|
27
|
+
}
|
|
28
|
+
}
|
|
29
|
+
exports.default = StoreFollowsList;
|
|
@@ -0,0 +1,16 @@
|
|
|
1
|
+
import { BaseCommand } from '../../../base-command';
|
|
2
|
+
/**
|
|
3
|
+
* Remove a follow by its follow id — the one way to drop a follow whose
|
|
4
|
+
* publisher has since renamed, hidden or lost its handle.
|
|
5
|
+
*/
|
|
6
|
+
export default class StoreFollowsRemove extends BaseCommand {
|
|
7
|
+
static description: string;
|
|
8
|
+
static examples: string[];
|
|
9
|
+
static args: {
|
|
10
|
+
followId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
11
|
+
};
|
|
12
|
+
static flags: {
|
|
13
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
14
|
+
};
|
|
15
|
+
run(): Promise<void>;
|
|
16
|
+
}
|