@credibledata/cred-cli 0.0.105 → 0.0.107

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 (73) hide show
  1. package/README.md +12 -4
  2. package/dist/agent-contract.spec.js +21 -6
  3. package/dist/agent-contract.spec.js.map +1 -1
  4. package/dist/client/api.js +900 -521
  5. package/dist/client/api.js.map +1 -1
  6. package/dist/client-codeassist/api.js.map +1 -1
  7. package/dist/commands/misc/mcp.boot.harness.js +14 -0
  8. package/dist/commands/misc/mcp.boot.harness.js.map +1 -0
  9. package/dist/commands/misc/mcp.harness.js +9 -0
  10. package/dist/commands/misc/mcp.harness.js.map +1 -0
  11. package/dist/commands/misc/mcp.js +258 -0
  12. package/dist/commands/misc/mcp.js.map +1 -0
  13. package/dist/commands/misc/mcp.spec.js +213 -0
  14. package/dist/commands/misc/mcp.spec.js.map +1 -0
  15. package/dist/commands/misc/setup.js +72 -3
  16. package/dist/commands/misc/setup.js.map +1 -1
  17. package/dist/commands/misc/setup.spec.js +51 -6
  18. package/dist/commands/misc/setup.spec.js.map +1 -1
  19. package/dist/commands/misc/status.js +7 -1
  20. package/dist/commands/misc/status.js.map +1 -1
  21. package/dist/commands/misc/status.spec.js +34 -0
  22. package/dist/commands/misc/status.spec.js.map +1 -1
  23. package/dist/index.js +52 -11
  24. package/dist/index.js.map +1 -1
  25. package/dist/setup-core/briefing.d.ts +68 -0
  26. package/dist/setup-core/briefing.js +20 -11
  27. package/dist/setup-core/briefing.js.map +1 -1
  28. package/dist/setup-core/credstore.d.ts +229 -0
  29. package/dist/setup-core/environments.d.ts +112 -0
  30. package/dist/setup-core/flow.d.ts +171 -0
  31. package/dist/setup-core/flow.js +54 -28
  32. package/dist/setup-core/flow.js.map +1 -1
  33. package/dist/setup-core/flow.spec.js +135 -54
  34. package/dist/setup-core/flow.spec.js.map +1 -1
  35. package/dist/setup-core/folderconfig.d.ts +184 -0
  36. package/dist/setup-core/folderconfig.js +82 -21
  37. package/dist/setup-core/folderconfig.js.map +1 -1
  38. package/dist/setup-core/git.d.ts +57 -0
  39. package/dist/setup-core/git.js +37 -0
  40. package/dist/setup-core/git.js.map +1 -1
  41. package/dist/setup-core/host.d.ts +121 -0
  42. package/dist/setup-core/importconnections.d.ts +171 -0
  43. package/dist/setup-core/liveness.d.ts +17 -0
  44. package/dist/setup-core/localpublisherconfig.d.ts +10 -0
  45. package/dist/setup-core/mcpconfig.d.ts +132 -0
  46. package/dist/setup-core/mcpconfig.js +41 -17
  47. package/dist/setup-core/mcpconfig.js.map +1 -1
  48. package/dist/setup-core/mcpconfig.spec.js +17 -6
  49. package/dist/setup-core/mcpconfig.spec.js.map +1 -1
  50. package/dist/setup-core/packagemanifest.d.ts +28 -0
  51. package/dist/setup-core/ports.d.ts +95 -0
  52. package/dist/setup-core/ports.js +6 -5
  53. package/dist/setup-core/ports.js.map +1 -1
  54. package/dist/setup-core/publisherconfig.d.ts +163 -0
  55. package/dist/setup-core/publisherversion.d.ts +22 -0
  56. package/dist/setup-core/publisherversion.js +1 -1
  57. package/dist/setup-core/readylines.d.ts +109 -0
  58. package/dist/setup-core/refresh.d.ts +80 -0
  59. package/dist/setup-core/skills.d.ts +114 -0
  60. package/dist/setup-core/skills.js +34 -11
  61. package/dist/setup-core/skills.js.map +1 -1
  62. package/dist/setup-core/skills.spec.js +20 -0
  63. package/dist/setup-core/skills.spec.js.map +1 -1
  64. package/dist/setup-core/staleservers.d.ts +27 -0
  65. package/dist/setup-core/staleservers.js +97 -0
  66. package/dist/setup-core/staleservers.js.map +1 -0
  67. package/dist/setup-core/staleservers.spec.js +63 -0
  68. package/dist/setup-core/staleservers.spec.js.map +1 -0
  69. package/dist/setup-core/supervisor.d.ts +187 -0
  70. package/dist/setup-core/supervisor.js +1 -0
  71. package/dist/setup-core/supervisor.js.map +1 -1
  72. package/dist/setup-core/writefile.d.ts +27 -0
  73. package/package.json +4 -3
