@prism-d1/cli 1.6.0 → 1.7.1

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 (68) hide show
  1. package/dist/src/aws/connect.d.ts +1 -1
  2. package/dist/src/aws/connect.d.ts.map +1 -1
  3. package/dist/src/aws/connect.js.map +1 -1
  4. package/dist/src/commands/_top/dashboard.d.ts +4 -0
  5. package/dist/src/commands/_top/dashboard.d.ts.map +1 -1
  6. package/dist/src/commands/_top/dashboard.js +57 -5
  7. package/dist/src/commands/_top/dashboard.js.map +1 -1
  8. package/dist/src/dashboard/actions.d.ts +112 -0
  9. package/dist/src/dashboard/actions.d.ts.map +1 -0
  10. package/dist/src/dashboard/actions.js +242 -0
  11. package/dist/src/dashboard/actions.js.map +1 -0
  12. package/dist/src/dashboard/aws-identity.d.ts +115 -0
  13. package/dist/src/dashboard/aws-identity.d.ts.map +1 -0
  14. package/dist/src/dashboard/aws-identity.js +240 -0
  15. package/dist/src/dashboard/aws-identity.js.map +1 -0
  16. package/dist/src/dashboard/page.d.ts +2 -2
  17. package/dist/src/dashboard/page.d.ts.map +1 -1
  18. package/dist/src/dashboard/page.js +68 -21
  19. package/dist/src/dashboard/page.js.map +1 -1
  20. package/dist/src/dashboard/payload.d.ts +28 -1
  21. package/dist/src/dashboard/payload.d.ts.map +1 -1
  22. package/dist/src/dashboard/payload.js +34 -7
  23. package/dist/src/dashboard/payload.js.map +1 -1
  24. package/dist/src/dashboard/server.d.ts +27 -6
  25. package/dist/src/dashboard/server.d.ts.map +1 -1
  26. package/dist/src/dashboard/server.js +75 -10
  27. package/dist/src/dashboard/server.js.map +1 -1
  28. package/dist/src/dashboard/setup-api.d.ts +63 -0
  29. package/dist/src/dashboard/setup-api.d.ts.map +1 -0
  30. package/dist/src/dashboard/setup-api.js +66 -0
  31. package/dist/src/dashboard/setup-api.js.map +1 -0
  32. package/dist/src/dashboard/setup-page.d.ts +16 -0
  33. package/dist/src/dashboard/setup-page.d.ts.map +1 -0
  34. package/dist/src/dashboard/setup-page.js +347 -0
  35. package/dist/src/dashboard/setup-page.js.map +1 -0
  36. package/dist/src/dashboard/setup.d.ts +53 -1
  37. package/dist/src/dashboard/setup.d.ts.map +1 -1
  38. package/dist/src/dashboard/setup.js +126 -16
  39. package/dist/src/dashboard/setup.js.map +1 -1
  40. package/dist/src/dashboard/summary.d.ts +2 -0
  41. package/dist/src/dashboard/summary.d.ts.map +1 -1
  42. package/dist/src/dashboard/summary.js +1 -1
  43. package/dist/src/dashboard/summary.js.map +1 -1
  44. package/dist/src/dashboard/usage-cache.d.ts +44 -0
  45. package/dist/src/dashboard/usage-cache.d.ts.map +1 -0
  46. package/dist/src/dashboard/usage-cache.js +90 -0
  47. package/dist/src/dashboard/usage-cache.js.map +1 -0
  48. package/dist/src/dashboard/usage.d.ts +14 -1
  49. package/dist/src/dashboard/usage.d.ts.map +1 -1
  50. package/dist/src/dashboard/usage.js +45 -12
  51. package/dist/src/dashboard/usage.js.map +1 -1
  52. package/dist/src/utils/aws.d.ts +2 -0
  53. package/dist/src/utils/aws.d.ts.map +1 -1
  54. package/dist/src/utils/aws.js +1 -1
  55. package/dist/src/utils/aws.js.map +1 -1
  56. package/dist/src/utils/exec.d.ts +1 -1
  57. package/dist/src/utils/exec.d.ts.map +1 -1
  58. package/dist/src/utils/exec.js +3 -1
  59. package/dist/src/utils/exec.js.map +1 -1
  60. package/dist/src/utils/private-json.d.ts +5 -0
  61. package/dist/src/utils/private-json.d.ts.map +1 -0
  62. package/dist/src/utils/private-json.js +29 -0
  63. package/dist/src/utils/private-json.js.map +1 -0
  64. package/dist/src/utils/tool-check.d.ts +9 -0
  65. package/dist/src/utils/tool-check.d.ts.map +1 -1
  66. package/dist/src/utils/tool-check.js +37 -2
  67. package/dist/src/utils/tool-check.js.map +1 -1
  68. package/package.json +1 -1
