@trigguard/cli 0.1.2 → 0.1.3
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/commands/auth.d.ts +4 -0
- package/dist/commands/auth.js +227 -0
- package/dist/commands/login-web.js +2 -1
- package/dist/commands/session.js +88 -28
- package/dist/cp/apiKeys.d.ts +26 -0
- package/dist/cp/apiKeys.js +38 -0
- package/dist/cp/config.d.ts +1 -0
- package/dist/cp/config.js +41 -3
- package/dist/cp/credentials.d.ts +1 -0
- package/dist/cp/credentials.js +9 -1
- package/dist/cp/errors.d.ts +5 -0
- package/dist/cp/errors.js +139 -0
- package/dist/cp/sessionContext.d.ts +21 -0
- package/dist/cp/sessionContext.js +86 -0
- package/dist/tg/help.js +6 -2
- package/dist/tg.js +5 -0
- package/package.json +1 -1
|
@@ -0,0 +1,4 @@
|
|
|
1
|
+
export declare function runAuthList(args: string[]): Promise<void>;
|
|
2
|
+
export declare function runAuthRevoke(args: string[]): Promise<void>;
|
|
3
|
+
export declare function runAuthRotate(args: string[]): Promise<void>;
|
|
4
|
+
export declare function runAuth(args: string[]): Promise<void>;
|
|
@@ -0,0 +1,227 @@
|
|
|
1
|
+
import readline from "node:readline/promises";
|
|
2
|
+
import { stdin as input, stdout as output } from "node:process";
|
|
3
|
+
import { hasFlag } from "../tg/args.js";
|
|
4
|
+
import { formatControlPlaneAuthError, formatCorruptConfigError, formatNotLoggedInError, } from "../cp/errors.js";
|
|
5
|
+
import { formatKeyField, listApiKeys, revokeApiKeyById, rotateApiKey, } from "../cp/apiKeys.js";
|
|
6
|
+
import { loadConfig, saveConfig, configPath, configFileCorrupted } from "../cp/config.js";
|
|
7
|
+
import { envOverridesConfigApiKey } from "../cp/credentials.js";
|
|
8
|
+
import { resolveActiveApiKeyRecord } from "../cp/sessionContext.js";
|
|
9
|
+
import { resolveSessionSnapshot } from "./session.js";
|
|
10
|
+
function flagValue(args, name) {
|
|
11
|
+
const i = args.indexOf(name);
|
|
12
|
+
if (i === -1)
|
|
13
|
+
return undefined;
|
|
14
|
+
const next = args[i + 1];
|
|
15
|
+
if (next === undefined || next.startsWith("-"))
|
|
16
|
+
return undefined;
|
|
17
|
+
return next;
|
|
18
|
+
}
|
|
19
|
+
async function requireSession() {
|
|
20
|
+
if (configFileCorrupted()) {
|
|
21
|
+
throw new Error(formatCorruptConfigError(configPath()));
|
|
22
|
+
}
|
|
23
|
+
const config = loadConfig();
|
|
24
|
+
const snap = await resolveSessionSnapshot();
|
|
25
|
+
if (!snap.authenticated || !config.sessionToken) {
|
|
26
|
+
throw new Error(formatNotLoggedInError());
|
|
27
|
+
}
|
|
28
|
+
const orgId = snap.activeOrg?.orgId;
|
|
29
|
+
if (!orgId) {
|
|
30
|
+
throw new Error([
|
|
31
|
+
"No active workspace.",
|
|
32
|
+
"",
|
|
33
|
+
"Verify your email in the console, then run:",
|
|
34
|
+
" tg login",
|
|
35
|
+
].join("\n"));
|
|
36
|
+
}
|
|
37
|
+
return { config, orgId, orgName: snap.activeOrg?.name ?? orgId };
|
|
38
|
+
}
|
|
39
|
+
function keyRecordForList(key, currentKeyId) {
|
|
40
|
+
return {
|
|
41
|
+
keyId: key.keyId,
|
|
42
|
+
displayName: formatKeyField(key.displayName),
|
|
43
|
+
workspace: key.orgId,
|
|
44
|
+
created: formatKeyField(key.createdAt),
|
|
45
|
+
lastUsed: formatKeyField(key.lastUsedAt ?? undefined),
|
|
46
|
+
scopes: key.scopes?.length ? key.scopes : ["Not reported"],
|
|
47
|
+
status: formatKeyField(key.status ?? (key.revoked ? "revoked" : "active")),
|
|
48
|
+
currentMachine: currentKeyId === key.keyId,
|
|
49
|
+
expires: formatKeyField(key.expiresAt ?? undefined),
|
|
50
|
+
lastRotatedFrom: formatKeyField(key.rotatedFromKeyId ?? undefined),
|
|
51
|
+
};
|
|
52
|
+
}
|
|
53
|
+
export async function runAuthList(args) {
|
|
54
|
+
const json = hasFlag(args, "--json");
|
|
55
|
+
const { config, orgId } = await requireSession();
|
|
56
|
+
try {
|
|
57
|
+
const keys = await listApiKeys(config, orgId);
|
|
58
|
+
const activeKeyId = resolveActiveApiKeyRecord(keys, config)?.keyId ?? config.apiKeyId;
|
|
59
|
+
if (json) {
|
|
60
|
+
console.log(JSON.stringify({
|
|
61
|
+
orgId,
|
|
62
|
+
keys: keys.map((k) => keyRecordForList(k, activeKeyId)),
|
|
63
|
+
}, null, 2));
|
|
64
|
+
return;
|
|
65
|
+
}
|
|
66
|
+
if (!keys.length) {
|
|
67
|
+
console.log("No API keys in this workspace.");
|
|
68
|
+
return;
|
|
69
|
+
}
|
|
70
|
+
for (const key of keys) {
|
|
71
|
+
const current = activeKeyId === key.keyId ? " (this machine)" : "";
|
|
72
|
+
console.log(`- ${formatKeyField(key.displayName)}${current}`);
|
|
73
|
+
console.log(` key id: ${key.keyId}`);
|
|
74
|
+
console.log(` status: ${formatKeyField(key.status ?? (key.revoked ? "revoked" : "active"))}`);
|
|
75
|
+
console.log(` created: ${formatKeyField(key.createdAt)}`);
|
|
76
|
+
console.log(` last used: ${formatKeyField(key.lastUsedAt ?? undefined)}`);
|
|
77
|
+
console.log(` scopes: ${key.scopes?.join(", ") || "Not reported"}`);
|
|
78
|
+
}
|
|
79
|
+
}
|
|
80
|
+
catch (e) {
|
|
81
|
+
throw new Error(formatControlPlaneAuthError(e, "API key list"));
|
|
82
|
+
}
|
|
83
|
+
}
|
|
84
|
+
async function confirmPrompt(question) {
|
|
85
|
+
const rl = readline.createInterface({ input, output });
|
|
86
|
+
try {
|
|
87
|
+
const answer = (await rl.question(question)).trim().toLowerCase();
|
|
88
|
+
return answer === "y" || answer === "yes";
|
|
89
|
+
}
|
|
90
|
+
finally {
|
|
91
|
+
rl.close();
|
|
92
|
+
}
|
|
93
|
+
}
|
|
94
|
+
export async function runAuthRevoke(args) {
|
|
95
|
+
const json = hasFlag(args, "--json");
|
|
96
|
+
const yes = hasFlag(args, "--yes");
|
|
97
|
+
const keyId = flagValue(args, "--key") ?? flagValue(args, "--id");
|
|
98
|
+
const { config } = await requireSession();
|
|
99
|
+
const targetId = keyId ?? config.apiKeyId;
|
|
100
|
+
if (!targetId) {
|
|
101
|
+
throw new Error([
|
|
102
|
+
"No API key specified.",
|
|
103
|
+
"",
|
|
104
|
+
"Run:",
|
|
105
|
+
" tg auth list",
|
|
106
|
+
" tg auth revoke --key <key-id>",
|
|
107
|
+
].join("\n"));
|
|
108
|
+
}
|
|
109
|
+
if (!yes) {
|
|
110
|
+
if (input.isTTY) {
|
|
111
|
+
const ok = await confirmPrompt(`Revoke API key ${targetId}? [y/N] `);
|
|
112
|
+
if (!ok) {
|
|
113
|
+
console.log("Revoke cancelled.");
|
|
114
|
+
return;
|
|
115
|
+
}
|
|
116
|
+
}
|
|
117
|
+
else {
|
|
118
|
+
throw new Error([
|
|
119
|
+
"Revoke requires confirmation in non-interactive mode.",
|
|
120
|
+
"",
|
|
121
|
+
"TrigGuard will not revoke API keys without an explicit --yes flag.",
|
|
122
|
+
"",
|
|
123
|
+
"Run:",
|
|
124
|
+
` tg auth revoke --key ${targetId} --yes`,
|
|
125
|
+
].join("\n"));
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
try {
|
|
129
|
+
await revokeApiKeyById(config, targetId);
|
|
130
|
+
}
|
|
131
|
+
catch (e) {
|
|
132
|
+
throw new Error(formatControlPlaneAuthError(e, "API key revoke"));
|
|
133
|
+
}
|
|
134
|
+
const clearsLocal = config.apiKeyId === targetId;
|
|
135
|
+
if (clearsLocal) {
|
|
136
|
+
saveConfig({
|
|
137
|
+
...config,
|
|
138
|
+
apiKey: undefined,
|
|
139
|
+
apiKeyId: undefined,
|
|
140
|
+
apiKeyDisplayName: undefined,
|
|
141
|
+
});
|
|
142
|
+
}
|
|
143
|
+
if (json) {
|
|
144
|
+
console.log(JSON.stringify({ ok: true, revokedKeyId: targetId, clearedLocal: clearsLocal }, null, 2));
|
|
145
|
+
return;
|
|
146
|
+
}
|
|
147
|
+
console.log(`Revoked API key ${targetId}.`);
|
|
148
|
+
if (clearsLocal) {
|
|
149
|
+
console.log("Local machine key cleared from ~/.trigguard/config.json.");
|
|
150
|
+
console.log("Run: tg login");
|
|
151
|
+
}
|
|
152
|
+
else {
|
|
153
|
+
console.log("Run: tg auth list");
|
|
154
|
+
}
|
|
155
|
+
}
|
|
156
|
+
export async function runAuthRotate(args) {
|
|
157
|
+
const json = hasFlag(args, "--json");
|
|
158
|
+
const { config, orgId } = await requireSession();
|
|
159
|
+
const rotateId = flagValue(args, "--key") ?? flagValue(args, "--id") ?? config.apiKeyId ?? undefined;
|
|
160
|
+
let rotated;
|
|
161
|
+
try {
|
|
162
|
+
rotated = await rotateApiKey(config, orgId, rotateId ?? undefined);
|
|
163
|
+
}
|
|
164
|
+
catch (e) {
|
|
165
|
+
throw new Error(formatControlPlaneAuthError(e, "API key rotation"));
|
|
166
|
+
}
|
|
167
|
+
const updatesLocal = !rotateId || rotateId === config.apiKeyId;
|
|
168
|
+
const envOverrides = envOverridesConfigApiKey(config);
|
|
169
|
+
if (updatesLocal) {
|
|
170
|
+
saveConfig({
|
|
171
|
+
...config,
|
|
172
|
+
apiKey: rotated.rawKey,
|
|
173
|
+
apiKeyId: rotated.keyId,
|
|
174
|
+
apiKeyDisplayName: rotated.displayName,
|
|
175
|
+
});
|
|
176
|
+
}
|
|
177
|
+
if (json) {
|
|
178
|
+
console.log(JSON.stringify({
|
|
179
|
+
ok: true,
|
|
180
|
+
keyId: rotated.keyId,
|
|
181
|
+
displayName: rotated.displayName,
|
|
182
|
+
revokedKeyId: rotated.revokedKeyId ?? null,
|
|
183
|
+
localConfigUpdated: updatesLocal,
|
|
184
|
+
activeCredentialSource: envOverrides ? "env" : updatesLocal ? "config" : "remote",
|
|
185
|
+
envOverrideWarning: envOverrides && updatesLocal,
|
|
186
|
+
}, null, 2));
|
|
187
|
+
return;
|
|
188
|
+
}
|
|
189
|
+
console.log("API key rotated.");
|
|
190
|
+
console.log(`New key id: ${rotated.keyId}`);
|
|
191
|
+
if (updatesLocal && envOverrides) {
|
|
192
|
+
console.log("Local config updated, but TRIGGUARD_API_KEY overrides ~/.trigguard/config.json.");
|
|
193
|
+
console.log("Update or unset TRIGGUARD_API_KEY before running tg authorize.");
|
|
194
|
+
console.log("Run: unset TRIGGUARD_API_KEY");
|
|
195
|
+
}
|
|
196
|
+
else if (updatesLocal) {
|
|
197
|
+
console.log("Local config updated for this machine.");
|
|
198
|
+
console.log("Run: tg authorize --surface <surface> --actor <actor> --intent \"…\"");
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
console.log("Run: tg auth list");
|
|
202
|
+
}
|
|
203
|
+
}
|
|
204
|
+
export async function runAuth(args) {
|
|
205
|
+
const sub = args[0];
|
|
206
|
+
const rest = args.slice(1);
|
|
207
|
+
if (sub === "list") {
|
|
208
|
+
await runAuthList(rest);
|
|
209
|
+
return;
|
|
210
|
+
}
|
|
211
|
+
if (sub === "revoke") {
|
|
212
|
+
await runAuthRevoke(rest);
|
|
213
|
+
return;
|
|
214
|
+
}
|
|
215
|
+
if (sub === "rotate") {
|
|
216
|
+
await runAuthRotate(rest);
|
|
217
|
+
return;
|
|
218
|
+
}
|
|
219
|
+
throw new Error([
|
|
220
|
+
"Unknown auth subcommand.",
|
|
221
|
+
"",
|
|
222
|
+
"Run:",
|
|
223
|
+
" tg auth list",
|
|
224
|
+
" tg auth revoke [--key <id>] [--yes]",
|
|
225
|
+
" tg auth rotate [--key <id>]",
|
|
226
|
+
].join("\n"));
|
|
227
|
+
}
|
|
@@ -35,7 +35,8 @@ export async function runLoginWeb(args) {
|
|
|
35
35
|
if (!json) {
|
|
36
36
|
console.log("Opening browser to sign in to TrigGuard...");
|
|
37
37
|
console.log(`If the browser does not open, visit:\n ${started.verificationUrl}`);
|
|
38
|
-
console.log(
|
|
38
|
+
console.log("Waiting for approval…");
|
|
39
|
+
console.log("Once approved, your CLI will continue automatically.");
|
|
39
40
|
}
|
|
40
41
|
try {
|
|
41
42
|
await openBrowser(started.verificationUrl);
|
package/dist/commands/session.js
CHANGED
|
@@ -1,8 +1,10 @@
|
|
|
1
1
|
import readline from "node:readline/promises";
|
|
2
2
|
import { stdin as input, stdout as output } from "node:process";
|
|
3
3
|
import { ControlPlaneError, fetchDashboardOrgs, fetchMe, loginWithPassword, resolveActiveOrg, } from "../cp/client.js";
|
|
4
|
-
import { loadConfig, saveConfig, clearSession } from "../cp/config.js";
|
|
5
|
-
import {
|
|
4
|
+
import { loadConfig, saveConfig, clearSession, configFileCorrupted, configPath } from "../cp/config.js";
|
|
5
|
+
import { storedCliApiKeyForSession } from "../cp/credentials.js";
|
|
6
|
+
import { formatCorruptConfigError, formatNotLoggedInError, formatRevokedKeyGuidance, } from "../cp/errors.js";
|
|
7
|
+
import { recommendedNextCommand, resolveKeyVisibility } from "../cp/sessionContext.js";
|
|
6
8
|
import { defaultMachineLabel, ensureCliApiKey } from "../cp/provisionCliKey.js";
|
|
7
9
|
import { runLoginWeb } from "./login-web.js";
|
|
8
10
|
import { formatNextAuthorizeCommand, printActivationEstablished, } from "../tg/activationCopy.js";
|
|
@@ -205,28 +207,41 @@ export async function runLogout(args) {
|
|
|
205
207
|
}
|
|
206
208
|
export async function runWhoami(args) {
|
|
207
209
|
const json = hasFlag(args, "--json");
|
|
210
|
+
if (configFileCorrupted()) {
|
|
211
|
+
throw new Error(formatCorruptConfigError(configPath()));
|
|
212
|
+
}
|
|
208
213
|
const config = loadConfig();
|
|
209
214
|
const snap = await resolveSessionSnapshot();
|
|
210
|
-
const
|
|
211
|
-
const apiKeyConfigured = apiKeySource !== "none";
|
|
215
|
+
const key = await resolveKeyVisibility(config, snap);
|
|
212
216
|
if (!snap.authenticated || !snap.user) {
|
|
213
217
|
if (json) {
|
|
214
218
|
console.log(JSON.stringify({ authenticated: false, apiKeyConfigured: false }, null, 2));
|
|
215
219
|
}
|
|
216
220
|
else {
|
|
217
|
-
console.log(
|
|
221
|
+
console.log(formatNotLoggedInError());
|
|
218
222
|
}
|
|
219
|
-
process.exit(
|
|
223
|
+
process.exit(1);
|
|
220
224
|
}
|
|
221
225
|
if (json) {
|
|
222
226
|
console.log(JSON.stringify({
|
|
223
227
|
authenticated: true,
|
|
224
228
|
user: snap.user,
|
|
225
|
-
|
|
226
|
-
|
|
229
|
+
organization: snap.activeOrg?.name ?? null,
|
|
230
|
+
workspace: snap.activeOrg?.orgId ?? null,
|
|
231
|
+
plan: snap.activeOrg?.plan ?? null,
|
|
227
232
|
environment: snap.environment,
|
|
228
|
-
|
|
229
|
-
|
|
233
|
+
authSource: "session",
|
|
234
|
+
apiKeyConfigured: key.apiKeyConfigured,
|
|
235
|
+
apiKeySource: key.apiKeySource,
|
|
236
|
+
apiKeyId: key.apiKeyId,
|
|
237
|
+
apiKeyDisplayName: key.apiKeyDisplayName,
|
|
238
|
+
machineName: key.machineName,
|
|
239
|
+
configPath: key.configPath,
|
|
240
|
+
keyStatus: key.keyStatus,
|
|
241
|
+
keyCreated: key.keyCreated,
|
|
242
|
+
keyLastUsed: key.keyLastUsed,
|
|
243
|
+
keyExpires: key.keyExpires,
|
|
244
|
+
keyScopes: key.keyScopes,
|
|
230
245
|
}, null, 2));
|
|
231
246
|
return;
|
|
232
247
|
}
|
|
@@ -241,15 +256,50 @@ export async function runWhoami(args) {
|
|
|
241
256
|
console.log("Email verification required before workspace listing.");
|
|
242
257
|
}
|
|
243
258
|
console.log(`Environment: ${snap.environment}`);
|
|
244
|
-
console.log(`
|
|
245
|
-
console.log(`API key
|
|
259
|
+
console.log(`Auth source: session`);
|
|
260
|
+
console.log(`API key configured: ${key.apiKeyConfigured ? "yes" : "no"}`);
|
|
261
|
+
console.log(`API key source: ${key.apiKeySource}`);
|
|
262
|
+
console.log(`API key id: ${key.apiKeyId}`);
|
|
263
|
+
console.log(`API key name: ${key.apiKeyDisplayName}`);
|
|
264
|
+
console.log(`Machine name: ${key.machineName}`);
|
|
265
|
+
console.log(`Config path: ${key.configPath}`);
|
|
266
|
+
console.log(`Key status: ${key.keyStatus}`);
|
|
267
|
+
console.log(`Key created: ${key.keyCreated}`);
|
|
268
|
+
console.log(`Key last used: ${key.keyLastUsed}`);
|
|
269
|
+
console.log(`Key expires: ${key.keyExpires}`);
|
|
270
|
+
console.log(`Key scopes: ${key.keyScopes}`);
|
|
271
|
+
if (key.keyRevoked) {
|
|
272
|
+
console.log(formatRevokedKeyGuidance());
|
|
273
|
+
}
|
|
246
274
|
}
|
|
247
275
|
export async function runStatus(args) {
|
|
248
276
|
const json = hasFlag(args, "--json");
|
|
277
|
+
if (configFileCorrupted()) {
|
|
278
|
+
throw new Error(formatCorruptConfigError(configPath()));
|
|
279
|
+
}
|
|
249
280
|
const config = loadConfig();
|
|
250
281
|
const snap = await resolveSessionSnapshot();
|
|
251
|
-
const
|
|
252
|
-
const
|
|
282
|
+
const key = await resolveKeyVisibility(config, snap);
|
|
283
|
+
const next = recommendedNextCommand(snap, key);
|
|
284
|
+
let controlPlaneReachable = false;
|
|
285
|
+
if (snap.authenticated && config.sessionToken) {
|
|
286
|
+
try {
|
|
287
|
+
await fetch(`${config.controlPlaneUrl}/me`, {
|
|
288
|
+
headers: { Authorization: `Bearer ${config.sessionToken}`, Accept: "application/json" },
|
|
289
|
+
signal: AbortSignal.timeout(5_000),
|
|
290
|
+
}).then((r) => {
|
|
291
|
+
controlPlaneReachable = r.ok;
|
|
292
|
+
});
|
|
293
|
+
}
|
|
294
|
+
catch {
|
|
295
|
+
controlPlaneReachable = false;
|
|
296
|
+
}
|
|
297
|
+
}
|
|
298
|
+
const authorityStatus = snap.authenticated && key.apiKeyConfigured && !key.keyRevoked
|
|
299
|
+
? "ready"
|
|
300
|
+
: key.keyRevoked
|
|
301
|
+
? "key_revoked"
|
|
302
|
+
: "needs_login";
|
|
253
303
|
const payload = {
|
|
254
304
|
authentication: snap.authenticated ? "signed_in" : "signed_out",
|
|
255
305
|
organization: snap.activeOrg?.name ?? null,
|
|
@@ -259,25 +309,35 @@ export async function runStatus(args) {
|
|
|
259
309
|
apiEndpoint: snap.controlPlaneUrl,
|
|
260
310
|
emailVerified: Boolean(snap.user?.emailVerifiedAt),
|
|
261
311
|
user: snap.user?.email ?? null,
|
|
262
|
-
apiKeyConfigured,
|
|
263
|
-
apiKeySource,
|
|
264
|
-
|
|
312
|
+
apiKeyConfigured: key.apiKeyConfigured,
|
|
313
|
+
apiKeySource: key.apiKeySource,
|
|
314
|
+
apiKeyId: key.apiKeyId,
|
|
315
|
+
apiKeyDisplayName: key.apiKeyDisplayName,
|
|
316
|
+
keyStatus: key.keyStatus,
|
|
317
|
+
controlPlaneReachable,
|
|
318
|
+
authorityStatus,
|
|
265
319
|
verificationStatus: "available",
|
|
320
|
+
recommendedNextCommand: next,
|
|
266
321
|
};
|
|
267
322
|
if (json) {
|
|
268
323
|
console.log(JSON.stringify(payload, null, 2));
|
|
269
324
|
return;
|
|
270
325
|
}
|
|
271
326
|
console.log("TrigGuard CLI status");
|
|
272
|
-
console.log(` Authentication:
|
|
273
|
-
console.log(` User:
|
|
274
|
-
console.log(` Organization:
|
|
275
|
-
console.log(` Workspace:
|
|
276
|
-
console.log(` Plan:
|
|
277
|
-
console.log(` Environment:
|
|
278
|
-
console.log(` API endpoint:
|
|
279
|
-
console.log(`
|
|
280
|
-
console.log(`
|
|
281
|
-
console.log(`
|
|
282
|
-
console.log(`
|
|
327
|
+
console.log(` Authentication: ${payload.authentication}`);
|
|
328
|
+
console.log(` User: ${payload.user ?? "—"}`);
|
|
329
|
+
console.log(` Organization: ${payload.organization ?? "—"}`);
|
|
330
|
+
console.log(` Workspace: ${payload.workspace ?? "—"}`);
|
|
331
|
+
console.log(` Plan: ${payload.plan ?? "—"}`);
|
|
332
|
+
console.log(` Environment: ${payload.environment}`);
|
|
333
|
+
console.log(` API endpoint: ${payload.apiEndpoint}`);
|
|
334
|
+
console.log(` Control plane: ${controlPlaneReachable ? "reachable" : "unreachable"}`);
|
|
335
|
+
console.log(` Email verified: ${payload.emailVerified ? "yes" : "no"}`);
|
|
336
|
+
console.log(` API key: ${key.apiKeyConfigured ? `configured (${key.apiKeySource})` : "not configured"}`);
|
|
337
|
+
console.log(` API key id: ${key.apiKeyId}`);
|
|
338
|
+
console.log(` API key name: ${key.apiKeyDisplayName}`);
|
|
339
|
+
console.log(` Key status: ${key.keyStatus}`);
|
|
340
|
+
console.log(` Authority readiness: ${authorityStatus}`);
|
|
341
|
+
console.log(` Verification: ${payload.verificationStatus}`);
|
|
342
|
+
console.log(` Next command: ${next}`);
|
|
283
343
|
}
|
|
@@ -0,0 +1,26 @@
|
|
|
1
|
+
import type { TrigGuardCliConfig } from "./types.js";
|
|
2
|
+
export interface ApiKeyPublicRecord {
|
|
3
|
+
readonly keyId: string;
|
|
4
|
+
readonly orgId: string;
|
|
5
|
+
readonly keyPrefix?: string;
|
|
6
|
+
readonly status?: string;
|
|
7
|
+
readonly displayName?: string;
|
|
8
|
+
readonly environment?: string;
|
|
9
|
+
readonly createdAt?: string;
|
|
10
|
+
readonly lastUsedAt?: string | null;
|
|
11
|
+
readonly expiresAt?: string | null;
|
|
12
|
+
readonly revokedAt?: string | null;
|
|
13
|
+
readonly rotatedFromKeyId?: string | null;
|
|
14
|
+
readonly revoked?: boolean;
|
|
15
|
+
readonly scopes?: readonly string[];
|
|
16
|
+
}
|
|
17
|
+
export declare function listApiKeys(config: TrigGuardCliConfig, orgId: string): Promise<ApiKeyPublicRecord[]>;
|
|
18
|
+
export declare function revokeApiKeyById(config: TrigGuardCliConfig, keyId: string): Promise<void>;
|
|
19
|
+
export declare function rotateApiKey(config: TrigGuardCliConfig, orgId: string, keyId?: string): Promise<{
|
|
20
|
+
rawKey: string;
|
|
21
|
+
keyId: string;
|
|
22
|
+
displayName: string;
|
|
23
|
+
revokedKeyId?: string;
|
|
24
|
+
}>;
|
|
25
|
+
/** Display field or explicit not-reported sentinel — never null/undefined in output. */
|
|
26
|
+
export declare function formatKeyField(value: string | null | undefined): string;
|
|
@@ -0,0 +1,38 @@
|
|
|
1
|
+
import { controlPlaneFetch } from "./client.js";
|
|
2
|
+
export async function listApiKeys(config, orgId) {
|
|
3
|
+
const data = await controlPlaneFetch(config, `/apikeys?orgId=${encodeURIComponent(orgId)}`, { method: "GET" });
|
|
4
|
+
return Array.isArray(data.keys) ? data.keys : [];
|
|
5
|
+
}
|
|
6
|
+
export async function revokeApiKeyById(config, keyId) {
|
|
7
|
+
await controlPlaneFetch(config, `/apikeys/${encodeURIComponent(keyId)}/revoke`, {
|
|
8
|
+
method: "POST",
|
|
9
|
+
headers: { "Content-Type": "application/json" },
|
|
10
|
+
body: JSON.stringify({}),
|
|
11
|
+
});
|
|
12
|
+
}
|
|
13
|
+
export async function rotateApiKey(config, orgId, keyId) {
|
|
14
|
+
const data = await controlPlaneFetch(config, "/apikeys/rotate", {
|
|
15
|
+
method: "POST",
|
|
16
|
+
headers: { "Content-Type": "application/json" },
|
|
17
|
+
body: JSON.stringify({ orgId, ...(keyId ? { keyId } : {}) }),
|
|
18
|
+
});
|
|
19
|
+
const rawKey = typeof data.rawKey === "string" ? data.rawKey : "";
|
|
20
|
+
const newKeyId = typeof data.keyId === "string" ? data.keyId : "";
|
|
21
|
+
const displayName = typeof data.displayName === "string" ? data.displayName : "TrigGuard CLI";
|
|
22
|
+
if (!rawKey.startsWith("tg_live_") || !newKeyId) {
|
|
23
|
+
throw new Error("rotate_response_invalid");
|
|
24
|
+
}
|
|
25
|
+
return {
|
|
26
|
+
rawKey,
|
|
27
|
+
keyId: newKeyId,
|
|
28
|
+
displayName,
|
|
29
|
+
revokedKeyId: typeof data.revokedKeyId === "string" ? data.revokedKeyId : undefined,
|
|
30
|
+
};
|
|
31
|
+
}
|
|
32
|
+
/** Display field or explicit not-reported sentinel — never null/undefined in output. */
|
|
33
|
+
export function formatKeyField(value) {
|
|
34
|
+
if (value === null || value === undefined || String(value).trim() === "") {
|
|
35
|
+
return "Not reported";
|
|
36
|
+
}
|
|
37
|
+
return String(value);
|
|
38
|
+
}
|
package/dist/cp/config.d.ts
CHANGED
|
@@ -1,6 +1,7 @@
|
|
|
1
1
|
import type { TrigGuardCliConfig, TrigGuardEnvironment } from "./types.js";
|
|
2
2
|
export declare function configDir(): string;
|
|
3
3
|
export declare function configPath(): string;
|
|
4
|
+
export declare function configFileCorrupted(): boolean;
|
|
4
5
|
export declare function defaultControlPlaneUrl(): string;
|
|
5
6
|
export declare function defaultEnvironment(): TrigGuardEnvironment;
|
|
6
7
|
export declare function loadConfig(): TrigGuardCliConfig;
|
package/dist/cp/config.js
CHANGED
|
@@ -12,6 +12,18 @@ export function configDir() {
|
|
|
12
12
|
export function configPath() {
|
|
13
13
|
return path.join(configDir(), "config.json");
|
|
14
14
|
}
|
|
15
|
+
export function configFileCorrupted() {
|
|
16
|
+
const file = configPath();
|
|
17
|
+
if (!fs.existsSync(file))
|
|
18
|
+
return false;
|
|
19
|
+
try {
|
|
20
|
+
JSON.parse(fs.readFileSync(file, "utf8"));
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
catch {
|
|
24
|
+
return true;
|
|
25
|
+
}
|
|
26
|
+
}
|
|
15
27
|
function parseEnvironment(raw) {
|
|
16
28
|
switch ((raw ?? "production").trim().toLowerCase()) {
|
|
17
29
|
case "staging":
|
|
@@ -43,12 +55,40 @@ function readConfigFile(base) {
|
|
|
43
55
|
return null;
|
|
44
56
|
}
|
|
45
57
|
}
|
|
58
|
+
function enforceConfigPermissions(dir, file) {
|
|
59
|
+
try {
|
|
60
|
+
fs.chmodSync(dir, 0o700);
|
|
61
|
+
}
|
|
62
|
+
catch {
|
|
63
|
+
/* best effort */
|
|
64
|
+
}
|
|
65
|
+
if (fs.existsSync(file)) {
|
|
66
|
+
try {
|
|
67
|
+
fs.chmodSync(file, 0o600);
|
|
68
|
+
}
|
|
69
|
+
catch {
|
|
70
|
+
/* best effort */
|
|
71
|
+
}
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
function writeConfigAtomic(payload) {
|
|
75
|
+
const dir = configDir();
|
|
76
|
+
const file = configPath();
|
|
77
|
+
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
78
|
+
const tmp = `${file}.${process.pid}.tmp`;
|
|
79
|
+
fs.writeFileSync(tmp, payload, { mode: 0o600 });
|
|
80
|
+
fs.renameSync(tmp, file);
|
|
81
|
+
enforceConfigPermissions(dir, file);
|
|
82
|
+
}
|
|
46
83
|
export function loadConfig() {
|
|
47
84
|
const base = {
|
|
48
85
|
version: CONFIG_VERSION,
|
|
49
86
|
controlPlaneUrl: defaultControlPlaneUrl(),
|
|
50
87
|
environment: defaultEnvironment(),
|
|
51
88
|
};
|
|
89
|
+
if (configFileCorrupted()) {
|
|
90
|
+
return base;
|
|
91
|
+
}
|
|
52
92
|
const parsed = readConfigFile(base);
|
|
53
93
|
if (parsed?.sessionRevoked) {
|
|
54
94
|
return {
|
|
@@ -85,8 +125,6 @@ export function loadConfig() {
|
|
|
85
125
|
};
|
|
86
126
|
}
|
|
87
127
|
export function saveConfig(config) {
|
|
88
|
-
const dir = configDir();
|
|
89
|
-
fs.mkdirSync(dir, { recursive: true, mode: 0o700 });
|
|
90
128
|
const payload = {
|
|
91
129
|
version: CONFIG_VERSION,
|
|
92
130
|
controlPlaneUrl: config.controlPlaneUrl.replace(/\/$/, ""),
|
|
@@ -99,7 +137,7 @@ export function saveConfig(config) {
|
|
|
99
137
|
apiKeyDisplayName: config.apiKeyDisplayName,
|
|
100
138
|
...(config.sessionToken ? {} : config.sessionRevoked ? { sessionRevoked: true } : {}),
|
|
101
139
|
};
|
|
102
|
-
|
|
140
|
+
writeConfigAtomic(`${JSON.stringify(payload, null, 2)}\n`);
|
|
103
141
|
}
|
|
104
142
|
export function clearSession(config) {
|
|
105
143
|
const next = {
|
package/dist/cp/credentials.d.ts
CHANGED
|
@@ -3,6 +3,7 @@ export type ApiKeySource = "env" | "config" | "none";
|
|
|
3
3
|
export declare function resolveApiKeyFromEnv(): string | undefined;
|
|
4
4
|
export declare function resolveApiKey(config: TrigGuardCliConfig): string | undefined;
|
|
5
5
|
export declare function resolveApiKeySource(config: TrigGuardCliConfig): ApiKeySource;
|
|
6
|
+
export declare function envOverridesConfigApiKey(config: TrigGuardCliConfig): boolean;
|
|
6
7
|
export declare function storedCliApiKeyForSession(config: TrigGuardCliConfig, user: {
|
|
7
8
|
email: string;
|
|
8
9
|
}, activeOrgId: string | undefined): string | null;
|
package/dist/cp/credentials.js
CHANGED
|
@@ -13,6 +13,9 @@ export function resolveApiKeySource(config) {
|
|
|
13
13
|
return "config";
|
|
14
14
|
return "none";
|
|
15
15
|
}
|
|
16
|
+
export function envOverridesConfigApiKey(config) {
|
|
17
|
+
return resolveApiKeySource(config) === "env";
|
|
18
|
+
}
|
|
16
19
|
export function storedCliApiKeyForSession(config, user, activeOrgId) {
|
|
17
20
|
if (!activeOrgId ||
|
|
18
21
|
config.user?.email !== user.email ||
|
|
@@ -25,7 +28,12 @@ export function storedCliApiKeyForSession(config, user, activeOrgId) {
|
|
|
25
28
|
export function missingConfiguredApiKeyMessage() {
|
|
26
29
|
return [
|
|
27
30
|
"No API key configured.",
|
|
28
|
-
"
|
|
31
|
+
"",
|
|
32
|
+
"TrigGuard CLI requires a machine-scoped API key to authorize execution.",
|
|
33
|
+
"",
|
|
34
|
+
"Run:",
|
|
35
|
+
" tg login",
|
|
36
|
+
"",
|
|
29
37
|
"Or export TRIGGUARD_API_KEY for CI/scripts.",
|
|
30
38
|
].join("\n");
|
|
31
39
|
}
|
|
@@ -0,0 +1,5 @@
|
|
|
1
|
+
export declare function formatNotLoggedInError(): string;
|
|
2
|
+
export declare function formatNoApiKeyError(): string;
|
|
3
|
+
export declare function formatCorruptConfigError(configPath: string): string;
|
|
4
|
+
export declare function formatControlPlaneAuthError(e: unknown, context: string): string;
|
|
5
|
+
export declare function formatRevokedKeyGuidance(): string;
|
|
@@ -0,0 +1,139 @@
|
|
|
1
|
+
import { ControlPlaneError } from "./client.js";
|
|
2
|
+
export function formatNotLoggedInError() {
|
|
3
|
+
return [
|
|
4
|
+
"Not signed in.",
|
|
5
|
+
"",
|
|
6
|
+
"TrigGuard CLI requires an authenticated session to manage API keys.",
|
|
7
|
+
"",
|
|
8
|
+
"Run:",
|
|
9
|
+
" tg login",
|
|
10
|
+
].join("\n");
|
|
11
|
+
}
|
|
12
|
+
export function formatNoApiKeyError() {
|
|
13
|
+
return [
|
|
14
|
+
"No API key configured.",
|
|
15
|
+
"",
|
|
16
|
+
"TrigGuard CLI requires a machine-scoped API key to authorize execution.",
|
|
17
|
+
"",
|
|
18
|
+
"Run:",
|
|
19
|
+
" tg login",
|
|
20
|
+
"",
|
|
21
|
+
"Or export TRIGGUARD_API_KEY for CI/scripts.",
|
|
22
|
+
].join("\n");
|
|
23
|
+
}
|
|
24
|
+
export function formatCorruptConfigError(configPath) {
|
|
25
|
+
return [
|
|
26
|
+
"Local TrigGuard config is corrupted and could not be parsed.",
|
|
27
|
+
"",
|
|
28
|
+
`Config path: ${configPath}`,
|
|
29
|
+
"",
|
|
30
|
+
"Run:",
|
|
31
|
+
" tg logout",
|
|
32
|
+
" tg login",
|
|
33
|
+
].join("\n");
|
|
34
|
+
}
|
|
35
|
+
export function formatControlPlaneAuthError(e, context) {
|
|
36
|
+
if (e instanceof ControlPlaneError) {
|
|
37
|
+
switch (e.code) {
|
|
38
|
+
case "forbidden":
|
|
39
|
+
return [
|
|
40
|
+
"Permission denied.",
|
|
41
|
+
"",
|
|
42
|
+
`TrigGuard control plane rejected this ${context} request.`,
|
|
43
|
+
"",
|
|
44
|
+
"Run:",
|
|
45
|
+
" tg whoami",
|
|
46
|
+
" tg status",
|
|
47
|
+
].join("\n");
|
|
48
|
+
case "email_verification_required":
|
|
49
|
+
return [
|
|
50
|
+
"Email verification required.",
|
|
51
|
+
"",
|
|
52
|
+
"Verify your email in the TrigGuard console, then retry.",
|
|
53
|
+
"",
|
|
54
|
+
"Run:",
|
|
55
|
+
" tg login",
|
|
56
|
+
].join("\n");
|
|
57
|
+
case "not_found":
|
|
58
|
+
case "key_not_found":
|
|
59
|
+
return [
|
|
60
|
+
"API key not found.",
|
|
61
|
+
"",
|
|
62
|
+
"The key may already be revoked or belongs to another workspace.",
|
|
63
|
+
"",
|
|
64
|
+
"Run:",
|
|
65
|
+
" tg auth list",
|
|
66
|
+
].join("\n");
|
|
67
|
+
case "no_active_key":
|
|
68
|
+
return [
|
|
69
|
+
"No active API key to rotate.",
|
|
70
|
+
"",
|
|
71
|
+
"Run:",
|
|
72
|
+
" tg login",
|
|
73
|
+
].join("\n");
|
|
74
|
+
case "rotate_failed":
|
|
75
|
+
return [
|
|
76
|
+
"API key rotation failed.",
|
|
77
|
+
"",
|
|
78
|
+
"The control plane could not rotate this key.",
|
|
79
|
+
"",
|
|
80
|
+
"Run:",
|
|
81
|
+
" tg auth list",
|
|
82
|
+
" tg login",
|
|
83
|
+
].join("\n");
|
|
84
|
+
default:
|
|
85
|
+
if (e.status === 401) {
|
|
86
|
+
return [
|
|
87
|
+
"Session expired or invalid.",
|
|
88
|
+
"",
|
|
89
|
+
"Run:",
|
|
90
|
+
" tg login",
|
|
91
|
+
].join("\n");
|
|
92
|
+
}
|
|
93
|
+
if (e.status >= 500) {
|
|
94
|
+
return [
|
|
95
|
+
"Control plane unavailable.",
|
|
96
|
+
"",
|
|
97
|
+
"TrigGuard could not reach the control plane API.",
|
|
98
|
+
"",
|
|
99
|
+
"Run:",
|
|
100
|
+
" tg status",
|
|
101
|
+
].join("\n");
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
}
|
|
105
|
+
if (e instanceof Error && e.message === "device_code_expired") {
|
|
106
|
+
return [
|
|
107
|
+
"Device authorization expired.",
|
|
108
|
+
"",
|
|
109
|
+
"Run:",
|
|
110
|
+
" tg login",
|
|
111
|
+
].join("\n");
|
|
112
|
+
}
|
|
113
|
+
if (e instanceof Error && e.message === "device_login_timeout") {
|
|
114
|
+
return [
|
|
115
|
+
"Timed out waiting for browser approval.",
|
|
116
|
+
"",
|
|
117
|
+
"Run:",
|
|
118
|
+
" tg login",
|
|
119
|
+
].join("\n");
|
|
120
|
+
}
|
|
121
|
+
const msg = e instanceof Error ? e.message : String(e);
|
|
122
|
+
return [
|
|
123
|
+
`${context} failed.`,
|
|
124
|
+
"",
|
|
125
|
+
msg,
|
|
126
|
+
"",
|
|
127
|
+
"Run:",
|
|
128
|
+
" tg status",
|
|
129
|
+
].join("\n");
|
|
130
|
+
}
|
|
131
|
+
export function formatRevokedKeyGuidance() {
|
|
132
|
+
return [
|
|
133
|
+
"Configured API key appears revoked or inactive.",
|
|
134
|
+
"",
|
|
135
|
+
"Run:",
|
|
136
|
+
" tg auth rotate",
|
|
137
|
+
" tg login",
|
|
138
|
+
].join("\n");
|
|
139
|
+
}
|
|
@@ -0,0 +1,21 @@
|
|
|
1
|
+
import { type ApiKeyPublicRecord } from "./apiKeys.js";
|
|
2
|
+
import { resolveApiKeySource } from "./credentials.js";
|
|
3
|
+
import type { SessionSnapshot, TrigGuardCliConfig } from "./types.js";
|
|
4
|
+
export interface KeyVisibility {
|
|
5
|
+
readonly apiKeyConfigured: boolean;
|
|
6
|
+
readonly apiKeySource: ReturnType<typeof resolveApiKeySource>;
|
|
7
|
+
readonly apiKeyId: string;
|
|
8
|
+
readonly apiKeyDisplayName: string;
|
|
9
|
+
readonly machineName: string;
|
|
10
|
+
readonly configPath: string;
|
|
11
|
+
readonly keyStatus: string;
|
|
12
|
+
readonly keyCreated: string;
|
|
13
|
+
readonly keyLastUsed: string;
|
|
14
|
+
readonly keyExpires: string;
|
|
15
|
+
readonly keyScopes: string;
|
|
16
|
+
readonly keyRevoked: boolean;
|
|
17
|
+
}
|
|
18
|
+
export declare function machineNameFromConfig(config: TrigGuardCliConfig): string;
|
|
19
|
+
export declare function resolveActiveApiKeyRecord(keys: readonly ApiKeyPublicRecord[], config: TrigGuardCliConfig): ApiKeyPublicRecord | undefined;
|
|
20
|
+
export declare function resolveKeyVisibility(config: TrigGuardCliConfig, snap: SessionSnapshot): Promise<KeyVisibility>;
|
|
21
|
+
export declare function recommendedNextCommand(snap: SessionSnapshot, key: KeyVisibility): string;
|
|
@@ -0,0 +1,86 @@
|
|
|
1
|
+
import os from "node:os";
|
|
2
|
+
import { listApiKeys, formatKeyField } from "./apiKeys.js";
|
|
3
|
+
import { configPath } from "./config.js";
|
|
4
|
+
import { resolveApiKeyFromEnv, resolveApiKeySource } from "./credentials.js";
|
|
5
|
+
export function machineNameFromConfig(config) {
|
|
6
|
+
const label = config.apiKeyDisplayName?.match(/TrigGuard CLI \((.+)\)/)?.[1];
|
|
7
|
+
return label?.trim() || os.hostname().trim() || "cli";
|
|
8
|
+
}
|
|
9
|
+
function matchByEnvPrefix(keys, envKey) {
|
|
10
|
+
return keys.find((k) => {
|
|
11
|
+
const prefix = k.keyPrefix?.trim();
|
|
12
|
+
return Boolean(prefix && envKey.startsWith(prefix));
|
|
13
|
+
});
|
|
14
|
+
}
|
|
15
|
+
export function resolveActiveApiKeyRecord(keys, config) {
|
|
16
|
+
const apiKeySource = resolveApiKeySource(config);
|
|
17
|
+
const envKey = resolveApiKeyFromEnv();
|
|
18
|
+
if (apiKeySource === "env" && envKey) {
|
|
19
|
+
return matchByEnvPrefix(keys, envKey);
|
|
20
|
+
}
|
|
21
|
+
if (config.apiKeyId) {
|
|
22
|
+
return keys.find((k) => k.keyId === config.apiKeyId);
|
|
23
|
+
}
|
|
24
|
+
return undefined;
|
|
25
|
+
}
|
|
26
|
+
export async function resolveKeyVisibility(config, snap) {
|
|
27
|
+
const apiKeySource = resolveApiKeySource(config);
|
|
28
|
+
const apiKeyConfigured = apiKeySource !== "none";
|
|
29
|
+
const base = {
|
|
30
|
+
apiKeyConfigured,
|
|
31
|
+
apiKeySource,
|
|
32
|
+
apiKeyId: formatKeyField(config.apiKeyId),
|
|
33
|
+
apiKeyDisplayName: formatKeyField(config.apiKeyDisplayName),
|
|
34
|
+
machineName: machineNameFromConfig(config),
|
|
35
|
+
configPath: configPath(),
|
|
36
|
+
keyStatus: "Not reported",
|
|
37
|
+
keyCreated: "Not reported",
|
|
38
|
+
keyLastUsed: "Not reported",
|
|
39
|
+
keyExpires: "Not reported",
|
|
40
|
+
keyScopes: "Not reported",
|
|
41
|
+
keyRevoked: false,
|
|
42
|
+
};
|
|
43
|
+
if (!snap.authenticated || !snap.activeOrg?.orgId || !config.sessionToken) {
|
|
44
|
+
return base;
|
|
45
|
+
}
|
|
46
|
+
try {
|
|
47
|
+
const keys = await listApiKeys(config, snap.activeOrg.orgId);
|
|
48
|
+
const stored = resolveActiveApiKeyRecord(keys, config);
|
|
49
|
+
if (!stored) {
|
|
50
|
+
if (apiKeySource === "env") {
|
|
51
|
+
return {
|
|
52
|
+
...base,
|
|
53
|
+
apiKeyId: "Not reported",
|
|
54
|
+
apiKeyDisplayName: "Not reported",
|
|
55
|
+
};
|
|
56
|
+
}
|
|
57
|
+
return base;
|
|
58
|
+
}
|
|
59
|
+
return {
|
|
60
|
+
...base,
|
|
61
|
+
apiKeyId: stored.keyId,
|
|
62
|
+
apiKeyDisplayName: formatKeyField(stored.displayName ?? config.apiKeyDisplayName),
|
|
63
|
+
keyStatus: formatKeyField(stored.status ?? (stored.revoked ? "revoked" : "active")),
|
|
64
|
+
keyCreated: formatKeyField(stored.createdAt),
|
|
65
|
+
keyLastUsed: formatKeyField(stored.lastUsedAt ?? undefined),
|
|
66
|
+
keyExpires: formatKeyField(stored.expiresAt ?? undefined),
|
|
67
|
+
keyScopes: stored.scopes?.length ? stored.scopes.join(", ") : "Not reported",
|
|
68
|
+
keyRevoked: Boolean(stored.revoked || stored.status === "revoked"),
|
|
69
|
+
};
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
return base;
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
export function recommendedNextCommand(snap, key) {
|
|
76
|
+
if (!snap.authenticated)
|
|
77
|
+
return "tg login";
|
|
78
|
+
if (!key.apiKeyConfigured)
|
|
79
|
+
return "tg login";
|
|
80
|
+
if (key.keyRevoked) {
|
|
81
|
+
return key.apiKeySource === "env"
|
|
82
|
+
? "unset TRIGGUARD_API_KEY && tg login"
|
|
83
|
+
: "tg auth rotate";
|
|
84
|
+
}
|
|
85
|
+
return 'tg authorize --surface <surface> --actor <actor> --intent "…"';
|
|
86
|
+
}
|
package/dist/tg/help.js
CHANGED
|
@@ -13,8 +13,11 @@ COMMANDS
|
|
|
13
13
|
surfaces List registered execution surfaces
|
|
14
14
|
login Sign in (browser device flow by default)
|
|
15
15
|
logout Clear local session
|
|
16
|
-
whoami Current user and
|
|
17
|
-
status Auth,
|
|
16
|
+
whoami Current user, workspace, and API key metadata
|
|
17
|
+
status Auth, connectivity, and next-step guidance
|
|
18
|
+
auth list List workspace API keys (no raw secrets)
|
|
19
|
+
auth revoke Revoke a machine API key
|
|
20
|
+
auth rotate Rotate credentials safely
|
|
18
21
|
|
|
19
22
|
QUICK START
|
|
20
23
|
npm install -g @trigguard/cli
|
|
@@ -30,6 +33,7 @@ ENVIRONMENT
|
|
|
30
33
|
TRIGGUARD_CONTROL_PLANE_URL Control plane URL (session commands)
|
|
31
34
|
|
|
32
35
|
DOCS
|
|
36
|
+
docs/developer/CLI_SECURITY_MODEL_V1.md
|
|
33
37
|
docs/developer/TRIGGUARD_CLI_ZERO_CONFIG_AUTH.md
|
|
34
38
|
docs/developer/TRIGGUARD_QUICKSTART.md
|
|
35
39
|
`);
|
package/dist/tg.js
CHANGED
|
@@ -4,6 +4,7 @@ import { runTgInit } from "./commands/tg-init.js";
|
|
|
4
4
|
import { runTgSetup } from "./commands/tg-setup.js";
|
|
5
5
|
import { runTgSurfaces } from "./commands/tg-surfaces.js";
|
|
6
6
|
import { runTgVerify } from "./commands/tg-verify.js";
|
|
7
|
+
import { runAuth } from "./commands/auth.js";
|
|
7
8
|
import { runLogin, runLogout, runStatus, runWhoami } from "./commands/session.js";
|
|
8
9
|
import { printAuthorizeHelp, printInitHelp, printLoginHelp, printRootHelp, printSetupHelp, printSurfacesHelp, printVerifyHelp, } from "./tg/help.js";
|
|
9
10
|
async function main() {
|
|
@@ -72,6 +73,10 @@ async function main() {
|
|
|
72
73
|
await runStatus(rest);
|
|
73
74
|
return;
|
|
74
75
|
}
|
|
76
|
+
if (cmd === "auth") {
|
|
77
|
+
await runAuth(rest);
|
|
78
|
+
return;
|
|
79
|
+
}
|
|
75
80
|
}
|
|
76
81
|
catch (e) {
|
|
77
82
|
if (e instanceof Error && e.message === "login_cancelled") {
|