@@ -0,0 +1,229 @@
1
+ import { type ResolvedTokens } from "@credibledata/cli-auth";
2
+ export type { ResolvedTokens };
3
+ import { Environment } from "./environments";
4
+ /**
5
+ * The `~/.cred` credential store, shared by the `cred` CLI and the VS Code
6
+ * extension.
7
+ *
8
+ * ## Why token reads are claim-based, not field-based
9
+ *
10
+ * The two tools disagree about what the `accessToken` FIELD holds:
11
+ *
12
+ * - the CLI writes the Auth0 **access token** there (`login.ts`), and sends it
13
+ * as `Bearer` to the control plane;
14
+ * - the extension writes the **id token** there on every refresh
15
+ * (`vscode-ext/src/user/user.ts`), and keeps the access token in
16
+ * `jwtAccessToken`.
17
+ *
18
+ * Whichever tool wrote last decides what the field means, so reading it by name
19
+ * makes the CLI's behaviour depend on a race. Instead every read resolves the
20
+ * token it wants by inspecting the JWT's `aud` claim: the access token is
21
+ * audienced to the Auth0 API, the id token to the client id. Both are known per
22
+ * deployment env, so classification is exact rather than heuristic.
23
+ *
24
+ * This is a PERMANENT contract, not a migration step. The shipped extension
25
+ * keeps writing the old shape on every refresh, so a one-time fixup would be
26
+ * undone within minutes of the user opening VS Code. It can be deleted only
27
+ * once the extension writes the same shape (Phase 2 of the local-devex plan).
28
+ *
29
+ * A schema-version marker was considered and rejected for the same reason: the
30
+ * extension mutates the file after we stamp it, so a version field would claim
31
+ * a shape the file no longer has. Claim-based classification needs no marker
32
+ * because it reads the tokens themselves.
33
+ *
34
+ * Writes stay in the shape the extension already understands — `accessToken`
35
+ * and `jwtAccessToken` both set to the access token, `idToken` added alongside —
36
+ * so nothing about this change alters what the extension sees today.
37
+ */
38
+ interface StoredEnvConfig {
39
+ organization?: string;
40
+ project?: string;
41
+ auth?: {
42
+ username: string;
43
+ password: string;
44
+ };
45
+ vsCodeUser?: {
46
+ username: string;
47
+ email: string;
48
+ };
49
+ /**
50
+ * Ambiguous by history: the access token when the CLI wrote it, the id token
51
+ * when the extension did. Never read by name — see {@link resolveTokens}.
52
+ */
53
+ accessToken?: string;
54
+ /** The access token, per the extension's convention. Also written by the CLI. */
55
+ jwtAccessToken?: string;
56
+ /** The id token. Written by this CLI only; absent in files the extension wrote. */
57
+ idToken?: string;
58
+ refreshToken?: string;
59
+ isServiceAccountToken?: boolean;
60
+ }
61
+ interface ConfigFile {
62
+ configPerEnvironment: Partial<Record<Environment, StoredEnvConfig>>;
63
+ activeEnvironment: Environment;
64
+ }
65
+ /**
66
+ * Thrown when `.cred` exists but cannot be used: either it does not parse as
67
+ * JSON or it parses but lacks the required top-level shape. Distinct from fs
68
+ * errors (ENOENT, EACCES) so callers that want to recover — `cred login` —
69
+ * can target *only* a corrupt-content failure and leave permission/IO errors
70
+ * to surface loudly.
71
+ */
72
+ export declare class InvalidConfigError extends Error {
73
+ }
74
+ /**
75
+ * Sort the stored token fields into access token and id token by audience,
76
+ * using the rules `cred` and `ms2ad` share (cli-auth/src/index.ts, where each
77
+ * rule is written down and tested), bound to this deployment's Auth0 values.
78
+ *
79
+ * A token it cannot identify comes back `undefined`, so the caller says "run
80
+ * `cred login`" instead of sending a token the control plane will refuse.
81
+ */
82
+ export declare function resolveTokens(stored: StoredEnvConfig, environment: Environment): ResolvedTokens;
83
+ /**
84
+ * Where a command run inside a pinned directory acts: the pin's deployment,
85
+ * organization and environment (`--cell` takes precedence over the pin). See {@link ClientConfiguration.scopeCommand}.
86
+ */
87
+ export interface CommandScope {
88
+ deployment: Environment;
89
+ organization?: string;
90
+ environment?: string;
91
+ }
92
+ export declare class ClientConfiguration {
93
+ /** Set once per process, before any command reads the store. */
94
+ private static commandScope?;
95
+ /**
96
+ * Make every default store (`new ClientConfiguration()`, the form the ~40
97
+ * CLI commands build for themselves) read `scope` instead of the global
98
+ * `activeEnvironment`, organization and environment, without writing them.
99
+ *
100
+ * The CLI calls this once at startup, for a command run in a directory
101
+ * `cred init` pinned, unless `--cell` names another cell: that
102
+ * cell's own store applies, and the pin is ignored. Without it those commands read only `~/.cred`, so in
103
+ * a directory pinned to one organization `cred ls connections` listed
104
+ * another's, and `cred rm connection` would have deleted from it.
105
+ *
106
+ * A store built with an explicit path or deployment is left alone: those
107
+ * are the callers that already chose where to read, and the specs.
108
+ */
109
+ static scopeCommand(scope: CommandScope | undefined): void;
110
+ private configPath;
111
+ private config;
112
+ private environmentOverride?;
113
+ private scope?;
114
+ /**
115
+ * @param customConfigPath an alternate `~/.cred` location, for tests.
116
+ * @param environmentOverride pin every per-environment read to this
117
+ * deployment instead of the file's `activeEnvironment`, without
118
+ * writing to disk. `~/.cred` stores credentials per deployment, so a
119
+ * caller that targets a deployment other than the active one needs
120
+ * its token, organization and environment from that deployment too:
121
+ * resolving only the URL would talk to one deployment's control plane
122
+ * with another's token.
123
+ */
124
+ constructor(customConfigPath?: string, environmentOverride?: Environment);
125
+ /**
126
+ * The same file, with every per-deployment read and write pinned to
127
+ * `environment` — the one this store resolves to now. For an operation that
128
+ * reads, waits and then writes: without the pin, the write resolves the
129
+ * deployment again and follows an `activeEnvironment` changed in between.
130
+ */
131
+ pinnedToCurrentEnvironment(): ClientConfiguration;
132
+ /** The same file, with every per-deployment read and write pinned to `environment`. */
133
+ pinnedTo(environment: Environment): ClientConfiguration;
134
+ /** Where this store lives on disk. The token refresh locks a file beside it. */
135
+ getConfigPath(): string;
136
+ loadConfig(): ConfigFile;
137
+ /**
138
+ * Ensure `.cred` is usable, recovering from a corrupt file if needed.
139
+ *
140
+ * `loadConfig` deliberately fails loud on a corrupt/invalid file so a read
141
+ * path never silently erases tokens. But `cred login` exists precisely to
142
+ * (re)establish config, so it must be able to get past a bad file — that is
143
+ * what the "re-run `cred login` to reset" message promises. We make the
144
+ * message true here: the bad file is renamed aside (never deleted, so a
145
+ * support engineer can still inspect it) and a fresh default is written.
146
+ *
147
+ * Only an {@link InvalidConfigError} triggers recovery. fs errors such as
148
+ * EACCES are real environment problems and are rethrown — resetting would
149
+ * just fail to write anyway, and we must not mask a permissions issue as a
150
+ * successful reset.
151
+ *
152
+ * @returns the path the corrupt file was backed up to, or null if the
153
+ * existing config was already valid. The caller decides how to
154
+ * report it; this module does not own a logger.
155
+ */
156
+ recoverIfCorrupt(): string | null;
157
+ private defaultConfig;
158
+ private writeConfig;
159
+ private currentEnvConfig;
160
+ setOrganization(organizationName: string): void;
161
+ setProject(projectName: string | undefined): void;
162
+ getOrganization(): string;
163
+ getProject(): string;
164
+ setAccessToken(userToken: boolean, accessToken: string): void;
165
+ /**
166
+ * Persist a freshly-issued Auth0 token pair.
167
+ *
168
+ * Writes the access token to BOTH `accessToken` and `jwtAccessToken`, which
169
+ * is the shape the CLI has always written and the shape the extension can
170
+ * still read (it takes the access token from `jwtAccessToken`). `idToken`
171
+ * gets its own field so it is never confused with the access token again.
172
+ */
173
+ setTokenPair(tokens: {
174
+ accessToken: string;
175
+ idToken?: string;
176
+ refreshToken?: string;
177
+ }): void;
178
+ /**
179
+ * The token to put in an `Authorization` header, resolved by audience rather
180
+ * than by field name so it is correct whichever tool wrote the file last.
181
+ */
182
+ getApiAccessToken(): string | undefined;
183
+ getIdToken(): string | undefined;
184
+ getAuthenticationHeader(): string;
185
+ /**
186
+ * @deprecated Ambiguous — returns the raw `accessToken` field, whose meaning
187
+ * depends on which tool wrote it last. Use {@link getApiAccessToken} for an
188
+ * `Authorization` header or {@link getIdToken} for profile claims.
189
+ */
190
+ getAccessToken(): string;
191
+ setJwtAccessToken(jwtAccessToken: string): void;
192
+ getJwtAccessToken(): string | undefined;
193
+ setVSCodeUser(username: string, email: string): void;
194
+ getVSCodeUser(): {
195
+ username: string;
196
+ email: string;
197
+ } | undefined;
198
+ setRefreshToken(refreshToken: string): void;
199
+ getRefreshToken(): string | undefined;
200
+ isServiceAccountToken(): boolean;
201
+ doesConfigExists(): boolean;
202
+ getHeaders(): {
203
+ Authorization: string;
204
+ };
205
+ /**
206
+ * Remove this deployment's tokens and keep the rest of its entry: the
207
+ * organization and environment (`project`) the user chose. For a session
208
+ * Auth0 has refused, where the next step is signing back in to the same
209
+ * place. `cred logout` uses `destroyConfig`, which forgets everything.
210
+ */
211
+ clearTokens(): void;
212
+ destroyConfig(): void;
213
+ setEnvironment(environment: Environment): void;
214
+ getEnvironment(): Environment;
215
+ /** The `activeEnvironment` the file records, whatever `--cell` or a pin says this store acts on. */
216
+ getGlobalEnvironment(): Environment;
217
+ /** The cells whose slot holds a token, in the order the file lists them. */
218
+ signedInCells(): Environment[];
219
+ /**
220
+ * Seconds until the API access token expires; negative once it has. Returns
221
+ * `undefined` when there is no decodable expiry — a service-account token or
222
+ * a malformed one — so callers can tell "no deadline" from "expires soon".
223
+ */
224
+ secondsUntilExpiry(): number | undefined;
225
+ isTokenValid(): {
226
+ valid: boolean;
227
+ reason?: string;
228
+ };
229
+ }
@@ -0,0 +1,112 @@
1
+ /**
2
+ * Deployment-environment tables: which control plane, which Auth0 tenant, and
3
+ * which cluster each deployment env lives on.
4
+ *
5
+ * These live in setup-core rather than in the CLI's `constants.ts` because both
6
+ * front doors need them: the credential store keys `~/.cred` by deployment env,
7
+ * and token refresh needs the Auth0 domain and client id for the env it is
8
+ * refreshing. `constants.ts` re-exports them, so existing CLI imports are
9
+ * unchanged.
10
+ *
11
+ * DELIBERATELY NO SECRETS HERE. The `e2e-test` machine-to-machine client secret
12
+ * stays in the CLI's `constants.ts`, where its single consumer (`loginM2M`)
13
+ * lives. setup-core is destined to be a published library; a credential in it
14
+ * would be shipped to every consumer of that library.
15
+ *
16
+ * Note the naming collision this file inherits and does not fix: `Environment`
17
+ * here is the DEPLOYMENT env (production/staging/dev/localhost), an internal
18
+ * axis. The user-facing "environment" is the Credible environment inside an
19
+ * organization, which the store calls `project` for legacy reasons.
20
+ */
21
+ export type Environment = "production" | "staging" | "dev" | "localhost" | "e2e-test" | "videoamp" | "aws-dev" | "aws-dev-2" | "aws-affinity";
22
+ export declare const controlPlanesByEnv: Record<Environment, string>;
23
+ /**
24
+ * The app per deployment, without the organization. Organizations are a
25
+ * subdomain (`<org>.<host>`), as `getOrganizationUrl` builds it in
26
+ * `app/src/components/Utils.ts`; see {@link packageVersionUrl}.
27
+ */
28
+ export declare const appHostsByEnv: Record<Environment, string>;
29
+ /**
30
+ * Where a published version opens in the app: the `/:project/:package/:version`
31
+ * route in `app/src/AdminApp.tsx`, on the organization's own subdomain.
32
+ */
33
+ export declare function packageVersionUrl(deployment: Environment, organization: string, environment: string, packageName: string, versionId: string): string;
34
+ /**
35
+ * The code-assist API per deployment, for the skills installer. Keyed off
36
+ * {@link Environment} like the other host tables, so adding a deployment
37
+ * without a host here fails the build rather than falling through to
38
+ * production. Values are the ones the VS Code extension ships in
39
+ * `src/config/public/<env>.json`.
40
+ */
41
+ export declare const codeAssistByEnv: Record<Environment, string>;
42
+ /** Environments that must have an object in `~/.cred` `configPerEnvironment` (avoids undefined on write). */
43
+ export declare const ALL_ENVIRONMENTS: Environment[];
44
+ /**
45
+ * The deployment env everything falls back to.
46
+ *
47
+ * Named because two places have to agree on it and neither is obviously the
48
+ * authority: it is what a fresh `~/.cred` records as `activeEnvironment`, and it
49
+ * is what a folder pin with no `deployment` means (earlier builds left it out
50
+ * for this value; see `resolveDeployment`).
51
+ */
52
+ export declare const DEFAULT_DEPLOYMENT: Environment;
53
+ /** Is `value` one of the deployment envs this build knows about? */
54
+ export declare function isEnvironment(value: unknown): value is Environment;
55
+ /**
56
+ * Public Auth0 parameters per deployment env. All three fields are public by
57
+ * construction: `clientId` and `audience` travel in every `/authorize` URL, and
58
+ * `domain` is the tenant hostname.
59
+ */
60
+ export interface Auth0EnvConfig {
61
+ domain: string;
62
+ clientId: string;
63
+ audience: string;
64
+ }
65
+ export declare const auth0ConfigsByEnv: Record<Environment, Auth0EnvConfig>;
66
+ /** Clusters a CLI org can target via `cred login <org> -c <cluster>`. */
67
+ export type Cluster = "gcp" | "aws";
68
+ /** Which cluster each {@link Environment} lives on. */
69
+ export declare const CLUSTER_BY_ENVIRONMENT: Record<Environment, Cluster>;
70
+ /**
71
+ * Default {@link Environment} to switch to when logging into a cluster and the
72
+ * current active env is on a different cluster. Picked per cluster so that
73
+ * `cred login <org> -c aws` flips to videoamp and `-c gcp` flips to production.
74
+ */
75
+ export declare const DEFAULT_ENVIRONMENT_BY_CLUSTER: Record<Cluster, Environment>;
76
+ /**
77
+ * Per-org CLI login config. One row per org that needs non-default behavior
78
+ * (multi-cluster support, Auth0 Organizations PKCE, etc.). Orgs not listed here
79
+ * fall back to: gcp cluster only, Device Authorization Flow, no Auth0 org id.
80
+ *
81
+ * Auth0 docs: `organization` is supported on `/authorize` for Organizations
82
+ * (https://auth0.com/docs/manage-users/organizations/configure-organizations/use-org-name-authentication-api)
83
+ * but NOT on `POST /oauth/device/code`
84
+ * (https://auth0.com/docs/get-started/authentication-and-authorization-flow/device-authorization-flow/call-your-api-using-the-device-authorization-flow#device-code-parameters),
85
+ * so orgs with an `auth0OrganizationIds` entry for the active cluster use Auth
86
+ * Code + PKCE via the CLI's loopback redirect URI. Keep aligned with the app's
87
+ * `ORGANIZATION_HOST_ORG_ID_MAP` / `getAuth0OrganizationAuthorizationParams`.
88
+ */
89
+ export interface CliOrgConfig {
90
+ /** Lowercase CLI org name (what the user types after `cred login`). */
91
+ cliOrgName: string;
92
+ /** Clusters this org can be logged into. */
93
+ supportedClusters: Cluster[];
94
+ /** Cluster used when `-c/--cluster` is omitted. Must be in `supportedClusters`. */
95
+ defaultCluster: Cluster;
96
+ /**
97
+ * Auth0 Organization ID per cluster. Presence triggers the PKCE + `organization=<id>`
98
+ * `/authorize` flow on that cluster; absence falls back to Device Authorization Flow.
99
+ */
100
+ auth0OrganizationIds?: Partial<Record<Cluster, string>>;
101
+ }
102
+ /** Returns the registry entry for an org, or `undefined` if it uses defaults (gcp + device flow). */
103
+ export declare function getCliOrgConfig(organizationName: string): CliOrgConfig | undefined;
104
+ /**
105
+ * Auth0 Organization ID for the given CLI org on the cluster it is logging into.
106
+ * Returning a value selects the Auth Code + PKCE branch in `login()`; `undefined`
107
+ * falls back to Device Authorization Flow.
108
+ *
109
+ * `cluster` defaults to the org's `defaultCluster` (or `gcp` for unregistered orgs)
110
+ * so callers that don't yet know the active cluster keep working.
111
+ */
112
+ export declare function getAuth0OrganizationIdForCliLogin(organizationName: string, cluster?: Cluster): string | undefined;
@@ -0,0 +1,171 @@
1
+ import { ClientConfiguration } from "./credstore";
2
+ import { Environment } from "./environments";
3
+ import { ConnectionCreator, ConnectionSelection } from "./importconnections";
4
+ import { Host } from "./host";
5
+ import { CredibleConnection, PublisherConfig } from "./publisherconfig";
6
+ import { SkillsFetcher, SkillsPolicy } from "./skills";
7
+ import { RunningPublisher } from "./supervisor";
8
+ /**
9
+ * The setup flow itself: the six steps both front doors run.
10
+ *
11
+ * Nothing here knows whether it is talking to a terminal or to VS Code — that is
12
+ * the {@link Host}'s job — and nothing here parses arguments or prints. The end
13
+ * state on disk is therefore identical whichever front door ran it, which is the
14
+ * point of having one flow rather than two implementations.
15
+ */
16
+ /** The one thing setup needs from the control plane, injected so it can be faked. */
17
+ export type ConnectionLister = (organization: string, credibleEnvironment: string) => Promise<CredibleConnection[]>;
18
+ export interface FlowContext {
19
+ host: Host;
20
+ credStore: ClientConfiguration;
21
+ listConnections: ConnectionLister;
22
+ /**
23
+ * Create a connection in Credible, or test one with `dryRun`.
24
+ *
25
+ * Optional: a front door that cannot create connections simply does not offer
26
+ * the import, rather than offering it and failing at the last step.
27
+ */
28
+ createConnection?: ConnectionCreator;
29
+ /**
30
+ * Fetch the modeling skills from code-assist.
31
+ *
32
+ * Optional for the same reason as `createConnection`: a front door without
33
+ * access to that API skips the step instead of offering it and failing.
34
+ */
35
+ fetchSkills?: SkillsFetcher;
36
+ deployment: Environment;
37
+ publisherVersion: string;
38
+ cwd: string;
39
+ /**
40
+ * Stream all of the server's output to the terminal, as `--debug` asks.
41
+ * Otherwise only its errors are shown, and the rest goes to its log file.
42
+ */
43
+ showAllPublisherOutput?: boolean;
44
+ }
45
+ export interface SetupOptions {
46
+ /** Wire this directory instead of the one setup was run in. */
47
+ directory?: string;
48
+ /**
49
+ * Which organization and Credible environment to wire this directory to.
50
+ *
51
+ * Passed in, never read from the credential store here. The store holds one
52
+ * organization and environment per *deployment*, so a flow that read them
53
+ * itself could only ever wire every directory to the same pair — which is
54
+ * exactly what it used to do, leaving the directory's own pin decorative.
55
+ * Resolving them is the front door's job (`resolveIdentity`), so the
56
+ * precedence between a flag, the pin and the global value is decided once.
57
+ */
58
+ organization: string;
59
+ credibleEnvironment: string;
60
+ /**
61
+ * Do not offer to adopt a local `publisher.config.json`.
62
+ *
63
+ * `cred dev` sets this: the offer belongs to the once-per-directory command,
64
+ * and re-asking every session would be nagging about a decision already made.
65
+ */
66
+ skipConnectionImport?: boolean;
67
+ /**
68
+ * Which local connections to import, when already decided (`--connections`).
69
+ * Absent means ask, which a host without a terminal refuses with the flag.
70
+ */
71
+ connections?: ConnectionSelection;
72
+ /**
73
+ * What to do with skills already in `.claude/skills/` that setup did not
74
+ * install (`--skills`, on `init` and `dev`). Absent means the pin's earlier
75
+ * `none`, else ask — only on a first install that finds some; later runs
76
+ * keep whatever is there.
77
+ */
78
+ skills?: SkillsPolicy;
79
+ /**
80
+ * Which listed package to serve, relative to the directory (`--package`),
81
+ * in a workspace whose `publisher.config.json` lists several. Absent means
82
+ * the pin's choice, else the only one, else ask.
83
+ */
84
+ package?: string;
85
+ /**
86
+ * The UI port to serve on (`--port`); MCP takes the port 40 above it.
87
+ * Remembered, and it wins over the remembered pair. Absent means the
88
+ * remembered pair, else 4000/4040, else one derived from the directory.
89
+ */
90
+ port?: number;
91
+ /** Write the files but do not start a server. */
92
+ start: boolean;
93
+ /** Start a server but do not open a browser. */
94
+ open: boolean;
95
+ }
96
+ export interface WiredDirectory {
97
+ /** The workspace: where `.mcp.json`, the briefing, the skills and the pin go. */
98
+ directory: string;
99
+ /**
100
+ * The package the publisher serves. Usually `directory`; for a Publisher
101
+ * server directory (a `publisher.config.json` listing packages, as
102
+ * `create-malloy-package` lays out), the package it lists.
103
+ */
104
+ packageDir: string;
105
+ organization: string;
106
+ credibleEnvironment: string;
107
+ config: PublisherConfig;
108
+ configPath: string;
109
+ /** Where the server keeps its own state. Not the user's directory — see below. */
110
+ serverRoot: string;
111
+ uiPort: number;
112
+ mcpPort: number;
113
+ }
114
+ /**
115
+ * Is this directory's own session running — not merely something answering on
116
+ * its pinned port?
117
+ *
118
+ * All three must hold: the session's stamped config and pid file exist (the
119
+ * supervisor removes both when the server exits), the pid is alive, and the
120
+ * port answers. The files alone were not enough: a `kill -9`, a reboot or a
121
+ * closed terminal leaves them behind, and two directories wired one after the
122
+ * other both pin 4000/4040, so the other directory's publisher answered and
123
+ * this one was reported as running.
124
+ */
125
+ export declare function isOwnSessionRunning(directory: string, uiPort: number): Promise<boolean>;
126
+ /** True when `file` is older than this machine's last boot. */
127
+ export declare function writtenBeforeBoot(file: string, now?: number): boolean;
128
+ /**
129
+ * Refuse, before anything is written, the questions `cred init` could not ask.
130
+ *
131
+ * Without a terminal a question exits 2. Asked in order, the connections and
132
+ * skills questions came after the environment was created and selected, so an
133
+ * agent that stopped at the exit 2 left an empty environment behind and
134
+ * `~/.cred` pointing at it. Checked up front, exit 2 means nothing happened,
135
+ * which is what a caller re-running with the flag assumes. A misspelled
136
+ * `--connections` name is refused here for the same reason.
137
+ */
138
+ export declare function refuseUnanswerable(directory: string, options: {
139
+ connections?: ConnectionSelection;
140
+ skills?: SkillsPolicy;
141
+ package?: string;
142
+ }, interactive: boolean): void;
143
+ /**
144
+ * Refuse to start beside someone else's publisher, before login and the
145
+ * environment lookup. Checked only inside `wireDirectory`, the refusal came
146
+ * after both, 25 seconds and more on a slow deployment.
147
+ */
148
+ export declare function refuseBesideOtherPublisher(directory: string, port?: number): Promise<void>;
149
+ export declare function wireDirectory(context: FlowContext, options: SetupOptions): Promise<WiredDirectory>;
150
+ /** The flag that answers {@link resolvePackageDir}'s question. */
151
+ export declare const PACKAGE_FLAG = "--package";
152
+ export declare const PORT_FLAG = "--port";
153
+ /** Step 5 and 6: start the server, and open it once it is genuinely serving. */
154
+ export declare function startSession(context: FlowContext, wired: WiredDirectory, options: {
155
+ open: boolean;
156
+ }): Promise<RunningPublisher>;
157
+ /**
158
+ * The whole flow: wire, then run.
159
+ *
160
+ * Setup deliberately ends in a running server rather than a printed
161
+ * instruction. Every step before that is invisible plumbing the user cannot
162
+ * check, and the Publisher UI is the first artifact that proves the whole chain
163
+ * — auth, connections, proxy, compile — is actually live. A setup that ends with
164
+ * "now run `cred dev`" ends on an unverified claim.
165
+ */
166
+ export declare function runSetup(context: FlowContext, options: SetupOptions): Promise<{
167
+ wired: WiredDirectory;
168
+ running?: RunningPublisher;
169
+ }>;
170
+ /** "a", "a and b", "a, b and c". */
171
+ export declare function joinNames(names: string[]): string;
@@ -226,14 +226,14 @@ function otherPublisherError(free) {
226
226
  /** The flag that moves this directory to a free pair, with the pair to pass. */
227
227
  function portFix(free) {
228
228
  return free
229
- ? `re-run with ${exports.PORT_FLAG} ${free.uiPort} (free now; MCP takes ${free.mcpPort}). cred remembers it and points .mcp.json there.`
230
- : `re-run with ${exports.PORT_FLAG} <port>, a port that is free along with the one 40 above it. cred remembers it and points .mcp.json there.`;
229
+ ? `re-run with ${exports.PORT_FLAG} ${free.uiPort} (free now; MCP takes ${free.mcpPort}). cred remembers it.`
230
+ : `re-run with ${exports.PORT_FLAG} <port>, a port that is free along with the one 40 above it. cred remembers it.`;
231
231
  }
232
232
  /**
233
233
  * Whether someone else's publisher holds the default pair this directory would
234
234
  * use: in the "I already run a publisher" journey, the user's own, started from
235
- * this same directory. Moving to a derived pair rewrote `.mcp.json` away from
236
- * the one server the agent could reach. Not this directory's own session; the
235
+ * this same directory. Moving to a derived pair left the agent on a port
236
+ * nothing served until `cred dev`. Not this directory's own session; the
237
237
  * caller checks that first.
238
238
  */
239
239
  function otherPublisherOnDefaults(directory, pinnedPorts) {
@@ -301,16 +301,15 @@ function wireDirectory(context, options) {
301
301
  // port pair, and compared against `findFolderConfig`'s always-absolute
302
302
  // `root` to find the ports this directory used last. A relative
303
303
  // `--directory` reached all three as a different string, so `cred init -d
304
- // sub` re-derived its ports every run and rewrote `.mcp.json` with a URL the
305
- // agent's saved config no longer matched. Everything downstream can then
306
- // treat it as absolute.
304
+ // sub` re-derived its ports every run, so the Publisher's address moved
305
+ // between runs. Everything downstream can then treat it as absolute.
307
306
  const directory = path_1.default.resolve((_a = options.directory) !== null && _a !== void 0 ? _a : context.cwd);
308
307
  const remembered = (0, folderconfig_1.findFolderConfig)(directory);
309
308
  const pinned = (remembered === null || remembered === void 0 ? void 0 : remembered.root) === directory ? remembered.config : undefined;
310
309
  // A second session for the same directory would find the pinned ports busy
311
- // — held by the first — fall through to another pair, rewrite `.mcp.json`
312
- // to it and start a second server, leaving the first serving on the URL any
313
- // open agent still has.
310
+ // — held by the first — fall through to another pair, move the pin to it
311
+ // and start a second server, leaving the first serving where any open
312
+ // agent's `cred mcp` is connected.
314
313
  const ownSessionRunning = Boolean((pinned === null || pinned === void 0 ? void 0 : pinned.ports) &&
315
314
  (yield isOwnSessionRunning(directory, pinned.ports.uiPort)));
316
315
  if (options.start && ownSessionRunning) {
@@ -318,9 +317,9 @@ function wireDirectory(context, options) {
318
317
  }
319
318
  // Someone else's publisher on the default pair — in the "I already run a
320
319
  // publisher" journey, the user's own, started from this same directory.
321
- // Moving to a derived pair rewrote `.mcp.json` away from the one server the
322
- // agent could reach, to a port nothing served until `cred dev`. Staying on
323
- // the default pair keeps that URL; the old server has to stop first.
320
+ // Moving to a derived pair left the agent on a port nothing served until
321
+ // `cred dev`. Staying on the default pair keeps the address; the old server
322
+ // has to stop first.
324
323
  const otherPublisher = options.port === undefined &&
325
324
  !ownSessionRunning &&
326
325
  (yield otherPublisherOnDefaults(directory, pinned === null || pinned === void 0 ? void 0 : pinned.ports));
@@ -388,11 +387,11 @@ function wireDirectory(context, options) {
388
387
  environmentName: (0, publisherconfig_1.localEnvironmentName)(credibleEnvironment),
389
388
  retrieval: retrieval && "settings" in retrieval ? retrieval.settings : undefined,
390
389
  });
391
- // Reuse this directory's remembered ports so an agent's saved `.mcp.json` URL
392
- // keeps resolving across sessions. While this directory's own session holds
393
- // them (`--no-start` beside a running `cred dev`) they are busy because they
394
- // are in use, so they are kept rather than probed: probing found them busy
395
- // and moved `.mcp.json` to a pair nothing was listening on.
390
+ // Reuse this directory's remembered ports so the Publisher keeps one address
391
+ // across sessions. While this directory's own session holds them
392
+ // (`--no-start` beside a running `cred dev`) they are busy because they are
393
+ // in use, so they are kept rather than probed: probing found them busy and
394
+ // moved the directory to a pair nothing was listening on.
396
395
  const ports = options.port !== undefined
397
396
  ? yield chosenPair(directory, options.port, {
398
397
  ownSessionRunning,
@@ -412,7 +411,6 @@ function wireDirectory(context, options) {
412
411
  const configPath = sessionConfigPath(directory);
413
412
  const mcp = (0, mcpconfig_1.writeMcpConfig)({
414
413
  directory,
415
- mcpPort: ports.mcpPort,
416
414
  deployment: context.deployment,
417
415
  organization,
418
416
  });
@@ -438,7 +436,7 @@ function wireDirectory(context, options) {
438
436
  // rewrites nothing and this line would be noise.
439
437
  context.host.log("info", `Wrote .mcp.json. An agent already running in this directory will not see the new MCP servers until it restarts — for Claude Code, exit and start \`claude\` here again; it asks once to trust the project's servers.`);
440
438
  }
441
- (0, briefing_1.writeBriefing)(directory, {
439
+ const briefing = (0, briefing_1.writeBriefing)(directory, {
442
440
  organization,
443
441
  credibleEnvironment,
444
442
  localEnvironment: config.environments[0].name,
@@ -482,7 +480,35 @@ function wireDirectory(context, options) {
482
480
  // The pin is this developer's choice of environment, not a property of the
483
481
  // models, so a teammate must not inherit it by cloning.
484
482
  if ((0, folderconfig_1.ensureConfigIgnored)(directory) === "added") {
485
- context.host.log("info", `Added ${folderconfig_1.FOLDER_CONFIG_DIR}/ to .gitignore — it records the environment you chose, which is yours rather than the repository's.`);
483
+ context.host.log("info", `Added ${folderconfig_1.FOLDER_CONFIG_DIR}/ to .git/info/exclude — it records the environment you chose, which is yours rather than the repository's.`);
484
+ }
485
+ // Agent files that hold nothing but what setup wrote are regenerated by
486
+ // every run, so they stay out of git like the pin. A file the repository
487
+ // tracks cannot be excluded, and one holding the user's own content is
488
+ // theirs to commit or not.
489
+ const agentFiles = [
490
+ ...(mcp.onlyOurs ? [mcpconfig_1.MCP_CONFIG_FILENAME] : []),
491
+ ...briefing
492
+ .filter((file) => file.onlyOurs)
493
+ .map((file) => path_1.default.basename(file.filePath)),
494
+ ].filter((file) => !(0, git_1.isTrackedByGit)(directory, file));
495
+ (0, folderconfig_1.ensureIgnored)(directory, agentFiles, "agent files cred init writes; re-run it to regenerate them");
496
+ // A file an earlier run excluded that now holds the user's own content
497
+ // is theirs again, so git must show it.
498
+ (0, folderconfig_1.removeIgnored)(directory, [
499
+ ...(mcp.onlyOurs ? [] : [mcpconfig_1.MCP_CONFIG_FILENAME]),
500
+ ...briefing
501
+ .filter((file) => !file.onlyOurs)
502
+ .map((file) => path_1.default.basename(file.filePath)),
503
+ ]);
504
+ const trackedAndChanged = [
505
+ ...(mcp.changed ? [mcpconfig_1.MCP_CONFIG_FILENAME] : []),
506
+ ...briefing
507
+ .filter((file) => file.action !== "unchanged")
508
+ .map((file) => path_1.default.basename(file.filePath)),
509
+ ].filter((file) => (0, git_1.isTrackedByGit)(directory, file));
510
+ if (trackedAndChanged.length) {
511
+ context.host.log("info", `Updated what Credible manages in ${joinNames(trackedAndChanged)}, which git tracks, so it shows as a change to commit or discard.`);
486
512
  }
487
513
  if (skills) {
488
514
  // Installer-managed files, regenerated by every setup and pinned in a
@@ -583,12 +609,12 @@ exports.PORT_FLAG = "--port";
583
609
  * The pair `--port` names, refused when something else holds it.
584
610
  *
585
611
  * Unlike the remembered pair, which quietly gives way to another when busy, a
586
- * chosen pair is used as given or not at all: moving off it would point
587
- * `.mcp.json` somewhere the user did not ask for. This directory's own running
588
- * session holding it is not a conflict (`--no-start` beside `cred dev`). That
589
- * session running on a different pair is: moving the pin and `.mcp.json` away
590
- * from the live server leaves the agent's URL pointing at nothing, and the
591
- * next ownership check would miss the running server.
612
+ * chosen pair is used as given or not at all: moving off it would serve
613
+ * somewhere the user did not ask for. This directory's own running session
614
+ * holding it is not a conflict (`--no-start` beside `cred dev`). That session
615
+ * running on a different pair is: moving the pin away from the live server
616
+ * means `cred mcp`, which finds the server through the pin, and the next
617
+ * ownership check would both miss it.
592
618
  */
593
619
  function chosenPair(directory, uiPort, own) {
594
620
  return __awaiter(this, void 0, void 0, function* () {
@@ -749,7 +775,7 @@ function runSetup(context, options) {
749
775
  context.host.log("info",
750
776
  // The last line an agent sees, so it repeats the restart: the agent
751
777
  // that ran this has no `malloy` tools until it starts again.
752
- `Wired ${wired.directory}. Next: restart any agent session in that directory so it loads .mcp.json, then run \`cred dev\` there to start the publisher.`);
778
+ `Wired ${wired.directory}. Next: restart any agent session in that directory so it loads .mcp.json; its \`malloy\` server starts the publisher through \`cred mcp\`. Without an agent, run \`cred dev\` there.`);
753
779
  return { wired };
754
780
  }
755
781
  const running = yield startSession(context, wired, { open: options.open });