@productbrain/cli 0.1.0-beta.4809 → 0.1.0-beta.4818
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/__tests__/config-prod-fallthrough.test.js +23 -12
- package/dist/__tests__/config-prod-fallthrough.test.js.map +1 -1
- package/dist/__tests__/config.test.js +343 -11
- package/dist/__tests__/config.test.js.map +1 -1
- package/dist/__tests__/init.test.js +51 -14
- package/dist/__tests__/init.test.js.map +1 -1
- package/dist/__tests__/state.test.js +21 -1
- package/dist/__tests__/state.test.js.map +1 -1
- package/dist/commands/__tests__/setup-state.test.js +1 -1
- package/dist/commands/__tests__/setup-state.test.js.map +1 -1
- package/dist/commands/doctor.d.ts.map +1 -1
- package/dist/commands/doctor.js +101 -35
- package/dist/commands/doctor.js.map +1 -1
- package/dist/commands/doctor.test.js +182 -6
- package/dist/commands/doctor.test.js.map +1 -1
- package/dist/commands/init.d.ts +1 -1
- package/dist/commands/init.d.ts.map +1 -1
- package/dist/commands/init.js +20 -9
- package/dist/commands/init.js.map +1 -1
- package/dist/commands/status.d.ts +7 -0
- package/dist/commands/status.d.ts.map +1 -0
- package/dist/commands/status.js +102 -0
- package/dist/commands/status.js.map +1 -0
- package/dist/commands/status.test.d.ts +2 -0
- package/dist/commands/status.test.d.ts.map +1 -0
- package/dist/commands/status.test.js +75 -0
- package/dist/commands/status.test.js.map +1 -0
- package/dist/commands/welcome.d.ts.map +1 -1
- package/dist/commands/welcome.js +5 -2
- package/dist/commands/welcome.js.map +1 -1
- package/dist/index.js +3 -112
- package/dist/index.js.map +1 -1
- package/dist/lib/config.d.ts +9 -22
- package/dist/lib/config.d.ts.map +1 -1
- package/dist/lib/config.js +37 -99
- package/dist/lib/config.js.map +1 -1
- package/dist/lib/configResult.d.ts +80 -0
- package/dist/lib/configResult.d.ts.map +1 -0
- package/dist/lib/configResult.js +176 -0
- package/dist/lib/configResult.js.map +1 -0
- package/dist/lib/localBinding.d.ts +51 -0
- package/dist/lib/localBinding.d.ts.map +1 -0
- package/dist/lib/localBinding.js +90 -0
- package/dist/lib/localBinding.js.map +1 -0
- package/dist/lib/projectConfig.d.ts +6 -0
- package/dist/lib/projectConfig.d.ts.map +1 -1
- package/dist/lib/projectConfig.js +14 -2
- package/dist/lib/projectConfig.js.map +1 -1
- package/dist/lib/state.d.ts +7 -0
- package/dist/lib/state.d.ts.map +1 -1
- package/dist/lib/state.js +7 -2
- package/dist/lib/state.js.map +1 -1
- package/package.json +1 -1
|
@@ -0,0 +1,80 @@
|
|
|
1
|
+
/** Structured, non-throwing view of the CLI configuration cascade. */
|
|
2
|
+
import { CLIError, type ErrorCategory, type ErrorCodeValue } from './errors.js';
|
|
3
|
+
export type ConfigSource = 'env' | 'project' | 'profile' | 'cwd' | 'default';
|
|
4
|
+
/**
|
|
5
|
+
* Where the resolved `siteUrl` came from — tracked SEPARATELY from `ConfigSource`.
|
|
6
|
+
*
|
|
7
|
+
* The credential and the gateway are picked by two different fallback chains: almost every
|
|
8
|
+
* credential branch prefers `.productbrain/config.json`'s siteUrl over its own. So a result
|
|
9
|
+
* labelled `profile`/`cwd` can be carrying a URL that came from config.json, and naming the
|
|
10
|
+
* credential's source in a URL repair would send the operator to edit a file that does not
|
|
11
|
+
* control the failing URL.
|
|
12
|
+
*/
|
|
13
|
+
export type SiteUrlSource = 'env' | 'local-binding' | 'profile-pin' | 'project-config' | 'profile' | 'legacy' | 'cwd' | 'default';
|
|
14
|
+
export interface ResolvedConfig {
|
|
15
|
+
apiKey?: string;
|
|
16
|
+
siteUrl: string;
|
|
17
|
+
source: ConfigSource;
|
|
18
|
+
/**
|
|
19
|
+
* Which candidate supplied `siteUrl`. Optional because a caller may construct a
|
|
20
|
+
* ResolvedConfig without observing the cascade; diagnostics must then name NO source
|
|
21
|
+
* rather than guess one.
|
|
22
|
+
*/
|
|
23
|
+
siteUrlSource?: SiteUrlSource;
|
|
24
|
+
/**
|
|
25
|
+
* The concrete file a source resolved to, where the source alone is ambiguous — `cwd` covers
|
|
26
|
+
* three different candidate paths, so naming the generic one would point the operator at a
|
|
27
|
+
* file that is not the one being read.
|
|
28
|
+
*/
|
|
29
|
+
siteUrlDetail?: string;
|
|
30
|
+
/**
|
|
31
|
+
* Which named profile supplied a `profile`-sourced credential or URL. Absent when the profile
|
|
32
|
+
* cascade landed on the nameless legacy `~/.config/productbrain/.env`, so diagnostics can name
|
|
33
|
+
* the profile that actually won instead of an "active profile" that may not be the one read.
|
|
34
|
+
*/
|
|
35
|
+
profileName?: string;
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Pick the first present siteUrl candidate and record which one won, so the URL and its
|
|
39
|
+
* reported origin are produced by one expression and cannot disagree. Candidates are given
|
|
40
|
+
* in the caller's existing precedence order; `DEFAULT_SITE_URL` closes every chain.
|
|
41
|
+
*/
|
|
42
|
+
export declare function pickSiteUrl(candidates: ReadonlyArray<readonly [SiteUrlSource, string | undefined, string?]>): {
|
|
43
|
+
siteUrl: string;
|
|
44
|
+
siteUrlSource: SiteUrlSource;
|
|
45
|
+
siteUrlDetail?: string;
|
|
46
|
+
};
|
|
47
|
+
export type ConfigFailureKind = 'missing-key' | 'invalid-key' | 'profile-pin' | 'preview-binding' | 'invalid-url' | 'unknown';
|
|
48
|
+
export type ConfigResult = {
|
|
49
|
+
ok: true;
|
|
50
|
+
apiKey: string;
|
|
51
|
+
siteUrl: string;
|
|
52
|
+
source: ConfigSource;
|
|
53
|
+
} | {
|
|
54
|
+
ok: false;
|
|
55
|
+
kind: ConfigFailureKind;
|
|
56
|
+
reason: string;
|
|
57
|
+
guidance: string;
|
|
58
|
+
nextCommand: string;
|
|
59
|
+
};
|
|
60
|
+
/**
|
|
61
|
+
* The transport-security bar every cascade enforces: HTTPS, or http://localhost while in dev mode.
|
|
62
|
+
* Both the structured result below and `getConfigOrGuide()`'s throwing path read it from here so
|
|
63
|
+
* the two can never diverge on what counts as a safe gateway to send an API key to.
|
|
64
|
+
*/
|
|
65
|
+
export declare function isInsecureSiteUrl(siteUrl: string): boolean;
|
|
66
|
+
/** A resolution refusal that diagnostic surfaces must preserve instead of relabelling. */
|
|
67
|
+
export declare class ConfigResolutionError extends CLIError {
|
|
68
|
+
readonly kind: ConfigFailureKind;
|
|
69
|
+
readonly nextCommand: string;
|
|
70
|
+
constructor(message: string, options: {
|
|
71
|
+
kind: ConfigFailureKind;
|
|
72
|
+
nextCommand: string;
|
|
73
|
+
code?: ErrorCodeValue;
|
|
74
|
+
category?: ErrorCategory;
|
|
75
|
+
guidance: string;
|
|
76
|
+
});
|
|
77
|
+
}
|
|
78
|
+
/** Convert a throwing resolution cascade into the single diagnostic contract. */
|
|
79
|
+
export declare function resolveConfigResult(resolveConfig: () => ResolvedConfig): ConfigResult;
|
|
80
|
+
//# sourceMappingURL=configResult.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configResult.d.ts","sourceRoot":"","sources":["../../src/lib/configResult.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAGtE,OAAO,EAAE,QAAQ,EAAa,KAAK,aAAa,EAAE,KAAK,cAAc,EAAE,MAAM,aAAa,CAAC;AAG3F,MAAM,MAAM,YAAY,GAAG,KAAK,GAAG,SAAS,GAAG,SAAS,GAAG,KAAK,GAAG,SAAS,CAAC;AAE7E;;;;;;;;GAQG;AACH,MAAM,MAAM,aAAa,GACrB,KAAK,GACL,eAAe,GACf,aAAa,GACb,gBAAgB,GAChB,SAAS,GACT,QAAQ,GACR,KAAK,GACL,SAAS,CAAC;AAEd,MAAM,WAAW,cAAc;IAC7B,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,OAAO,EAAE,MAAM,CAAC;IAChB,MAAM,EAAE,YAAY,CAAC;IACrB;;;;OAIG;IACH,aAAa,CAAC,EAAE,aAAa,CAAC;IAC9B;;;;OAIG;IACH,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB;;;;OAIG;IACH,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAED;;;;GAIG;AACH,wBAAgB,WAAW,CACzB,UAAU,EAAE,aAAa,CAAC,SAAS,CAAC,aAAa,EAAE,MAAM,GAAG,SAAS,EAAE,MAAM,CAAC,CAAC,CAAC,GAC/E;IAAE,OAAO,EAAE,MAAM,CAAC;IAAC,aAAa,EAAE,aAAa,CAAC;IAAC,aAAa,CAAC,EAAE,MAAM,CAAA;CAAE,CAK3E;AAED,MAAM,MAAM,iBAAiB,GACzB,aAAa,GACb,aAAa,GACb,aAAa,GACb,iBAAiB,GACjB,aAAa,GACb,SAAS,CAAC;AAEd,MAAM,MAAM,YAAY,GACpB;IAAE,EAAE,EAAE,IAAI,CAAC;IAAC,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAC;IAAC,MAAM,EAAE,YAAY,CAAA;CAAE,GACnE;IACE,EAAE,EAAE,KAAK,CAAC;IACV,IAAI,EAAE,iBAAiB,CAAC;IACxB,MAAM,EAAE,MAAM,CAAC;IACf,QAAQ,EAAE,MAAM,CAAC;IACjB,WAAW,EAAE,MAAM,CAAC;CACrB,CAAC;AA2EN;;;;GAIG;AACH,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAE1D;AAED,0FAA0F;AAC1F,qBAAa,qBAAsB,SAAQ,QAAQ;IACjD,QAAQ,CAAC,IAAI,EAAE,iBAAiB,CAAC;IACjC,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;gBAG3B,OAAO,EAAE,MAAM,EACf,OAAO,EAAE;QACP,IAAI,EAAE,iBAAiB,CAAC;QACxB,WAAW,EAAE,MAAM,CAAC;QACpB,IAAI,CAAC,EAAE,cAAc,CAAC;QACtB,QAAQ,CAAC,EAAE,aAAa,CAAC;QACzB,QAAQ,EAAE,MAAM,CAAC;KAClB;CAOJ;AAED,iFAAiF;AACjF,wBAAgB,mBAAmB,CAAC,aAAa,EAAE,MAAM,cAAc,GAAG,YAAY,CAqErF"}
|
|
@@ -0,0 +1,176 @@
|
|
|
1
|
+
/** Structured, non-throwing view of the CLI configuration cascade. */
|
|
2
|
+
import { DEFAULT_SITE_URL, isDevMode } from './constants.js';
|
|
3
|
+
import { CLIError } from './errors.js';
|
|
4
|
+
import { API_KEY_PREFIX } from './tokenConstants.js';
|
|
5
|
+
/**
|
|
6
|
+
* Pick the first present siteUrl candidate and record which one won, so the URL and its
|
|
7
|
+
* reported origin are produced by one expression and cannot disagree. Candidates are given
|
|
8
|
+
* in the caller's existing precedence order; `DEFAULT_SITE_URL` closes every chain.
|
|
9
|
+
*/
|
|
10
|
+
export function pickSiteUrl(candidates) {
|
|
11
|
+
for (const [siteUrlSource, value, siteUrlDetail] of candidates) {
|
|
12
|
+
if (value)
|
|
13
|
+
return { siteUrl: value.replace(/\/$/, ''), siteUrlSource, siteUrlDetail };
|
|
14
|
+
}
|
|
15
|
+
return { siteUrl: DEFAULT_SITE_URL.replace(/\/$/, ''), siteUrlSource: 'default' };
|
|
16
|
+
}
|
|
17
|
+
/**
|
|
18
|
+
* What to call a `profile` source that has no name.
|
|
19
|
+
*
|
|
20
|
+
* `resolveProfileConfig()` reaches a credential four ways — `PB_PROFILE`, the active-profile
|
|
21
|
+
* marker, a `default` fallback, and the legacy `~/.config/productbrain/.env` — and `resolveConfig()`
|
|
22
|
+
* reports the legacy home file as `profile` too. Only the first three have a name, so a fixed
|
|
23
|
+
* "the active profile" label would claim the operator's active profile supplied a credential that
|
|
24
|
+
* came from `PB_PROFILE`, from `default`, or from a file that is not a profile at all.
|
|
25
|
+
*/
|
|
26
|
+
const UNNAMED_PROFILE_ORIGIN = 'the stored profile configuration';
|
|
27
|
+
/** Name the profile that won, or fall back to the nameless label above. */
|
|
28
|
+
function describeProfileOrigin(profileName) {
|
|
29
|
+
return profileName ? `the profile "${profileName}"` : UNNAMED_PROFILE_ORIGIN;
|
|
30
|
+
}
|
|
31
|
+
/** The repo pin knows which profile it names, so say so rather than "the profile it pins". */
|
|
32
|
+
function describePinnedOrigin(profileName) {
|
|
33
|
+
return profileName
|
|
34
|
+
? `the profile "${profileName}" pinned by .productbrain/config.local.json`
|
|
35
|
+
: 'the profile pinned by .productbrain/config.local.json';
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* The operator-facing name of each siteUrl origin.
|
|
39
|
+
*
|
|
40
|
+
* Only an environment API key outranks a file in the cascade, so every file-sourced URL keeps
|
|
41
|
+
* winning over `CONVEX_SITE_URL`. Telling every caller to set that variable would send the
|
|
42
|
+
* operator around the same failure again — the repair has to name the origin that actually won.
|
|
43
|
+
*/
|
|
44
|
+
const SITE_URL_ORIGIN = {
|
|
45
|
+
env: 'the CONVEX_SITE_URL environment variable',
|
|
46
|
+
'local-binding': '.productbrain/config.local.json',
|
|
47
|
+
'profile-pin': 'the profile pinned by .productbrain/config.local.json',
|
|
48
|
+
'project-config': '.productbrain/config.json',
|
|
49
|
+
profile: UNNAMED_PROFILE_ORIGIN,
|
|
50
|
+
legacy: 'the legacy config at ~/.config/productbrain/.env',
|
|
51
|
+
cwd: 'the .env file in this directory',
|
|
52
|
+
default: 'the built-in default',
|
|
53
|
+
};
|
|
54
|
+
/**
|
|
55
|
+
* Name the origin, preferring the concrete file or profile when the source covers several.
|
|
56
|
+
* `cwd` spans three candidate paths (`.env.mcp`, `packages/mcp-server/.env.mcp`, `.env`), so the
|
|
57
|
+
* generic label would send the operator to edit a file the cascade never read.
|
|
58
|
+
*/
|
|
59
|
+
function describeSiteUrlOrigin(source, detail, profileName) {
|
|
60
|
+
if (detail)
|
|
61
|
+
return `${detail} in this directory`;
|
|
62
|
+
if (source === 'profile')
|
|
63
|
+
return describeProfileOrigin(profileName);
|
|
64
|
+
if (source === 'profile-pin')
|
|
65
|
+
return describePinnedOrigin(profileName);
|
|
66
|
+
return SITE_URL_ORIGIN[source];
|
|
67
|
+
}
|
|
68
|
+
/** The operator-facing name of each credential origin, for `invalid-key` messages. */
|
|
69
|
+
const API_KEY_ORIGIN = {
|
|
70
|
+
env: 'the PRODUCTBRAIN_API_KEY environment variable',
|
|
71
|
+
project: '.productbrain/config.local.json (or the profile it pins)',
|
|
72
|
+
profile: UNNAMED_PROFILE_ORIGIN,
|
|
73
|
+
cwd: 'the .env file in this directory',
|
|
74
|
+
default: 'the built-in default',
|
|
75
|
+
};
|
|
76
|
+
/**
|
|
77
|
+
* Name the credential's origin, preferring the profile that actually supplied it.
|
|
78
|
+
* `project` covers both the direct local binding (no profile) and the repo pin (which names one),
|
|
79
|
+
* so the pin is only claimed when a profile name is actually present.
|
|
80
|
+
*/
|
|
81
|
+
function describeApiKeyOrigin(source, profileName) {
|
|
82
|
+
if (source === 'profile')
|
|
83
|
+
return describeProfileOrigin(profileName);
|
|
84
|
+
if (source === 'project' && profileName)
|
|
85
|
+
return describePinnedOrigin(profileName);
|
|
86
|
+
return API_KEY_ORIGIN[source];
|
|
87
|
+
}
|
|
88
|
+
/**
|
|
89
|
+
* The transport-security bar every cascade enforces: HTTPS, or http://localhost while in dev mode.
|
|
90
|
+
* Both the structured result below and `getConfigOrGuide()`'s throwing path read it from here so
|
|
91
|
+
* the two can never diverge on what counts as a safe gateway to send an API key to.
|
|
92
|
+
*/
|
|
93
|
+
export function isInsecureSiteUrl(siteUrl) {
|
|
94
|
+
return !siteUrl.startsWith('https://') && !(isDevMode() && siteUrl.startsWith('http://localhost'));
|
|
95
|
+
}
|
|
96
|
+
/** A resolution refusal that diagnostic surfaces must preserve instead of relabelling. */
|
|
97
|
+
export class ConfigResolutionError extends CLIError {
|
|
98
|
+
kind;
|
|
99
|
+
nextCommand;
|
|
100
|
+
constructor(message, options) {
|
|
101
|
+
super(message, options);
|
|
102
|
+
this.name = 'ConfigResolutionError';
|
|
103
|
+
this.kind = options.kind;
|
|
104
|
+
this.nextCommand = options.nextCommand;
|
|
105
|
+
}
|
|
106
|
+
}
|
|
107
|
+
/** Convert a throwing resolution cascade into the single diagnostic contract. */
|
|
108
|
+
export function resolveConfigResult(resolveConfig) {
|
|
109
|
+
let resolved;
|
|
110
|
+
try {
|
|
111
|
+
resolved = resolveConfig();
|
|
112
|
+
}
|
|
113
|
+
catch (error) {
|
|
114
|
+
if (error instanceof ConfigResolutionError) {
|
|
115
|
+
return {
|
|
116
|
+
ok: false,
|
|
117
|
+
kind: error.kind,
|
|
118
|
+
reason: error.message,
|
|
119
|
+
guidance: error.guidance ?? `Run \`${error.nextCommand}\`.`,
|
|
120
|
+
nextCommand: error.nextCommand,
|
|
121
|
+
};
|
|
122
|
+
}
|
|
123
|
+
const reason = error instanceof Error ? error.message : String(error);
|
|
124
|
+
return {
|
|
125
|
+
ok: false,
|
|
126
|
+
kind: 'unknown',
|
|
127
|
+
reason,
|
|
128
|
+
guidance: 'Run `pb doctor` to diagnose the configuration.',
|
|
129
|
+
nextCommand: 'pb doctor',
|
|
130
|
+
};
|
|
131
|
+
}
|
|
132
|
+
if (!resolved.apiKey) {
|
|
133
|
+
return {
|
|
134
|
+
ok: false,
|
|
135
|
+
kind: 'missing-key',
|
|
136
|
+
reason: 'No API key configured.',
|
|
137
|
+
guidance: 'Run `pb login` to configure your API key, or set PRODUCTBRAIN_API_KEY.',
|
|
138
|
+
nextCommand: 'pb login',
|
|
139
|
+
};
|
|
140
|
+
}
|
|
141
|
+
if (!resolved.apiKey.startsWith(API_KEY_PREFIX)) {
|
|
142
|
+
// `source` IS the credential's origin by definition, so it can be named without hedging.
|
|
143
|
+
// An exported key wins step 1 of the cascade, which `pb login` cannot outrank: login
|
|
144
|
+
// persists into a profile or the legacy home file, both of which lose to the env var —
|
|
145
|
+
// so telling the operator to log in would replay the identical failure forever.
|
|
146
|
+
const fromEnv = resolved.source === 'env';
|
|
147
|
+
return {
|
|
148
|
+
ok: false,
|
|
149
|
+
kind: 'invalid-key',
|
|
150
|
+
reason: `Invalid API key format (expected ${API_KEY_PREFIX}..., from ${describeApiKeyOrigin(resolved.source, resolved.profileName)}).`,
|
|
151
|
+
guidance: fromEnv
|
|
152
|
+
? `Unset PRODUCTBRAIN_API_KEY, or replace it with a valid ${API_KEY_PREFIX} key — it outranks every stored profile, so \`pb login\` cannot repair this.`
|
|
153
|
+
: 'Run `pb login` to configure a valid API key.',
|
|
154
|
+
nextCommand: fromEnv ? 'pb doctor' : 'pb login',
|
|
155
|
+
};
|
|
156
|
+
}
|
|
157
|
+
if (isInsecureSiteUrl(resolved.siteUrl)) {
|
|
158
|
+
const got = `got "${resolved.siteUrl.slice(0, 30)}…"`;
|
|
159
|
+
// No recorded provenance means the URL's origin was never observed. Naming a specific file
|
|
160
|
+
// here would be a guess, and a wrong repair target is worse than an unnamed one.
|
|
161
|
+
const origin = resolved.siteUrlSource
|
|
162
|
+
? describeSiteUrlOrigin(resolved.siteUrlSource, resolved.siteUrlDetail, resolved.profileName)
|
|
163
|
+
: null;
|
|
164
|
+
return {
|
|
165
|
+
ok: false,
|
|
166
|
+
kind: 'invalid-url',
|
|
167
|
+
reason: `Site URL must use HTTPS (${got}${origin ? `, from ${origin}` : ''}). API keys must not be sent over unencrypted connections.`,
|
|
168
|
+
guidance: origin
|
|
169
|
+
? `Set an https:// URL in ${origin}.`
|
|
170
|
+
: 'Run `pb doctor` to see which source supplied the URL, then set an https:// URL there.',
|
|
171
|
+
nextCommand: 'pb doctor',
|
|
172
|
+
};
|
|
173
|
+
}
|
|
174
|
+
return { ok: true, ...resolved, apiKey: resolved.apiKey };
|
|
175
|
+
}
|
|
176
|
+
//# sourceMappingURL=configResult.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"configResult.js","sourceRoot":"","sources":["../../src/lib/configResult.ts"],"names":[],"mappings":"AAAA,sEAAsE;AAEtE,OAAO,EAAE,gBAAgB,EAAE,SAAS,EAAE,MAAM,gBAAgB,CAAC;AAC7D,OAAO,EAAE,QAAQ,EAAsD,MAAM,aAAa,CAAC;AAC3F,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AA+CrD;;;;GAIG;AACH,MAAM,UAAU,WAAW,CACzB,UAAgF;IAEhF,KAAK,MAAM,CAAC,aAAa,EAAE,KAAK,EAAE,aAAa,CAAC,IAAI,UAAU,EAAE,CAAC;QAC/D,IAAI,KAAK;YAAE,OAAO,EAAE,OAAO,EAAE,KAAK,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE,aAAa,EAAE,CAAC;IACxF,CAAC;IACD,OAAO,EAAE,OAAO,EAAE,gBAAgB,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,aAAa,EAAE,SAAS,EAAE,CAAC;AACpF,CAAC;AAoBD;;;;;;;;GAQG;AACH,MAAM,sBAAsB,GAAG,kCAAkC,CAAC;AAElE,2EAA2E;AAC3E,SAAS,qBAAqB,CAAC,WAAoB;IACjD,OAAO,WAAW,CAAC,CAAC,CAAC,gBAAgB,WAAW,GAAG,CAAC,CAAC,CAAC,sBAAsB,CAAC;AAC/E,CAAC;AAED,8FAA8F;AAC9F,SAAS,oBAAoB,CAAC,WAAoB;IAChD,OAAO,WAAW;QAChB,CAAC,CAAC,gBAAgB,WAAW,6CAA6C;QAC1E,CAAC,CAAC,uDAAuD,CAAC;AAC9D,CAAC;AAED;;;;;;GAMG;AACH,MAAM,eAAe,GAAkC;IACrD,GAAG,EAAE,0CAA0C;IAC/C,eAAe,EAAE,iCAAiC;IAClD,aAAa,EAAE,uDAAuD;IACtE,gBAAgB,EAAE,2BAA2B;IAC7C,OAAO,EAAE,sBAAsB;IAC/B,MAAM,EAAE,kDAAkD;IAC1D,GAAG,EAAE,iCAAiC;IACtC,OAAO,EAAE,sBAAsB;CAChC,CAAC;AAEF;;;;GAIG;AACH,SAAS,qBAAqB,CAAC,MAAqB,EAAE,MAAe,EAAE,WAAoB;IACzF,IAAI,MAAM;QAAE,OAAO,GAAG,MAAM,oBAAoB,CAAC;IACjD,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,qBAAqB,CAAC,WAAW,CAAC,CAAC;IACpE,IAAI,MAAM,KAAK,aAAa;QAAE,OAAO,oBAAoB,CAAC,WAAW,CAAC,CAAC;IACvE,OAAO,eAAe,CAAC,MAAM,CAAC,CAAC;AACjC,CAAC;AAED,sFAAsF;AACtF,MAAM,cAAc,GAAiC;IACnD,GAAG,EAAE,+CAA+C;IACpD,OAAO,EAAE,0DAA0D;IACnE,OAAO,EAAE,sBAAsB;IAC/B,GAAG,EAAE,iCAAiC;IACtC,OAAO,EAAE,sBAAsB;CAChC,CAAC;AAEF;;;;GAIG;AACH,SAAS,oBAAoB,CAAC,MAAoB,EAAE,WAAoB;IACtE,IAAI,MAAM,KAAK,SAAS;QAAE,OAAO,qBAAqB,CAAC,WAAW,CAAC,CAAC;IACpE,IAAI,MAAM,KAAK,SAAS,IAAI,WAAW;QAAE,OAAO,oBAAoB,CAAC,WAAW,CAAC,CAAC;IAClF,OAAO,cAAc,CAAC,MAAM,CAAC,CAAC;AAChC,CAAC;AAED;;;;GAIG;AACH,MAAM,UAAU,iBAAiB,CAAC,OAAe;IAC/C,OAAO,CAAC,OAAO,CAAC,UAAU,CAAC,UAAU,CAAC,IAAI,CAAC,CAAC,SAAS,EAAE,IAAI,OAAO,CAAC,UAAU,CAAC,kBAAkB,CAAC,CAAC,CAAC;AACrG,CAAC;AAED,0FAA0F;AAC1F,MAAM,OAAO,qBAAsB,SAAQ,QAAQ;IACxC,IAAI,CAAoB;IACxB,WAAW,CAAS;IAE7B,YACE,OAAe,EACf,OAMC;QAED,KAAK,CAAC,OAAO,EAAE,OAAO,CAAC,CAAC;QACxB,IAAI,CAAC,IAAI,GAAG,uBAAuB,CAAC;QACpC,IAAI,CAAC,IAAI,GAAG,OAAO,CAAC,IAAI,CAAC;QACzB,IAAI,CAAC,WAAW,GAAG,OAAO,CAAC,WAAW,CAAC;IACzC,CAAC;CACF;AAED,iFAAiF;AACjF,MAAM,UAAU,mBAAmB,CAAC,aAAmC;IACrE,IAAI,QAAwB,CAAC;IAC7B,IAAI,CAAC;QACH,QAAQ,GAAG,aAAa,EAAE,CAAC;IAC7B,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,IAAI,KAAK,YAAY,qBAAqB,EAAE,CAAC;YAC3C,OAAO;gBACL,EAAE,EAAE,KAAK;gBACT,IAAI,EAAE,KAAK,CAAC,IAAI;gBAChB,MAAM,EAAE,KAAK,CAAC,OAAO;gBACrB,QAAQ,EAAE,KAAK,CAAC,QAAQ,IAAI,SAAS,KAAK,CAAC,WAAW,KAAK;gBAC3D,WAAW,EAAE,KAAK,CAAC,WAAW;aAC/B,CAAC;QACJ,CAAC;QACD,MAAM,MAAM,GAAG,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,MAAM,CAAC,KAAK,CAAC,CAAC;QACtE,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,SAAS;YACf,MAAM;YACN,QAAQ,EAAE,gDAAgD;YAC1D,WAAW,EAAE,WAAW;SACzB,CAAC;IACJ,CAAC;IAED,IAAI,CAAC,QAAQ,CAAC,MAAM,EAAE,CAAC;QACrB,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,wBAAwB;YAChC,QAAQ,EAAE,wEAAwE;YAClF,WAAW,EAAE,UAAU;SACxB,CAAC;IACJ,CAAC;IACD,IAAI,CAAC,QAAQ,CAAC,MAAM,CAAC,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QAChD,yFAAyF;QACzF,qFAAqF;QACrF,uFAAuF;QACvF,gFAAgF;QAChF,MAAM,OAAO,GAAG,QAAQ,CAAC,MAAM,KAAK,KAAK,CAAC;QAC1C,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,oCAAoC,cAAc,aAAa,oBAAoB,CAAC,QAAQ,CAAC,MAAM,EAAE,QAAQ,CAAC,WAAW,CAAC,IAAI;YACtI,QAAQ,EAAE,OAAO;gBACf,CAAC,CAAC,0DAA0D,cAAc,8EAA8E;gBACxJ,CAAC,CAAC,8CAA8C;YAClD,WAAW,EAAE,OAAO,CAAC,CAAC,CAAC,WAAW,CAAC,CAAC,CAAC,UAAU;SAChD,CAAC;IACJ,CAAC;IAED,IAAI,iBAAiB,CAAC,QAAQ,CAAC,OAAO,CAAC,EAAE,CAAC;QACxC,MAAM,GAAG,GAAG,QAAQ,QAAQ,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC,EAAE,EAAE,CAAC,IAAI,CAAC;QACtD,2FAA2F;QAC3F,iFAAiF;QACjF,MAAM,MAAM,GAAG,QAAQ,CAAC,aAAa;YACnC,CAAC,CAAC,qBAAqB,CAAC,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,aAAa,EAAE,QAAQ,CAAC,WAAW,CAAC;YAC7F,CAAC,CAAC,IAAI,CAAC;QACT,OAAO;YACL,EAAE,EAAE,KAAK;YACT,IAAI,EAAE,aAAa;YACnB,MAAM,EAAE,4BAA4B,GAAG,GAAG,MAAM,CAAC,CAAC,CAAC,UAAU,MAAM,EAAE,CAAC,CAAC,CAAC,EAAE,4DAA4D;YACtI,QAAQ,EAAE,MAAM;gBACd,CAAC,CAAC,0BAA0B,MAAM,GAAG;gBACrC,CAAC,CAAC,uFAAuF;YAC3F,WAAW,EAAE,WAAW;SACzB,CAAC;IACJ,CAAC;IAED,OAAO,EAAE,EAAE,EAAE,IAAI,EAAE,GAAG,QAAQ,EAAE,MAAM,EAAE,QAAQ,CAAC,MAAM,EAAE,CAAC;AAC5D,CAAC"}
|
|
@@ -0,0 +1,51 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo-local binding resolution — turns what `.productbrain/config.local.json` declares into a
|
|
3
|
+
* credential, or refuses outright.
|
|
4
|
+
*
|
|
5
|
+
* Both cascades that serve ordinary commands (`resolveConfig()`'s return path and `loadEnv()`'s
|
|
6
|
+
* process.env path) call these two helpers, so the refusal rules cannot drift apart between them
|
|
7
|
+
* (STD-3). The shared rule is the same in both: when the repo has declared a tenant, resolution
|
|
8
|
+
* either honors that declaration or fails loudly — it never substitutes a different workspace and
|
|
9
|
+
* carries on. Fail loud, fail closed, fail fast (PRI-24); default closed, explicitly opened
|
|
10
|
+
* (PRI-22).
|
|
11
|
+
*
|
|
12
|
+
* `adminConfig.ts` keeps its own inline copy on purpose: it excludes config.json's siteUrl and the
|
|
13
|
+
* CWD .env files, so it is a narrower cascade, not this one. Folding it in is the remaining step
|
|
14
|
+
* toward a single code path (STA-3).
|
|
15
|
+
*/
|
|
16
|
+
import type { LocalProjectConfig } from './projectConfig.js';
|
|
17
|
+
/**
|
|
18
|
+
* Resolve the repo-local profile pin to a credential, or refuse outright.
|
|
19
|
+
*
|
|
20
|
+
* FAILS CLOSED on a DANGLING pin: a pin naming a profile that has since been deleted, or whose
|
|
21
|
+
* file carries no key, must NEVER fall through to the global active profile. The repo has
|
|
22
|
+
* explicitly declared its tenant; substituting a different one silently retargets every read AND
|
|
23
|
+
* write at a foreign workspace, and the gateway derives the workspace from the credential (BR-18)
|
|
24
|
+
* — so nothing downstream catches it.
|
|
25
|
+
*
|
|
26
|
+
* INS-2455 is the admin-path instance of exactly this fall-through: `resolveAdminConfig()` was
|
|
27
|
+
* hardened on its own, which left the two cascades every other command uses still fail-open.
|
|
28
|
+
*/
|
|
29
|
+
export declare function resolvePinnedProfile(profileName: string): {
|
|
30
|
+
apiKey: string;
|
|
31
|
+
siteUrl?: string;
|
|
32
|
+
};
|
|
33
|
+
/**
|
|
34
|
+
* Resolve the direct credential binding in config.local.json, or null when the file does not
|
|
35
|
+
* claim one (the profile pin then owns resolution).
|
|
36
|
+
*
|
|
37
|
+
* The claim is the `preview` marker OR a credential of its own — deliberately NOT the marker
|
|
38
|
+
* alone. A file carrying an apiKey is asserting which workspace this repo talks to whether or not
|
|
39
|
+
* `pb:preview` stamped the marker, so gating on the marker would let a hand-written or
|
|
40
|
+
* partially-scrubbed binding be skipped and a sibling profile pin silently supply a different
|
|
41
|
+
* tenant's credential instead (the failure class TEN-3237/TEN-3238 closed for the pin path).
|
|
42
|
+
*
|
|
43
|
+
* Once claimed, only the atomic apiKey+siteUrl pair (SF-3) may bind — never a preview key paired
|
|
44
|
+
* with config.json's real-dev gateway (STD-261). Anything else refuses here, ahead of the pin, so
|
|
45
|
+
* a broken binding is named rather than routed around.
|
|
46
|
+
*/
|
|
47
|
+
export declare function resolveLocalBinding(localConfig: LocalProjectConfig | null): {
|
|
48
|
+
apiKey: string;
|
|
49
|
+
siteUrl: string;
|
|
50
|
+
} | null;
|
|
51
|
+
//# sourceMappingURL=localBinding.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localBinding.d.ts","sourceRoot":"","sources":["../../src/lib/localBinding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAKH,OAAO,KAAK,EAAE,kBAAkB,EAAE,MAAM,oBAAoB,CAAC;AAG7D;;;;;;;;;;;GAWG;AACH,wBAAgB,oBAAoB,CAAC,WAAW,EAAE,MAAM,GAAG;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,CAAC,EAAE,MAAM,CAAA;CAAE,CAqB9F;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,mBAAmB,CACjC,WAAW,EAAE,kBAAkB,GAAG,IAAI,GACrC;IAAE,MAAM,EAAE,MAAM,CAAC;IAAC,OAAO,EAAE,MAAM,CAAA;CAAE,GAAG,IAAI,CA2B5C"}
|
|
@@ -0,0 +1,90 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Repo-local binding resolution — turns what `.productbrain/config.local.json` declares into a
|
|
3
|
+
* credential, or refuses outright.
|
|
4
|
+
*
|
|
5
|
+
* Both cascades that serve ordinary commands (`resolveConfig()`'s return path and `loadEnv()`'s
|
|
6
|
+
* process.env path) call these two helpers, so the refusal rules cannot drift apart between them
|
|
7
|
+
* (STD-3). The shared rule is the same in both: when the repo has declared a tenant, resolution
|
|
8
|
+
* either honors that declaration or fails loudly — it never substitutes a different workspace and
|
|
9
|
+
* carries on. Fail loud, fail closed, fail fast (PRI-24); default closed, explicitly opened
|
|
10
|
+
* (PRI-22).
|
|
11
|
+
*
|
|
12
|
+
* `adminConfig.ts` keeps its own inline copy on purpose: it excludes config.json's siteUrl and the
|
|
13
|
+
* CWD .env files, so it is a narrower cascade, not this one. Folding it in is the remaining step
|
|
14
|
+
* toward a single code path (STA-3).
|
|
15
|
+
*/
|
|
16
|
+
import { ConfigResolutionError } from './configResult.js';
|
|
17
|
+
import { ErrorCode } from './errors.js';
|
|
18
|
+
import { resolveNamedProfileConfig } from './profiles.js';
|
|
19
|
+
import { API_KEY_PREFIX } from './tokenConstants.js';
|
|
20
|
+
/**
|
|
21
|
+
* Resolve the repo-local profile pin to a credential, or refuse outright.
|
|
22
|
+
*
|
|
23
|
+
* FAILS CLOSED on a DANGLING pin: a pin naming a profile that has since been deleted, or whose
|
|
24
|
+
* file carries no key, must NEVER fall through to the global active profile. The repo has
|
|
25
|
+
* explicitly declared its tenant; substituting a different one silently retargets every read AND
|
|
26
|
+
* write at a foreign workspace, and the gateway derives the workspace from the credential (BR-18)
|
|
27
|
+
* — so nothing downstream catches it.
|
|
28
|
+
*
|
|
29
|
+
* INS-2455 is the admin-path instance of exactly this fall-through: `resolveAdminConfig()` was
|
|
30
|
+
* hardened on its own, which left the two cascades every other command uses still fail-open.
|
|
31
|
+
*/
|
|
32
|
+
export function resolvePinnedProfile(profileName) {
|
|
33
|
+
const namedConfig = resolveNamedProfileConfig(profileName);
|
|
34
|
+
// Same validity bar as adminConfig.ts: a present-but-malformed key is a broken pin, not a
|
|
35
|
+
// credential. Accepting it here would surface as a generic "no API key, run pb login" far
|
|
36
|
+
// downstream, pointing the operator away from the pin that actually broke.
|
|
37
|
+
if (!namedConfig?.apiKey?.startsWith(API_KEY_PREFIX)) {
|
|
38
|
+
throw new ConfigResolutionError(`This repo is pinned to profile "${profileName}" (.productbrain/config.local.json), ` +
|
|
39
|
+
`but that profile has no valid API key — it may have been deleted or never fully set up. ` +
|
|
40
|
+
`Refusing to silently fall back to a different workspace. ` +
|
|
41
|
+
`Run \`pb profile use --local <profile>\` to fix the pin, or \`pb profile create ${profileName}\` to restore it.`, {
|
|
42
|
+
code: ErrorCode.AUTH_MISSING,
|
|
43
|
+
category: 'auth',
|
|
44
|
+
kind: 'profile-pin',
|
|
45
|
+
nextCommand: 'pb profile use --local <profile>',
|
|
46
|
+
guidance: 'Run `pb profile use --local <profile>` to fix the repo pin.',
|
|
47
|
+
});
|
|
48
|
+
}
|
|
49
|
+
return { apiKey: namedConfig.apiKey, siteUrl: namedConfig.siteUrl };
|
|
50
|
+
}
|
|
51
|
+
/**
|
|
52
|
+
* Resolve the direct credential binding in config.local.json, or null when the file does not
|
|
53
|
+
* claim one (the profile pin then owns resolution).
|
|
54
|
+
*
|
|
55
|
+
* The claim is the `preview` marker OR a credential of its own — deliberately NOT the marker
|
|
56
|
+
* alone. A file carrying an apiKey is asserting which workspace this repo talks to whether or not
|
|
57
|
+
* `pb:preview` stamped the marker, so gating on the marker would let a hand-written or
|
|
58
|
+
* partially-scrubbed binding be skipped and a sibling profile pin silently supply a different
|
|
59
|
+
* tenant's credential instead (the failure class TEN-3237/TEN-3238 closed for the pin path).
|
|
60
|
+
*
|
|
61
|
+
* Once claimed, only the atomic apiKey+siteUrl pair (SF-3) may bind — never a preview key paired
|
|
62
|
+
* with config.json's real-dev gateway (STD-261). Anything else refuses here, ahead of the pin, so
|
|
63
|
+
* a broken binding is named rather than routed around.
|
|
64
|
+
*/
|
|
65
|
+
export function resolveLocalBinding(localConfig) {
|
|
66
|
+
if (!localConfig)
|
|
67
|
+
return null;
|
|
68
|
+
const claimsBinding = localConfig.preview === true || Boolean(localConfig.apiKey) || localConfig.apiKeyMalformed === true;
|
|
69
|
+
if (!claimsBinding)
|
|
70
|
+
return null;
|
|
71
|
+
if (localConfig.apiKey && localConfig.siteUrl) {
|
|
72
|
+
return { apiKey: localConfig.apiKey, siteUrl: localConfig.siteUrl.replace(/\/$/, '') };
|
|
73
|
+
}
|
|
74
|
+
// A malformed key is present-but-unusable, not absent — readLocalProjectConfig() withholds it
|
|
75
|
+
// from `apiKey` so it can never bind, and flags it so this refusal can name the real fault.
|
|
76
|
+
const fault = localConfig.apiKeyMalformed
|
|
77
|
+
? `its API key is not a valid ${API_KEY_PREFIX} key`
|
|
78
|
+
: !localConfig.apiKey
|
|
79
|
+
? 'missing API key'
|
|
80
|
+
: 'missing deployment URL';
|
|
81
|
+
throw new ConfigResolutionError(`This repo has an incomplete preview binding (.productbrain/config.local.json): ` +
|
|
82
|
+
`${fault}. Refusing to use its profile pin or another workspace instead.`, {
|
|
83
|
+
code: ErrorCode.CONFIG_INVALID,
|
|
84
|
+
category: 'config',
|
|
85
|
+
kind: 'preview-binding',
|
|
86
|
+
nextCommand: 'npm run pb:preview',
|
|
87
|
+
guidance: 'Run `npm run pb:preview` to repair the preview binding, or `npm run pb:real` to return to the repo pin.',
|
|
88
|
+
});
|
|
89
|
+
}
|
|
90
|
+
//# sourceMappingURL=localBinding.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"localBinding.js","sourceRoot":"","sources":["../../src/lib/localBinding.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,qBAAqB,EAAE,MAAM,mBAAmB,CAAC;AAC1D,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,yBAAyB,EAAE,MAAM,eAAe,CAAC;AAE1D,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAErD;;;;;;;;;;;GAWG;AACH,MAAM,UAAU,oBAAoB,CAAC,WAAmB;IACtD,MAAM,WAAW,GAAG,yBAAyB,CAAC,WAAW,CAAC,CAAC;IAC3D,0FAA0F;IAC1F,0FAA0F;IAC1F,2EAA2E;IAC3E,IAAI,CAAC,WAAW,EAAE,MAAM,EAAE,UAAU,CAAC,cAAc,CAAC,EAAE,CAAC;QACrD,MAAM,IAAI,qBAAqB,CAC7B,mCAAmC,WAAW,uCAAuC;YACnF,0FAA0F;YAC1F,2DAA2D;YAC3D,mFAAmF,WAAW,mBAAmB,EACnH;YACE,IAAI,EAAE,SAAS,CAAC,YAAY;YAC5B,QAAQ,EAAE,MAAM;YAChB,IAAI,EAAE,aAAa;YACnB,WAAW,EAAE,kCAAkC;YAC/C,QAAQ,EAAE,6DAA6D;SACxE,CACF,CAAC;IACJ,CAAC;IACD,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,EAAE,CAAC;AACtE,CAAC;AAED;;;;;;;;;;;;;GAaG;AACH,MAAM,UAAU,mBAAmB,CACjC,WAAsC;IAEtC,IAAI,CAAC,WAAW;QAAE,OAAO,IAAI,CAAC;IAC9B,MAAM,aAAa,GACjB,WAAW,CAAC,OAAO,KAAK,IAAI,IAAI,OAAO,CAAC,WAAW,CAAC,MAAM,CAAC,IAAI,WAAW,CAAC,eAAe,KAAK,IAAI,CAAC;IACtG,IAAI,CAAC,aAAa;QAAE,OAAO,IAAI,CAAC;IAChC,IAAI,WAAW,CAAC,MAAM,IAAI,WAAW,CAAC,OAAO,EAAE,CAAC;QAC9C,OAAO,EAAE,MAAM,EAAE,WAAW,CAAC,MAAM,EAAE,OAAO,EAAE,WAAW,CAAC,OAAO,CAAC,OAAO,CAAC,KAAK,EAAE,EAAE,CAAC,EAAE,CAAC;IACzF,CAAC;IAED,8FAA8F;IAC9F,4FAA4F;IAC5F,MAAM,KAAK,GAAG,WAAW,CAAC,eAAe;QACvC,CAAC,CAAC,8BAA8B,cAAc,MAAM;QACpD,CAAC,CAAC,CAAC,WAAW,CAAC,MAAM;YACnB,CAAC,CAAC,iBAAiB;YACnB,CAAC,CAAC,wBAAwB,CAAC;IAC/B,MAAM,IAAI,qBAAqB,CAC7B,iFAAiF;QAC/E,GAAG,KAAK,iEAAiE,EAC3E;QACE,IAAI,EAAE,SAAS,CAAC,cAAc;QAC9B,QAAQ,EAAE,QAAQ;QAClB,IAAI,EAAE,iBAAiB;QACvB,WAAW,EAAE,oBAAoB;QACjC,QAAQ,EAAE,yGAAyG;KACpH,CACF,CAAC;AACJ,CAAC"}
|
|
@@ -22,6 +22,12 @@ export interface LocalProjectConfig {
|
|
|
22
22
|
profile?: string;
|
|
23
23
|
/** Direct preview API key — set by `npm run pb:preview`, cleared by `npm run pb:real`. */
|
|
24
24
|
apiKey?: string;
|
|
25
|
+
/**
|
|
26
|
+
* An `apiKey` was present but failed the `API_KEY_PREFIX` bar, so it was not loaded.
|
|
27
|
+
* Kept apart from `apiKey` so a malformed key can never bind, while diagnostics can still
|
|
28
|
+
* say "malformed" instead of misreporting it as absent.
|
|
29
|
+
*/
|
|
30
|
+
apiKeyMalformed?: boolean;
|
|
25
31
|
/** Preview site URL — set alongside apiKey by `npm run pb:preview`. */
|
|
26
32
|
siteUrl?: string;
|
|
27
33
|
/** Marks this config as preview-bound; the CLI shows a loud banner on every command. */
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectConfig.d.ts","sourceRoot":"","sources":["../../src/lib/projectConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,aAAa,GAAG,IAAI,CAuBxD;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,kBAAkB,GAAG,IAAI,
|
|
1
|
+
{"version":3,"file":"projectConfig.d.ts","sourceRoot":"","sources":["../../src/lib/projectConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAOH,iFAAiF;AACjF,MAAM,WAAW,aAAa;IAC5B,OAAO,CAAC,EAAE,MAAM,CAAC;CAClB;AAED,+FAA+F;AAC/F,MAAM,WAAW,kBAAkB;IACjC,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,0FAA0F;IAC1F,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB;;;;OAIG;IACH,eAAe,CAAC,EAAE,OAAO,CAAC;IAC1B,uEAAuE;IACvE,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,wFAAwF;IACxF,OAAO,CAAC,EAAE,OAAO,CAAC;IAClB,uFAAuF;IACvF,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAED;;;;;;;;;;;;;;GAcG;AACH,wBAAgB,eAAe,CAAC,QAAQ,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CAgB/D;AAED;;;;;GAKG;AACH,wBAAgB,iBAAiB,IAAI,aAAa,GAAG,IAAI,CAuBxD;AAED;;;;;;;GAOG;AACH,wBAAgB,sBAAsB,IAAI,kBAAkB,GAAG,IAAI,CA8ClE"}
|
|
@@ -107,8 +107,20 @@ export function readLocalProjectConfig() {
|
|
|
107
107
|
result.profile = parsed.profile;
|
|
108
108
|
}
|
|
109
109
|
// Preview binding fields — written by `npm run pb:preview`, cleared by `npm run pb:real`.
|
|
110
|
-
|
|
111
|
-
|
|
110
|
+
//
|
|
111
|
+
// Fail closed on ANY present-but-invalid apiKey, not just a wrong-prefix string: a number,
|
|
112
|
+
// an empty string, a boolean or an object is a corrupt or hand-written binding, and treating
|
|
113
|
+
// it as absent would let the caller skip the claimed binding and adopt a sibling profile pin
|
|
114
|
+
// — the workspace substitution this whole path exists to prevent. Only `undefined`/`null`
|
|
115
|
+
// count as absent: JSON `null` conventionally means "no value", and `npm run pb:real` strips
|
|
116
|
+
// the preview keys entirely rather than nulling them.
|
|
117
|
+
if (parsed.apiKey !== undefined && parsed.apiKey !== null) {
|
|
118
|
+
if (typeof parsed.apiKey === 'string' && parsed.apiKey.startsWith(API_KEY_PREFIX)) {
|
|
119
|
+
result.apiKey = parsed.apiKey;
|
|
120
|
+
}
|
|
121
|
+
else {
|
|
122
|
+
result.apiKeyMalformed = true;
|
|
123
|
+
}
|
|
112
124
|
}
|
|
113
125
|
if (typeof parsed.siteUrl === 'string' && parsed.siteUrl) {
|
|
114
126
|
result.siteUrl = parsed.siteUrl;
|
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"projectConfig.js","sourceRoot":"","sources":["../../src/lib/projectConfig.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;;;GAcG;AAEH,OAAO,EAAE,YAAY,EAAE,UAAU,EAAE,MAAM,IAAI,CAAC;AAC9C,OAAO,EAAE,OAAO,EAAE,MAAM,MAAM,CAAC;AAC/B,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;
|
|
1
|
+
{"version":3,"file":"projectConfig.js","sourceRoot":"","sources":["../../src/lib/projectConfig.ts"],"names":[],"mappings":"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"}
|
package/dist/lib/state.d.ts
CHANGED
|
@@ -11,6 +11,7 @@
|
|
|
11
11
|
* workspace-bound → key + workspace reachable, but no active session
|
|
12
12
|
* active → key + workspace + active session
|
|
13
13
|
*/
|
|
14
|
+
import { type ConfigFailureKind } from './config.js';
|
|
14
15
|
/** Four-stage setup journey from cold start to active use. */
|
|
15
16
|
export type SetupStage = 'unconfigured' | 'authenticated' | 'workspace-bound' | 'active';
|
|
16
17
|
export interface SetupState {
|
|
@@ -25,6 +26,12 @@ export interface SetupState {
|
|
|
25
26
|
entryCount?: number;
|
|
26
27
|
/** Whether a write session is currently active. */
|
|
27
28
|
sessionActive?: boolean;
|
|
29
|
+
/** Exact local-binding failure, when this is more specific than ordinary first-time setup. */
|
|
30
|
+
configurationIssue?: {
|
|
31
|
+
kind: ConfigFailureKind;
|
|
32
|
+
reason: string;
|
|
33
|
+
guidance: string;
|
|
34
|
+
};
|
|
28
35
|
}
|
|
29
36
|
/** Output render tier for the current runtime context. */
|
|
30
37
|
export type RenderTier = 'json' | 'plain' | 'ink';
|
package/dist/lib/state.d.ts.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/lib/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;
|
|
1
|
+
{"version":3,"file":"state.d.ts","sourceRoot":"","sources":["../../src/lib/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAiB,KAAK,iBAAiB,EAAE,MAAM,aAAa,CAAC;AASpE,8DAA8D;AAC9D,MAAM,MAAM,UAAU,GAAG,cAAc,GAAG,eAAe,GAAG,iBAAiB,GAAG,QAAQ,CAAC;AAEzF,MAAM,WAAW,UAAU;IACzB,KAAK,EAAE,UAAU,CAAC;IAClB,mFAAmF;IACnF,YAAY,EAAE,MAAM,EAAE,CAAC;IACvB,iDAAiD;IACjD,UAAU,EAAE,MAAM,CAAC;IACnB,gEAAgE;IAChE,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,sFAAsF;IACtF,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,mDAAmD;IACnD,aAAa,CAAC,EAAE,OAAO,CAAC;IACxB,8FAA8F;IAC9F,kBAAkB,CAAC,EAAE;QACnB,IAAI,EAAE,iBAAiB,CAAC;QACxB,MAAM,EAAE,MAAM,CAAC;QACf,QAAQ,EAAE,MAAM,CAAC;KAClB,CAAC;CACH;AAED,0DAA0D;AAC1D,MAAM,MAAM,UAAU,GAAG,MAAM,GAAG,OAAO,GAAG,KAAK,CAAC;AAMlD;;;;;;;;;GASG;AACH,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,UAAU,CAAC,CAgD7D;AAMD;;;;;;;GAOG;AACH,wBAAgB,gBAAgB,IAAI,UAAU,CAI7C"}
|
package/dist/lib/state.js
CHANGED
|
@@ -34,8 +34,13 @@ export async function resolveSetupState() {
|
|
|
34
34
|
if (!configResult.ok) {
|
|
35
35
|
return {
|
|
36
36
|
stage: 'unconfigured',
|
|
37
|
-
missingSteps: [
|
|
38
|
-
nextAction:
|
|
37
|
+
missingSteps: [configResult.reason],
|
|
38
|
+
nextAction: configResult.nextCommand,
|
|
39
|
+
configurationIssue: {
|
|
40
|
+
kind: configResult.kind,
|
|
41
|
+
reason: configResult.reason,
|
|
42
|
+
guidance: configResult.guidance,
|
|
43
|
+
},
|
|
39
44
|
};
|
|
40
45
|
}
|
|
41
46
|
// Step 2 — can we reach the workspace?
|
package/dist/lib/state.js.map
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/lib/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,
|
|
1
|
+
{"version":3,"file":"state.js","sourceRoot":"","sources":["../../src/lib/state.ts"],"names":[],"mappings":"AAAA;;;;;;;;;;;;GAYG;AAEH,OAAO,EAAE,aAAa,EAA0B,MAAM,aAAa,CAAC;AACpE,OAAO,EAAE,cAAc,EAAE,MAAM,sBAAsB,CAAC;AACtD,OAAO,EAAE,WAAW,EAAE,MAAM,cAAc,CAAC;AAC3C,OAAO,EAAE,UAAU,EAAE,MAAM,aAAa,CAAC;AAgCzC,8EAA8E;AAC9E,oBAAoB;AACpB,8EAA8E;AAE9E;;;;;;;;;GASG;AACH,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,qCAAqC;IACrC,MAAM,YAAY,GAAG,aAAa,EAAE,CAAC;IACrC,IAAI,CAAC,YAAY,CAAC,EAAE,EAAE,CAAC;QACrB,OAAO;YACL,KAAK,EAAE,cAAc;YACrB,YAAY,EAAE,CAAC,YAAY,CAAC,MAAM,CAAC;YACnC,UAAU,EAAE,YAAY,CAAC,WAAW;YACpC,kBAAkB,EAAE;gBAClB,IAAI,EAAE,YAAY,CAAC,IAAI;gBACvB,MAAM,EAAE,YAAY,CAAC,MAAM;gBAC3B,QAAQ,EAAE,YAAY,CAAC,QAAQ;aAChC;SACF,CAAC;IACJ,CAAC;IAED,uCAAuC;IACvC,MAAM,MAAM,GAAG,MAAM,cAAc,EAAE,CAAC;IACtC,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,OAAO;YACL,KAAK,EAAE,eAAe;YACtB,YAAY,EAAE,CAAC,wCAAwC,CAAC;YACxD,UAAU,EAAE,YAAY;SACzB,CAAC;IACJ,CAAC;IAED,6CAA6C;IAC7C,MAAM,OAAO,GAAG,WAAW,EAAE,CAAC;IAC9B,IAAI,CAAC,OAAO,EAAE,CAAC;QACb,OAAO;YACL,KAAK,EAAE,iBAAiB;YACxB,YAAY,EAAE,CAAC,yBAAyB,CAAC;YACzC,UAAU,EAAE,kBAAkB;YAC9B,aAAa,EAAE,MAAM,CAAC,IAAI;YAC1B,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,aAAa,EAAE,KAAK;SACrB,CAAC;IACJ,CAAC;IAED,qCAAqC;IACrC,OAAO;QACL,KAAK,EAAE,QAAQ;QACf,YAAY,EAAE,EAAE;QAChB,UAAU,EAAE,cAAc;QAC1B,aAAa,EAAE,MAAM,CAAC,IAAI;QAC1B,UAAU,EAAE,MAAM,CAAC,UAAU;QAC7B,aAAa,EAAE,IAAI;KACpB,CAAC;AACJ,CAAC;AAED,8EAA8E;AAC9E,mBAAmB;AACnB,8EAA8E;AAE9E;;;;;;;GAOG;AACH,MAAM,UAAU,gBAAgB;IAC9B,IAAI,UAAU,EAAE;QAAE,OAAO,MAAM,CAAC;IAChC,IAAI,CAAC,OAAO,CAAC,MAAM,CAAC,KAAK;QAAE,OAAO,OAAO,CAAC;IAC1C,OAAO,KAAK,CAAC;AACf,CAAC"}
|