impel-cli 0.8.0 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +33 -1
- package/package.json +1 -1
- package/src/cli.js +8 -1
- package/src/commands/pat.js +331 -0
package/README.md
CHANGED
|
@@ -121,6 +121,9 @@ impel update [--check] [--skip-apps] Update EVERYTHING: reinstall the CL
|
|
|
121
121
|
cascade to `app update all` and `skills sync all`
|
|
122
122
|
impel auth [--pat <pat>] [--gateway <url>] [--app <url>]
|
|
123
123
|
Store your Impel PAT + URLs
|
|
124
|
+
impel pat create --label <label> [options] Mint a personal PAT through the control plane
|
|
125
|
+
impel pat create --agent <agent-id> [options]
|
|
126
|
+
Mint a PAT associated with an org agent
|
|
124
127
|
impel token [--tenant <org>] Print a selected-tenant bearer
|
|
125
128
|
impel mcp Run the local MCP bridge used by Claude/Codex
|
|
126
129
|
impel claude [claude args...] Launch isolated Impel Claude Code
|
|
@@ -183,6 +186,35 @@ Stores your PAT, gateway base URL, and app/control-plane URL in `~/.config/impel
|
|
|
183
186
|
impel auth --pat impel_pat_xxxxxxxxxxxx --gateway https://gateway.useimpel.com --app https://www.useimpel.com
|
|
184
187
|
```
|
|
185
188
|
|
|
189
|
+
### `impel pat create ...` (alias `impel pat mint ...`)
|
|
190
|
+
|
|
191
|
+
Mints a new PAT through the app/control-plane API, authenticated by the PAT
|
|
192
|
+
already stored by `impel auth`. Identity remains the minting authority: the
|
|
193
|
+
control plane verifies the current owner, target-org membership, requested
|
|
194
|
+
scopes, product access, and (when applicable) agent access before it requests a
|
|
195
|
+
new secret. The gateway never mints or verifies PATs.
|
|
196
|
+
|
|
197
|
+
Use `--label` for a personal token or `--agent` for a token associated with an
|
|
198
|
+
org agent. Agent PATs are still personal credentials: they act as you, are
|
|
199
|
+
org-scoped, and use the same stable `Agent · <agent-id>` association as the agent
|
|
200
|
+
setup UI.
|
|
201
|
+
|
|
202
|
+
```sh
|
|
203
|
+
impel pat create --label "Rahul's laptop"
|
|
204
|
+
impel pat create --agent support-agent --org acme
|
|
205
|
+
impel pat create --label ci-automation --scopes codex-gateway,tasks --ttl-days 30 --json
|
|
206
|
+
```
|
|
207
|
+
|
|
208
|
+
`--org` defaults to the selected tenant (`--tenant` is an alias). `--scopes`
|
|
209
|
+
accepts `claude-code-gateway`, `codex-gateway`, and `tasks`; when omitted, the
|
|
210
|
+
control plane chooses the defaults allowed by your access tier. `--ttl-days`
|
|
211
|
+
accepts 1–365 and defaults to 90 days.
|
|
212
|
+
|
|
213
|
+
The new secret is returned once on stdout and is deliberately **not** written to
|
|
214
|
+
the config file. To rotate this CLI onto it, explicitly run
|
|
215
|
+
`impel auth --pat <new-pat>` after copying it. `--json` includes the raw secret,
|
|
216
|
+
so treat redirected output as a credential.
|
|
217
|
+
|
|
186
218
|
### `impel update`
|
|
187
219
|
|
|
188
220
|
One command brings everything current, in dependency order: the CLI itself
|
|
@@ -625,7 +657,7 @@ successful tenant-specific rebuild.
|
|
|
625
657
|
|
|
626
658
|
The official Claude and ChatGPT apps remain separate installations and keep
|
|
627
659
|
their normal accounts, updater, profiles, and bundle identities. Each CLI
|
|
628
|
-
release pins exact, tested vendor builds (v0.
|
|
660
|
+
release pins exact, tested vendor builds (v0.9.0 pins Claude `1.20186.9` and
|
|
629
661
|
ChatGPT `26.707.72221`) and their archive SHA-256 digests. The installer reuses
|
|
630
662
|
an existing official app only when its version exactly matches; otherwise it
|
|
631
663
|
downloads the pinned signed archive into Impel's managed vendor cache under
|
package/package.json
CHANGED
package/src/cli.js
CHANGED
|
@@ -2,6 +2,7 @@ import fs from "node:fs";
|
|
|
2
2
|
import { fileURLToPath } from "node:url";
|
|
3
3
|
|
|
4
4
|
import { cmdAuth } from "./commands/auth.js";
|
|
5
|
+
import { cmdPat } from "./commands/pat.js";
|
|
5
6
|
import { cmdToken } from "./commands/token.js";
|
|
6
7
|
import { cmdUse } from "./commands/use.js";
|
|
7
8
|
import { cmdStatus } from "./commands/status.js";
|
|
@@ -34,6 +35,8 @@ Run:
|
|
|
34
35
|
|
|
35
36
|
Manage:
|
|
36
37
|
impel auth [--pat <pat>] Store your PAT + URLs (--gateway/--app <url>)
|
|
38
|
+
impel pat create --label <label> Mint a personal PAT (--org/--scopes/--ttl-days)
|
|
39
|
+
impel pat create --agent <agent-id> Mint an org-scoped PAT associated with an agent
|
|
37
40
|
impel tenant list|current|use <org> List or select the active organization
|
|
38
41
|
impel tasks list|get|create|update|delete CRUD Impel tickets (aliases: task, ticket[s])
|
|
39
42
|
impel token [--tenant <org>] Print the selected-tenant bearer
|
|
@@ -59,7 +62,7 @@ Native profile switching (advanced; setup never touches native tools):
|
|
|
59
62
|
|
|
60
63
|
Env:
|
|
61
64
|
IMPEL_GATEWAY_URL Default gateway URL when --gateway isn't passed.
|
|
62
|
-
IMPEL_APP_URL Default app/control-plane URL for setup, auth, and tasks.
|
|
65
|
+
IMPEL_APP_URL Default app/control-plane URL for setup, auth, PATs, and tasks.
|
|
63
66
|
IMPEL_SKIP_UPDATE_CHECK=1 Silence launch-time update notices.
|
|
64
67
|
|
|
65
68
|
Config file:
|
|
@@ -101,6 +104,10 @@ export async function main(argv) {
|
|
|
101
104
|
case "auth":
|
|
102
105
|
return cmdAuth(rest);
|
|
103
106
|
|
|
107
|
+
case "pat":
|
|
108
|
+
case "pats":
|
|
109
|
+
return cmdPat(rest);
|
|
110
|
+
|
|
104
111
|
case "token":
|
|
105
112
|
return cmdToken(rest);
|
|
106
113
|
|
|
@@ -0,0 +1,331 @@
|
|
|
1
|
+
import { parseFlags } from "../args.js";
|
|
2
|
+
import {
|
|
3
|
+
loadConfig,
|
|
4
|
+
normalizeGatewayUrl,
|
|
5
|
+
redactSecretText,
|
|
6
|
+
resolveDefaultAppUrl,
|
|
7
|
+
} from "../config.js";
|
|
8
|
+
import {
|
|
9
|
+
fetchTenants,
|
|
10
|
+
normalizeTenantId,
|
|
11
|
+
PAT_SCOPE_CLAUDE,
|
|
12
|
+
PAT_SCOPE_CODEX,
|
|
13
|
+
PAT_SCOPE_TASKS,
|
|
14
|
+
} from "../tenants.js";
|
|
15
|
+
|
|
16
|
+
const AGENT_PAT_LABEL_PREFIX = "Agent · ";
|
|
17
|
+
const MAX_PAT_LABEL_LENGTH = 120;
|
|
18
|
+
const MAX_PAT_TTL_DAYS = 365;
|
|
19
|
+
const PAT_CREATE_PATH = "/api/cli/pats";
|
|
20
|
+
const PAT_TOKEN_RE = /^impel_pat_[A-Za-z0-9_-]+\.[A-Za-z0-9_-]+$/u;
|
|
21
|
+
const PAT_TOKEN_ID_RE = /^[A-Za-z0-9_-]{8,128}$/u;
|
|
22
|
+
const AGENT_ID_RE = /^[A-Za-z0-9][A-Za-z0-9._-]{0,111}$/u;
|
|
23
|
+
const CONTROL_CHARACTER_RE = /[\u0000-\u001F\u007F-\u009F]/u;
|
|
24
|
+
const PAT_SCOPES = [PAT_SCOPE_CLAUDE, PAT_SCOPE_CODEX, PAT_SCOPE_TASKS];
|
|
25
|
+
const PAT_SCOPE_SET = new Set(PAT_SCOPES);
|
|
26
|
+
const FLAG_NAMES = new Set([
|
|
27
|
+
"agent",
|
|
28
|
+
"app",
|
|
29
|
+
"help",
|
|
30
|
+
"json",
|
|
31
|
+
"label",
|
|
32
|
+
"org",
|
|
33
|
+
"scopes",
|
|
34
|
+
"tenant",
|
|
35
|
+
"ttl-days",
|
|
36
|
+
]);
|
|
37
|
+
|
|
38
|
+
const HELP = `impel pat - mint a personal access token through Impel
|
|
39
|
+
|
|
40
|
+
Usage:
|
|
41
|
+
impel pat create --label <label> [options]
|
|
42
|
+
impel pat create --agent <agent-id> [options]
|
|
43
|
+
|
|
44
|
+
Aliases:
|
|
45
|
+
impel pats ...
|
|
46
|
+
impel pat mint ...
|
|
47
|
+
|
|
48
|
+
Options:
|
|
49
|
+
--org <org> Target org. Defaults to the selected tenant.
|
|
50
|
+
--tenant <org> Alias for --org.
|
|
51
|
+
--scopes <csv> Requested scopes: ${PAT_SCOPES.join(", ")}.
|
|
52
|
+
Defaults are chosen by the control plane for your access tier.
|
|
53
|
+
--ttl-days <days> Expiry from 1 to ${MAX_PAT_TTL_DAYS} days. Default: 90.
|
|
54
|
+
--app <url> Override the app/control-plane URL for this command.
|
|
55
|
+
--json Print the created PAT and metadata as JSON.
|
|
56
|
+
|
|
57
|
+
The stored PAT authenticates this request. The control plane verifies the owner,
|
|
58
|
+
organization membership, requested scopes, and agent access before identity mints
|
|
59
|
+
the new secret. The new PAT is shown once and is not stored automatically.
|
|
60
|
+
`;
|
|
61
|
+
|
|
62
|
+
class PatCommandError extends Error {}
|
|
63
|
+
|
|
64
|
+
function fail(message) {
|
|
65
|
+
throw new PatCommandError(message);
|
|
66
|
+
}
|
|
67
|
+
|
|
68
|
+
function flagSpec() {
|
|
69
|
+
return {
|
|
70
|
+
agent: { type: "string" },
|
|
71
|
+
app: { type: "string" },
|
|
72
|
+
help: { type: "boolean" },
|
|
73
|
+
json: { type: "boolean" },
|
|
74
|
+
label: { type: "string" },
|
|
75
|
+
org: { type: "string" },
|
|
76
|
+
scopes: { type: "string" },
|
|
77
|
+
tenant: { type: "string" },
|
|
78
|
+
"ttl-days": { type: "string" },
|
|
79
|
+
};
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
function requireConfig(flags) {
|
|
83
|
+
const config = loadConfig();
|
|
84
|
+
if (!config?.pat) {
|
|
85
|
+
fail("impel pat: not authenticated. Run `impel setup` (or `impel auth`) first.");
|
|
86
|
+
}
|
|
87
|
+
|
|
88
|
+
const appUrl = normalizeGatewayUrl(flags.app || config.appUrl || resolveDefaultAppUrl());
|
|
89
|
+
let parsed;
|
|
90
|
+
try {
|
|
91
|
+
parsed = new URL(appUrl);
|
|
92
|
+
} catch {
|
|
93
|
+
fail("impel pat: --app must be an HTTP or HTTPS URL.");
|
|
94
|
+
}
|
|
95
|
+
if (
|
|
96
|
+
(parsed.protocol !== "http:" && parsed.protocol !== "https:")
|
|
97
|
+
|| parsed.username
|
|
98
|
+
|| parsed.password
|
|
99
|
+
|| parsed.pathname !== "/"
|
|
100
|
+
|| parsed.search
|
|
101
|
+
|| parsed.hash
|
|
102
|
+
) {
|
|
103
|
+
fail("impel pat: --app must be a bare HTTP or HTTPS origin without credentials, a path, query, or fragment.");
|
|
104
|
+
}
|
|
105
|
+
return { appUrl, config };
|
|
106
|
+
}
|
|
107
|
+
|
|
108
|
+
function requestedScopes(value) {
|
|
109
|
+
if (value === undefined) return undefined;
|
|
110
|
+
const requested = String(value)
|
|
111
|
+
.split(",")
|
|
112
|
+
.map((scope) => scope.trim())
|
|
113
|
+
.filter(Boolean);
|
|
114
|
+
if (requested.length === 0) {
|
|
115
|
+
fail("impel pat: --scopes must contain at least one scope.");
|
|
116
|
+
}
|
|
117
|
+
for (const scope of requested) {
|
|
118
|
+
if (!PAT_SCOPE_SET.has(scope)) {
|
|
119
|
+
fail(`impel pat: unknown scope "${redactSecretText(scope)}".`);
|
|
120
|
+
}
|
|
121
|
+
}
|
|
122
|
+
const requestedSet = new Set(requested);
|
|
123
|
+
return PAT_SCOPES.filter((scope) => requestedSet.has(scope));
|
|
124
|
+
}
|
|
125
|
+
|
|
126
|
+
function requestedTtlDays(value) {
|
|
127
|
+
if (value === undefined) return undefined;
|
|
128
|
+
if (!/^\d+$/u.test(String(value))) {
|
|
129
|
+
fail(`impel pat: --ttl-days must be an integer from 1 to ${MAX_PAT_TTL_DAYS}.`);
|
|
130
|
+
}
|
|
131
|
+
const days = Number(value);
|
|
132
|
+
if (!Number.isSafeInteger(days) || days < 1 || days > MAX_PAT_TTL_DAYS) {
|
|
133
|
+
fail(`impel pat: --ttl-days must be an integer from 1 to ${MAX_PAT_TTL_DAYS}.`);
|
|
134
|
+
}
|
|
135
|
+
return days;
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
function requestedSubject(flags) {
|
|
139
|
+
const agentId = flags.agent === undefined ? null : String(flags.agent).trim();
|
|
140
|
+
const personalLabel = flags.label === undefined ? null : String(flags.label).trim();
|
|
141
|
+
if (agentId !== null && personalLabel !== null) {
|
|
142
|
+
fail("impel pat create: use either --label for a personal PAT or --agent for an agent PAT, not both.");
|
|
143
|
+
}
|
|
144
|
+
if (agentId !== null) {
|
|
145
|
+
if (!AGENT_ID_RE.test(agentId)) {
|
|
146
|
+
fail("impel pat create: --agent must be a valid agent id (letters, numbers, dots, dashes, or underscores).");
|
|
147
|
+
}
|
|
148
|
+
const label = `${AGENT_PAT_LABEL_PREFIX}${agentId}`;
|
|
149
|
+
if (label.length > MAX_PAT_LABEL_LENGTH) {
|
|
150
|
+
fail(`impel pat create: the generated agent label exceeds ${MAX_PAT_LABEL_LENGTH} characters.`);
|
|
151
|
+
}
|
|
152
|
+
return { agentId, kind: "agent", label };
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
if (!personalLabel) {
|
|
156
|
+
fail("impel pat create: --label is required for a personal PAT; use --agent <id> for an agent PAT.");
|
|
157
|
+
}
|
|
158
|
+
if (personalLabel.length > MAX_PAT_LABEL_LENGTH || CONTROL_CHARACTER_RE.test(personalLabel)) {
|
|
159
|
+
fail(`impel pat create: --label must be 1 to ${MAX_PAT_LABEL_LENGTH} characters without control characters.`);
|
|
160
|
+
}
|
|
161
|
+
return { agentId: null, kind: "personal", label: personalLabel };
|
|
162
|
+
}
|
|
163
|
+
|
|
164
|
+
async function requestedOrgId(flags, config, appUrl) {
|
|
165
|
+
if (flags.org !== undefined && flags.tenant !== undefined && flags.org !== flags.tenant) {
|
|
166
|
+
fail("impel pat: --org and --tenant must name the same org when both are passed.");
|
|
167
|
+
}
|
|
168
|
+
const requested = flags.org ?? flags.tenant;
|
|
169
|
+
try {
|
|
170
|
+
if (requested !== undefined) return normalizeTenantId(requested);
|
|
171
|
+
if (config.tenantId) return normalizeTenantId(config.tenantId);
|
|
172
|
+
const listing = await fetchTenants({ ...config, appUrl });
|
|
173
|
+
return listing.defaultTenantId;
|
|
174
|
+
} catch (error) {
|
|
175
|
+
fail(`impel pat: could not resolve the target org: ${redactSecretText(error?.message || error)}`);
|
|
176
|
+
}
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
function timestamp(value, { nullable = false } = {}) {
|
|
180
|
+
if (nullable && value === null) return null;
|
|
181
|
+
if (!Number.isSafeInteger(value) || Number.isNaN(new Date(value).getTime())) return undefined;
|
|
182
|
+
return value;
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
function normalizeCreatedPat(payload, expected) {
|
|
186
|
+
const value = payload?.pat && typeof payload.pat === "object" ? payload.pat : payload;
|
|
187
|
+
if (!value || typeof value !== "object") return null;
|
|
188
|
+
const scopes = Array.isArray(value.scopes)
|
|
189
|
+
&& value.scopes.every((scope) => typeof scope === "string" && PAT_SCOPE_SET.has(scope))
|
|
190
|
+
? [...new Set(value.scopes)]
|
|
191
|
+
: null;
|
|
192
|
+
const createdAt = timestamp(value.createdAt);
|
|
193
|
+
const expiresAt = timestamp(value.expiresAt, { nullable: true });
|
|
194
|
+
if (
|
|
195
|
+
typeof value.token !== "string"
|
|
196
|
+
|| !PAT_TOKEN_RE.test(value.token)
|
|
197
|
+
|| typeof value.tokenId !== "string"
|
|
198
|
+
|| !PAT_TOKEN_ID_RE.test(value.tokenId)
|
|
199
|
+
|| value.orgId !== expected.orgId
|
|
200
|
+
|| value.label !== expected.label
|
|
201
|
+
|| !scopes
|
|
202
|
+
|| createdAt === undefined
|
|
203
|
+
|| expiresAt === undefined
|
|
204
|
+
) {
|
|
205
|
+
return null;
|
|
206
|
+
}
|
|
207
|
+
return {
|
|
208
|
+
token: value.token,
|
|
209
|
+
tokenId: value.tokenId,
|
|
210
|
+
orgId: value.orgId,
|
|
211
|
+
label: value.label,
|
|
212
|
+
scopes,
|
|
213
|
+
createdAt,
|
|
214
|
+
expiresAt,
|
|
215
|
+
};
|
|
216
|
+
}
|
|
217
|
+
|
|
218
|
+
async function createPat({ appUrl, currentPat, body }) {
|
|
219
|
+
const controller = new AbortController();
|
|
220
|
+
const timeout = setTimeout(() => controller.abort(), 10_000);
|
|
221
|
+
let response;
|
|
222
|
+
try {
|
|
223
|
+
response = await fetch(new URL(PAT_CREATE_PATH, appUrl), {
|
|
224
|
+
method: "POST",
|
|
225
|
+
headers: {
|
|
226
|
+
accept: "application/json",
|
|
227
|
+
authorization: `Bearer ${currentPat}`,
|
|
228
|
+
"content-type": "application/json",
|
|
229
|
+
},
|
|
230
|
+
body: JSON.stringify(body),
|
|
231
|
+
signal: controller.signal,
|
|
232
|
+
});
|
|
233
|
+
} catch (error) {
|
|
234
|
+
const message = error?.name === "AbortError" ? "request timed out" : error?.message || error;
|
|
235
|
+
fail(`impel pat: could not reach ${appUrl}: ${redactSecretText(message)}`);
|
|
236
|
+
} finally {
|
|
237
|
+
clearTimeout(timeout);
|
|
238
|
+
}
|
|
239
|
+
|
|
240
|
+
const text = await response.text();
|
|
241
|
+
let payload = null;
|
|
242
|
+
try {
|
|
243
|
+
payload = text ? JSON.parse(text) : null;
|
|
244
|
+
} catch {
|
|
245
|
+
payload = null;
|
|
246
|
+
}
|
|
247
|
+
if (!response.ok) {
|
|
248
|
+
if (response.status === 404) {
|
|
249
|
+
fail(`impel pat: PAT minting is not available on ${appUrl}; update the Impel control plane and try again.`);
|
|
250
|
+
}
|
|
251
|
+
const message = redactSecretText(payload?.error || `${response.status} ${response.statusText}`.trim());
|
|
252
|
+
fail(`impel pat: ${message}`);
|
|
253
|
+
}
|
|
254
|
+
|
|
255
|
+
const created = normalizeCreatedPat(payload, body);
|
|
256
|
+
if (!created) {
|
|
257
|
+
fail("impel pat: the control plane returned an invalid PAT creation response; the secret was not displayed.");
|
|
258
|
+
}
|
|
259
|
+
return created;
|
|
260
|
+
}
|
|
261
|
+
|
|
262
|
+
function printCreatedPat(created, subject, json) {
|
|
263
|
+
if (json) {
|
|
264
|
+
console.log(JSON.stringify({
|
|
265
|
+
kind: subject.kind,
|
|
266
|
+
...(subject.agentId ? { agentId: subject.agentId } : {}),
|
|
267
|
+
...created,
|
|
268
|
+
}, null, 2));
|
|
269
|
+
return;
|
|
270
|
+
}
|
|
271
|
+
|
|
272
|
+
if (subject.kind === "agent") {
|
|
273
|
+
console.log(`Created an agent PAT for ${subject.agentId} in ${created.orgId}.`);
|
|
274
|
+
} else {
|
|
275
|
+
console.log(`Created personal PAT ${JSON.stringify(created.label)} in ${created.orgId}.`);
|
|
276
|
+
}
|
|
277
|
+
console.log(`Scopes: ${created.scopes.join(", ") || "none"}`);
|
|
278
|
+
console.log(`Expires: ${created.expiresAt === null ? "never" : new Date(created.expiresAt).toISOString()}`);
|
|
279
|
+
console.log("");
|
|
280
|
+
console.log("Copy this token now. Impel cannot show the secret again:");
|
|
281
|
+
console.log(created.token);
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
export async function cmdPat(argv) {
|
|
285
|
+
const [action, ...rest] = argv;
|
|
286
|
+
if (!action || action === "help" || action === "--help" || action === "-h") {
|
|
287
|
+
console.log(HELP);
|
|
288
|
+
return;
|
|
289
|
+
}
|
|
290
|
+
if (action !== "create" && action !== "mint") {
|
|
291
|
+
console.error(`impel pat: unknown subcommand "${redactSecretText(action)}".`);
|
|
292
|
+
console.error(" Use `impel pat create --label <label>` or `impel pat create --agent <agent-id>`.");
|
|
293
|
+
process.exitCode = 1;
|
|
294
|
+
return;
|
|
295
|
+
}
|
|
296
|
+
|
|
297
|
+
const { flags, positionals } = parseFlags(rest, flagSpec());
|
|
298
|
+
try {
|
|
299
|
+
if (flags.help) {
|
|
300
|
+
console.log(HELP);
|
|
301
|
+
return;
|
|
302
|
+
}
|
|
303
|
+
const unknownFlag = Object.keys(flags).find((name) => !FLAG_NAMES.has(name));
|
|
304
|
+
if (unknownFlag) fail(`impel pat: unknown option --${redactSecretText(unknownFlag)}.`);
|
|
305
|
+
if (positionals.length > 0) {
|
|
306
|
+
fail(`impel pat create: unexpected argument "${redactSecretText(positionals[0])}".`);
|
|
307
|
+
}
|
|
308
|
+
|
|
309
|
+
const subject = requestedSubject(flags);
|
|
310
|
+
const scopes = requestedScopes(flags.scopes);
|
|
311
|
+
const ttlDays = requestedTtlDays(flags["ttl-days"]);
|
|
312
|
+
const { appUrl, config } = requireConfig(flags);
|
|
313
|
+
const orgId = await requestedOrgId(flags, config, appUrl);
|
|
314
|
+
const body = {
|
|
315
|
+
orgId,
|
|
316
|
+
label: subject.label,
|
|
317
|
+
...(subject.agentId ? { agentId: subject.agentId } : {}),
|
|
318
|
+
...(scopes ? { scopes } : {}),
|
|
319
|
+
...(ttlDays ? { ttlDays } : {}),
|
|
320
|
+
};
|
|
321
|
+
const created = await createPat({ appUrl, currentPat: config.pat, body });
|
|
322
|
+
printCreatedPat(created, subject, flags.json === true);
|
|
323
|
+
} catch (error) {
|
|
324
|
+
if (error instanceof PatCommandError) {
|
|
325
|
+
console.error(error.message);
|
|
326
|
+
process.exitCode = 1;
|
|
327
|
+
return;
|
|
328
|
+
}
|
|
329
|
+
throw error;
|
|
330
|
+
}
|
|
331
|
+
}
|