@skrr-ai/cli 0.1.79 → 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.
Files changed (114) hide show
  1. package/dist/base-command.d.ts +4 -0
  2. package/dist/base-command.js +40 -4
  3. package/dist/commands/agents/visibility.d.ts +14 -0
  4. package/dist/commands/agents/visibility.js +52 -0
  5. package/dist/commands/create-token.d.ts +0 -1
  6. package/dist/commands/create-token.js +3 -1
  7. package/dist/commands/inbox/index.js +4 -2
  8. package/dist/commands/list-tokens.d.ts +0 -1
  9. package/dist/commands/list-tokens.js +1 -1
  10. package/dist/commands/login.d.ts +8 -3
  11. package/dist/commands/login.js +14 -4
  12. package/dist/commands/machines/dedicated/ssh-proxy.js +1 -0
  13. package/dist/commands/revoke-token.d.ts +0 -1
  14. package/dist/commands/revoke-token.js +1 -1
  15. package/dist/commands/spaces/workflows/events.js +9 -2
  16. package/dist/commands/store/follow.d.ts +1 -0
  17. package/dist/commands/store/follow.js +7 -1
  18. package/dist/commands/store/follows/list.d.ts +11 -0
  19. package/dist/commands/store/follows/list.js +29 -0
  20. package/dist/commands/store/follows/remove.d.ts +16 -0
  21. package/dist/commands/store/follows/remove.js +37 -0
  22. package/dist/commands/store/publishers/profile/draft.d.ts +17 -0
  23. package/dist/commands/store/publishers/profile/draft.js +54 -0
  24. package/dist/commands/store/publishers/profile/handle.d.ts +14 -0
  25. package/dist/commands/store/publishers/profile/handle.js +39 -0
  26. package/dist/commands/store/publishers/profile/hide.d.ts +12 -0
  27. package/dist/commands/store/publishers/profile/hide.js +39 -0
  28. package/dist/commands/store/publishers/profile/preview.d.ts +11 -0
  29. package/dist/commands/store/publishers/profile/preview.js +39 -0
  30. package/dist/commands/store/publishers/profile/publish.d.ts +12 -0
  31. package/dist/commands/store/publishers/profile/publish.js +41 -0
  32. package/dist/commands/store/publishers/profile/restore.d.ts +15 -0
  33. package/dist/commands/store/publishers/profile/restore.js +49 -0
  34. package/dist/commands/store/publishers/profile/revisions.d.ts +11 -0
  35. package/dist/commands/store/publishers/profile/revisions.js +30 -0
  36. package/dist/commands/store/publishers/profile/show.d.ts +11 -0
  37. package/dist/commands/store/publishers/profile/show.js +39 -0
  38. package/dist/commands/store/review.d.ts +26 -0
  39. package/dist/commands/store/review.js +93 -0
  40. package/dist/commands/store/reviews.d.ts +29 -0
  41. package/dist/commands/store/reviews.js +170 -0
  42. package/dist/commands/store/tip.d.ts +20 -0
  43. package/dist/commands/store/tip.js +57 -0
  44. package/dist/commands/tokens/create.d.ts +28 -0
  45. package/dist/commands/tokens/create.js +136 -0
  46. package/dist/commands/tokens/list.d.ts +11 -0
  47. package/dist/commands/tokens/list.js +53 -0
  48. package/dist/commands/tokens/revoke.d.ts +14 -0
  49. package/dist/commands/tokens/revoke.js +42 -0
  50. package/dist/lib/access-tokens.d.ts +59 -0
  51. package/dist/lib/access-tokens.js +50 -0
  52. package/dist/lib/agentic-stream.js +13 -4
  53. package/dist/lib/api-fetch.js +6 -2
  54. package/dist/lib/auth-core-init.d.ts +6 -0
  55. package/dist/lib/auth-core-init.js +28 -6
  56. package/dist/lib/brokered-dpop.d.ts +34 -0
  57. package/dist/lib/brokered-dpop.js +89 -0
  58. package/dist/lib/credential-resolver.d.ts +1 -1
  59. package/dist/lib/credential-resolver.js +8 -2
  60. package/dist/lib/daemonBroker.d.ts +84 -4
  61. package/dist/lib/daemonBroker.js +209 -40
  62. package/dist/lib/daemonBrokerRefusal.d.ts +26 -0
  63. package/dist/lib/daemonBrokerRefusal.js +83 -4
  64. package/dist/lib/daemonRestartWait.d.ts +39 -0
  65. package/dist/lib/daemonRestartWait.js +181 -0
  66. package/dist/lib/dedicated-service.js +10 -5
  67. package/dist/lib/dedicated-ssh.d.ts +9 -0
  68. package/dist/lib/dedicated-ssh.js +235 -63
  69. package/dist/lib/dedicated-terminal.js +6 -3
  70. package/dist/lib/dpop-auth.d.ts +35 -0
  71. package/dist/lib/dpop-auth.js +57 -0
  72. package/dist/lib/login.d.ts +6 -0
  73. package/dist/lib/login.js +13 -4
  74. package/dist/lib/node-adapter.js +6 -2
  75. package/dist/lib/publisher-profile-flags.d.ts +19 -0
  76. package/dist/lib/publisher-profile-flags.js +48 -0
  77. package/dist/lib/refresh.d.ts +1 -1
  78. package/dist/lib/refresh.js +6 -1
  79. package/dist/lib/ssh-ordered-bytes.d.ts +8 -0
  80. package/dist/lib/ssh-ordered-bytes.js +31 -0
  81. package/dist/lib/usd-micros.d.ts +9 -0
  82. package/dist/lib/usd-micros.js +21 -0
  83. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.d.ts +42 -1
  84. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/cliHandoffWire.js +35 -3
  85. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.d.ts +22 -3
  86. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialResolver.js +29 -5
  87. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.d.ts +49 -1
  88. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/credentialSession.js +47 -9
  89. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.d.ts +7 -1
  90. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceIdentityBridge.js +7 -1
  91. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.d.ts +56 -0
  92. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/deviceKey.js +42 -20
  93. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.d.ts +101 -0
  94. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/dpop.js +179 -0
  95. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.d.ts +2 -1
  96. package/dist/node_modules/@skrr-ai/auth-core/dist/cjs/index.js +23 -5
  97. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.d.ts +42 -1
  98. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/cliHandoffWire.js +32 -2
  99. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.d.ts +22 -3
  100. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialResolver.js +27 -4
  101. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.d.ts +49 -1
  102. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/credentialSession.js +47 -9
  103. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.d.ts +7 -1
  104. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceIdentityBridge.js +7 -1
  105. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.d.ts +56 -0
  106. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/deviceKey.js +39 -20
  107. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.d.ts +101 -0
  108. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/dpop.js +138 -0
  109. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.d.ts +2 -1
  110. package/dist/node_modules/@skrr-ai/auth-core/dist/esm/index.js +5 -1
  111. package/dist/node_modules/@skrr-ai/auth-core/package.json +1 -1
  112. package/dist/node_modules/@skrr-ai/data-provider/index.js +3895 -3882
  113. package/oclif.manifest.json +34985 -33643
  114. package/package.json +28 -4