@@ -0,0 +1,115 @@
1
+ import { aws } from '../utils/aws.js';
2
+ import { writeConnectionFile, type SavedConnection } from '../aws/connection.js';
3
+ /** Section name -> its key names (lower case), plus the region value: the only value kept. */
4
+ export type IniKeys = Map<string, {
5
+ keys: Set<string>;
6
+ region?: string;
7
+ }>;
8
+ /**
9
+ * Key names per section. Values are discarded line by line; `region` is kept
10
+ * only when it looks like a region name. Indented lines are nested settings
11
+ * (e.g. s3 = ...) and are skipped.
12
+ */
13
+ export declare function iniKeys(text: string, kind: 'config' | 'credentials'): IniKeys;
14
+ export type CredentialType = 'credential_process' | 'sso' | 'static-credentials-file' | 'static-config-file' | 'assume-role' | 'web-identity' | 'env' | 'container' | 'none';
15
+ export declare const CREDENTIAL_TYPE_LABEL: Record<CredentialType, string>;
16
+ /**
17
+ * The type the AWS CLI would use for `profile`, in its provider order within a
18
+ * profile: role_arn, web identity, SSO, keys in the credentials file,
19
+ * credential_process, keys in the config file. `shadowed` lists the types that
20
+ * are configured but lose.
21
+ */
22
+ export declare function classifyProfile(profile: string, config: IniKeys, creds: IniKeys): {
23
+ type: CredentialType;
24
+ shadowed: CredentialType[];
25
+ };
26
+ export interface ResolutionStep {
27
+ id: 'flags' | 'env' | 'repo' | 'user' | 'default';
28
+ label: string;
29
+ /** used: this step chose the credentials. skipped: set, but an earlier step won. */
30
+ state: 'used' | 'skipped' | 'not-set' | 'n/a';
31
+ detail: string;
32
+ }
33
+ export interface AwsIdentityView {
34
+ /** Profile name the AWS CLI is given, or null (env keys, container, or nothing). */
35
+ profile: string | null;
36
+ /** Why: "AWS_PROFILE", "aws connect, repo", "default chain", ... */
37
+ source: string;
38
+ credentialType: CredentialType;
39
+ credentialLabel: string;
40
+ region: {
41
+ value: string;
42
+ source: string;
43
+ } | null;
44
+ steps: ResolutionStep[];
45
+ /** The saved aws connect connection (the repo file, else the user file), whether or not it is in use. */
46
+ saved: null | {
47
+ scope: 'repo' | 'user';
48
+ path: string;
49
+ profile: string;
50
+ region: string;
51
+ account: string;
52
+ alias: string | null;
53
+ verifiedAt: string;
54
+ inUse: boolean;
55
+ };
56
+ warnings: string[];
57
+ files: {
58
+ config: string;
59
+ credentials: string;
60
+ configFound: boolean;
61
+ credentialsFound: boolean;
62
+ };
63
+ }
64
+ export interface IdentityDeps {
65
+ env: NodeJS.ProcessEnv;
66
+ home: string;
67
+ /** The repo the dashboard runs in (for .prism/aws.local.json), or null. */
68
+ repoRoot: string | null;
69
+ /** File reader; null when the file is missing or unreadable. */
70
+ readText?: (path: string) => string | null;
71
+ }
72
+ export declare const STATIC_DEFAULT_WARNING = "The default profile has static keys in ~/.aws/credentials. Static keys win over a credential_process or SSO setting for the same profile, and they often expire. If you sign in with credential_process or SSO, remove the keys from [default] in ~/.aws/credentials.";
73
+ export declare const NO_CREDENTIALS_WARNING = "No AWS credentials found: no AWS env var, no saved aws connect profile, and no default profile in ~/.aws/config or ~/.aws/credentials. Set one up with aws configure sso (or aws configure), then run prism-cli aws connect.";
74
+ /** What prism-cli would use, from local files and env only. No AWS call, no aws CLI call. */
75
+ export declare function resolveAwsIdentity(d: IdentityDeps): AwsIdentityView;
76
+ export type CheckNowResult = {
77
+ ok: true;
78
+ account: string;
79
+ arn: string;
80
+ region: string | null;
81
+ profile: string | null;
82
+ savedAccount: string | null;
83
+ matches: boolean | null;
84
+ updated: boolean;
85
+ note: string;
86
+ } | {
87
+ ok: false;
88
+ expired: boolean;
89
+ error: string;
90
+ profile: string | null;
91
+ region: string | null;
92
+ fixes: string[];
93
+ };
94
+ export interface CheckNowDeps {
95
+ identity: AwsIdentityView;
96
+ /** The saved connection behind identity.saved (with its real path), or null. */
97
+ saved: SavedConnection | null;
98
+ /** Same shape as aws connect's deps.aws: one read-only `aws <args>` call. */
99
+ aws(args: string[], profile: string, region: string): ReturnType<typeof aws>;
100
+ now(): Date;
101
+ write?: typeof writeConnectionFile;
102
+ redact(s: string): string;
103
+ }
104
+ /** The fixes to suggest for a failed check, by credential type. */
105
+ export declare function checkFixes(v: AwsIdentityView): string[];
106
+ /** The default `aws` call for Check now: aws connect's call, with the region optional and a timeout. */
107
+ export declare const CHECK_NOW_TIMEOUT_MS = 30000;
108
+ export declare function checkNowAws(args: string[], profile: string, region: string): import("../utils/aws.js").AwsResult;
109
+ /**
110
+ * One read-only `sts get-caller-identity`, through aws connect's callerIdentity.
111
+ * On a match with the saved connection (same profile and region), the saved
112
+ * file's verifiedAt is refreshed; nothing else is written.
113
+ */
114
+ export declare function checkNow(d: CheckNowDeps): CheckNowResult;
115
+ //# sourceMappingURL=aws-identity.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws-identity.d.ts","sourceRoot":"","sources":["../../../src/dashboard/aws-identity.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,240 @@
1
+ /**
2
+ * The Setup page's AWS section: which credentials prism-cli would use and why,
3
+ * with no AWS call, and the one read-only "Check now" call.
4
+ *
5
+ * Resolution follows the order every AWS command uses (aws/connection.ts):
6
+ * flags (none here), env credentials, .prism/aws.local.json, ~/.prism/aws.json,
7
+ * then the default chain. The credential TYPE comes from the KEY NAMES in
8
+ * ~/.aws/config and ~/.aws/credentials. The parser drops every value as it
9
+ * reads a line, except `region`, so no secret is kept, returned or shown.
10
+ */
11
+ import { readFileSync } from 'node:fs';
12
+ import { join } from 'node:path';
13
+ import { aws } from '../utils/aws.js';
14
+ import { callerIdentity } from '../aws/connect.js';
15
+ import { applyConnection, connectionLabel, loadConnection, REGION_RE, writeConnectionFile } from '../aws/connection.js';
16
+ import { resolveRegion } from '../security/setup.js';
17
+ import { tildeHome } from './payload.js';
18
+ /**
19
+ * Key names per section. Values are discarded line by line; `region` is kept
20
+ * only when it looks like a region name. Indented lines are nested settings
21
+ * (e.g. s3 = ...) and are skipped.
22
+ */
23
+ export function iniKeys(text, kind) {
24
+ const out = new Map();
25
+ let cur = null;
26
+ for (const raw of text.split(/\r?\n/)) {
27
+ if (/^\s/.test(raw))
28
+ continue;
29
+ const line = raw.trim();
30
+ if (!line || line.startsWith('#') || line.startsWith(';'))
31
+ continue;
32
+ const sec = /^\[\s*([^\]]+?)\s*\]$/.exec(line);
33
+ if (sec) {
34
+ // config: [default] and [profile name]; [sso-session x] and others are not profiles.
35
+ const name = kind === 'credentials' ? sec[1] : sec[1] === 'default' ? 'default' : /^profile\s+(.+)$/.exec(sec[1])?.[1]?.trim();
36
+ cur = name ? (out.get(name) ?? { keys: new Set() }) : null;
37
+ if (name && cur)
38
+ out.set(name, cur);
39
+ continue;
40
+ }
41
+ const eq = line.indexOf('=');
42
+ if (!cur || eq <= 0)
43
+ continue;
44
+ const key = line.slice(0, eq).trim().toLowerCase();
45
+ cur.keys.add(key);
46
+ if (key === 'region') {
47
+ const v = line.slice(eq + 1).trim();
48
+ if (REGION_RE.test(v))
49
+ cur.region = v;
50
+ }
51
+ }
52
+ return out;
53
+ }
54
+ export const CREDENTIAL_TYPE_LABEL = {
55
+ credential_process: 'credential_process',
56
+ sso: 'IAM Identity Center (SSO)',
57
+ 'static-credentials-file': 'static keys in ~/.aws/credentials',
58
+ 'static-config-file': 'static keys in ~/.aws/config',
59
+ 'assume-role': 'assume role (role_arn)',
60
+ 'web-identity': 'web identity token',
61
+ env: 'environment variables',
62
+ container: 'container credentials',
63
+ none: 'none found',
64
+ };
65
+ /**
66
+ * The type the AWS CLI would use for `profile`, in its provider order within a
67
+ * profile: role_arn, web identity, SSO, keys in the credentials file,
68
+ * credential_process, keys in the config file. `shadowed` lists the types that
69
+ * are configured but lose.
70
+ */
71
+ export function classifyProfile(profile, config, creds) {
72
+ const c = config.get(profile)?.keys ?? new Set();
73
+ const k = creds.get(profile)?.keys ?? new Set();
74
+ const present = [];
75
+ if (c.has('role_arn') && (c.has('web_identity_token_file')))
76
+ present.push('web-identity');
77
+ else if (c.has('role_arn') || k.has('role_arn'))
78
+ present.push('assume-role');
79
+ if (c.has('sso_session') || c.has('sso_start_url') || c.has('sso_account_id'))
80
+ present.push('sso');
81
+ if (k.has('aws_access_key_id'))
82
+ present.push('static-credentials-file');
83
+ if (c.has('credential_process') || k.has('credential_process'))
84
+ present.push('credential_process');
85
+ if (c.has('aws_access_key_id'))
86
+ present.push('static-config-file');
87
+ if (!present.length)
88
+ return { type: 'none', shadowed: [] };
89
+ return { type: present[0], shadowed: present.slice(1) };
90
+ }
91
+ const readTextFile = (p) => { try {
92
+ return readFileSync(p, 'utf8');
93
+ }
94
+ catch {
95
+ return null;
96
+ } };
97
+ export const STATIC_DEFAULT_WARNING = 'The default profile has static keys in ~/.aws/credentials. Static keys win over a credential_process or SSO setting for the same profile, and they often expire. If you sign in with credential_process or SSO, remove the keys from [default] in ~/.aws/credentials.';
98
+ export const NO_CREDENTIALS_WARNING = 'No AWS credentials found: no AWS env var, no saved aws connect profile, and no default profile in ~/.aws/config or ~/.aws/credentials. Set one up with aws configure sso (or aws configure), then run prism-cli aws connect.';
99
+ /** What prism-cli would use, from local files and env only. No AWS call, no aws CLI call. */
100
+ export function resolveAwsIdentity(d) {
101
+ const read = d.readText ?? readTextFile;
102
+ const env = d.env;
103
+ const configPath = env.AWS_CONFIG_FILE || join(d.home, '.aws', 'config');
104
+ const credsPath = env.AWS_SHARED_CREDENTIALS_FILE || join(d.home, '.aws', 'credentials');
105
+ const configText = read(configPath), credsText = read(credsPath);
106
+ const config = iniKeys(configText ?? '', 'config');
107
+ const creds = iniKeys(credsText ?? '', 'credentials');
108
+ const saved = loadConnection(d.home, d.repoRoot);
109
+ const applied = applyConnection({}, saved, env);
110
+ const warnings = [];
111
+ let profile = null, source, type, shadowed = [];
112
+ let envDetail = 'no AWS credential env var set';
113
+ let decided;
114
+ if (env.AWS_ACCESS_KEY_ID) {
115
+ type = 'env';
116
+ source = 'AWS_ACCESS_KEY_ID';
117
+ decided = 'env';
118
+ envDetail = `AWS_ACCESS_KEY_ID is set${env.AWS_PROFILE ? ' (it wins over AWS_PROFILE)' : ''}`;
119
+ }
120
+ else if (env.AWS_PROFILE) {
121
+ profile = env.AWS_PROFILE;
122
+ source = 'AWS_PROFILE';
123
+ decided = 'env';
124
+ ({ type, shadowed } = classifyProfile(profile, config, creds));
125
+ envDetail = `AWS_PROFILE=${profile}`;
126
+ }
127
+ else if (env.AWS_WEB_IDENTITY_TOKEN_FILE) {
128
+ type = 'web-identity';
129
+ source = 'AWS_WEB_IDENTITY_TOKEN_FILE';
130
+ decided = 'env';
131
+ envDetail = 'AWS_WEB_IDENTITY_TOKEN_FILE is set';
132
+ }
133
+ else if (env.AWS_CONTAINER_CREDENTIALS_FULL_URI || env.AWS_CONTAINER_CREDENTIALS_RELATIVE_URI) {
134
+ type = 'container';
135
+ source = 'container credentials env var';
136
+ decided = 'env';
137
+ envDetail = 'a container credentials env var is set';
138
+ }
139
+ else if (saved && applied.profileSource) {
140
+ profile = saved.profile;
141
+ source = connectionLabel(saved);
142
+ decided = saved.scope;
143
+ ({ type, shadowed } = classifyProfile(profile, config, creds));
144
+ }
145
+ else {
146
+ profile = 'default';
147
+ source = 'default chain';
148
+ decided = 'default';
149
+ ({ type, shadowed } = classifyProfile(profile, config, creds));
150
+ }
151
+ const repoFile = d.repoRoot ? join(d.repoRoot, '.prism', 'aws.local.json') : null;
152
+ const savedStep = (scope) => decided === scope ? 'used' : saved?.scope === scope ? 'skipped' : 'not-set';
153
+ const steps = [
154
+ { id: 'flags', label: '--profile / --region', state: 'n/a', detail: 'not used by the dashboard' },
155
+ { id: 'env', label: 'Environment', state: decided === 'env' ? 'used' : 'not-set', detail: envDetail },
156
+ { id: 'repo', label: 'Repo: .prism/aws.local.json', state: savedStep('repo'),
157
+ detail: !repoFile ? 'the dashboard is not running in a git repo' : saved?.scope === 'repo' ? `profile ${saved.profile}` : 'not set' },
158
+ { id: 'user', label: 'User: ~/.prism/aws.json', state: savedStep('user'),
159
+ detail: saved?.scope === 'user' ? `profile ${saved.profile}` : saved?.scope === 'repo' ? 'the repo file wins' : 'not set' },
160
+ { id: 'default', label: 'Default chain', state: decided === 'default' ? 'used' : 'not-set', detail: 'the [default] profile' },
161
+ ];
162
+ const configRegion = (p) => config.get(p ?? env.AWS_PROFILE ?? 'default')?.region ?? null;
163
+ const region = resolveRegion({ profile: profile && profile !== 'default' ? profile : undefined, connection: applied.connection }, { env, configRegion });
164
+ if (creds.get('default')?.keys.has('aws_access_key_id'))
165
+ warnings.push(STATIC_DEFAULT_WARNING);
166
+ if (profile && profile !== 'default' && type === 'static-credentials-file' && shadowed.length) {
167
+ warnings.push(`Profile ${profile} has static keys in ~/.aws/credentials, and they win over its ${shadowed.map((t) => CREDENTIAL_TYPE_LABEL[t]).join(' and ')}. Remove the keys if they are stale.`);
168
+ }
169
+ if (type === 'none') {
170
+ warnings.push(profile && profile !== 'default'
171
+ ? `Profile ${profile} (${source}) is not configured in ~/.aws/config or ~/.aws/credentials. Create it with aws configure sso --profile ${profile}, or re-run prism-cli aws connect.`
172
+ : NO_CREDENTIALS_WARNING);
173
+ }
174
+ if (saved && !applied.profileSource)
175
+ warnings.push(`The saved aws connect profile ${saved.profile} is not used: ${source} is set in the dashboard's environment and wins.`);
176
+ return {
177
+ profile, source, credentialType: type, credentialLabel: CREDENTIAL_TYPE_LABEL[type],
178
+ region: region ? { value: region.region, source: region.source } : null,
179
+ steps,
180
+ saved: saved ? {
181
+ scope: saved.scope, path: tildeHome(saved.path, d.home), profile: saved.profile, region: saved.region, account: saved.account,
182
+ alias: saved.alias, verifiedAt: saved.verifiedAt, inUse: !!applied.profileSource,
183
+ } : null,
184
+ warnings,
185
+ files: { config: tildeHome(configPath, d.home), credentials: tildeHome(credsPath, d.home), configFound: configText !== null, credentialsFound: credsText !== null },
186
+ };
187
+ }
188
+ const EXPIRED_RE = /expired|ExpiredToken|InvalidClientTokenId|UnrecognizedClient|RequestExpired|SignatureDoesNotMatch|AuthFailure|Unable to locate credentials|NoCredentialProviders|refresh|sso|credential_process|custom-process/i;
189
+ /** The fixes to suggest for a failed check, by credential type. */
190
+ export function checkFixes(v) {
191
+ const p = v.profile && v.profile !== 'default' ? ` --profile ${v.profile}` : '';
192
+ const out = [];
193
+ if (v.credentialType === 'credential_process')
194
+ out.push('Sign in again with the tool your credential_process runs, then Check now again');
195
+ if (v.credentialType === 'sso')
196
+ out.push(`aws sso login${p}`);
197
+ if (v.credentialType === 'static-credentials-file' || v.warnings.includes(STATIC_DEFAULT_WARNING))
198
+ out.push('Remove or rotate the stale static keys in ~/.aws/credentials');
199
+ if (v.credentialType === 'none')
200
+ out.push(`aws configure sso${p}`);
201
+ out.push('prism-cli aws connect');
202
+ return out;
203
+ }
204
+ /** The default `aws` call for Check now: aws connect's call, with the region optional and a timeout. */
205
+ export const CHECK_NOW_TIMEOUT_MS = 30_000;
206
+ export function checkNowAws(args, profile, region) {
207
+ return aws(region ? [...args, '--region', region] : args, { profile: profile || undefined, env: { AWS_PAGER: '' }, timeoutMs: CHECK_NOW_TIMEOUT_MS });
208
+ }
209
+ /**
210
+ * One read-only `sts get-caller-identity`, through aws connect's callerIdentity.
211
+ * On a match with the saved connection (same profile and region), the saved
212
+ * file's verifiedAt is refreshed; nothing else is written.
213
+ */
214
+ export function checkNow(d) {
215
+ const v = d.identity;
216
+ // --profile only for a saved aws connect profile; env and the default chain are left to the AWS CLI, as every AWS command does.
217
+ const profile = d.saved && v.saved?.inUse ? d.saved.profile : '';
218
+ const region = v.region?.value ?? '';
219
+ const id = callerIdentity({ aws: d.aws }, profile, region);
220
+ if ('error' in id) {
221
+ const error = d.redact(id.error);
222
+ return { ok: false, expired: EXPIRED_RE.test(error), error, profile: v.profile, region: region || null, fixes: checkFixes(v) };
223
+ }
224
+ const s = d.saved;
225
+ const matches = s ? s.account === id.account : null;
226
+ let updated = false, note;
227
+ if (!s)
228
+ note = 'No saved aws connect connection; nothing was written. Save one with prism-cli aws connect.';
229
+ else if (!matches)
230
+ note = `Does not match the saved account ${s.account}. The saved file was not changed; check the profile, then re-run prism-cli aws connect${s.scope === 'repo' ? ' --repo' : ''}.`;
231
+ else if (s.profile !== v.profile || s.region !== region)
232
+ note = `Matches the saved account, but the check used ${v.profile ? `profile ${v.profile}` : v.source} in ${region || 'no region'}, not the saved pair; the saved file was not changed.`;
233
+ else {
234
+ (d.write ?? writeConnectionFile)(s.path, { profile: s.profile, region: s.region, account: s.account, alias: s.alias, verifiedAt: d.now().toISOString() });
235
+ updated = true;
236
+ note = `Matches the saved account; updated the verified time in ${v.saved?.path ?? s.path}.`;
237
+ }
238
+ return { ok: true, account: id.account, arn: id.arn, region: region || null, profile: v.profile, savedAccount: s?.account ?? null, matches, updated, note };
239
+ }
240
+ //# sourceMappingURL=aws-identity.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"aws-identity.js","sourceRoot":"","sources":["../../../src/dashboard/aws-identity.ts"],"names":[],"mappings":"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"}
@@ -1,10 +1,10 @@
1
1
  /**
2
2
  * The dashboard's single-page app: one self-contained HTML document with
3
3
  * inline CSS and JS (nonce-gated by the CSP), no CDN and no network fetch
4
- * other than its own GET /api/data, so it works offline.
4
+ * other than its own GET routes (/api/data, then /api/usage), so it works offline.
5
5
  *
6
6
  * Every value from the payload is placed with textContent or a DOM attribute;
7
7
  * innerHTML is never used for data.
8
8
  */
