@skrr-ai/cli 0.1.80 → 0.1.81
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 +4 -0
- package/dist/base-command.js +31 -2
- 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/list-tokens.d.ts +0 -1
- package/dist/commands/list-tokens.js +1 -1
- 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/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/credential-resolver.d.ts +1 -1
- package/dist/lib/credential-resolver.js +8 -2
- package/dist/lib/daemonBroker.d.ts +13 -2
- package/dist/lib/daemonBroker.js +6 -4
- package/dist/lib/dedicated-ssh.d.ts +9 -0
- package/dist/lib/dedicated-ssh.js +232 -63
- 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 +6 -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 +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +7 -5
- 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 +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +1 -1
- package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
- package/dist/node_modules/@skrr-ai/data-provider/index.js +3857 -3859
- package/oclif.manifest.json +38008 -36666
- package/package.json +28 -4
|
@@ -0,0 +1,136 @@
|
|
|
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
|
+
const access_tokens_1 = require("../../lib/access-tokens");
|
|
7
|
+
const api_fetch_1 = require("../../lib/api-fetch");
|
|
8
|
+
const prompt_1 = require("../../lib/prompt");
|
|
9
|
+
/**
|
|
10
|
+
* `skrr tokens create` — mint a personal access token (`osk_pat_*`, OSK-13208)
|
|
11
|
+
* for CI. The secret is printed EXACTLY ONCE.
|
|
12
|
+
*
|
|
13
|
+
* Creating one needs a sign-in session (a token cannot mint a token) and, for
|
|
14
|
+
* an account with two-factor authentication, a fresh authenticator code from
|
|
15
|
+
* that session. `--otp` / `--backup-code` supply it up front; at a terminal the
|
|
16
|
+
* command asks for it when the server does.
|
|
17
|
+
*/
|
|
18
|
+
class TokensCreate extends base_command_1.BaseCommand {
|
|
19
|
+
static description = 'Create a personal access token for CI (use it as SKRR_TOKEN). The secret is shown once and never again.';
|
|
20
|
+
static examples = [
|
|
21
|
+
'<%= config.bin %> tokens create github-actions',
|
|
22
|
+
'<%= config.bin %> tokens create deploy --scope write --expires-in-days 30',
|
|
23
|
+
'<%= config.bin %> tokens create ci --scope write --otp 123456 --json',
|
|
24
|
+
];
|
|
25
|
+
static args = {
|
|
26
|
+
name: core_1.Args.string({
|
|
27
|
+
description: 'Label for the token, e.g. the CI secret it will live in (also --name)',
|
|
28
|
+
ignoreStdin: true,
|
|
29
|
+
}),
|
|
30
|
+
};
|
|
31
|
+
static flags = {
|
|
32
|
+
name: core_1.Flags.string({ description: 'Label for the token (1-64 chars)' }),
|
|
33
|
+
scope: core_1.Flags.string({
|
|
34
|
+
description: 'read: GET requests only. write: everything the CLI can do as you, except sign-in, credential, account-deletion, admin and payment actions',
|
|
35
|
+
options: ['read', 'write'],
|
|
36
|
+
default: 'read',
|
|
37
|
+
}),
|
|
38
|
+
'expires-in-days': core_1.Flags.integer({
|
|
39
|
+
description: 'Days until the token expires (1-365)',
|
|
40
|
+
min: 1,
|
|
41
|
+
max: 365,
|
|
42
|
+
default: 90,
|
|
43
|
+
}),
|
|
44
|
+
otp: core_1.Flags.string({
|
|
45
|
+
description: 'Current code from your authenticator app, if your account uses two-factor',
|
|
46
|
+
exclusive: ['backup-code'],
|
|
47
|
+
}),
|
|
48
|
+
'backup-code': core_1.Flags.string({
|
|
49
|
+
description: 'One of your unused backup codes (it is spent)',
|
|
50
|
+
exclusive: ['otp'],
|
|
51
|
+
}),
|
|
52
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON (includes the secret)' }),
|
|
53
|
+
};
|
|
54
|
+
async run() {
|
|
55
|
+
this.requireAuth();
|
|
56
|
+
const { args, flags } = await this.parse(TokensCreate);
|
|
57
|
+
const name = flags.name || args.name;
|
|
58
|
+
if (!name) {
|
|
59
|
+
this.error('A name is required — pass it positionally or with --name.', { exit: 1 });
|
|
60
|
+
}
|
|
61
|
+
const input = {
|
|
62
|
+
name,
|
|
63
|
+
scopes: [flags.scope],
|
|
64
|
+
expiresInDays: flags['expires-in-days'],
|
|
65
|
+
};
|
|
66
|
+
let code = null;
|
|
67
|
+
if (flags.otp)
|
|
68
|
+
code = { token: flags.otp.trim() };
|
|
69
|
+
else if (flags['backup-code'])
|
|
70
|
+
code = { backupCode: flags['backup-code'].trim() };
|
|
71
|
+
if (code)
|
|
72
|
+
await this.verify(code);
|
|
73
|
+
let created;
|
|
74
|
+
try {
|
|
75
|
+
created = await (0, access_tokens_1.createAccessToken)(input);
|
|
76
|
+
}
|
|
77
|
+
catch (err) {
|
|
78
|
+
const challenge = stepUpChallenge(err);
|
|
79
|
+
if (!challenge)
|
|
80
|
+
return this.handleApiError(err);
|
|
81
|
+
if (code || challenge.reason !== 'no_recent_step_up') {
|
|
82
|
+
return this.refuseStepUp(challenge);
|
|
83
|
+
}
|
|
84
|
+
if (!process.stdin.isTTY) {
|
|
85
|
+
return this.refuseStepUp(challenge);
|
|
86
|
+
}
|
|
87
|
+
const entered = (await (0, prompt_1.prompt)('Code from your authenticator app: ')).trim();
|
|
88
|
+
code = { token: entered };
|
|
89
|
+
await this.verify(code);
|
|
90
|
+
try {
|
|
91
|
+
created = await (0, access_tokens_1.createAccessToken)(input);
|
|
92
|
+
}
|
|
93
|
+
catch (retryErr) {
|
|
94
|
+
return this.handleApiError(retryErr);
|
|
95
|
+
}
|
|
96
|
+
}
|
|
97
|
+
if (flags.json) {
|
|
98
|
+
this.log(JSON.stringify(created, null, 2));
|
|
99
|
+
return;
|
|
100
|
+
}
|
|
101
|
+
const t = created.accessToken;
|
|
102
|
+
this.log('');
|
|
103
|
+
this.log(` ${created.token}`);
|
|
104
|
+
this.log('');
|
|
105
|
+
this.log('This is the only time the token will be displayed. Store it as a CI secret now.');
|
|
106
|
+
this.log(`Use it as: SKRR_TOKEN=<token> ${this.config.bin} <command>`);
|
|
107
|
+
this.log('');
|
|
108
|
+
this.log(` id ${t.id}`);
|
|
109
|
+
this.log(` name ${t.name}`);
|
|
110
|
+
this.log(` scopes ${t.scopes.join(', ')}`);
|
|
111
|
+
this.log(` expires ${t.expiresAt ?? '(unknown)'}`);
|
|
112
|
+
}
|
|
113
|
+
async verify(code) {
|
|
114
|
+
try {
|
|
115
|
+
await data_provider_1.dataService.verifyStepUp({ purpose: data_provider_1.ACCESS_TOKEN_CREATE_STEP_UP_PURPOSE, ...code });
|
|
116
|
+
}
|
|
117
|
+
catch (err) {
|
|
118
|
+
this.handleApiError(err);
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
refuseStepUp(challenge) {
|
|
122
|
+
const bin = this.config.bin;
|
|
123
|
+
const messages = {
|
|
124
|
+
credential_not_session_bound: `Creating an access token needs a sign-in session, and this credential is not one (an access token cannot create another). Run \`${bin} login\` and try again. Nothing changed.`,
|
|
125
|
+
two_factor_not_enabled: 'Creating an access token needs two-factor authentication on this account. Turn it on in the web app, then try again. Nothing changed.',
|
|
126
|
+
no_recent_step_up: 'Creating an access token needs a fresh code from your authenticator app. Run this command again with --otp <code> (or --backup-code <code>). Nothing changed.',
|
|
127
|
+
};
|
|
128
|
+
this.error(messages[challenge.reason], { code: 'STEP_UP_REQUIRED', exit: 2 });
|
|
129
|
+
}
|
|
130
|
+
}
|
|
131
|
+
exports.default = TokensCreate;
|
|
132
|
+
function stepUpChallenge(err) {
|
|
133
|
+
if (!(err instanceof api_fetch_1.ApiFetchError) || err.status !== 403)
|
|
134
|
+
return null;
|
|
135
|
+
return (0, data_provider_1.stepUpChallengeFromError)(err);
|
|
136
|
+
}
|
|
@@ -0,0 +1,11 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/** `skrr tokens list` — your personal access tokens (never their secrets). */
|
|
3
|
+
export default class TokensList extends BaseCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static flags: {
|
|
7
|
+
all: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
8
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
9
|
+
};
|
|
10
|
+
run(): Promise<void>;
|
|
11
|
+
}
|
|
@@ -0,0 +1,53 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_command_1 = require("../../base-command");
|
|
5
|
+
const access_tokens_1 = require("../../lib/access-tokens");
|
|
6
|
+
/** `skrr tokens list` — your personal access tokens (never their secrets). */
|
|
7
|
+
class TokensList extends base_command_1.BaseCommand {
|
|
8
|
+
static description = 'List your personal access tokens (secrets are never shown again)';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> tokens list',
|
|
11
|
+
'<%= config.bin %> tokens list --all',
|
|
12
|
+
'<%= config.bin %> tokens list --json',
|
|
13
|
+
];
|
|
14
|
+
static flags = {
|
|
15
|
+
all: core_1.Flags.boolean({ description: 'Include revoked and expired tokens' }),
|
|
16
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
17
|
+
};
|
|
18
|
+
async run() {
|
|
19
|
+
this.requireAuth();
|
|
20
|
+
const { flags } = await this.parse(TokensList);
|
|
21
|
+
let tokens;
|
|
22
|
+
try {
|
|
23
|
+
tokens = await (0, access_tokens_1.listAccessTokens)();
|
|
24
|
+
}
|
|
25
|
+
catch (err) {
|
|
26
|
+
this.handleApiError(err);
|
|
27
|
+
return;
|
|
28
|
+
}
|
|
29
|
+
const shown = flags.all ? tokens : tokens.filter((t) => t.state === 'active');
|
|
30
|
+
if (flags.json) {
|
|
31
|
+
this.log(JSON.stringify({ accessTokens: shown }, null, 2));
|
|
32
|
+
return;
|
|
33
|
+
}
|
|
34
|
+
if (shown.length === 0) {
|
|
35
|
+
this.log(flags.all || tokens.length === 0
|
|
36
|
+
? `No access tokens. Create one with \`${this.config.bin} tokens create <name>\`.`
|
|
37
|
+
: `No active access tokens (${tokens.length} revoked or expired; --all shows them).`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
for (const t of shown) {
|
|
41
|
+
this.log([
|
|
42
|
+
t.id,
|
|
43
|
+
t.name,
|
|
44
|
+
`…${t.lastFour}`,
|
|
45
|
+
t.scopes.join('+'),
|
|
46
|
+
(0, access_tokens_1.describeAccessTokenState)(t),
|
|
47
|
+
`expires ${t.expiresAt ?? '?'}`,
|
|
48
|
+
`last used ${t.lastUsedAt ?? 'never'}`,
|
|
49
|
+
].join(' '));
|
|
50
|
+
}
|
|
51
|
+
}
|
|
52
|
+
}
|
|
53
|
+
exports.default = TokensList;
|
|
@@ -0,0 +1,14 @@
|
|
|
1
|
+
import { BaseCommand } from '../../base-command';
|
|
2
|
+
/** `skrr tokens revoke <id>` — irreversible; a revoked token is replaced, never re-enabled. */
|
|
3
|
+
export default class TokensRevoke extends BaseCommand {
|
|
4
|
+
static description: string;
|
|
5
|
+
static examples: string[];
|
|
6
|
+
static args: {
|
|
7
|
+
id: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
|
|
8
|
+
};
|
|
9
|
+
static flags: {
|
|
10
|
+
compromised: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
11
|
+
json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
|
|
12
|
+
};
|
|
13
|
+
run(): Promise<void>;
|
|
14
|
+
}
|
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
const core_1 = require("@oclif/core");
|
|
4
|
+
const base_command_1 = require("../../base-command");
|
|
5
|
+
const access_tokens_1 = require("../../lib/access-tokens");
|
|
6
|
+
/** `skrr tokens revoke <id>` — irreversible; a revoked token is replaced, never re-enabled. */
|
|
7
|
+
class TokensRevoke extends base_command_1.BaseCommand {
|
|
8
|
+
static description = 'Revoke a personal access token. Anything still presenting it is refused on its next request.';
|
|
9
|
+
static examples = [
|
|
10
|
+
'<%= config.bin %> tokens revoke <token-id>',
|
|
11
|
+
'<%= config.bin %> tokens revoke <token-id> --compromised',
|
|
12
|
+
];
|
|
13
|
+
static args = {
|
|
14
|
+
id: core_1.Args.string({
|
|
15
|
+
description: 'Token id (from `tokens list`)',
|
|
16
|
+
required: true,
|
|
17
|
+
ignoreStdin: true,
|
|
18
|
+
}),
|
|
19
|
+
};
|
|
20
|
+
static flags = {
|
|
21
|
+
compromised: core_1.Flags.boolean({ description: 'Record that the token leaked' }),
|
|
22
|
+
json: core_1.Flags.boolean({ description: 'Output as JSON' }),
|
|
23
|
+
};
|
|
24
|
+
async run() {
|
|
25
|
+
this.requireAuth();
|
|
26
|
+
const { args, flags } = await this.parse(TokensRevoke);
|
|
27
|
+
try {
|
|
28
|
+
const res = await (0, access_tokens_1.revokeAccessToken)(args.id, { compromised: flags.compromised });
|
|
29
|
+
if (flags.json) {
|
|
30
|
+
this.log(JSON.stringify(res, null, 2));
|
|
31
|
+
return;
|
|
32
|
+
}
|
|
33
|
+
this.log(res.alreadyRevoked
|
|
34
|
+
? `${args.id} was already revoked.`
|
|
35
|
+
: `Revoked ${args.id}. Anything still presenting it will now be refused.`);
|
|
36
|
+
}
|
|
37
|
+
catch (err) {
|
|
38
|
+
this.handleApiError(err);
|
|
39
|
+
}
|
|
40
|
+
}
|
|
41
|
+
}
|
|
42
|
+
exports.default = TokensRevoke;
|
|
@@ -0,0 +1,59 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* access-tokens.ts — request client for `/api/user/access-tokens`, the
|
|
3
|
+
* personal access tokens (`osk_pat_*`, OSK-13208) behind `skrr tokens`.
|
|
4
|
+
*
|
|
5
|
+
* A personal access token is how CI runs `skrr` AS a person: set it as
|
|
6
|
+
* `SKRR_TOKEN` and every command authenticates with it, never refreshing,
|
|
7
|
+
* until it expires or is revoked. It is not `skrr create-token` (an `osk_ci_`
|
|
8
|
+
* DAEMON bootstrap token), not `skrr token mint` (a rotating refresh bundle),
|
|
9
|
+
* and not `skrr api-keys` (Space-scoped Managed-Agents keys).
|
|
10
|
+
*
|
|
11
|
+
* Path literals are inline on purpose: `domain-api-parity` resolves a command
|
|
12
|
+
* to an endpoint by reading these strings.
|
|
13
|
+
*/
|
|
14
|
+
/** A token as the API returns it. The secret is never part of this shape. */
|
|
15
|
+
export interface AccessToken {
|
|
16
|
+
id: string;
|
|
17
|
+
name: string;
|
|
18
|
+
lastFour: string;
|
|
19
|
+
scopes: string[];
|
|
20
|
+
/** Derived server-side: natural expiry is not stored as a revocation. */
|
|
21
|
+
state: 'active' | 'expired' | 'revoked';
|
|
22
|
+
createdAt: string | null;
|
|
23
|
+
expiresAt: string | null;
|
|
24
|
+
lastUsedAt: string | null;
|
|
25
|
+
revokedAt: string | null;
|
|
26
|
+
revokedReason: string | null;
|
|
27
|
+
createdFromIp: string | null;
|
|
28
|
+
createdFromUserAgent: string | null;
|
|
29
|
+
}
|
|
30
|
+
/** The create response. `token` is shown exactly once and never again. */
|
|
31
|
+
export interface AccessTokenCreated {
|
|
32
|
+
token: string;
|
|
33
|
+
accessToken: AccessToken;
|
|
34
|
+
}
|
|
35
|
+
export interface CreateAccessTokenInput {
|
|
36
|
+
name: string;
|
|
37
|
+
scopes?: string[];
|
|
38
|
+
expiresInDays?: number;
|
|
39
|
+
}
|
|
40
|
+
/**
|
|
41
|
+
* The words for each stored revocation reason. Every member of the schema's
|
|
42
|
+
* `revokedReason` enum appears here, so the list shows WHY a token died —
|
|
43
|
+
* "revoked" alone cannot tell a deliberate revoke from a sign-out sweep.
|
|
44
|
+
*/
|
|
45
|
+
export declare const ACCESS_TOKEN_REVOKED_REASON_LABELS: Record<string, string>;
|
|
46
|
+
export declare function createAccessToken(input: CreateAccessTokenInput): Promise<AccessTokenCreated>;
|
|
47
|
+
/** Every token the caller owns, newest first, including revoked and expired rows. */
|
|
48
|
+
export declare function listAccessTokens(): Promise<AccessToken[]>;
|
|
49
|
+
export interface RevokeAccessTokenResult {
|
|
50
|
+
revoked: boolean;
|
|
51
|
+
alreadyRevoked: boolean;
|
|
52
|
+
accessToken?: AccessToken;
|
|
53
|
+
}
|
|
54
|
+
/** Revoke one token. Irreversible: a revoked token is replaced, never re-enabled. */
|
|
55
|
+
export declare function revokeAccessToken(id: string, opts?: {
|
|
56
|
+
compromised?: boolean;
|
|
57
|
+
}): Promise<RevokeAccessTokenResult>;
|
|
58
|
+
/** One-line status for a listed token. */
|
|
59
|
+
export declare function describeAccessTokenState(token: AccessToken): string;
|
|
@@ -0,0 +1,50 @@
|
|
|
1
|
+
"use strict";
|
|
2
|
+
Object.defineProperty(exports, "__esModule", { value: true });
|
|
3
|
+
exports.ACCESS_TOKEN_REVOKED_REASON_LABELS = void 0;
|
|
4
|
+
exports.createAccessToken = createAccessToken;
|
|
5
|
+
exports.listAccessTokens = listAccessTokens;
|
|
6
|
+
exports.revokeAccessToken = revokeAccessToken;
|
|
7
|
+
exports.describeAccessTokenState = describeAccessTokenState;
|
|
8
|
+
const api_fetch_1 = require("./api-fetch");
|
|
9
|
+
/**
|
|
10
|
+
* The words for each stored revocation reason. Every member of the schema's
|
|
11
|
+
* `revokedReason` enum appears here, so the list shows WHY a token died —
|
|
12
|
+
* "revoked" alone cannot tell a deliberate revoke from a sign-out sweep.
|
|
13
|
+
*/
|
|
14
|
+
exports.ACCESS_TOKEN_REVOKED_REASON_LABELS = {
|
|
15
|
+
user: 'revoked by you',
|
|
16
|
+
sign_out_everywhere: 'revoked by sign-out everywhere / password or 2FA change',
|
|
17
|
+
admin: 'revoked by an administrator',
|
|
18
|
+
compromised: 'revoked as compromised',
|
|
19
|
+
};
|
|
20
|
+
async function createAccessToken(input) {
|
|
21
|
+
return (0, api_fetch_1.apiFetch)('/api/user/access-tokens', {
|
|
22
|
+
method: 'POST',
|
|
23
|
+
body: {
|
|
24
|
+
name: input.name,
|
|
25
|
+
...(input.scopes ? { scopes: input.scopes } : {}),
|
|
26
|
+
...(input.expiresInDays !== undefined ? { expiresInDays: input.expiresInDays } : {}),
|
|
27
|
+
},
|
|
28
|
+
});
|
|
29
|
+
}
|
|
30
|
+
/** Every token the caller owns, newest first, including revoked and expired rows. */
|
|
31
|
+
async function listAccessTokens() {
|
|
32
|
+
const res = await (0, api_fetch_1.apiFetch)('/api/user/access-tokens');
|
|
33
|
+
return Array.isArray(res?.accessTokens) ? res.accessTokens : [];
|
|
34
|
+
}
|
|
35
|
+
/** Revoke one token. Irreversible: a revoked token is replaced, never re-enabled. */
|
|
36
|
+
async function revokeAccessToken(id, opts = {}) {
|
|
37
|
+
return (0, api_fetch_1.apiFetch)(`/api/user/access-tokens/${encodeURIComponent(id)}`, {
|
|
38
|
+
method: 'DELETE',
|
|
39
|
+
...(opts.compromised ? { body: { reason: 'compromised' } } : {}),
|
|
40
|
+
});
|
|
41
|
+
}
|
|
42
|
+
/** One-line status for a listed token. */
|
|
43
|
+
function describeAccessTokenState(token) {
|
|
44
|
+
if (token.state === 'revoked') {
|
|
45
|
+
return exports.ACCESS_TOKEN_REVOKED_REASON_LABELS[token.revokedReason ?? ''] ?? 'revoked';
|
|
46
|
+
}
|
|
47
|
+
if (token.state === 'expired')
|
|
48
|
+
return 'expired';
|
|
49
|
+
return 'active';
|
|
50
|
+
}
|
|
@@ -36,6 +36,12 @@ export declare function plainCredentialEnvelopeWarning(args: readonly unknown[])
|
|
|
36
36
|
* own when a local daemon can hand a credential over.
|
|
37
37
|
*/
|
|
38
38
|
export declare const CREDENTIAL_KEY_CHANGED_NOTICE: string;
|
|
39
|
+
/**
|
|
40
|
+
* The one line an unreadable device key prints when no credential-key change
|
|
41
|
+
* explains it. The CLI never enrolls or requires a device key (OSK-12063), so
|
|
42
|
+
* this degrades a proof, not access — say exactly that.
|
|
43
|
+
*/
|
|
44
|
+
export declare const DEVICE_KEY_RECOVERY_REQUIRED_NOTICE: string;
|
|
39
45
|
export declare function createCredentialEnvelopeWarningPresenter(): (args: readonly unknown[]) => string | null;
|
|
40
46
|
type DedicatedGenerationPreparationOptions = {
|
|
41
47
|
machineUuid?: string;
|
|
@@ -3,7 +3,7 @@ var __importDefault = (this && this.__importDefault) || function (mod) {
|
|
|
3
3
|
return (mod && mod.__esModule) ? mod : { "default": mod };
|
|
4
4
|
};
|
|
5
5
|
Object.defineProperty(exports, "__esModule", { value: true });
|
|
6
|
-
exports.CliCredentialInitializationError = exports.CREDENTIAL_KEY_CHANGED_NOTICE = void 0;
|
|
6
|
+
exports.CliCredentialInitializationError = exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE = exports.CREDENTIAL_KEY_CHANGED_NOTICE = void 0;
|
|
7
7
|
exports.plainCredentialEnvelopeWarning = plainCredentialEnvelopeWarning;
|
|
8
8
|
exports.createCredentialEnvelopeWarningPresenter = createCredentialEnvelopeWarningPresenter;
|
|
9
9
|
exports.prepareDedicatedRuntimeAuthGeneration = prepareDedicatedRuntimeAuthGeneration;
|
|
@@ -108,6 +108,20 @@ exports.CREDENTIAL_KEY_CHANGED_NOTICE = "note: this machine's credential key cha
|
|
|
108
108
|
*/
|
|
109
109
|
/** auth-core's device-identity warning for a private key it could not open. */
|
|
110
110
|
const DEVICE_KEY_DECRYPT_FAILED = '[deviceIdentity] private_key_decrypt_failed';
|
|
111
|
+
/**
|
|
112
|
+
* auth-core's device-identity warning for a stored key it will NOT replace on
|
|
113
|
+
* ordinary boot (`b5e683280b`): minting a new key here would make the next
|
|
114
|
+
* refresh sign with a key the server never pinned. Only an explicit login with
|
|
115
|
+
* `recoverUnreadable` replaces it.
|
|
116
|
+
*/
|
|
117
|
+
const DEVICE_KEY_RECOVERY_REQUIRED = '[deviceIdentity] recovery_required';
|
|
118
|
+
/**
|
|
119
|
+
* The one line an unreadable device key prints when no credential-key change
|
|
120
|
+
* explains it. The CLI never enrolls or requires a device key (OSK-12063), so
|
|
121
|
+
* this degrades a proof, not access — say exactly that.
|
|
122
|
+
*/
|
|
123
|
+
exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE = "warning: this machine's stored device key could not be read, so skrr sends requests " +
|
|
124
|
+
'without a device proof until the local daemon signs in again. (DEBUG=1 shows the underlying error.)';
|
|
111
125
|
function createCredentialEnvelopeWarningPresenter() {
|
|
112
126
|
let keyChanged = false;
|
|
113
127
|
return (args) => {
|
|
@@ -133,12 +147,20 @@ function createCredentialEnvelopeWarningPresenter() {
|
|
|
133
147
|
if (keyChanged && event === 'cred_envelope.read.decrypt_failed')
|
|
134
148
|
return '';
|
|
135
149
|
// The device private key is one of those values: auth-core's device
|
|
136
|
-
// identity loads it next
|
|
137
|
-
//
|
|
138
|
-
//
|
|
139
|
-
//
|
|
140
|
-
|
|
150
|
+
// identity loads it next and cannot open it. Since `b5e683280b` it does
|
|
151
|
+
// NOT mint a replacement on ordinary boot — it parks in
|
|
152
|
+
// `recovery_required` until an explicit login recovers it. Both warnings
|
|
153
|
+
// come through the `[deviceIdentity]` prefix and reached the terminal as
|
|
154
|
+
// raw `[auth-core warn]` lines right under the notice, which already says
|
|
155
|
+
// the old key is gone and what replaces it.
|
|
156
|
+
if (keyChanged &&
|
|
157
|
+
(message === DEVICE_KEY_DECRYPT_FAILED || message === DEVICE_KEY_RECOVERY_REQUIRED)) {
|
|
141
158
|
return '';
|
|
159
|
+
}
|
|
160
|
+
// Without a key change an unreadable device key is a real fault: one
|
|
161
|
+
// plain line instead of an object dump.
|
|
162
|
+
if (message === DEVICE_KEY_RECOVERY_REQUIRED)
|
|
163
|
+
return exports.DEVICE_KEY_RECOVERY_REQUIRED_NOTICE;
|
|
142
164
|
return plainCredentialEnvelopeWarning(args);
|
|
143
165
|
};
|
|
144
166
|
}
|
|
@@ -5,7 +5,7 @@
|
|
|
5
5
|
* CLI-appropriate backends. The resolver walks the published chain:
|
|
6
6
|
*
|
|
7
7
|
* 1. flag — --token <jwt> (debugging; BaseCommand parses)
|
|
8
|
-
* 2. env-token — OVERSKY_TOKEN (CI / static; kind
|
|
8
|
+
* 2. env-token — OVERSKY_TOKEN, then SKRR_TOKEN (CI / static; kind 'ci' or 'pat' detected)
|
|
9
9
|
* 3. auth-helper — OVERSKY_AUTH_HELPER script output
|
|
10
10
|
* 4. fd-handoff — N/A for CLI (skipped; no parent fd)
|
|
11
11
|
* 5. keychain — ai.skrr.cli.<profile>
|
|
@@ -12,7 +12,7 @@ exports.formatCredentialSource = formatCredentialSource;
|
|
|
12
12
|
* CLI-appropriate backends. The resolver walks the published chain:
|
|
13
13
|
*
|
|
14
14
|
* 1. flag — --token <jwt> (debugging; BaseCommand parses)
|
|
15
|
-
* 2. env-token — OVERSKY_TOKEN (CI / static; kind
|
|
15
|
+
* 2. env-token — OVERSKY_TOKEN, then SKRR_TOKEN (CI / static; kind 'ci' or 'pat' detected)
|
|
16
16
|
* 3. auth-helper — OVERSKY_AUTH_HELPER script output
|
|
17
17
|
* 4. fd-handoff — N/A for CLI (skipped; no parent fd)
|
|
18
18
|
* 5. keychain — ai.skrr.cli.<profile>
|
|
@@ -78,6 +78,9 @@ async function resolveCredential(opts = {}) {
|
|
|
78
78
|
flagToken: opts.flagToken,
|
|
79
79
|
bareMode,
|
|
80
80
|
storeMode,
|
|
81
|
+
// SKRR_TOKEN is the name the CI recipe uses (OSK-13208); OVERSKY_TOKEN still
|
|
82
|
+
// wins when both are set, as in the config overlay (config.ts).
|
|
83
|
+
envTokenNames: ['OVERSKY_TOKEN', 'SKRR_TOKEN'],
|
|
81
84
|
// CLI never has an interactive slot — users run `skrr login` explicitly.
|
|
82
85
|
isHeadless: () => true,
|
|
83
86
|
// Stored credentials are only usable against the origin that issued
|
|
@@ -160,7 +163,10 @@ function formatCredentialSource(source) {
|
|
|
160
163
|
case 'flag':
|
|
161
164
|
return 'CLI --token flag';
|
|
162
165
|
case 'env-token':
|
|
163
|
-
|
|
166
|
+
// Name the variable that actually supplied it (OVERSKY_TOKEN wins).
|
|
167
|
+
return !process.env.OVERSKY_TOKEN?.trim() && process.env.SKRR_TOKEN?.trim()
|
|
168
|
+
? 'SKRR_TOKEN'
|
|
169
|
+
: 'OVERSKY_TOKEN';
|
|
164
170
|
case 'auth-helper':
|
|
165
171
|
return 'OVERSKY_AUTH_HELPER';
|
|
166
172
|
case 'fd-handoff':
|
|
@@ -1,5 +1,5 @@
|
|
|
1
1
|
/**
|
|
2
|
-
* daemonBroker.ts —
|
|
2
|
+
* daemonBroker.ts — skrr CLI client for the local `skrrd` daemon's
|
|
3
3
|
* cli-handoff endpoint.
|
|
4
4
|
*
|
|
5
5
|
* When the daemon is already authenticated for the user on this machine,
|
|
@@ -199,7 +199,7 @@ export interface DaemonBrokerOptions {
|
|
|
199
199
|
/** Stable per-machine cliId the CLI wants the new refresh row bound to. */
|
|
200
200
|
cliId: string;
|
|
201
201
|
/**
|
|
202
|
-
* The
|
|
202
|
+
* The skrr CLI's currently-configured baseURL (e.g. `https://skrr.ai`).
|
|
203
203
|
* Two roles:
|
|
204
204
|
* 1. Pre-flight check vs `bootstrap.serverUrl` — when they don't
|
|
205
205
|
* match, we refuse to call the daemon so we never mint a token
|
|
@@ -461,6 +461,17 @@ export interface MaybeAutoBrokerOptions {
|
|
|
461
461
|
* everywhere else. Production callers omit it and behave exactly as before.
|
|
462
462
|
*/
|
|
463
463
|
bootstrapPathOverride?: string;
|
|
464
|
+
/**
|
|
465
|
+
* The profile whose daemon may be borrowed. Defaults to the active profile.
|
|
466
|
+
*
|
|
467
|
+
* A silent hand-off must never cross profiles (OSK-13257). The `default`
|
|
468
|
+
* candidate list falls through to any running daemon, and every daemon is
|
|
469
|
+
* signed in as whoever started it — so `--profile buyer`, with its own
|
|
470
|
+
* session refused, used to run as the machine owner after one stderr line.
|
|
471
|
+
* A named profile now borrows only a daemon started under that same
|
|
472
|
+
* profile, and otherwise reports NOT_SIGNED_IN for the profile it was given.
|
|
473
|
+
*/
|
|
474
|
+
profile?: string;
|
|
464
475
|
}
|
|
465
476
|
export interface MaybeAutoBrokerResult {
|
|
466
477
|
/**
|
package/dist/lib/daemonBroker.js
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
"use strict";
|
|
2
2
|
/**
|
|
3
|
-
* daemonBroker.ts —
|
|
3
|
+
* daemonBroker.ts — skrr CLI client for the local `skrrd` daemon's
|
|
4
4
|
* cli-handoff endpoint.
|
|
5
5
|
*
|
|
6
6
|
* When the daemon is already authenticated for the user on this machine,
|
|
@@ -101,6 +101,7 @@ const auth_storage_1 = require("./auth-storage");
|
|
|
101
101
|
const config_1 = require("./config");
|
|
102
102
|
const cli_identity_1 = require("./cli-identity");
|
|
103
103
|
const daemonRestartWait_1 = require("./daemonRestartWait");
|
|
104
|
+
const keychain_1 = require("./keychain");
|
|
104
105
|
const REQUEST_TIMEOUT_MS = 20_000;
|
|
105
106
|
/**
|
|
106
107
|
* Normalize a base URL for equality comparison. Mirrors the daemon-side
|
|
@@ -247,9 +248,9 @@ exports.HOSTED_MACHINE_CLI_HANDOFF_DESCRIPTOR = '/run/skrr-hosted-machine/cli-ha
|
|
|
247
248
|
/** Where the cli-handoff broker looks, in order. */
|
|
248
249
|
function resolveCliHandoffCandidatePaths(profile = 'default') {
|
|
249
250
|
const candidates = resolveBootstrapCandidatePaths(profile);
|
|
250
|
-
// Any profile: a guest runs one daemon
|
|
251
|
-
//
|
|
252
|
-
// only
|
|
251
|
+
// Any profile: a guest runs one daemon holding the one account the guest
|
|
252
|
+
// serves, so every profile on it is that account. On a laptop a named profile
|
|
253
|
+
// reaches only its own island file above (OSK-13257).
|
|
253
254
|
if (process.platform === 'linux') {
|
|
254
255
|
candidates.push(exports.DEDICATED_RUNTIME_CLI_HANDOFF_DESCRIPTOR, exports.HOSTED_MACHINE_CLI_HANDOFF_DESCRIPTOR);
|
|
255
256
|
}
|
|
@@ -1252,6 +1253,7 @@ async function maybeAutoBroker(opts) {
|
|
|
1252
1253
|
// family mint remains the fallback for v1 descriptors and the redeem
|
|
1253
1254
|
// path for explicit `skrr login`.
|
|
1254
1255
|
preferHandoffMode: 'access_token',
|
|
1256
|
+
profile: opts.profile ?? (0, keychain_1.getActiveProfile)(),
|
|
1255
1257
|
...(opts.bootstrapPathOverride ? { bootstrapPathOverride: opts.bootstrapPathOverride } : {}),
|
|
1256
1258
|
});
|
|
1257
1259
|
return {
|
|
@@ -104,6 +104,10 @@ export declare function installSshConfigBlock(home?: string): {
|
|
|
104
104
|
changed: boolean;
|
|
105
105
|
};
|
|
106
106
|
export interface SshStartAck {
|
|
107
|
+
failed?: boolean;
|
|
108
|
+
closing?: boolean;
|
|
109
|
+
closed?: boolean;
|
|
110
|
+
retryable?: boolean;
|
|
107
111
|
channelId?: string;
|
|
108
112
|
ok?: boolean;
|
|
109
113
|
error?: string;
|
|
@@ -113,6 +117,9 @@ export interface SshStartAck {
|
|
|
113
117
|
key: string;
|
|
114
118
|
}[];
|
|
115
119
|
receiverAckV1?: boolean;
|
|
120
|
+
resumeV2?: boolean;
|
|
121
|
+
epoch?: string;
|
|
122
|
+
generation?: number;
|
|
116
123
|
}
|
|
117
124
|
export interface SshProxyOptions {
|
|
118
125
|
baseURL: string;
|
|
@@ -123,6 +130,8 @@ export interface SshProxyOptions {
|
|
|
123
130
|
/** Diagnostics; never stdout — ssh reads stdout as protocol bytes. */
|
|
124
131
|
errorOutput: NodeJS.WritableStream;
|
|
125
132
|
ttlSeconds?: number;
|
|
133
|
+
getToken?: () => Promise<string>;
|
|
134
|
+
recoveryMs?: number;
|
|
126
135
|
/** Injected for tests. */
|
|
127
136
|
connect?: (url: string, opts: Parameters<typeof io>[1]) => Socket;
|
|
128
137
|
/** Injected for tests. */
|