@@ -0,0 +1,170 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ exports.describeReview = describeReview;
4
+ const core_1 = require("@oclif/core");
5
+ const data_provider_1 = require("@skrr-ai/data-provider");
6
+ const base_command_1 = require("../../base-command");
7
+ const review_1 = require("./review");
8
+ const REVIEW_LIST_MAX = 50;
9
+ function reviewerName(review) {
10
+ return review.reviewer?.displayName || review.reviewer?.username || 'a former user';
11
+ }
12
+ /** Plain-text lines for one written review and the publisher's reply to it. */
13
+ function describeReview(review) {
14
+ const when = (review.reviewedAt || review.updatedAt || review.createdAt || '').slice(0, 10);
15
+ const lines = [
16
+ `${review.id} ${review.rating}/${data_provider_1.STORE_AGENT_RATING_MAX} ${(0, review_1.describeRatedRelease)(review.ratedRelease)} by ${reviewerName(review)}${when ? ` ${when}` : ''}`,
17
+ ];
18
+ if (review.review)
19
+ lines.push(` ${review.review}`);
20
+ const reply = review.publisherReply;
21
+ if (reply) {
22
+ lines.push(` Publisher reply${reply.predatesReviewEdit ? ' (written before the review was last edited)' : ''}: ${reply.body}`);
23
+ }
24
+ return lines;
25
+ }
26
+ /**
27
+ * Read a Store agent's written reviews, or your own rating of it, and — as the
28
+ * agent's publisher — answer a review publicly.
29
+ *
30
+ * Star-only ratings are not listed; they are anonymous contributions to the
31
+ * average. Only the agent's author may reply, and the server decides that
32
+ * (`viewerIsPublisher`), not this command.
33
+ */
34
+ class StoreReviews extends base_command_1.BaseCommand {
35
+ static description = "List a Store agent's written reviews, show your own rating, or reply to a review as its publisher";
36
+ static examples = [
37
+ '<%= config.bin %> store reviews agent_abc',
38
+ '<%= config.bin %> store reviews agent_abc --limit 20 --cursor <next-cursor>',
39
+ '<%= config.bin %> store reviews agent_abc --mine',
40
+ '<%= config.bin %> store reviews agent_abc --reply <review-id> --body "Fixed in 1.4.1, thanks"',
41
+ '<%= config.bin %> store reviews agent_abc --remove-reply <review-id>',
42
+ ];
43
+ static args = {
44
+ agentId: core_1.Args.string({
45
+ description: 'The Store agent (or, to reply, your own published agent)',
46
+ required: true,
47
+ ignoreStdin: true,
48
+ }),
49
+ };
50
+ static flags = {
51
+ limit: core_1.Flags.integer({
52
+ description: `Reviews per page (max ${REVIEW_LIST_MAX})`,
53
+ min: 1,
54
+ max: REVIEW_LIST_MAX,
55
+ }),
56
+ cursor: core_1.Flags.string({ description: 'Continue from the cursor a previous page printed' }),
57
+ mine: core_1.Flags.boolean({
58
+ description: 'Show only your own rating and review of this agent',
59
+ exclusive: ['reply', 'remove-reply', 'limit', 'cursor'],
60
+ }),
61
+ reply: core_1.Flags.string({
62
+ description: 'Review id to answer publicly (the agent publisher only); needs --body',
63
+ exclusive: ['remove-reply', 'mine', 'limit', 'cursor'],
64
+ dependsOn: ['body'],
65
+ }),
66
+ body: core_1.Flags.string({
67
+ description: `Reply text (at most ${data_provider_1.STORE_AGENT_REVIEW_MAX_LENGTH} characters). Replaces any earlier reply`,
68
+ dependsOn: ['reply'],
69
+ }),
70
+ 'remove-reply': core_1.Flags.string({
71
+ description: 'Review id whose publisher reply to withdraw (the agent publisher only)',
72
+ exclusive: ['reply', 'mine', 'limit', 'cursor'],
73
+ }),
74
+ json: core_1.Flags.boolean({ description: 'Output JSON' }),
75
+ };
76
+ async run() {
77
+ this.requireAuth();
78
+ const { args, flags } = await this.parse(StoreReviews);
79
+ const agentId = args.agentId;
80
+ if (flags.reply !== undefined) {
81
+ const body = (flags.body ?? '').trim();
82
+ if (!body)
83
+ this.error('--body is empty; a reply needs text.', { exit: 2 });
84
+ if (body.length > data_provider_1.STORE_AGENT_REVIEW_MAX_LENGTH) {
85
+ this.error(`--body is ${body.length} characters; a reply holds at most ${data_provider_1.STORE_AGENT_REVIEW_MAX_LENGTH}.`, { exit: 2 });
86
+ }
87
+ try {
88
+ const res = await data_provider_1.dataService.replyToAgentReview({
89
+ agentId,
90
+ reviewId: flags.reply,
91
+ body,
92
+ });
93
+ if (flags.json) {
94
+ this.log(JSON.stringify(res, null, 2));
95
+ return;
96
+ }
97
+ this.log(`Replied to review ${res.review.id}.`);
98
+ describeReview(res.review).forEach((line) => this.log(line));
99
+ }
100
+ catch (err) {
101
+ this.handleApiError(err, { subject: `Replying to review ${flags.reply}` });
102
+ }
103
+ return;
104
+ }
105
+ if (flags['remove-reply'] !== undefined) {
106
+ try {
107
+ const res = await data_provider_1.dataService.deleteAgentReviewReply({
108
+ agentId,
109
+ reviewId: flags['remove-reply'],
110
+ });
111
+ this.log(flags.json
112
+ ? JSON.stringify(res, null, 2)
113
+ : `Withdrew your reply to review ${res.review.id}.`);
114
+ }
115
+ catch (err) {
116
+ this.handleApiError(err, { subject: `Withdrawing the reply to ${flags['remove-reply']}` });
117
+ }
118
+ return;
119
+ }
120
+ if (flags.mine) {
121
+ try {
122
+ const res = await data_provider_1.dataService.getAgentUserRating(agentId);
123
+ if (flags.json) {
124
+ this.log(JSON.stringify(res, null, 2));
125
+ return;
126
+ }
127
+ if (res.userRating == null) {
128
+ this.log(`You have not rated ${agentId}. Rate it with: skrr store review ${agentId} --rating <1-5>`);
129
+ return;
130
+ }
131
+ this.log(`You rated ${agentId} ${res.userRating}/${data_provider_1.STORE_AGENT_RATING_MAX} on ${(0, review_1.describeRatedRelease)(res.ratedRelease)}.`);
132
+ this.log(res.userReview ? `Review: ${res.userReview}` : 'No written review.');
133
+ }
134
+ catch (err) {
135
+ this.handleApiError(err, { subject: `Reading your rating of ${agentId}` });
136
+ }
137
+ return;
138
+ }
139
+ try {
140
+ const res = await data_provider_1.dataService.getStoreAgentReviews(agentId, {
141
+ ...(flags.limit !== undefined ? { limit: flags.limit } : {}),
142
+ ...(flags.cursor ? { cursor: flags.cursor } : {}),
143
+ });
144
+ if (flags.json) {
145
+ this.log(JSON.stringify(res, null, 2));
146
+ return;
147
+ }
148
+ if (res.data.length === 0) {
149
+ this.log(`No written reviews of ${agentId}${flags.cursor ? ' on this page' : ''}.`);
150
+ }
151
+ res.data.forEach((review, index) => {
152
+ if (index > 0)
153
+ this.log('');
154
+ describeReview(review).forEach((line) => this.log(line));
155
+ });
156
+ if (res.has_more && res.next_cursor) {
157
+ this.log('');
158
+ this.log(`More: skrr store reviews ${agentId} --cursor ${res.next_cursor}`);
159
+ }
160
+ if (res.viewerIsPublisher && res.data.length > 0) {
161
+ this.log('');
162
+ this.log(`You publish this agent. Answer a review with: skrr store reviews ${agentId} --reply <review-id> --body "..."`);
163
+ }
164
+ }
165
+ catch (err) {
166
+ this.handleApiError(err, { subject: `Listing reviews of ${agentId}` });
167
+ }
168
+ }
169
+ }
170
+ exports.default = StoreReviews;
@@ -0,0 +1,20 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * Tip a public listing (SVC10). A tip is charged once from your balance under
4
+ * the fee schedule's tipping rate and grants nothing. `--tip-id` is the
5
+ * idempotency identity: repeat it to retry the SAME tip safely; omit it and
6
+ * every invocation is a new charge.
7
+ */
8
+ export default class StoreTip extends BaseCommand {
9
+ static description: string;
10
+ static examples: string[];
11
+ static args: {
12
+ agentId: import("@oclif/core/lib/interfaces").Arg<string, Record<string, unknown>>;
13
+ };
14
+ static flags: {
15
+ amount: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
16
+ 'tip-id': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
17
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
18
+ };
19
+ run(): Promise<void>;
20
+ }
@@ -0,0 +1,57 @@
1
+ "use strict";
2
+ Object.defineProperty(exports, "__esModule", { value: true });
3
+ const node_crypto_1 = require("node:crypto");
4
+ const core_1 = require("@oclif/core");
5
+ const data_provider_1 = require("@skrr-ai/data-provider");
6
+ const base_command_1 = require("../../base-command");
7
+ const usd_micros_1 = require("../../lib/usd-micros");
8
+ /**
9
+ * Tip a public listing (SVC10). A tip is charged once from your balance under
10
+ * the fee schedule's tipping rate and grants nothing. `--tip-id` is the
11
+ * idempotency identity: repeat it to retry the SAME tip safely; omit it and
12
+ * every invocation is a new charge.
13
+ */
14
+ class StoreTip extends base_command_1.BaseCommand {
15
+ static description = 'Tip the publisher of a public store listing — charged once, grants no entitlement';
16
+ static examples = [
17
+ '<%= config.bin %> store tip agent_abc123 --amount 5',
18
+ '<%= config.bin %> store tip agent_abc123 --amount 2.50 --tip-id my-retry-key',
19
+ ];
20
+ static args = {
21
+ agentId: core_1.Args.string({
22
+ description: 'Store listing (agent) id',
23
+ required: true,
24
+ ignoreStdin: true,
25
+ }),
26
+ };
27
+ static flags = {
28
+ amount: core_1.Flags.string({ description: 'Amount in USD, e.g. 5 or 2.50', required: true }),
29
+ 'tip-id': core_1.Flags.string({
30
+ description: 'Idempotency key — reuse it to retry the same tip without charging twice',
31
+ }),
32
+ json: core_1.Flags.boolean({ description: 'Output as JSON' }),
33
+ };
34
+ async run() {
35
+ this.requireAuth();
36
+ const { args, flags } = await this.parse(StoreTip);
37
+ const amountMicros = (0, usd_micros_1.usdToMicros)(flags.amount);
38
+ if (amountMicros === null) {
39
+ this.error(`--amount must be a positive USD amount with at most 2 decimals (got "${flags.amount}")`);
40
+ }
41
+ const tipId = flags['tip-id'] ?? (0, node_crypto_1.randomUUID)();
42
+ try {
43
+ const result = await data_provider_1.dataService.tipStoreAgent(args.agentId, { amountMicros, tipId });
44
+ if (flags.json) {
45
+ this.log(JSON.stringify(result, null, 2));
46
+ return;
47
+ }
48
+ this.log(result.replayed
49
+ ? `Already tipped under ${tipId}; nothing new was charged.`
50
+ : `Tipped $${(result.amountMicros / 1_000_000).toFixed(2)} (tip ${tipId}).`);
51
+ }
52
+ catch (err) {
53
+ this.handleApiError(err);
54
+ }
55
+ }
56
+ }
57
+ exports.default = StoreTip;
@@ -0,0 +1,28 @@
1
+ import { BaseCommand } from '../../base-command';
2
+ /**
3
+ * `skrr tokens create` — mint a personal access token (`osk_pat_*`, OSK-13208)
4
+ * for CI. The secret is printed EXACTLY ONCE.
5
+ *
6
+ * Creating one needs a sign-in session (a token cannot mint a token) and, for
7
+ * an account with two-factor authentication, a fresh authenticator code from
8
+ * that session. `--otp` / `--backup-code` supply it up front; at a terminal the
9
+ * command asks for it when the server does.
10
+ */
11
+ export default class TokensCreate extends BaseCommand {
12
+ static description: string;
13
+ static examples: string[];
14
+ static args: {
15
+ name: import("@oclif/core/lib/interfaces").Arg<string | undefined, Record<string, unknown>>;
16
+ };
17
+ static flags: {
18
+ name: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
19
+ scope: import("@oclif/core/lib/interfaces").OptionFlag<string, import("@oclif/core/lib/interfaces").CustomOptions>;
20
+ 'expires-in-days': import("@oclif/core/lib/interfaces").OptionFlag<number, import("@oclif/core/lib/interfaces").CustomOptions>;
21
+ otp: import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
22
+ 'backup-code': import("@oclif/core/lib/interfaces").OptionFlag<string | undefined, import("@oclif/core/lib/interfaces").CustomOptions>;
23
+ json: import("@oclif/core/lib/interfaces").BooleanFlag<boolean>;
24
+ };
25
+ run(): Promise<void>;
26
+ private verify;
27
+ private refuseStepUp;
28
+ }
@@ -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
+ }