9
- export declare function renderPage(nonce: string): string;
9
+ export declare function renderPage(nonce: string, csrf?: string): string;
10
10
  //# sourceMappingURL=page.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AA6nBH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,GAAG,MAAM,CAQhD"}
1
+ {"version":3,"file":"page.d.ts","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAqqBH,wBAAgB,UAAU,CAAC,KAAK,EAAE,MAAM,EAAE,IAAI,SAAK,GAAG,MAAM,CAgB3D"}
@@ -1,16 +1,19 @@
1
1
  /**
2
2
  * The dashboard's single-page app: one self-contained HTML document with
3
3
  * inline CSS and JS (nonce-gated by the CSP), no CDN and no network fetch
4
- * other than its own GET /api/data, so it works offline.
4
+ * other than its own GET routes (/api/data, then /api/usage), so it works offline.
5
5
  *
6
6
  * Every value from the payload is placed with textContent or a DOM attribute;
7
7
  * innerHTML is never used for data.
8
8
  */
9
+ import { SETUP_SCRIPT, SETUP_STYLE, THEME_BOOT, THEME_SCRIPT } from './setup-page.js';
10
+ /** Colour tokens; the dark set applies by system preference unless the toggle picks Light, and always with Dark. */
11
+ const LIGHT_VARS = '--bg:#f7f7f8; --card:#fff; --text:#1d1d1f; --muted:#6b6b73; --line:#e3e3e8; --ok:#1a7f37; --warn:#9a6700; --bad:#cf222e; --high:#cf222e; --medium:#bf8700; --low:#3d6fa8; --accent:#0969da; --raised:var(--bg);';
12
+ const DARK_VARS = '--bg:#0d1117; --card:#161b22; --text:#e6edf3; --muted:#8b949e; --line:#30363d; --ok:#3fb950; --warn:#d29922; --bad:#f85149; --high:#f85149; --medium:#d29922; --low:#a5d6ff; --accent:#58a6ff; --raised:#21262d;';
9
13
  const STYLE = `
10
- :root { color-scheme: light dark; --bg:#f7f7f8; --card:#fff; --text:#1d1d1f; --muted:#6b6b73; --line:#e3e3e8;
11
- --ok:#1a7f37; --warn:#9a6700; --bad:#cf222e; --high:#cf222e; --medium:#bf8700; --low:#3d6fa8; --accent:#0969da; --raised:var(--bg); }
12
- @media (prefers-color-scheme: dark) { :root { --bg:#0d1117; --card:#161b22; --text:#e6edf3; --muted:#8b949e; --line:#30363d;
13
- --ok:#3fb950; --warn:#d29922; --bad:#f85149; --high:#f85149; --medium:#d29922; --low:#a5d6ff; --accent:#58a6ff; --raised:#21262d; } }
14
+ :root { color-scheme: light; ${LIGHT_VARS} }
15
+ @media (prefers-color-scheme: dark) { :root:not([data-theme="light"]) { color-scheme: dark; ${DARK_VARS} } }
16
+ :root[data-theme="dark"] { color-scheme: dark; ${DARK_VARS} }
14
17
  * { box-sizing: border-box; }
15
18
  body { margin:0; font:14px/1.45 -apple-system,BlinkMacSystemFont,"Segoe UI",Roboto,Helvetica,Arial,sans-serif; background:var(--bg); color:var(--text); }
16
19
  header { display:flex; flex-wrap:wrap; align-items:center; gap:8px 12px; padding:14px 20px; border-bottom:1px solid var(--line); background:var(--card); }
@@ -59,8 +62,10 @@ svg.trend line { stroke:var(--line); }
59
62
  .slim-line { padding:5px 0; border-top:1px solid var(--line); font-size:13px; overflow-wrap:anywhere; } .slim-line:first-of-type { border-top:none; } .slim-line code { white-space:normal; overflow-wrap:anywhere; }
60
63
  .empty-line { color:var(--muted); margin:10px 0 4px; font-size:13px; overflow-wrap:anywhere; } .empty-line b { color:var(--text); }
61
64
  .changed { outline:2px solid var(--accent); outline-offset:1px; border-radius:4px; }
65
+ .loading { color:var(--muted); }
62
66
  @keyframes prism-flash { from { background-color:color-mix(in srgb, var(--accent) 35%, transparent); } to { background-color:transparent; } }
63
67
  @media (prefers-reduced-motion: no-preference) { .changed { animation:prism-flash 2.5s ease-out 1; } }
68
+ ${SETUP_STYLE}
64
69
  `;
