sim 2.1.6-preview.84.1 → 2.1.6-preview.89.1
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +18 -1
- package/dist/index.js +326 -0
- package/package.json +1 -1
package/README.md
CHANGED
|
@@ -256,9 +256,26 @@ The main environment variables are:
|
|
|
256
256
|
| `SIM_API_KEY` | API key, usually for CI |
|
|
257
257
|
| `SIM_WORKSPACE` | Workspace to target |
|
|
258
258
|
| `SIM_OUTPUT` | `table`, `json`, `yaml`, or `text` |
|
|
259
|
-
| `SIM_CONFIG_DIR` |
|
|
259
|
+
| `SIM_CONFIG_DIR` | Base directory for CLI config, credentials, and the update cache |
|
|
260
260
|
| `SIM_TIMEOUT_SECONDS` | Per-request timeout; `0` waits indefinitely |
|
|
261
261
|
| `SIM_DEBUG` | Print request diagnostics to stderr |
|
|
262
|
+
| `SIM_NO_UPDATE_CHECK` | Turn off the update notice |
|
|
263
|
+
|
|
264
|
+
On eligible interactive invocations, `sim` uses a daily cache before asking
|
|
265
|
+
`registry.npmjs.org` what is published under the `latest` tag and prints one
|
|
266
|
+
line on stderr when a newer version exists. Prerelease installs are skipped
|
|
267
|
+
entirely. The cache lives in `~/.sim` by default and follows `SIM_CONFIG_DIR`;
|
|
268
|
+
without a writable cache, each eligible invocation checks again. Concurrent
|
|
269
|
+
invocations can also perform duplicate checks. The registry request has a
|
|
270
|
+
one-second deadline; the short-lived request process is terminated on expiry.
|
|
271
|
+
Apart from the configured registry URL, it sends only its own version and never
|
|
272
|
+
your Sim API key. If `npm_config_registry` points at a private mirror, its query
|
|
273
|
+
string is preserved, including any query-string credentials. Registry URLs
|
|
274
|
+
containing username/password userinfo are rejected. Set
|
|
275
|
+
`SIM_NO_UPDATE_CHECK=1` to turn it off. Empty or whitespace-only registry values
|
|
276
|
+
use the public default; non-empty malformed or non-HTTP(S) values fail closed.
|
|
277
|
+
The full list of cases where it stays quiet is in the
|
|
278
|
+
[configuration guide](https://docs.sim.ai/cli/configuration).
|
|
262
279
|
|
|
263
280
|
## Documentation
|
|
264
281
|
|
package/dist/index.js
CHANGED
|
@@ -2310,6 +2310,9 @@ function configPath() {
|
|
|
2310
2310
|
function credentialsPath() {
|
|
2311
2311
|
return process.env.SIM_CREDENTIALS_FILE || join(configDir(), "credentials");
|
|
2312
2312
|
}
|
|
2313
|
+
function updateCachePath() {
|
|
2314
|
+
return join(configDir(), "update-check.json");
|
|
2315
|
+
}
|
|
2313
2316
|
// src/config/profile.ts
|
|
2314
2317
|
import { chmodSync, existsSync, mkdirSync, readFileSync, writeFileSync } from "node:fs";
|
|
2315
2318
|
import { dirname } from "node:path";
|
|
@@ -16454,6 +16457,328 @@ function attachSecretCommands(program2) {
|
|
|
16454
16457
|
secrets.command("set").argument("<name>", "Secret name, as referenced in workflows").description(describeOperation(V2_OPERATIONS.setSecret, "Create or replace a named secret")).addOption(new Option("--scope <scope>", "Secret ownership scope (required)").choices([...SECRET_SCOPES]).makeOptionMandatory()).option("--value <value|@file>", "Secret value. Passing it inline exposes it to shell history and process listings; @path reads it from a file and @- from stdin, verbatim — a trailing newline is part of the value, so write the file with printf rather than echo. Prefix a literal leading @ with a second one").option("--description <description>", "What the secret is for, shown to teammates; workspace scope only. Omit to leave an existing description unchanged").option("--unredacted", `${V2_OPERATIONS.setSecret.body.unredacted.describe} Pass --no-unredacted to restore redaction`).option("--no-unredacted", "Send --unredacted as false").on("option:unredacted", () => redactionSpellings.add("--unredacted")).on("option:no-unredacted", () => redactionSpellings.add("--no-unredacted")).action((name, options, command) => setSecret(name, options, command, redactionSpellings));
|
|
16455
16458
|
}
|
|
16456
16459
|
|
|
16460
|
+
// src/update/check.ts
|
|
16461
|
+
import { spawn as spawn2 } from "node:child_process";
|
|
16462
|
+
import {
|
|
16463
|
+
closeSync,
|
|
16464
|
+
constants as constants2,
|
|
16465
|
+
fstatSync,
|
|
16466
|
+
lstatSync,
|
|
16467
|
+
mkdirSync as mkdirSync2,
|
|
16468
|
+
openSync,
|
|
16469
|
+
readSync as readSync2,
|
|
16470
|
+
renameSync,
|
|
16471
|
+
unlinkSync,
|
|
16472
|
+
writeFileSync as writeFileSync2
|
|
16473
|
+
} from "node:fs";
|
|
16474
|
+
import { dirname as dirname3 } from "node:path";
|
|
16475
|
+
import { fileURLToPath } from "node:url";
|
|
16476
|
+
var CHECK_INTERVAL_MS = 24 * 60 * 60 * 1000;
|
|
16477
|
+
var REGISTRY_TIMEOUT_MS = 1000;
|
|
16478
|
+
var DEFAULT_REGISTRY = "https://registry.npmjs.org";
|
|
16479
|
+
var PACKAGE_NAME = "sim";
|
|
16480
|
+
var DIST_TAGS_PATH = `-/package/${PACKAGE_NAME}/dist-tags`;
|
|
16481
|
+
var MAX_RESPONSE_BYTES = 64 * 1024;
|
|
16482
|
+
var MAX_CACHE_BYTES = 4 * 1024;
|
|
16483
|
+
var STABLE_VERSION_PATTERN = /^(0|[1-9]\d*)\.(0|[1-9]\d*)\.(0|[1-9]\d*)(?:\+[0-9A-Za-z-]+(?:\.[0-9A-Za-z-]+)*)?$/;
|
|
16484
|
+
function parseStableVersion(version) {
|
|
16485
|
+
const match = STABLE_VERSION_PATTERN.exec(version);
|
|
16486
|
+
if (!match)
|
|
16487
|
+
return null;
|
|
16488
|
+
const parsed = [Number(match[1]), Number(match[2]), Number(match[3])];
|
|
16489
|
+
return parsed.every(Number.isSafeInteger) ? parsed : null;
|
|
16490
|
+
}
|
|
16491
|
+
function isNewerVersion(candidate, current) {
|
|
16492
|
+
if (candidate[0] !== current[0])
|
|
16493
|
+
return candidate[0] > current[0];
|
|
16494
|
+
if (candidate[1] !== current[1])
|
|
16495
|
+
return candidate[1] > current[1];
|
|
16496
|
+
return candidate[2] > current[2];
|
|
16497
|
+
}
|
|
16498
|
+
var CI_VARIABLES = [
|
|
16499
|
+
"CI",
|
|
16500
|
+
"GITHUB_ACTIONS",
|
|
16501
|
+
"JENKINS_URL",
|
|
16502
|
+
"TEAMCITY_VERSION",
|
|
16503
|
+
"BUILDKITE"
|
|
16504
|
+
];
|
|
16505
|
+
var CACHE_VERSION = 1;
|
|
16506
|
+
var cacheWriteSequence = 0;
|
|
16507
|
+
function isEnabled(value) {
|
|
16508
|
+
if (value === undefined)
|
|
16509
|
+
return false;
|
|
16510
|
+
const normalized = value.trim().toLowerCase();
|
|
16511
|
+
return normalized !== "" && normalized !== "0" && normalized !== "false";
|
|
16512
|
+
}
|
|
16513
|
+
function isProjectLocalInstall(modulePath, cwd) {
|
|
16514
|
+
const normalizedModulePath = normalizeModulePath(modulePath);
|
|
16515
|
+
const nodeModulesIndex = normalizedModulePath.indexOf("/node_modules/");
|
|
16516
|
+
if (nodeModulesIndex < 0)
|
|
16517
|
+
return false;
|
|
16518
|
+
const installRoot = normalizedModulePath.slice(0, nodeModulesIndex);
|
|
16519
|
+
const workingDirectory = normalizeModulePath(cwd).replace(/\/+$/, "");
|
|
16520
|
+
return workingDirectory === installRoot || workingDirectory.startsWith(`${installRoot}/`);
|
|
16521
|
+
}
|
|
16522
|
+
function isUnadvisableInstall(modulePath, env2, cwd) {
|
|
16523
|
+
const normalized = normalizeModulePath(modulePath);
|
|
16524
|
+
return env2.npm_command === "exec" || normalized.includes("/_npx/") || normalized.includes("/packages/sim-cli/") || isProjectLocalInstall(modulePath, cwd);
|
|
16525
|
+
}
|
|
16526
|
+
function normalizeModulePath(modulePath) {
|
|
16527
|
+
return modulePath.replace(/\\/g, "/").toLowerCase();
|
|
16528
|
+
}
|
|
16529
|
+
function registryUrl(env2) {
|
|
16530
|
+
const fallback = new URL(DIST_TAGS_PATH, DEFAULT_REGISTRY);
|
|
16531
|
+
const configured = env2.npm_config_registry?.trim();
|
|
16532
|
+
if (!configured)
|
|
16533
|
+
return fallback;
|
|
16534
|
+
try {
|
|
16535
|
+
const base = new URL(configured);
|
|
16536
|
+
if (base.protocol !== "http:" && base.protocol !== "https:")
|
|
16537
|
+
return null;
|
|
16538
|
+
if (base.username || base.password)
|
|
16539
|
+
return null;
|
|
16540
|
+
base.pathname = `${base.pathname.replace(/\/$/, "")}/${DIST_TAGS_PATH}`;
|
|
16541
|
+
return base;
|
|
16542
|
+
} catch {
|
|
16543
|
+
return null;
|
|
16544
|
+
}
|
|
16545
|
+
}
|
|
16546
|
+
var REGISTRY_REQUEST_SCRIPT = `
|
|
16547
|
+
let input = ''
|
|
16548
|
+
process.stdin.setEncoding('utf8')
|
|
16549
|
+
for await (const chunk of process.stdin) input += chunk
|
|
16550
|
+
|
|
16551
|
+
try {
|
|
16552
|
+
const { url, headers, maxResponseBytes, timeoutMs } = JSON.parse(input)
|
|
16553
|
+
const deadline = setTimeout(() => process.exit(1), timeoutMs)
|
|
16554
|
+
const response = await fetch(url, { headers, redirect: 'error' })
|
|
16555
|
+
const declared = Number(response.headers.get('content-length'))
|
|
16556
|
+
|
|
16557
|
+
if (!response.ok || !response.body || (Number.isFinite(declared) && declared > maxResponseBytes)) {
|
|
16558
|
+
process.exit(1)
|
|
16559
|
+
}
|
|
16560
|
+
|
|
16561
|
+
const reader = response.body.getReader()
|
|
16562
|
+
const chunks = []
|
|
16563
|
+
let seen = 0
|
|
16564
|
+
|
|
16565
|
+
while (true) {
|
|
16566
|
+
const { done, value } = await reader.read()
|
|
16567
|
+
if (done) break
|
|
16568
|
+
seen += value.byteLength
|
|
16569
|
+
if (seen > maxResponseBytes) {
|
|
16570
|
+
process.exit(1)
|
|
16571
|
+
}
|
|
16572
|
+
chunks.push(Buffer.from(value))
|
|
16573
|
+
}
|
|
16574
|
+
|
|
16575
|
+
clearTimeout(deadline)
|
|
16576
|
+
process.stdout.write(Buffer.concat(chunks), () => process.exit(0))
|
|
16577
|
+
} catch {
|
|
16578
|
+
process.exit(1)
|
|
16579
|
+
}
|
|
16580
|
+
`;
|
|
16581
|
+
function registryProcessEnv() {
|
|
16582
|
+
const env2 = { ...process.env };
|
|
16583
|
+
for (const key of Object.keys(env2)) {
|
|
16584
|
+
const normalized = key.toLowerCase();
|
|
16585
|
+
if (normalized === "npm_config_registry" || normalized === "sim_api_key")
|
|
16586
|
+
delete env2[key];
|
|
16587
|
+
}
|
|
16588
|
+
return env2;
|
|
16589
|
+
}
|
|
16590
|
+
function requestRegistry(url, { headers, maxResponseBytes, timeoutMs }) {
|
|
16591
|
+
return new Promise((resolve3, reject) => {
|
|
16592
|
+
const proxyArguments = process.execArgv.filter((argument) => argument === "--use-env-proxy" || argument === "--no-use-env-proxy");
|
|
16593
|
+
const child = spawn2(process.execPath, [...proxyArguments, "--input-type=module", "--eval", REGISTRY_REQUEST_SCRIPT], {
|
|
16594
|
+
env: registryProcessEnv(),
|
|
16595
|
+
killSignal: "SIGKILL",
|
|
16596
|
+
stdio: ["pipe", "pipe", "ignore"],
|
|
16597
|
+
timeout: timeoutMs,
|
|
16598
|
+
windowsHide: true
|
|
16599
|
+
});
|
|
16600
|
+
const chunks = [];
|
|
16601
|
+
let failed = false;
|
|
16602
|
+
let seen = 0;
|
|
16603
|
+
child.stdout.on("data", (chunk) => {
|
|
16604
|
+
seen += chunk.byteLength;
|
|
16605
|
+
if (seen > maxResponseBytes) {
|
|
16606
|
+
failed = true;
|
|
16607
|
+
child.kill("SIGKILL");
|
|
16608
|
+
return;
|
|
16609
|
+
}
|
|
16610
|
+
chunks.push(chunk);
|
|
16611
|
+
});
|
|
16612
|
+
child.stdout.on("error", () => {
|
|
16613
|
+
failed = true;
|
|
16614
|
+
child.kill("SIGKILL");
|
|
16615
|
+
});
|
|
16616
|
+
child.stdin.on("error", () => {});
|
|
16617
|
+
child.once("error", reject);
|
|
16618
|
+
child.once("close", (code) => {
|
|
16619
|
+
resolve3(code === 0 && !failed ? Buffer.concat(chunks).toString("utf8") : null);
|
|
16620
|
+
});
|
|
16621
|
+
child.stdin.end(JSON.stringify({ headers, maxResponseBytes, timeoutMs, url: url.href }));
|
|
16622
|
+
});
|
|
16623
|
+
}
|
|
16624
|
+
async function fetchDistTags(env2, request) {
|
|
16625
|
+
try {
|
|
16626
|
+
const url = registryUrl(env2);
|
|
16627
|
+
if (!url)
|
|
16628
|
+
return null;
|
|
16629
|
+
const text2 = await request(url, {
|
|
16630
|
+
headers: { accept: "application/json", "user-agent": `${PACKAGE_NAME}-cli/${CLI_VERSION}` },
|
|
16631
|
+
maxResponseBytes: MAX_RESPONSE_BYTES,
|
|
16632
|
+
timeoutMs: REGISTRY_TIMEOUT_MS
|
|
16633
|
+
});
|
|
16634
|
+
if (text2 === null || Buffer.byteLength(text2) > MAX_RESPONSE_BYTES)
|
|
16635
|
+
return null;
|
|
16636
|
+
const body = JSON.parse(text2);
|
|
16637
|
+
if (typeof body !== "object" || body === null || Array.isArray(body))
|
|
16638
|
+
return null;
|
|
16639
|
+
const tags = {};
|
|
16640
|
+
for (const [tag, version] of Object.entries(body)) {
|
|
16641
|
+
if (typeof version === "string")
|
|
16642
|
+
tags[tag] = version;
|
|
16643
|
+
}
|
|
16644
|
+
return tags;
|
|
16645
|
+
} catch {
|
|
16646
|
+
return null;
|
|
16647
|
+
}
|
|
16648
|
+
}
|
|
16649
|
+
function readCache(path) {
|
|
16650
|
+
let descriptor = null;
|
|
16651
|
+
try {
|
|
16652
|
+
if (!lstatSync(path).isFile())
|
|
16653
|
+
return null;
|
|
16654
|
+
descriptor = openSync(path, constants2.O_RDONLY | constants2.O_NONBLOCK | constants2.O_NOFOLLOW);
|
|
16655
|
+
const descriptorStats = fstatSync(descriptor);
|
|
16656
|
+
if (!descriptorStats.isFile() || descriptorStats.size > MAX_CACHE_BYTES) {
|
|
16657
|
+
return null;
|
|
16658
|
+
}
|
|
16659
|
+
const buffer = Buffer.allocUnsafe(MAX_CACHE_BYTES + 1);
|
|
16660
|
+
let bytesRead = 0;
|
|
16661
|
+
while (bytesRead < buffer.byteLength) {
|
|
16662
|
+
const count = readSync2(descriptor, buffer, bytesRead, buffer.byteLength - bytesRead, bytesRead);
|
|
16663
|
+
if (count === 0)
|
|
16664
|
+
break;
|
|
16665
|
+
bytesRead += count;
|
|
16666
|
+
}
|
|
16667
|
+
if (bytesRead > MAX_CACHE_BYTES)
|
|
16668
|
+
return null;
|
|
16669
|
+
const parsed = JSON.parse(buffer.subarray(0, bytesRead).toString("utf8"));
|
|
16670
|
+
if (typeof parsed !== "object" || parsed === null)
|
|
16671
|
+
return null;
|
|
16672
|
+
const entry = parsed;
|
|
16673
|
+
if (entry.version !== CACHE_VERSION)
|
|
16674
|
+
return null;
|
|
16675
|
+
if (typeof entry.checkedAt !== "string" || Number.isNaN(Date.parse(entry.checkedAt)))
|
|
16676
|
+
return null;
|
|
16677
|
+
return {
|
|
16678
|
+
version: CACHE_VERSION,
|
|
16679
|
+
checkedAt: entry.checkedAt
|
|
16680
|
+
};
|
|
16681
|
+
} catch {
|
|
16682
|
+
return null;
|
|
16683
|
+
} finally {
|
|
16684
|
+
if (descriptor !== null) {
|
|
16685
|
+
try {
|
|
16686
|
+
closeSync(descriptor);
|
|
16687
|
+
} catch {}
|
|
16688
|
+
}
|
|
16689
|
+
}
|
|
16690
|
+
}
|
|
16691
|
+
function writeCache(path, entry) {
|
|
16692
|
+
let descriptor = null;
|
|
16693
|
+
let temporaryCreated = false;
|
|
16694
|
+
const temporaryPath = `${path}.${process.pid}.${Date.now()}.${cacheWriteSequence++}.tmp`;
|
|
16695
|
+
try {
|
|
16696
|
+
mkdirSync2(dirname3(path), { recursive: true, mode: 448 });
|
|
16697
|
+
descriptor = openSync(temporaryPath, "wx", 420);
|
|
16698
|
+
temporaryCreated = true;
|
|
16699
|
+
writeFileSync2(descriptor, `${JSON.stringify(entry, null, 2)}
|
|
16700
|
+
`);
|
|
16701
|
+
closeSync(descriptor);
|
|
16702
|
+
descriptor = null;
|
|
16703
|
+
renameSync(temporaryPath, path);
|
|
16704
|
+
temporaryCreated = false;
|
|
16705
|
+
} catch {} finally {
|
|
16706
|
+
if (descriptor !== null) {
|
|
16707
|
+
try {
|
|
16708
|
+
closeSync(descriptor);
|
|
16709
|
+
} catch {}
|
|
16710
|
+
}
|
|
16711
|
+
if (temporaryCreated) {
|
|
16712
|
+
try {
|
|
16713
|
+
unlinkSync(temporaryPath);
|
|
16714
|
+
} catch {}
|
|
16715
|
+
}
|
|
16716
|
+
}
|
|
16717
|
+
}
|
|
16718
|
+
function isFresh(entry, now) {
|
|
16719
|
+
const age = now.getTime() - Date.parse(entry.checkedAt);
|
|
16720
|
+
return age >= 0 && age < CHECK_INTERVAL_MS;
|
|
16721
|
+
}
|
|
16722
|
+
function upgradeCommand(modulePath = fileURLToPath(import.meta.url), env2 = process.env) {
|
|
16723
|
+
const target = `${PACKAGE_NAME}@latest`;
|
|
16724
|
+
const normalized = normalizeModulePath(modulePath);
|
|
16725
|
+
if (normalized.includes(".bun/install/global"))
|
|
16726
|
+
return `bun add -g ${target}`;
|
|
16727
|
+
if (normalized.includes("/pnpm/") || normalized.includes("/.pnpm/")) {
|
|
16728
|
+
return `pnpm add -g ${target}`;
|
|
16729
|
+
}
|
|
16730
|
+
if (normalized.includes("/.yarn/") || normalized.includes("/yarn/")) {
|
|
16731
|
+
return `yarn global add ${target}`;
|
|
16732
|
+
}
|
|
16733
|
+
const agent = env2.npm_config_user_agent ?? "";
|
|
16734
|
+
if (agent.startsWith("pnpm/"))
|
|
16735
|
+
return `pnpm add -g ${target}`;
|
|
16736
|
+
if (agent.startsWith("yarn/"))
|
|
16737
|
+
return `yarn global add ${target}`;
|
|
16738
|
+
if (agent.startsWith("bun/"))
|
|
16739
|
+
return `bun add -g ${target}`;
|
|
16740
|
+
return `npm install -g ${target}`;
|
|
16741
|
+
}
|
|
16742
|
+
async function announceUpdateIfAvailable(options = {}) {
|
|
16743
|
+
try {
|
|
16744
|
+
const env2 = options.env ?? process.env;
|
|
16745
|
+
const isTty = options.isTty ?? process.stderr.isTTY === true;
|
|
16746
|
+
const modulePath = options.modulePath ?? fileURLToPath(import.meta.url);
|
|
16747
|
+
const cwd = options.cwd ?? process.cwd();
|
|
16748
|
+
const now = options.now ?? new Date;
|
|
16749
|
+
if (isEnabled(env2.SIM_NO_UPDATE_CHECK))
|
|
16750
|
+
return;
|
|
16751
|
+
if (!isTty)
|
|
16752
|
+
return;
|
|
16753
|
+
if (CI_VARIABLES.some((variable) => isEnabled(env2[variable])))
|
|
16754
|
+
return;
|
|
16755
|
+
if (isUnadvisableInstall(modulePath, env2, cwd))
|
|
16756
|
+
return;
|
|
16757
|
+
const currentVersion = options.currentVersion ?? CLI_VERSION;
|
|
16758
|
+
const current = parseStableVersion(currentVersion);
|
|
16759
|
+
if (!current)
|
|
16760
|
+
return;
|
|
16761
|
+
const cachePath = updateCachePath();
|
|
16762
|
+
const cached = readCache(cachePath);
|
|
16763
|
+
if (cached && isFresh(cached, now))
|
|
16764
|
+
return;
|
|
16765
|
+
const tags = await fetchDistTags(env2, options.registryRequest ?? requestRegistry);
|
|
16766
|
+
const latest = tags?.latest ?? null;
|
|
16767
|
+
const available = latest ? parseStableVersion(latest) : null;
|
|
16768
|
+
writeCache(cachePath, {
|
|
16769
|
+
version: CACHE_VERSION,
|
|
16770
|
+
checkedAt: now.toISOString()
|
|
16771
|
+
});
|
|
16772
|
+
if (!latest || !available)
|
|
16773
|
+
return;
|
|
16774
|
+
if (!isNewerVersion(available, current))
|
|
16775
|
+
return;
|
|
16776
|
+
const write = options.write ?? ((message) => void process.stderr.write(message));
|
|
16777
|
+
write(`Update available: sim ${currentVersion} → ${latest}. Run: ${upgradeCommand(modulePath, env2)}
|
|
16778
|
+
`);
|
|
16779
|
+
} catch {}
|
|
16780
|
+
}
|
|
16781
|
+
|
|
16457
16782
|
// src/program.ts
|
|
16458
16783
|
var PROGRAM_DESCRIPTION = "Talk to the Sim API from your terminal";
|
|
16459
16784
|
var HELP_EPILOGUE = `
|
|
@@ -16533,6 +16858,7 @@ function buildProgram(options = {}) {
|
|
|
16533
16858
|
attachProtocolCommands(program2);
|
|
16534
16859
|
attachSecretCommands(program2);
|
|
16535
16860
|
program2.addHelpText("after", HELP_EPILOGUE);
|
|
16861
|
+
program2.hook("preAction", () => announceUpdateIfAvailable());
|
|
16536
16862
|
refuseHelpAfterUnknownCommand(program2);
|
|
16537
16863
|
assertNoReservedProgramFlags(program2);
|
|
16538
16864
|
return program2;
|