atmn 1.1.24 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +120 -0
- package/dist/bin.js +23102 -0
- package/dist/index.js +2906 -0
- package/dist/tsconfig.tsbuildinfo +1 -1
- package/package.json +27 -74
- package/src/actions/api/callApi.ts +280 -0
- package/src/actions/api/registerApiCommands.ts +121 -0
- package/src/actions/env/fetchOrgInfo.ts +32 -0
- package/src/actions/env/types/orgInfo.ts +18 -0
- package/src/actions/env.ts +94 -0
- package/src/actions/init/runInit.ts +401 -0
- package/src/actions/login/keyless.ts +135 -0
- package/src/actions/login.ts +144 -0
- package/src/actions/pull/appendPlanVersionFixture.ts +373 -0
- package/src/actions/pull/applyPreview.ts +553 -0
- package/src/actions/pull/applySettingsPreview.ts +114 -0
- package/src/actions/pull/changedFixtureKeys.ts +88 -0
- package/src/actions/pull/listSourceFiles.ts +29 -0
- package/src/actions/pull/locateFixture.ts +78 -0
- package/src/actions/pull/resolveCollectionTarget.ts +198 -0
- package/src/actions/pull/rewriteConfig.ts +57 -0
- package/src/actions/pull/scaffoldConfig.ts +118 -0
- package/src/actions/pull.ts +399 -0
- package/src/actions/push/backfillInternalIds.ts +394 -0
- package/src/actions/push/deprecatedFields.ts +55 -0
- package/src/actions/push.ts +294 -0
- package/src/actions/reset/runReset.ts +58 -0
- package/src/actions/sandbox/createSandbox.ts +83 -0
- package/src/actions/sandbox/deleteSandbox.ts +91 -0
- package/src/actions/sandbox/listSandboxes.ts +28 -0
- package/src/actions/sandbox/types/sandboxClient.ts +16 -0
- package/src/actions/sandbox/useSandbox.ts +157 -0
- package/src/actions/sandbox/withSandboxScopeHint.ts +27 -0
- package/src/actions/skills/skills.ts +246 -0
- package/src/auth/announceAuthorizationUrl.ts +26 -0
- package/src/auth/browser/openSystemBrowser.ts +7 -0
- package/src/auth/browser/tryOpenBrowser.ts +17 -0
- package/src/auth/browser/watchLauncher.ts +46 -0
- package/src/auth/buildAuthorizationUrl.ts +36 -0
- package/src/auth/callbackPages.ts +126 -0
- package/src/auth/createOrgApiKeys.ts +46 -0
- package/src/auth/keyless.ts +119 -0
- package/src/auth/oauthConfig.ts +63 -0
- package/src/auth/runOAuthFlow.ts +230 -0
- package/src/auth/types/browserOpener.ts +5 -0
- package/src/auth/types/impersonationTokens.ts +23 -0
- package/src/auth/types/oauthTokens.ts +13 -0
- package/src/auth/types/orgApiKeys.ts +6 -0
- package/src/bin.ts +9 -0
- package/src/cli.ts +648 -0
- package/src/config/configPackageName.ts +9 -0
- package/src/config/legacyConfig.ts +25 -0
- package/src/config/loadConfig.ts +231 -0
- package/src/env/assertSandboxTarget.ts +20 -0
- package/src/env/loadEnv.ts +184 -0
- package/src/env/resolveTarget.ts +134 -0
- package/src/env/sandboxKeyName.ts +18 -0
- package/src/generated/apiRoutes.ts +3787 -0
- package/src/generated/client.ts +51564 -0
- package/src/generated/emit.ts +1163 -0
- package/src/generated/emitRuntime.ts +522 -0
- package/src/generated/features.ts +146 -0
- package/src/generated/labels.ts +29 -0
- package/src/generated/licenses.ts +287 -0
- package/src/generated/lintRules.ts +2207 -0
- package/src/generated/lintRuntime.ts +865 -0
- package/src/generated/plans.ts +1628 -0
- package/src/generated/referralPrograms.ts +22 -0
- package/src/generated/rewards.ts +58 -0
- package/src/generated/settings.ts +21 -0
- package/src/generated/skills.ts +305 -0
- package/src/generated/variants.ts +934 -0
- package/src/generated/wire.ts +334 -0
- package/src/http/autumnFetch.ts +30 -0
- package/src/index.ts +20 -0
- package/src/project/chooseConfigDir.ts +41 -0
- package/src/project/resolveProject.ts +115 -0
- package/src/project/rootMarker.ts +40 -0
- package/src/prompt/prompt.ts +186 -0
- package/src/prompt/select.ts +162 -0
- package/src/render/renderEnv.ts +77 -0
- package/src/render/renderPreview.ts +945 -0
- package/src/render/renderSandboxes.ts +92 -0
- package/src/render/stripTerminalControls.ts +19 -0
- package/src/repo/findRepoRoot.ts +79 -0
- package/src/surgery/appendPropertyEdit.ts +67 -0
- package/src/surgery/appendToArray.ts +87 -0
- package/src/surgery/appendToBinding.ts +19 -0
- package/src/surgery/appendToCollection.ts +40 -0
- package/src/surgery/appendToFixtureArray.ts +71 -0
- package/src/surgery/arrayBinding.ts +30 -0
- package/src/surgery/deleteFixtureLiteral.ts +81 -0
- package/src/surgery/deleteReference.ts +48 -0
- package/src/surgery/ensureBuilderImport.ts +65 -0
- package/src/surgery/findFixture.ts +238 -0
- package/src/surgery/fixtureEdit.ts +156 -0
- package/src/surgery/fixtureLocation.ts +32 -0
- package/src/surgery/insertCollection.ts +86 -0
- package/src/surgery/insertFirstProperty.ts +73 -0
- package/src/surgery/patchFixtureProperty.ts +152 -0
- package/src/surgery/patchSingletonProperty.ts +221 -0
- package/src/surgery/replaceFixture.ts +28 -0
- package/src/surgery/setFixtureProperty.ts +55 -0
- package/src/surgery/staticFixtureRule.ts +48 -0
- package/src/version.ts +5 -0
- package/dist/cli.js +0 -142410
- package/dist/compose/index.js +0 -122
- package/dist/src/compose/builders/builderFunctions.d.ts +0 -84
- package/dist/src/compose/builders/rewardFunctions.d.ts +0 -5
- package/dist/src/compose/builders/variantFunctions.d.ts +0 -2
- package/dist/src/compose/index.d.ts +0 -19
- package/dist/src/compose/models/featureModels.d.ts +0 -262
- package/dist/src/compose/models/index.d.ts +0 -3
- package/dist/src/compose/models/planModels.d.ts +0 -367
- package/dist/src/compose/models/rewardModels.d.ts +0 -52
- package/dist/src/compose/models/variantModels.d.ts +0 -34
- package/readme.md +0 -186
|
@@ -0,0 +1,186 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
|
|
3
|
+
/** Where a command writes its lines. Injected so tests can capture it. */
|
|
4
|
+
export type WriteLine = (text: string) => void;
|
|
5
|
+
|
|
6
|
+
/**
|
|
7
|
+
* Every prompt has a flag. In a terminal the question is asked inline; with
|
|
8
|
+
* `--headless`, or no TTY, the same line is printed as a hint and the command
|
|
9
|
+
* stops there, so an agent reads what to pass and runs again.
|
|
10
|
+
*/
|
|
11
|
+
export type Prompter = {
|
|
12
|
+
interactive: boolean;
|
|
13
|
+
write: WriteLine;
|
|
14
|
+
/** Reads one answer, showing `prompt` at the cursor; injected for tests. */
|
|
15
|
+
readLine: (prompt: string) => Promise<string | null>;
|
|
16
|
+
};
|
|
17
|
+
|
|
18
|
+
export const done = (text: string): string => `${chalk.green("✓")} ${text}`;
|
|
19
|
+
export const needs = (text: string): string => `${chalk.cyan("→")} ${text}`;
|
|
20
|
+
export const soft = (text: string): string => `${chalk.yellow("!")} ${text}`;
|
|
21
|
+
export const same = (text: string): string => `${chalk.dim("=")} ${text}`;
|
|
22
|
+
export const hint = (text: string): string => ` ${chalk.cyan(text)}`;
|
|
23
|
+
|
|
24
|
+
/** Thrown when a headless run stops at a prompt; the CLI exits 0 on it. */
|
|
25
|
+
export class NeedsInputError extends Error {
|
|
26
|
+
constructor(message: string) {
|
|
27
|
+
super(message);
|
|
28
|
+
this.name = "NeedsInputError";
|
|
29
|
+
}
|
|
30
|
+
}
|
|
31
|
+
|
|
32
|
+
/**
|
|
33
|
+
* A fresh readline per question loses keys typed before it opened; one
|
|
34
|
+
* interface per process keeps them. Readline owns the prompt text: it redraws
|
|
35
|
+
* the line on every keystroke, so anything written beside it would vanish.
|
|
36
|
+
*/
|
|
37
|
+
export const defaultReadLine = (() => {
|
|
38
|
+
let rl: import("node:readline").Interface | undefined;
|
|
39
|
+
return async (prompt: string): Promise<string | null> => {
|
|
40
|
+
if (rl === undefined) {
|
|
41
|
+
const { createInterface } = await import("node:readline");
|
|
42
|
+
rl = createInterface({ input: process.stdin, output: process.stdout });
|
|
43
|
+
}
|
|
44
|
+
const active = rl;
|
|
45
|
+
return new Promise((resolve) => {
|
|
46
|
+
const onClose = () => resolve(null);
|
|
47
|
+
active.once("close", onClose);
|
|
48
|
+
active.question(prompt, (answer) => {
|
|
49
|
+
active.off("close", onClose);
|
|
50
|
+
resolve(answer);
|
|
51
|
+
});
|
|
52
|
+
});
|
|
53
|
+
};
|
|
54
|
+
})();
|
|
55
|
+
|
|
56
|
+
/** Nothing else reads stdin after the last question; without this the process waits on it. */
|
|
57
|
+
export const releaseStdin = (): void => {
|
|
58
|
+
process.stdin.pause();
|
|
59
|
+
};
|
|
60
|
+
|
|
61
|
+
export const createPrompter = ({
|
|
62
|
+
interactive,
|
|
63
|
+
write = (text) => process.stdout.write(text),
|
|
64
|
+
readLine = defaultReadLine,
|
|
65
|
+
}: {
|
|
66
|
+
interactive: boolean;
|
|
67
|
+
write?: WriteLine;
|
|
68
|
+
readLine?: (prompt: string) => Promise<string | null>;
|
|
69
|
+
}): Prompter => ({ interactive, write, readLine });
|
|
70
|
+
|
|
71
|
+
/**
|
|
72
|
+
* A value the command needs. Given → returned as is. Absent and interactive →
|
|
73
|
+
* asked, the flag's example as the placeholder. Absent and headless → the
|
|
74
|
+
* `→` line and the flag hint are printed and the run stops.
|
|
75
|
+
*/
|
|
76
|
+
export const ask = async ({
|
|
77
|
+
prompter,
|
|
78
|
+
value,
|
|
79
|
+
question,
|
|
80
|
+
flag,
|
|
81
|
+
example,
|
|
82
|
+
defaultValue,
|
|
83
|
+
}: {
|
|
84
|
+
prompter: Prompter;
|
|
85
|
+
value: string | undefined;
|
|
86
|
+
question: string;
|
|
87
|
+
/** `--path <dir>`: what a headless caller passes. */
|
|
88
|
+
flag: string;
|
|
89
|
+
example?: string;
|
|
90
|
+
/** Enter with nothing typed picks this; headless never assumes it. */
|
|
91
|
+
defaultValue?: string;
|
|
92
|
+
}): Promise<string> => {
|
|
93
|
+
if (value !== undefined && value !== "") return value;
|
|
94
|
+
const flagHint = `Provide ${flag}${example === undefined ? "" : `, e.g. ${example}`}`;
|
|
95
|
+
if (!prompter.interactive) {
|
|
96
|
+
prompter.write(`${needs(question)}\n${hint(flagHint)}\n`);
|
|
97
|
+
throw new NeedsInputError(flagHint);
|
|
98
|
+
}
|
|
99
|
+
const placeholder =
|
|
100
|
+
defaultValue !== undefined
|
|
101
|
+
? chalk.dim(` (${defaultValue})`)
|
|
102
|
+
: example !== undefined
|
|
103
|
+
? chalk.dim(` (e.g. ${example})`)
|
|
104
|
+
: "";
|
|
105
|
+
const answer =
|
|
106
|
+
(await prompter.readLine(`${needs(question)}${placeholder} `))?.trim() ??
|
|
107
|
+
"";
|
|
108
|
+
if (answer !== "") return answer;
|
|
109
|
+
if (defaultValue !== undefined) return defaultValue;
|
|
110
|
+
throw new NeedsInputError(flagHint);
|
|
111
|
+
};
|
|
112
|
+
|
|
113
|
+
/** A yes/no. Headless prints the hint for the flag that says yes and stops. */
|
|
114
|
+
export const confirm = async ({
|
|
115
|
+
prompter,
|
|
116
|
+
value,
|
|
117
|
+
question,
|
|
118
|
+
flag,
|
|
119
|
+
}: {
|
|
120
|
+
prompter: Prompter;
|
|
121
|
+
value: boolean | undefined;
|
|
122
|
+
question: string;
|
|
123
|
+
flag: string;
|
|
124
|
+
}): Promise<boolean> => {
|
|
125
|
+
if (value !== undefined) return value;
|
|
126
|
+
if (!prompter.interactive) {
|
|
127
|
+
prompter.write(`${needs(question)}\n${hint(`Pass ${flag} to continue`)}\n`);
|
|
128
|
+
throw new NeedsInputError(`Pass ${flag} to continue`);
|
|
129
|
+
}
|
|
130
|
+
const answer =
|
|
131
|
+
(await prompter.readLine(`${needs(question)} ${chalk.dim("[Y/n]")} `))
|
|
132
|
+
?.trim()
|
|
133
|
+
.toLowerCase() ?? "";
|
|
134
|
+
return answer === "" || answer === "y" || answer === "yes";
|
|
135
|
+
};
|
|
136
|
+
|
|
137
|
+
/**
|
|
138
|
+
* One question, a short list of flags as answers. Headless prints the
|
|
139
|
+
* question and one line per flag; interactive numbers them and reads a pick.
|
|
140
|
+
*/
|
|
141
|
+
export const choose = async <T extends string>({
|
|
142
|
+
prompter,
|
|
143
|
+
value,
|
|
144
|
+
question,
|
|
145
|
+
options,
|
|
146
|
+
defaultValue,
|
|
147
|
+
}: {
|
|
148
|
+
prompter: Prompter;
|
|
149
|
+
/** The option already picked by flag, when one was. */
|
|
150
|
+
value: T | undefined;
|
|
151
|
+
question: string;
|
|
152
|
+
options: readonly { value: T; flag: string; label: string }[];
|
|
153
|
+
defaultValue: T;
|
|
154
|
+
}): Promise<T> => {
|
|
155
|
+
if (value !== undefined) return value;
|
|
156
|
+
const width = Math.max(...options.map((option) => option.flag.length));
|
|
157
|
+
if (!prompter.interactive) {
|
|
158
|
+
prompter.write(
|
|
159
|
+
`${needs(question)}\n${options
|
|
160
|
+
.map((option) => hint(`${option.flag.padEnd(width)} ${option.label}`))
|
|
161
|
+
.join("\n")}\n`,
|
|
162
|
+
);
|
|
163
|
+
throw new NeedsInputError(
|
|
164
|
+
`Pass ${options.map((option) => option.flag).join(" or ")}`,
|
|
165
|
+
);
|
|
166
|
+
}
|
|
167
|
+
const defaultIndex = options.findIndex(
|
|
168
|
+
(option) => option.value === defaultValue,
|
|
169
|
+
);
|
|
170
|
+
prompter.write(`${needs(question)}\n`);
|
|
171
|
+
for (const [index, option] of options.entries())
|
|
172
|
+
prompter.write(` ${index + 1}) ${option.label}\n`);
|
|
173
|
+
const answer =
|
|
174
|
+
(
|
|
175
|
+
await prompter.readLine(` ${chalk.dim(`[${defaultIndex + 1}]`)}: `)
|
|
176
|
+
)?.trim() ?? "";
|
|
177
|
+
if (answer === "") return defaultValue;
|
|
178
|
+
const picked = /^\d+$/.test(answer)
|
|
179
|
+
? options[Number(answer) - 1]
|
|
180
|
+
: options.find(
|
|
181
|
+
(option) => option.value === answer || option.flag === answer,
|
|
182
|
+
);
|
|
183
|
+
if (picked === undefined)
|
|
184
|
+
throw new NeedsInputError(`Unknown answer ${JSON.stringify(answer)}`);
|
|
185
|
+
return picked.value;
|
|
186
|
+
};
|
|
@@ -0,0 +1,162 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import { hint, needs, type Prompter } from "./prompt";
|
|
3
|
+
|
|
4
|
+
export type Choice = {
|
|
5
|
+
/** What is matched by the filter and returned. */
|
|
6
|
+
value: string;
|
|
7
|
+
/** Printed in white. */
|
|
8
|
+
label: string;
|
|
9
|
+
/** Printed dim in brackets. */
|
|
10
|
+
detail?: string;
|
|
11
|
+
current?: boolean;
|
|
12
|
+
};
|
|
13
|
+
|
|
14
|
+
const PAGE_SIZE = 10;
|
|
15
|
+
|
|
16
|
+
const CURRENT_MARKER = "← current";
|
|
17
|
+
|
|
18
|
+
export const renderChoices = ({
|
|
19
|
+
choices,
|
|
20
|
+
cursor,
|
|
21
|
+
filter,
|
|
22
|
+
}: {
|
|
23
|
+
choices: Choice[];
|
|
24
|
+
cursor: number;
|
|
25
|
+
filter: string;
|
|
26
|
+
}): string => {
|
|
27
|
+
const visible = filterChoices({ choices, filter });
|
|
28
|
+
const page = Math.floor(cursor / PAGE_SIZE);
|
|
29
|
+
const start = page * PAGE_SIZE;
|
|
30
|
+
const rows = visible.slice(start, start + PAGE_SIZE).map((choice, index) => {
|
|
31
|
+
const selected = start + index === cursor;
|
|
32
|
+
const pointer = selected ? chalk.cyan("❯") : " ";
|
|
33
|
+
const label = selected ? chalk.bold(choice.label) : choice.label;
|
|
34
|
+
const detail =
|
|
35
|
+
choice.detail === undefined ? "" : ` ${chalk.dim(`(${choice.detail})`)}`;
|
|
36
|
+
const current = choice.current ? ` ${chalk.dim(CURRENT_MARKER)}` : "";
|
|
37
|
+
return `${pointer} ${label}${detail}${current}`;
|
|
38
|
+
});
|
|
39
|
+
const footer =
|
|
40
|
+
visible.length > PAGE_SIZE
|
|
41
|
+
? chalk.dim(
|
|
42
|
+
` ${start + 1}-${Math.min(start + PAGE_SIZE, visible.length)} of ${visible.length}`,
|
|
43
|
+
)
|
|
44
|
+
: "";
|
|
45
|
+
const search = ` ${chalk.dim("type to filter:")} ${filter}`;
|
|
46
|
+
return [...rows, ...(footer ? [footer] : []), search].join("\n");
|
|
47
|
+
};
|
|
48
|
+
|
|
49
|
+
export const filterChoices = ({
|
|
50
|
+
choices,
|
|
51
|
+
filter,
|
|
52
|
+
}: {
|
|
53
|
+
choices: Choice[];
|
|
54
|
+
filter: string;
|
|
55
|
+
}): Choice[] => {
|
|
56
|
+
const needle = filter.trim().toLowerCase();
|
|
57
|
+
if (needle === "") return choices;
|
|
58
|
+
return choices.filter(
|
|
59
|
+
(choice) =>
|
|
60
|
+
choice.label.toLowerCase().includes(needle) ||
|
|
61
|
+
choice.value.toLowerCase().includes(needle),
|
|
62
|
+
);
|
|
63
|
+
};
|
|
64
|
+
|
|
65
|
+
/** A name or id typed by an agent: exact id first, then exact label, then a unique prefix. */
|
|
66
|
+
export const matchChoice = ({
|
|
67
|
+
choices,
|
|
68
|
+
query,
|
|
69
|
+
}: {
|
|
70
|
+
choices: Choice[];
|
|
71
|
+
query: string;
|
|
72
|
+
}): Choice | null => {
|
|
73
|
+
const byValue = choices.find((choice) => choice.value === query);
|
|
74
|
+
if (byValue) return byValue;
|
|
75
|
+
const lower = query.toLowerCase();
|
|
76
|
+
const byLabel = choices.filter(
|
|
77
|
+
(choice) => choice.label.toLowerCase() === lower,
|
|
78
|
+
);
|
|
79
|
+
if (byLabel.length === 1) return byLabel[0] ?? null;
|
|
80
|
+
const byPrefix = filterChoices({ choices, filter: query });
|
|
81
|
+
return byPrefix.length === 1 ? (byPrefix[0] ?? null) : null;
|
|
82
|
+
};
|
|
83
|
+
|
|
84
|
+
const ESC = "\u001b";
|
|
85
|
+
|
|
86
|
+
/**
|
|
87
|
+
* Arrow keys move, typing filters, enter picks. Raw-mode stdin, redrawn in
|
|
88
|
+
* place. Headless callers never get here: `select` prints the hint instead.
|
|
89
|
+
*/
|
|
90
|
+
const interactiveSelect = async ({
|
|
91
|
+
choices,
|
|
92
|
+
write,
|
|
93
|
+
}: {
|
|
94
|
+
choices: Choice[];
|
|
95
|
+
write: (text: string) => void;
|
|
96
|
+
}): Promise<Choice | null> => {
|
|
97
|
+
const stdin = process.stdin;
|
|
98
|
+
let cursor = Math.max(
|
|
99
|
+
0,
|
|
100
|
+
choices.findIndex((choice) => choice.current),
|
|
101
|
+
);
|
|
102
|
+
let filter = "";
|
|
103
|
+
let lines = 0;
|
|
104
|
+
|
|
105
|
+
const draw = () => {
|
|
106
|
+
if (lines > 0) write(`${ESC}[${lines}A${ESC}[J`);
|
|
107
|
+
const text = renderChoices({ choices, cursor, filter });
|
|
108
|
+
lines = text.split("\n").length;
|
|
109
|
+
write(`${text}\n`);
|
|
110
|
+
};
|
|
111
|
+
|
|
112
|
+
return new Promise((resolve) => {
|
|
113
|
+
stdin.setRawMode?.(true);
|
|
114
|
+
stdin.resume();
|
|
115
|
+
stdin.setEncoding("utf8");
|
|
116
|
+
draw();
|
|
117
|
+
const finish = (choice: Choice | null) => {
|
|
118
|
+
stdin.off("data", onData);
|
|
119
|
+
stdin.setRawMode?.(false);
|
|
120
|
+
stdin.pause();
|
|
121
|
+
resolve(choice);
|
|
122
|
+
};
|
|
123
|
+
const onData = (key: string) => {
|
|
124
|
+
const visible = filterChoices({ choices, filter });
|
|
125
|
+
if (key === "\u0003" || key === `${ESC}`) return finish(null);
|
|
126
|
+
if (key === "\r" || key === "\n") return finish(visible[cursor] ?? null);
|
|
127
|
+
if (key === `${ESC}[A`) cursor = Math.max(0, cursor - 1);
|
|
128
|
+
else if (key === `${ESC}[B`)
|
|
129
|
+
cursor = Math.min(Math.max(0, visible.length - 1), cursor + 1);
|
|
130
|
+
else if (key === "\u007f" || key === "\b") {
|
|
131
|
+
filter = filter.slice(0, -1);
|
|
132
|
+
cursor = 0;
|
|
133
|
+
} else if (!key.startsWith(ESC) && [...key].every((c) => c >= " ")) {
|
|
134
|
+
// A paste arrives as one chunk; typing as one char at a time.
|
|
135
|
+
filter += key;
|
|
136
|
+
cursor = 0;
|
|
137
|
+
}
|
|
138
|
+
draw();
|
|
139
|
+
};
|
|
140
|
+
stdin.on("data", onData);
|
|
141
|
+
});
|
|
142
|
+
};
|
|
143
|
+
|
|
144
|
+
export const select = async ({
|
|
145
|
+
prompter,
|
|
146
|
+
choices,
|
|
147
|
+
question,
|
|
148
|
+
flag,
|
|
149
|
+
}: {
|
|
150
|
+
prompter: Prompter;
|
|
151
|
+
choices: Choice[];
|
|
152
|
+
question: string;
|
|
153
|
+
/** `atmn sandbox use <name|id>`: what a headless caller runs instead. */
|
|
154
|
+
flag: string;
|
|
155
|
+
}): Promise<Choice | null> => {
|
|
156
|
+
if (!prompter.interactive) {
|
|
157
|
+
prompter.write(`${needs(question)}\n${hint(flag)}\n`);
|
|
158
|
+
return null;
|
|
159
|
+
}
|
|
160
|
+
prompter.write(`${needs(question)}\n`);
|
|
161
|
+
return interactiveSelect({ choices, write: prompter.write });
|
|
162
|
+
};
|
|
@@ -0,0 +1,77 @@
|
|
|
1
|
+
import chalk from "chalk";
|
|
2
|
+
import type { OrgInfo } from "../actions/env/types/orgInfo";
|
|
3
|
+
import { stripTerminalControls } from "./stripTerminalControls";
|
|
4
|
+
|
|
5
|
+
const GAP = " ";
|
|
6
|
+
|
|
7
|
+
const envLabel = ({ env }: { env: string }): string => {
|
|
8
|
+
if (env === "sandbox") return "Sandbox";
|
|
9
|
+
if (env === "live") return "Production";
|
|
10
|
+
return env;
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
/**
|
|
14
|
+
* A sandbox key authenticates as the sandbox's own org, so the id the server
|
|
15
|
+
* answers with is the sandbox the key really unlocks. A pin whose key answers
|
|
16
|
+
* as something else is a stale or copied key, and worth saying out loud.
|
|
17
|
+
*/
|
|
18
|
+
const sandboxCell = ({
|
|
19
|
+
sandboxId,
|
|
20
|
+
authenticatedId,
|
|
21
|
+
}: {
|
|
22
|
+
sandboxId: string;
|
|
23
|
+
authenticatedId: string;
|
|
24
|
+
}): string =>
|
|
25
|
+
sandboxId === authenticatedId
|
|
26
|
+
? sandboxId
|
|
27
|
+
: `${sandboxId} ${chalk.yellow(`← key belongs to ${authenticatedId}`)}`;
|
|
28
|
+
|
|
29
|
+
/**
|
|
30
|
+
* Headless rendering, like the sandbox table: a label column a terminal or a
|
|
31
|
+
* CI log can read. Nothing here decides anything — it reports what the server
|
|
32
|
+
* said about the key, plus where that key came from.
|
|
33
|
+
*/
|
|
34
|
+
export const renderEnv = ({
|
|
35
|
+
info,
|
|
36
|
+
secretKeyName,
|
|
37
|
+
baseUrl,
|
|
38
|
+
sandboxId,
|
|
39
|
+
}: {
|
|
40
|
+
info: OrgInfo;
|
|
41
|
+
/** The env var the key was read from; says which flag or pin picked it. */
|
|
42
|
+
secretKeyName: string;
|
|
43
|
+
/** Only shown when it is not the spec's server. */
|
|
44
|
+
baseUrl?: string;
|
|
45
|
+
sandboxId?: string;
|
|
46
|
+
}): string => {
|
|
47
|
+
const rows: [string, string][] = [
|
|
48
|
+
[
|
|
49
|
+
"Organization",
|
|
50
|
+
`${stripTerminalControls(info.name)} ${chalk.dim(`(${stripTerminalControls(info.slug)})`)}`,
|
|
51
|
+
],
|
|
52
|
+
["Environment", envLabel({ env: stripTerminalControls(info.env) })],
|
|
53
|
+
];
|
|
54
|
+
if (sandboxId !== undefined) {
|
|
55
|
+
rows.push([
|
|
56
|
+
"Sandbox",
|
|
57
|
+
sandboxCell({
|
|
58
|
+
sandboxId,
|
|
59
|
+
authenticatedId: stripTerminalControls(info.id),
|
|
60
|
+
}),
|
|
61
|
+
]);
|
|
62
|
+
}
|
|
63
|
+
if (info.claim_state === "pending")
|
|
64
|
+
rows.push([
|
|
65
|
+
"Owner",
|
|
66
|
+
`${chalk.yellow("unclaimed")} ${chalk.dim("(link with atmn login --claim <email>)")}`,
|
|
67
|
+
]);
|
|
68
|
+
if (info.user?.email !== undefined)
|
|
69
|
+
rows.push(["User", stripTerminalControls(info.user.email)]);
|
|
70
|
+
rows.push(["Key", secretKeyName]);
|
|
71
|
+
if (baseUrl !== undefined) rows.push(["Server", baseUrl]);
|
|
72
|
+
|
|
73
|
+
const width = Math.max(...rows.map(([label]) => label.length));
|
|
74
|
+
return rows
|
|
75
|
+
.map(([label, value]) => `${chalk.dim(label.padEnd(width))}${GAP}${value}`)
|
|
76
|
+
.join("\n");
|
|
77
|
+
};
|