65
70
  const SCRIPT = `
66
71
  'use strict';
@@ -165,9 +170,11 @@ function summaryPanel(sum) {
165
170
  }
166
171
  const sp = sum && sum.spend;
167
172
  const ok = sp && sp.status === 'ok';
173
+ const loadingUsage = sp && sp.status === 'loading';
174
+ const updated = sp && sp.updatedAt ? ' · updated ' + when(sp.updatedAt) : '';
168
175
  const spendCard = (label, key, n, det) => scard(label,
169
- ok ? track(key, n, el('div', { class: 'v' }, money(n, sp.currency))) : el('div', { class: 'v' }, '-'),
170
- detail(ok ? det : txt((sp && sp.message) || (sp && sp.status) || 'no codeburn data')));
176
+ ok ? track(key, n, el('div', { class: 'v' }, money(n, sp.currency))) : el('div', { class: 'v' + (loadingUsage ? ' loading' : '') }, loadingUsage ? 'loading' : '-'),
177
+ detail(ok ? det + updated : txt((sp && sp.message) || (sp && sp.status) || 'no codeburn data')));
171
178
  box.append(spendCard('Spend today', 'sum:today', sp && sp.today, 'codeburn, all projects'));
172
179
  box.append(spendCard('Spend this month', 'sum:month', sp && sp.month, 'codeburn, this calendar month'));
173
180
  const w = (sum && sum.warnings) || { count: 0, first: null };
@@ -326,7 +333,17 @@ function codewatchPanel(data) {
326
333
  return s;
327
334
  }
328
335
 
336
+ /** "updated <time>", and whether a refresh is running or the last one failed. */
337
+ function usageUpdated(u) {
338
+ if (!u.updatedAt) return null;
339
+ return el('p', { class: 'note' }, 'updated ' + when(u.updatedAt)
340
+ + (u.refreshError ? ' · refresh failed: ' + txt(u.refreshError) : u.stale ? ' · refreshing in the background' : ''));
341
+ }
342
+
329
343
  function usagePanel(u) {
344
+ if (u.status === 'loading') {
345
+ return el('section', { class: 'card' }, el('h2', {}, 'Usage (codeburn)'), el('p', { class: 'empty loading' }, txt(u.message)));
346
+ }
330
347
  if (u.status === 'not-installed' || u.status === 'no-data') {
331
348
  return emptyLine('Usage (codeburn)', txt(u.message), u.status === 'not-installed' ? '' : el('span', {}, ' Usage appears once a coding tool records sessions; full charts: ', el('code', {}, txt(u.fullView)), '.'));
332
349
  }
@@ -342,7 +359,7 @@ function usagePanel(u) {
342
359
  s.append(el('h3', {}, 'Top projects this month'));
343
360
  s.append(table(['Project', 'Cost', 'Sessions'], u.topProjects.map((p) => [el('span', { title: p.path }, p.name), num(money(p.cost, u.currency)), num(p.sessions)])));
344
361
  }
345
- s.append(el('p', { class: 'note' }, 'Full charts: run ', el('code', {}, txt(u.fullView)), ' in a terminal.'));
362
+ s.append(usageUpdated(u) || '', el('p', { class: 'note' }, 'Full charts: run ', el('code', {}, txt(u.fullView)), ' in a terminal.'));
346
363
  return s;
347
364
  }
348
365
 
@@ -591,10 +608,29 @@ function awsPanel(aws) {
591
608
  const AUTO_REFRESH_MS = 60000;
592
609
  let lastLoad = 0, loading = false;
593
610
 
594
- async function load() {
611
+ /**
612
+ * Two stages: /api/data (local checks, with cached usage or "loading") paints
613
+ * at once; then /api/usage, and when its usage differs from what was painted,
614
+ * /api/data again (cheap: the server rebuilds it from the cache). fresh: the
615
+ * Refresh button, which runs new codeburn exports; auto-refresh uses the cache.
616
+ */
617
+ async function load(fresh) {
595
618
  if (loading) return;
596
619
  loading = true;
597
- try { await render(); } finally { loading = false; lastLoad = Date.now(); }
620
+ try {
621
+ const data = await render();
622
+ if (!data) return;
623
+ let u;
624
+ try { u = await getJson('/api/usage' + (fresh ? '?fresh=1' : '')); } catch (e) { return; }
625
+ const was = data.usage || {};
626
+ if (u && u.usage && (u.usage.updatedAt !== was.updatedAt || u.usage.status !== was.status)) await render();
627
+ } finally { loading = false; lastLoad = Date.now(); }
628
+ }
629
+
630
+ async function getJson(path) {
631
+ const res = await fetch(path, { credentials: 'same-origin', cache: 'no-store' });
632
+ if (!res.ok) throw new Error('HTTP ' + res.status);
633
+ return res.json();
598
634
  }
599
635
 
600
636
  async function render() {
@@ -603,13 +639,11 @@ async function render() {
603
639
  meta.textContent = 'loading...';
604
640
  let data;
605
641
  try {
606
- const res = await fetch('/api/data', { credentials: 'same-origin', cache: 'no-store' });
607
- if (!res.ok) throw new Error('HTTP ' + res.status);
608
- data = await res.json();
642
+ data = await getJson('/api/data');
609
643
  } catch (e) {
610
644
  // Keep the last good view; say the refresh failed.
611
645
  meta.textContent = 'failed to load: ' + e.message + (before && before !== 'loading...' ? ' · showing ' + before : '');
612
- return;
646
+ return null;
613
647
  }
614
648
  // Open/closed state of keyed <details> survives the redraw.
615
649
  const openState = new Map([...main.querySelectorAll('details[data-key]')].map((d) => [d.getAttribute('data-key'), d.open]));
@@ -633,19 +667,32 @@ async function render() {
633
667
  const c = data.overview.counts;
634
668
  meta.textContent = c.repos + ' repos under ' + txt(data.scope.root_display) + ' · ' + c.codewatch + ' with codewatch' + (data.scope.truncated ? ' (scan capped)' : '')
635
669
  + ' · updated ' + when(data.generated_at) + (changed.size ? ' · ' + changed.size + ' value' + (changed.size === 1 ? '' : 's') + ' changed (outlined)' : '') + ' · refreshes every minute while visible';
670
+ await renderSetup(data);
671
+ return data;
636
672
  }
637
- document.getElementById('refresh').addEventListener('click', load);
638
- setInterval(() => { if (document.visibilityState === 'visible' && Date.now() - lastLoad >= AUTO_REFRESH_MS - 1000) load(); }, AUTO_REFRESH_MS / 4);
639
- document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && Date.now() - lastLoad >= AUTO_REFRESH_MS) load(); });
640
- load();
673
+ ${SETUP_SCRIPT}
674
+ ${THEME_SCRIPT}
675
+ document.getElementById('refresh').addEventListener('click', () => load(true));
676
+ setInterval(() => { if (document.visibilityState === 'visible' && Date.now() - lastLoad >= AUTO_REFRESH_MS - 1000) load(false); }, AUTO_REFRESH_MS / 4);
677
+ document.addEventListener('visibilitychange', () => { if (document.visibilityState === 'visible' && Date.now() - lastLoad >= AUTO_REFRESH_MS) load(false); });
678
+ load(false);
641
679
  `;
