sentinelayer-cli 0.1.2 → 0.4.4
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 +998 -996
- package/bin/create-sentinelayer.js +5 -5
- package/bin/sentinelayer-cli.js +4 -4
- package/bin/sl.js +5 -5
- package/package.json +63 -54
- package/src/agents/jules/config/definition.js +209 -209
- package/src/agents/jules/config/system-prompt.js +175 -175
- package/src/agents/jules/error-intake.js +51 -51
- package/src/agents/jules/fix-cycle.js +377 -377
- package/src/agents/jules/loop.js +367 -367
- package/src/agents/jules/pulse.js +327 -319
- package/src/agents/jules/stream.js +186 -186
- package/src/agents/jules/swarm/file-scanner.js +74 -74
- package/src/agents/jules/swarm/index.js +11 -11
- package/src/agents/jules/swarm/orchestrator.js +362 -362
- package/src/agents/jules/swarm/pattern-hunter.js +123 -123
- package/src/agents/jules/swarm/sub-agent.js +308 -308
- package/src/agents/jules/tools/auth-audit.js +557 -222
- package/src/agents/jules/tools/dispatch.js +327 -327
- package/src/agents/jules/tools/file-edit.js +180 -180
- package/src/agents/jules/tools/file-read.js +100 -100
- package/src/agents/jules/tools/frontend-analyze.js +570 -570
- package/src/agents/jules/tools/glob.js +168 -168
- package/src/agents/jules/tools/grep.js +228 -228
- package/src/agents/jules/tools/index.js +29 -29
- package/src/agents/jules/tools/path-guards.js +161 -161
- package/src/agents/jules/tools/runtime-audit.js +503 -493
- package/src/agents/jules/tools/shell.js +383 -383
- package/src/agents/jules/tools/url-policy.js +100 -0
- package/src/ai/aidenid.js +972 -945
- package/src/ai/client.js +508 -508
- package/src/ai/domain-target-store.js +268 -268
- package/src/ai/identity-store.js +270 -270
- package/src/ai/site-store.js +145 -145
- package/src/audit/agents/architecture.js +180 -180
- package/src/audit/agents/compliance.js +179 -179
- package/src/audit/agents/documentation.js +165 -165
- package/src/audit/agents/performance.js +145 -145
- package/src/audit/agents/security.js +215 -215
- package/src/audit/agents/testing.js +172 -172
- package/src/audit/orchestrator.js +557 -557
- package/src/audit/package.js +204 -204
- package/src/audit/registry.js +284 -284
- package/src/audit/replay.js +103 -103
- package/src/auth/gate.js +45 -11
- package/src/auth/http.js +270 -113
- package/src/auth/service.js +891 -848
- package/src/auth/session-store.js +359 -345
- package/src/cli.js +252 -252
- package/src/commands/ai/identity-lifecycle.js +1338 -1337
- package/src/commands/ai/provision-governance.js +1272 -1246
- package/src/commands/ai/shared.js +147 -147
- package/src/commands/ai.js +11 -11
- package/src/commands/apply.js +12 -12
- package/src/commands/audit.js +1166 -1166
- package/src/commands/auth.js +375 -366
- package/src/commands/chat.js +191 -191
- package/src/commands/config.js +184 -184
- package/src/commands/cost.js +311 -311
- package/src/commands/daemon/core.js +850 -850
- package/src/commands/daemon/extended.js +1048 -1048
- package/src/commands/daemon/shared.js +213 -213
- package/src/commands/daemon.js +11 -11
- package/src/commands/guide.js +174 -174
- package/src/commands/ingest.js +58 -58
- package/src/commands/init.js +55 -55
- package/src/commands/legacy-args.js +10 -10
- package/src/commands/mcp.js +461 -404
- package/src/commands/omargate.js +15 -15
- package/src/commands/persona.js +20 -20
- package/src/commands/plugin.js +260 -260
- package/src/commands/policy.js +132 -132
- package/src/commands/prompt.js +238 -238
- package/src/commands/review.js +704 -704
- package/src/commands/scan.js +866 -788
- package/src/commands/spec.js +716 -716
- package/src/commands/swarm.js +651 -651
- package/src/commands/telemetry.js +202 -202
- package/src/commands/watch.js +510 -510
- package/src/config/agent-dictionary.js +182 -182
- package/src/config/io.js +56 -56
- package/src/config/paths.js +18 -18
- package/src/config/schema.js +55 -55
- package/src/config/service.js +184 -184
- package/src/cost/budget.js +235 -235
- package/src/cost/history.js +188 -188
- package/src/cost/tracker.js +171 -171
- package/src/daemon/artifact-lineage.js +534 -534
- package/src/daemon/assignment-ledger.js +770 -770
- package/src/daemon/ast-parser-layer.js +258 -258
- package/src/daemon/budget-governor.js +633 -633
- package/src/daemon/callgraph-overlay.js +646 -646
- package/src/daemon/error-worker.js +626 -626
- package/src/daemon/hybrid-mapper.js +929 -929
- package/src/daemon/jira-lifecycle.js +632 -632
- package/src/daemon/operator-control.js +657 -657
- package/src/daemon/reliability-lane.js +471 -471
- package/src/daemon/watchdog.js +971 -971
- package/src/guide/generator.js +316 -316
- package/src/ingest/engine.js +918 -918
- package/src/legacy-cli.js +2592 -2435
- package/src/mcp/registry.js +695 -695
- package/src/memory/blackboard.js +301 -301
- package/src/memory/retrieval.js +581 -581
- package/src/plugin/manifest.js +553 -553
- package/src/policy/packs.js +144 -144
- package/src/prompt/generator.js +118 -106
- package/src/review/ai-review.js +669 -669
- package/src/review/local-review.js +1295 -1284
- package/src/review/replay.js +235 -235
- package/src/review/report.js +664 -664
- package/src/review/spec-binding.js +487 -487
- package/src/scaffold/generator.js +67 -0
- package/src/scaffold/templates.js +150 -0
- package/src/scan/generator.js +418 -351
- package/src/scan/gh-secrets.js +107 -0
- package/src/spec/generator.js +519 -519
- package/src/spec/regenerate.js +237 -237
- package/src/spec/templates.js +91 -91
- package/src/swarm/dashboard.js +247 -247
- package/src/swarm/factory.js +363 -363
- package/src/swarm/pentest.js +934 -934
- package/src/swarm/registry.js +419 -419
- package/src/swarm/report.js +158 -158
- package/src/swarm/runtime.js +576 -576
- package/src/swarm/scenario-dsl.js +272 -272
- package/src/telemetry/ledger.js +302 -302
- package/src/telemetry/sync.js +107 -61
- package/src/ui/markdown.js +220 -220
|
@@ -1,345 +1,359 @@
|
|
|
1
|
-
import crypto from "node:crypto";
|
|
2
|
-
import os from "node:os";
|
|
3
|
-
import path from "node:path";
|
|
4
|
-
import fsp from "node:fs/promises";
|
|
5
|
-
import process from "node:process";
|
|
6
|
-
|
|
7
|
-
const CREDENTIALS_VERSION = 1;
|
|
8
|
-
const KEYRING_SERVICE = "sentinelayer-cli";
|
|
9
|
-
|
|
10
|
-
function nowIso() {
|
|
11
|
-
return new Date().toISOString();
|
|
12
|
-
}
|
|
13
|
-
|
|
14
|
-
function resolveHomeDir(homeDir) {
|
|
15
|
-
return path.resolve(String(homeDir || os.homedir()));
|
|
16
|
-
}
|
|
17
|
-
|
|
18
|
-
/**
|
|
19
|
-
* Resolve the deterministic credentials metadata file path for the current user/home override.
|
|
20
|
-
*
|
|
21
|
-
* @param {{ homeDir?: string }} [options]
|
|
22
|
-
* @returns {string}
|
|
23
|
-
*/
|
|
24
|
-
export function resolveCredentialsFilePath({ homeDir } = {}) {
|
|
25
|
-
const resolvedHome = resolveHomeDir(homeDir);
|
|
26
|
-
return path.join(resolvedHome, ".sentinelayer", "credentials.json");
|
|
27
|
-
}
|
|
28
|
-
|
|
29
|
-
function buildKeyringAccountName(apiUrl) {
|
|
30
|
-
const digest = crypto
|
|
31
|
-
.createHash("sha256")
|
|
32
|
-
.update(String(apiUrl || "").trim().toLowerCase())
|
|
33
|
-
.digest("hex")
|
|
34
|
-
.slice(0, 16);
|
|
35
|
-
return `default-${digest}`;
|
|
36
|
-
}
|
|
37
|
-
|
|
38
|
-
function normalizeUser(user = {}) {
|
|
39
|
-
return {
|
|
40
|
-
id: String(user.id || "").trim(),
|
|
41
|
-
githubUsername: String(user.githubUsername || user.github_username || "").trim(),
|
|
42
|
-
email: String(user.email || "").trim(),
|
|
43
|
-
avatarUrl: String(user.avatarUrl || user.avatar_url || "").trim(),
|
|
44
|
-
isAdmin: Boolean(user.isAdmin || user.is_admin),
|
|
45
|
-
};
|
|
46
|
-
}
|
|
47
|
-
|
|
48
|
-
function
|
|
49
|
-
return
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
|
|
53
|
-
|
|
54
|
-
|
|
55
|
-
|
|
56
|
-
|
|
57
|
-
|
|
58
|
-
|
|
59
|
-
|
|
60
|
-
|
|
61
|
-
|
|
62
|
-
|
|
63
|
-
|
|
64
|
-
|
|
65
|
-
|
|
66
|
-
|
|
67
|
-
.trim()
|
|
68
|
-
.
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
|
|
72
|
-
|
|
73
|
-
|
|
74
|
-
|
|
75
|
-
|
|
76
|
-
|
|
77
|
-
|
|
78
|
-
|
|
79
|
-
|
|
80
|
-
|
|
81
|
-
|
|
82
|
-
)
|
|
83
|
-
|
|
84
|
-
|
|
85
|
-
|
|
86
|
-
|
|
87
|
-
|
|
88
|
-
|
|
89
|
-
|
|
90
|
-
|
|
91
|
-
|
|
92
|
-
|
|
93
|
-
|
|
94
|
-
|
|
95
|
-
|
|
96
|
-
|
|
97
|
-
return
|
|
98
|
-
}
|
|
99
|
-
return
|
|
100
|
-
} catch
|
|
101
|
-
|
|
102
|
-
|
|
103
|
-
|
|
104
|
-
|
|
105
|
-
|
|
106
|
-
}
|
|
107
|
-
|
|
108
|
-
|
|
109
|
-
|
|
110
|
-
|
|
111
|
-
|
|
112
|
-
|
|
113
|
-
|
|
114
|
-
|
|
115
|
-
|
|
116
|
-
|
|
117
|
-
|
|
118
|
-
|
|
119
|
-
}
|
|
120
|
-
|
|
121
|
-
|
|
122
|
-
|
|
123
|
-
|
|
124
|
-
|
|
125
|
-
|
|
126
|
-
|
|
127
|
-
|
|
128
|
-
|
|
129
|
-
|
|
130
|
-
|
|
131
|
-
|
|
132
|
-
|
|
133
|
-
|
|
134
|
-
|
|
135
|
-
|
|
136
|
-
*
|
|
137
|
-
*
|
|
138
|
-
*
|
|
139
|
-
*
|
|
140
|
-
*
|
|
141
|
-
*
|
|
142
|
-
*
|
|
143
|
-
*
|
|
144
|
-
*
|
|
145
|
-
*
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
|
|
151
|
-
|
|
152
|
-
|
|
153
|
-
|
|
154
|
-
|
|
155
|
-
|
|
156
|
-
|
|
157
|
-
|
|
158
|
-
|
|
159
|
-
|
|
160
|
-
|
|
161
|
-
|
|
162
|
-
|
|
163
|
-
|
|
164
|
-
|
|
165
|
-
|
|
166
|
-
|
|
167
|
-
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
|
|
171
|
-
|
|
172
|
-
|
|
173
|
-
|
|
174
|
-
|
|
175
|
-
|
|
176
|
-
|
|
177
|
-
|
|
178
|
-
|
|
179
|
-
|
|
180
|
-
|
|
181
|
-
|
|
182
|
-
|
|
183
|
-
|
|
184
|
-
|
|
185
|
-
|
|
186
|
-
|
|
187
|
-
|
|
188
|
-
|
|
189
|
-
|
|
190
|
-
|
|
191
|
-
|
|
192
|
-
|
|
193
|
-
|
|
194
|
-
|
|
195
|
-
|
|
196
|
-
|
|
197
|
-
|
|
198
|
-
|
|
199
|
-
*
|
|
200
|
-
*
|
|
201
|
-
*
|
|
202
|
-
*
|
|
203
|
-
*
|
|
204
|
-
*
|
|
205
|
-
*
|
|
206
|
-
*
|
|
207
|
-
*
|
|
208
|
-
*
|
|
209
|
-
|
|
210
|
-
|
|
211
|
-
|
|
212
|
-
|
|
213
|
-
|
|
214
|
-
|
|
215
|
-
|
|
216
|
-
|
|
217
|
-
|
|
218
|
-
|
|
219
|
-
|
|
220
|
-
|
|
221
|
-
|
|
222
|
-
|
|
223
|
-
|
|
224
|
-
|
|
225
|
-
|
|
226
|
-
|
|
227
|
-
|
|
228
|
-
|
|
229
|
-
|
|
230
|
-
|
|
231
|
-
|
|
232
|
-
|
|
233
|
-
|
|
234
|
-
|
|
235
|
-
|
|
236
|
-
|
|
237
|
-
*
|
|
238
|
-
*
|
|
239
|
-
*
|
|
240
|
-
*
|
|
241
|
-
*
|
|
242
|
-
*
|
|
243
|
-
*
|
|
244
|
-
*
|
|
245
|
-
* user
|
|
246
|
-
*
|
|
247
|
-
*
|
|
248
|
-
*
|
|
249
|
-
*
|
|
250
|
-
*
|
|
251
|
-
*
|
|
252
|
-
*
|
|
253
|
-
*
|
|
254
|
-
*
|
|
255
|
-
|
|
256
|
-
|
|
257
|
-
|
|
258
|
-
|
|
259
|
-
|
|
260
|
-
|
|
261
|
-
|
|
262
|
-
|
|
263
|
-
|
|
264
|
-
|
|
265
|
-
|
|
266
|
-
|
|
267
|
-
|
|
268
|
-
|
|
269
|
-
|
|
270
|
-
|
|
271
|
-
|
|
272
|
-
|
|
273
|
-
|
|
274
|
-
|
|
275
|
-
|
|
276
|
-
|
|
277
|
-
|
|
278
|
-
|
|
279
|
-
|
|
280
|
-
|
|
281
|
-
const
|
|
282
|
-
|
|
283
|
-
|
|
284
|
-
|
|
285
|
-
|
|
286
|
-
|
|
287
|
-
|
|
288
|
-
|
|
289
|
-
|
|
290
|
-
});
|
|
291
|
-
|
|
292
|
-
|
|
293
|
-
|
|
294
|
-
|
|
295
|
-
|
|
296
|
-
|
|
297
|
-
|
|
298
|
-
|
|
299
|
-
|
|
300
|
-
|
|
301
|
-
|
|
302
|
-
|
|
303
|
-
|
|
304
|
-
}
|
|
305
|
-
|
|
306
|
-
|
|
307
|
-
|
|
308
|
-
|
|
309
|
-
|
|
310
|
-
|
|
311
|
-
|
|
312
|
-
|
|
313
|
-
|
|
314
|
-
|
|
315
|
-
|
|
316
|
-
|
|
317
|
-
|
|
318
|
-
}
|
|
319
|
-
|
|
320
|
-
|
|
321
|
-
|
|
322
|
-
|
|
323
|
-
|
|
324
|
-
|
|
325
|
-
|
|
326
|
-
|
|
327
|
-
|
|
328
|
-
|
|
329
|
-
|
|
330
|
-
|
|
331
|
-
|
|
332
|
-
|
|
333
|
-
|
|
334
|
-
|
|
335
|
-
|
|
336
|
-
|
|
337
|
-
|
|
338
|
-
|
|
339
|
-
|
|
340
|
-
|
|
341
|
-
|
|
342
|
-
|
|
343
|
-
|
|
344
|
-
|
|
345
|
-
|
|
1
|
+
import crypto from "node:crypto";
|
|
2
|
+
import os from "node:os";
|
|
3
|
+
import path from "node:path";
|
|
4
|
+
import fsp from "node:fs/promises";
|
|
5
|
+
import process from "node:process";
|
|
6
|
+
|
|
7
|
+
const CREDENTIALS_VERSION = 1;
|
|
8
|
+
const KEYRING_SERVICE = "sentinelayer-cli";
|
|
9
|
+
|
|
10
|
+
function nowIso() {
|
|
11
|
+
return new Date().toISOString();
|
|
12
|
+
}
|
|
13
|
+
|
|
14
|
+
function resolveHomeDir(homeDir) {
|
|
15
|
+
return path.resolve(String(homeDir || os.homedir()));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
/**
|
|
19
|
+
* Resolve the deterministic credentials metadata file path for the current user/home override.
|
|
20
|
+
*
|
|
21
|
+
* @param {{ homeDir?: string }} [options]
|
|
22
|
+
* @returns {string}
|
|
23
|
+
*/
|
|
24
|
+
export function resolveCredentialsFilePath({ homeDir } = {}) {
|
|
25
|
+
const resolvedHome = resolveHomeDir(homeDir);
|
|
26
|
+
return path.join(resolvedHome, ".sentinelayer", "credentials.json");
|
|
27
|
+
}
|
|
28
|
+
|
|
29
|
+
function buildKeyringAccountName(apiUrl) {
|
|
30
|
+
const digest = crypto
|
|
31
|
+
.createHash("sha256")
|
|
32
|
+
.update(String(apiUrl || "").trim().toLowerCase())
|
|
33
|
+
.digest("hex")
|
|
34
|
+
.slice(0, 16);
|
|
35
|
+
return `default-${digest}`;
|
|
36
|
+
}
|
|
37
|
+
|
|
38
|
+
function normalizeUser(user = {}) {
|
|
39
|
+
return {
|
|
40
|
+
id: String(user.id || "").trim(),
|
|
41
|
+
githubUsername: String(user.githubUsername || user.github_username || "").trim(),
|
|
42
|
+
email: String(user.email || "").trim(),
|
|
43
|
+
avatarUrl: String(user.avatarUrl || user.avatar_url || "").trim(),
|
|
44
|
+
isAdmin: Boolean(user.isAdmin || user.is_admin),
|
|
45
|
+
};
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
function normalizeAidenId(raw) {
|
|
49
|
+
if (!raw || typeof raw !== "object") return null;
|
|
50
|
+
const orgId = String(raw.orgId || "").trim();
|
|
51
|
+
const projectId = String(raw.projectId || "").trim();
|
|
52
|
+
if (!orgId && !projectId) return null;
|
|
53
|
+
return {
|
|
54
|
+
orgId: orgId || null,
|
|
55
|
+
projectId: projectId || null,
|
|
56
|
+
apiKeyPrefix: String(raw.apiKeyPrefix || "").trim() || null,
|
|
57
|
+
provisionedAt: String(raw.provisionedAt || "").trim() || null,
|
|
58
|
+
};
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
function normalizeMetadata(raw = {}) {
|
|
62
|
+
return {
|
|
63
|
+
version: Number(raw.version || CREDENTIALS_VERSION),
|
|
64
|
+
apiUrl: String(raw.apiUrl || "").trim(),
|
|
65
|
+
storage: String(raw.storage || "file").trim(),
|
|
66
|
+
keyringService: String(raw.keyringService || KEYRING_SERVICE).trim(),
|
|
67
|
+
keyringAccount: String(raw.keyringAccount || "").trim(),
|
|
68
|
+
tokenId: String(raw.tokenId || "").trim() || null,
|
|
69
|
+
tokenPrefix: String(raw.tokenPrefix || "").trim() || null,
|
|
70
|
+
tokenExpiresAt: String(raw.tokenExpiresAt || "").trim() || null,
|
|
71
|
+
createdAt: String(raw.createdAt || "").trim() || nowIso(),
|
|
72
|
+
updatedAt: String(raw.updatedAt || "").trim() || nowIso(),
|
|
73
|
+
user: normalizeUser(raw.user),
|
|
74
|
+
token: String(raw.token || "").trim() || null,
|
|
75
|
+
aidenid: normalizeAidenId(raw.aidenid),
|
|
76
|
+
};
|
|
77
|
+
}
|
|
78
|
+
|
|
79
|
+
async function loadKeytarClient() {
|
|
80
|
+
const disableKeyring = String(process.env.SENTINELAYER_DISABLE_KEYRING || "")
|
|
81
|
+
.trim()
|
|
82
|
+
.toLowerCase();
|
|
83
|
+
if (disableKeyring === "1" || disableKeyring === "true" || disableKeyring === "yes" || disableKeyring === "on") {
|
|
84
|
+
return null;
|
|
85
|
+
}
|
|
86
|
+
try {
|
|
87
|
+
const mod = await import("keytar");
|
|
88
|
+
const client = mod && typeof mod === "object" ? mod.default || mod : null;
|
|
89
|
+
if (!client) {
|
|
90
|
+
return null;
|
|
91
|
+
}
|
|
92
|
+
if (
|
|
93
|
+
typeof client.getPassword !== "function" ||
|
|
94
|
+
typeof client.setPassword !== "function" ||
|
|
95
|
+
typeof client.deletePassword !== "function"
|
|
96
|
+
) {
|
|
97
|
+
return null;
|
|
98
|
+
}
|
|
99
|
+
return client;
|
|
100
|
+
} catch {
|
|
101
|
+
return null;
|
|
102
|
+
}
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
async function readMetadata({ homeDir } = {}) {
|
|
106
|
+
const filePath = resolveCredentialsFilePath({ homeDir });
|
|
107
|
+
try {
|
|
108
|
+
const raw = await fsp.readFile(filePath, "utf-8");
|
|
109
|
+
const parsed = JSON.parse(raw);
|
|
110
|
+
if (!parsed || typeof parsed !== "object" || Array.isArray(parsed)) {
|
|
111
|
+
return { filePath, metadata: null };
|
|
112
|
+
}
|
|
113
|
+
return { filePath, metadata: normalizeMetadata(parsed) };
|
|
114
|
+
} catch (error) {
|
|
115
|
+
if (error && typeof error === "object" && error.code === "ENOENT") {
|
|
116
|
+
return { filePath, metadata: null };
|
|
117
|
+
}
|
|
118
|
+
throw error;
|
|
119
|
+
}
|
|
120
|
+
}
|
|
121
|
+
|
|
122
|
+
async function writeMetadata(filePath, metadata) {
|
|
123
|
+
await fsp.mkdir(path.dirname(filePath), { recursive: true });
|
|
124
|
+
await fsp.writeFile(filePath, `${JSON.stringify(metadata, null, 2)}\n`, {
|
|
125
|
+
encoding: "utf-8",
|
|
126
|
+
mode: 0o600,
|
|
127
|
+
});
|
|
128
|
+
try {
|
|
129
|
+
await fsp.chmod(filePath, 0o600);
|
|
130
|
+
} catch {
|
|
131
|
+
// Windows does not reliably support POSIX chmod semantics.
|
|
132
|
+
}
|
|
133
|
+
}
|
|
134
|
+
|
|
135
|
+
/**
|
|
136
|
+
* Load the active stored session, resolving keyring-backed tokens when configured.
|
|
137
|
+
*
|
|
138
|
+
* @param {{ homeDir?: string }} [options]
|
|
139
|
+
* @returns {Promise<null | {
|
|
140
|
+
* version: number,
|
|
141
|
+
* apiUrl: string,
|
|
142
|
+
* storage: "file" | "keyring",
|
|
143
|
+
* keyringService: string,
|
|
144
|
+
* keyringAccount: string,
|
|
145
|
+
* tokenId: string | null,
|
|
146
|
+
* tokenPrefix: string | null,
|
|
147
|
+
* tokenExpiresAt: string | null,
|
|
148
|
+
* createdAt: string,
|
|
149
|
+
* updatedAt: string,
|
|
150
|
+
* user: {
|
|
151
|
+
* id: string,
|
|
152
|
+
* githubUsername: string,
|
|
153
|
+
* email: string,
|
|
154
|
+
* avatarUrl: string,
|
|
155
|
+
* isAdmin: boolean
|
|
156
|
+
* },
|
|
157
|
+
* token: string,
|
|
158
|
+
* filePath: string
|
|
159
|
+
* }>}
|
|
160
|
+
*/
|
|
161
|
+
export async function readStoredSession({ homeDir } = {}) {
|
|
162
|
+
const { filePath, metadata } = await readMetadata({ homeDir });
|
|
163
|
+
if (!metadata) {
|
|
164
|
+
return null;
|
|
165
|
+
}
|
|
166
|
+
|
|
167
|
+
if (metadata.storage === "keyring") {
|
|
168
|
+
const keytar = await loadKeytarClient();
|
|
169
|
+
if (!keytar || !metadata.keyringAccount) {
|
|
170
|
+
return null;
|
|
171
|
+
}
|
|
172
|
+
const token = await keytar.getPassword(
|
|
173
|
+
metadata.keyringService || KEYRING_SERVICE,
|
|
174
|
+
metadata.keyringAccount
|
|
175
|
+
);
|
|
176
|
+
if (!token) {
|
|
177
|
+
return null;
|
|
178
|
+
}
|
|
179
|
+
return {
|
|
180
|
+
...metadata,
|
|
181
|
+
filePath,
|
|
182
|
+
token,
|
|
183
|
+
storage: "keyring",
|
|
184
|
+
};
|
|
185
|
+
}
|
|
186
|
+
|
|
187
|
+
if (!metadata.token) {
|
|
188
|
+
return null;
|
|
189
|
+
}
|
|
190
|
+
return {
|
|
191
|
+
...metadata,
|
|
192
|
+
filePath,
|
|
193
|
+
token: metadata.token,
|
|
194
|
+
storage: "file",
|
|
195
|
+
};
|
|
196
|
+
}
|
|
197
|
+
|
|
198
|
+
/**
|
|
199
|
+
* Read persisted session metadata without returning secret token material.
|
|
200
|
+
*
|
|
201
|
+
* @param {{ homeDir?: string }} [options]
|
|
202
|
+
* @returns {Promise<null | {
|
|
203
|
+
* version: number,
|
|
204
|
+
* apiUrl: string,
|
|
205
|
+
* storage: string,
|
|
206
|
+
* keyringService: string,
|
|
207
|
+
* keyringAccount: string,
|
|
208
|
+
* tokenId: string | null,
|
|
209
|
+
* tokenPrefix: string | null,
|
|
210
|
+
* tokenExpiresAt: string | null,
|
|
211
|
+
* createdAt: string,
|
|
212
|
+
* updatedAt: string,
|
|
213
|
+
* user: {
|
|
214
|
+
* id: string,
|
|
215
|
+
* githubUsername: string,
|
|
216
|
+
* email: string,
|
|
217
|
+
* avatarUrl: string,
|
|
218
|
+
* isAdmin: boolean
|
|
219
|
+
* },
|
|
220
|
+
* filePath: string,
|
|
221
|
+
* token: null
|
|
222
|
+
* }>}
|
|
223
|
+
*/
|
|
224
|
+
export async function readStoredSessionMetadata({ homeDir } = {}) {
|
|
225
|
+
const { filePath, metadata } = await readMetadata({ homeDir });
|
|
226
|
+
if (!metadata) {
|
|
227
|
+
return null;
|
|
228
|
+
}
|
|
229
|
+
return {
|
|
230
|
+
...metadata,
|
|
231
|
+
filePath,
|
|
232
|
+
token: null,
|
|
233
|
+
};
|
|
234
|
+
}
|
|
235
|
+
|
|
236
|
+
/**
|
|
237
|
+
* Persist a new session token and metadata using keyring storage when available.
|
|
238
|
+
*
|
|
239
|
+
* @param {{
|
|
240
|
+
* apiUrl: string,
|
|
241
|
+
* token: string,
|
|
242
|
+
* tokenId?: string | null,
|
|
243
|
+
* tokenPrefix?: string | null,
|
|
244
|
+
* tokenExpiresAt?: string | null,
|
|
245
|
+
* user?: Record<string, unknown>
|
|
246
|
+
* }} [session]
|
|
247
|
+
* @param {{ homeDir?: string }} [options]
|
|
248
|
+
* @returns {Promise<{
|
|
249
|
+
* version: number,
|
|
250
|
+
* apiUrl: string,
|
|
251
|
+
* storage: "file" | "keyring",
|
|
252
|
+
* keyringService: string,
|
|
253
|
+
* keyringAccount: string,
|
|
254
|
+
* tokenId: string | null,
|
|
255
|
+
* tokenPrefix: string | null,
|
|
256
|
+
* tokenExpiresAt: string | null,
|
|
257
|
+
* createdAt: string,
|
|
258
|
+
* updatedAt: string,
|
|
259
|
+
* user: {
|
|
260
|
+
* id: string,
|
|
261
|
+
* githubUsername: string,
|
|
262
|
+
* email: string,
|
|
263
|
+
* avatarUrl: string,
|
|
264
|
+
* isAdmin: boolean
|
|
265
|
+
* },
|
|
266
|
+
* filePath: string,
|
|
267
|
+
* token: string
|
|
268
|
+
* }>}
|
|
269
|
+
*/
|
|
270
|
+
export async function writeStoredSession(
|
|
271
|
+
{
|
|
272
|
+
apiUrl,
|
|
273
|
+
token,
|
|
274
|
+
tokenId = null,
|
|
275
|
+
tokenPrefix = null,
|
|
276
|
+
tokenExpiresAt = null,
|
|
277
|
+
user = {},
|
|
278
|
+
} = {},
|
|
279
|
+
{ homeDir } = {}
|
|
280
|
+
) {
|
|
281
|
+
const normalizedApiUrl = String(apiUrl || "").trim();
|
|
282
|
+
const normalizedToken = String(token || "").trim();
|
|
283
|
+
if (!normalizedApiUrl) {
|
|
284
|
+
throw new Error("apiUrl is required to persist CLI auth session.");
|
|
285
|
+
}
|
|
286
|
+
if (!normalizedToken) {
|
|
287
|
+
throw new Error("token is required to persist CLI auth session.");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
const { filePath, metadata: existingMetadata } = await readMetadata({ homeDir });
|
|
291
|
+
const keytar = await loadKeytarClient();
|
|
292
|
+
const keyringAccount = buildKeyringAccountName(normalizedApiUrl);
|
|
293
|
+
const updatedAt = nowIso();
|
|
294
|
+
|
|
295
|
+
const nextMetadata = normalizeMetadata({
|
|
296
|
+
version: CREDENTIALS_VERSION,
|
|
297
|
+
apiUrl: normalizedApiUrl,
|
|
298
|
+
tokenId,
|
|
299
|
+
tokenPrefix,
|
|
300
|
+
tokenExpiresAt,
|
|
301
|
+
user: normalizeUser(user),
|
|
302
|
+
createdAt: existingMetadata?.createdAt || updatedAt,
|
|
303
|
+
updatedAt,
|
|
304
|
+
});
|
|
305
|
+
|
|
306
|
+
if (keytar) {
|
|
307
|
+
const previousKeyringAccount = String(existingMetadata?.keyringAccount || "").trim();
|
|
308
|
+
const previousStorage = String(existingMetadata?.storage || "").trim();
|
|
309
|
+
if (previousStorage === "keyring" && previousKeyringAccount && previousKeyringAccount !== keyringAccount) {
|
|
310
|
+
await keytar.deletePassword(KEYRING_SERVICE, previousKeyringAccount);
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
await keytar.setPassword(KEYRING_SERVICE, keyringAccount, normalizedToken);
|
|
314
|
+
nextMetadata.storage = "keyring";
|
|
315
|
+
nextMetadata.keyringService = KEYRING_SERVICE;
|
|
316
|
+
nextMetadata.keyringAccount = keyringAccount;
|
|
317
|
+
nextMetadata.token = null;
|
|
318
|
+
} else {
|
|
319
|
+
nextMetadata.storage = "file";
|
|
320
|
+
nextMetadata.keyringService = KEYRING_SERVICE;
|
|
321
|
+
nextMetadata.keyringAccount = "";
|
|
322
|
+
nextMetadata.token = normalizedToken;
|
|
323
|
+
}
|
|
324
|
+
|
|
325
|
+
await writeMetadata(filePath, nextMetadata);
|
|
326
|
+
|
|
327
|
+
return {
|
|
328
|
+
...nextMetadata,
|
|
329
|
+
filePath,
|
|
330
|
+
token: normalizedToken,
|
|
331
|
+
};
|
|
332
|
+
}
|
|
333
|
+
|
|
334
|
+
/**
|
|
335
|
+
* Remove local session metadata and keyring credentials for the active account.
|
|
336
|
+
*
|
|
337
|
+
* @param {{ homeDir?: string }} [options]
|
|
338
|
+
* @returns {Promise<{ filePath: string, hadSession: boolean }>}
|
|
339
|
+
*/
|
|
340
|
+
export async function clearStoredSession({ homeDir } = {}) {
|
|
341
|
+
const { filePath, metadata } = await readMetadata({ homeDir });
|
|
342
|
+
if (metadata && metadata.storage === "keyring") {
|
|
343
|
+
const keytar = await loadKeytarClient();
|
|
344
|
+
if (keytar && metadata.keyringAccount) {
|
|
345
|
+
await keytar.deletePassword(metadata.keyringService || KEYRING_SERVICE, metadata.keyringAccount);
|
|
346
|
+
}
|
|
347
|
+
}
|
|
348
|
+
|
|
349
|
+
try {
|
|
350
|
+
await fsp.rm(filePath, { force: true });
|
|
351
|
+
} catch {
|
|
352
|
+
// Ignore cleanup errors.
|
|
353
|
+
}
|
|
354
|
+
|
|
355
|
+
return {
|
|
356
|
+
filePath,
|
|
357
|
+
hadSession: Boolean(metadata),
|
|
358
|
+
};
|
|
359
|
+
}
|