642
- export function renderPage(nonce) {
643
- // The nonce is base64 generated server-side; nothing user-derived is interpolated here.
680
+ export function renderPage(nonce, csrf = '') {
681
+ // The nonce and the CSRF token are base64/base64url generated server-side; nothing user-derived is interpolated here.
682
+ if (!/^[A-Za-z0-9_-]*$/.test(csrf))
683
+ throw new Error('bad CSRF token');
644
684
  return `<!DOCTYPE html>
645
685
  <html lang="en"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1">
686
+ <meta name="prism-csrf" content="${csrf}">
687
+ <script nonce="${nonce}">${THEME_BOOT}</script>
646
688
  <title>PRISM dashboard</title><style nonce="${nonce}">${STYLE}</style></head>
647
- <body><header><h1>PRISM dashboard</h1><button id="refresh" type="button">Refresh</button><span class="meta" id="meta"></span></header>
689
+ <body><header><h1>PRISM dashboard</h1>
690
+ <nav class="tabs" aria-label="Views"><a href="#" data-view="dashboard">Dashboard</a><a href="#setup" data-view="setup">Setup</a></nav>
691
+ <button id="refresh" type="button">Refresh</button>
692
+ <div class="theme" role="group" aria-label="Theme"><button type="button" data-theme-set="light">Light</button><button type="button" data-theme-set="dark">Dark</button><button type="button" data-theme-set="system">System</button></div>
693
+ <span class="meta" id="meta"></span></header>
648
694
  <main id="main"></main>
695
+ <main id="setup" hidden></main>
649
696
  <script nonce="${nonce}">${SCRIPT}</script></body></html>`;
650
697
  }
651
698
  //# sourceMappingURL=page.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,MAAM,KAAK,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAuDb,CAAC;AAEF,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;CAgkBd,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,KAAa;IACtC,wFAAwF;IACxF,OAAO;;8CAEqC,KAAK,KAAK,KAAK;;;iBAG5C,KAAK,KAAK,MAAM,yBAAyB,CAAC;AAC3D,CAAC"}
1
+ {"version":3,"file":"page.js","sourceRoot":"","sources":["../../../src/dashboard/page.ts"],"names":[],"mappings":"AAAA;;;;;;;GAOG;AAEH,OAAO,EAAE,YAAY,EAAE,WAAW,EAAE,UAAU,EAAE,YAAY,EAAE,MAAM,iBAAiB,CAAC;AAEtF,oHAAoH;AACpH,MAAM,UAAU,GAAG,iNAAiN,CAAC;AACrO,MAAM,SAAS,GAAG,kNAAkN,CAAC;AAErO,MAAM,KAAK,GAAG;+BACiB,UAAU;8FACqD,SAAS;iDACtD,SAAS;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EAoDxD,WAAW;CACZ,CAAC;AAEF,MAAM,MAAM,GAAG;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;EA2lBb,YAAY;EACZ,YAAY;;;;;CAKb,CAAC;AAEF,MAAM,UAAU,UAAU,CAAC,KAAa,EAAE,IAAI,GAAG,EAAE;IACjD,sHAAsH;IACtH,IAAI,CAAC,kBAAkB,CAAC,IAAI,CAAC,IAAI,CAAC;QAAE,MAAM,IAAI,KAAK,CAAC,gBAAgB,CAAC,CAAC;IACtE,OAAO;;mCAE0B,IAAI;iBACtB,KAAK,KAAK,UAAU;8CACS,KAAK,KAAK,KAAK;;;;;;;;iBAQ5C,KAAK,KAAK,MAAM,yBAAyB,CAAC;AAC3D,CAAC"}
@@ -5,7 +5,7 @@
5
5
  */
6
6
  import { type CodewatchPanel, type SecretScanning } from './codewatch.js';
7
7
  import { type DiscoverOptions } from './repos.js';
8
- import { type CheckItem, type CodewatchSetup, type MachineDeps, type SecuritySetup } from './setup.js';
8
+ import { type GitRunner, type CheckItem, type CodewatchSetup, type MachineDeps, type SecuritySetup } from './setup.js';
9
9
  import { type DashboardSummary } from './summary.js';
10
10
  import { type CheckHelp, type SyncMode, type SyncModeSource } from '../utils/sync-mode.js';
11
11
  import { type GlobalStatus } from '../codewatch/globalHooks.js';
@@ -129,10 +129,37 @@ export interface DashboardPayload {
129
129
  aws: AwsPanel;
130
130
  /** The one-glance summary shown at the top of the page (buildSummary). */
131
131
  summary: DashboardSummary;
132
+ /**
133
+ * Milliseconds per stage, for diagnosis (`dashboard --json --timing`). scan:
134
+ * repo discovery. setup: checks and codewatch results for every repo and
135
+ * this machine. usage: codeburn (runs alongside setup). cacheHit: usage came
136
+ * from the cache.
137
+ */
138
+ timing: PayloadTiming;
132
139
  }
140
+ export interface PayloadTiming {
141
+ scan: number;
142
+ setup: number;
143
+ usage: number;
144
+ total: number;
145
+ cacheHit: boolean;
146
+ }
147
+ /** The usage panel and whether it came from the cache (usage-cache.ts). */
148
+ export type UsageSource = () => Promise<{
149
+ usage: UsagePanel;
150
+ cacheHit: boolean;
151
+ }>;
152
+ /** At most this many repos are checked at once (each check is one git process). */
153
+ export declare const GIT_CONCURRENCY = 8;
154
+ /** fn over items, at most `limit` running at a time; results keep the input order. */
155
+ export declare function mapLimit<T, R>(items: T[], limit: number, fn: (item: T) => Promise<R>): Promise<R[]>;
133
156
  export interface PayloadOptions extends DiscoverOptions {
134
157
  trendRuns?: number;
135
158
  exporter?: CodeburnExport;
159
+ /** Default: collectUsage with `exporter`, uncached. */
160
+ usage?: UsageSource;
161
+ /** Runs the per-repo git calls; injected in tests. */
162
+ git?: GitRunner;
136
163
  machineDeps?: MachineDeps;
137
164
  pathEnv?: string;
138
165
  }