argsbarg 4.0.4 → 4.1.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/CHANGELOG.md +77 -1
- package/README.md +91 -85
- package/docs/README.md +6 -6
- package/docs/ai-skills.md +8 -5
- package/docs/bundled-docs.md +1 -1
- package/docs/cli-program.md +9 -7
- package/docs/config-schema.md +37 -13
- package/docs/developing.md +8 -8
- package/docs/distribution-homebrew.md +103 -0
- package/docs/install.md +143 -106
- package/docs/mcp.md +23 -12
- package/docs/output-schema.md +1 -1
- package/examples/full-example/Formula/.gitkeep +0 -0
- package/examples/full-example/README.md +98 -0
- package/examples/full-example/biome.json +22 -0
- package/examples/{consumer-app → full-example}/bun.lock +2 -0
- package/examples/full-example/justfile +134 -0
- package/examples/{consumer-app → full-example}/package.json +10 -3
- package/examples/{consumer-app → full-example}/schemas/generated/app-config.json +1 -1
- package/examples/{consumer-app → full-example}/schemas/generated/status.json +1 -1
- package/examples/full-example/scripts/create-identity.ts +11 -0
- package/examples/full-example/scripts/formula-shared.ts +73 -0
- package/examples/full-example/scripts/gen-dev-formula.ts +26 -0
- package/examples/full-example/scripts/print-identity.ts +27 -0
- package/examples/full-example/src/commands/echo/command.ts +21 -0
- package/examples/full-example/src/commands/status/command.test.ts +10 -0
- package/examples/full-example/src/commands/status/command.ts +36 -0
- package/examples/{consumer-app → full-example}/src/commands/status/types.ts +1 -1
- package/examples/full-example/src/index.ts +10 -0
- package/examples/full-example/src/program.ts +57 -0
- package/examples/{consumer-app → full-example}/src/types.ts +1 -1
- package/examples/mcp-test.ts +8 -24
- package/examples/nested.ts +1 -3
- package/index.d.ts +81 -65
- package/package.json +2 -2
- package/src/builtins/builtins.test.ts +37 -22
- package/src/builtins/completion-group.ts +17 -15
- package/src/builtins/config.test.ts +31 -25
- package/src/builtins/config.ts +4 -3
- package/src/builtins/dispatch.ts +25 -1
- package/src/builtins/install.ts +45 -82
- package/src/builtins/mcp.ts +1 -1
- package/src/builtins/registry.ts +2 -0
- package/src/builtins/uninstall.ts +80 -0
- package/src/capabilities.ts +5 -7
- package/src/cli-tool/cli-smoke.test.ts +19 -0
- package/src/cli-tool/create.test.ts +119 -0
- package/src/cli-tool/create.ts +380 -0
- package/{examples/consumer-app/capabilities.test.ts → src/cli-tool/full-example-capabilities.test.ts} +15 -14
- package/src/cli-tool/main.ts +8 -0
- package/src/cli-tool/post-create.ts +111 -0
- package/src/cli-tool/program.ts +82 -0
- package/src/cli-tool/prompt.ts +28 -0
- package/src/cli-tool/run-create.ts +149 -0
- package/src/config/bootstrap.ts +174 -66
- package/src/config/context.test.ts +22 -36
- package/src/config/context.ts +5 -4
- package/src/config/file.test.ts +66 -56
- package/src/config/file.ts +33 -25
- package/src/config/resolve.test.ts +192 -1
- package/src/config/resolve.ts +92 -13
- package/src/config.integration.test.ts +17 -10
- package/src/docs/api-guide.test.ts +4 -5
- package/src/docs/docs.test.ts +2 -1
- package/src/docs/mcp-guide.ts +7 -8
- package/src/hidden-mcpb.test.ts +41 -1
- package/src/index.ts +7 -10
- package/src/install/binary-placement.test.ts +101 -0
- package/src/install/binary-placement.ts +47 -0
- package/src/install/detect-installed.ts +2 -97
- package/src/install/index.ts +239 -168
- package/src/install/install-validate.test.ts +61 -0
- package/src/install/install.test.ts +170 -90
- package/src/install/mcp-openclaw.test.ts +40 -0
- package/src/install/mcp-openclaw.ts +106 -0
- package/src/install/normalize-uninstall.ts +11 -0
- package/src/install/normalize.ts +20 -0
- package/src/install/paths.ts +18 -26
- package/src/install/plan.ts +40 -261
- package/src/install/shell.ts +0 -14
- package/src/install/status.test.ts +85 -0
- package/src/install/status.ts +22 -15
- package/src/install/target-base.ts +93 -0
- package/src/install/target-detect.ts +20 -0
- package/src/install/target-effective.ts +129 -0
- package/src/install/target-mcp-cli.ts +149 -0
- package/src/install/target-mcp-json.ts +130 -0
- package/src/install/target-plan-build.ts +67 -0
- package/src/install/target-registry.ts +57 -0
- package/src/install/target-scope.ts +253 -0
- package/src/install/target-skill.ts +104 -0
- package/src/install/target-types.ts +129 -0
- package/src/install/targets/app.ts +60 -0
- package/src/install/targets/chatgpt-mcp.ts +12 -0
- package/src/install/targets/claude-code-mcp.ts +15 -0
- package/src/install/targets/claude-desktop-mcp.ts +12 -0
- package/src/install/targets/claude-skill.ts +16 -0
- package/src/install/targets/codex-mcp.ts +25 -0
- package/src/install/targets/codex-skill.ts +14 -0
- package/src/install/targets/configure.ts +63 -0
- package/src/install/targets/cursor-mcp.ts +15 -0
- package/src/install/targets/cursor-skill.ts +16 -0
- package/src/install/targets/index.ts +50 -0
- package/src/install/targets/openclaw-mcp.ts +25 -0
- package/src/install/targets/openclaw-skill.ts +17 -0
- package/src/install/targets/opencode-mcp.ts +101 -0
- package/src/install/targets/opencode-skill.ts +15 -0
- package/src/install/targets.test.ts +118 -0
- package/src/install/uninstall.ts +16 -152
- package/src/invoke.test.ts +7 -1
- package/src/mcp/bundle.ts +16 -4
- package/src/mcp/claude.test.ts +14 -1
- package/src/mcp/claude.ts +11 -4
- package/src/mcp/env.test.ts +92 -0
- package/src/mcp/env.ts +15 -14
- package/src/mcp/zip.test.ts +17 -0
- package/src/mcp/zip.ts +62 -9
- package/src/mcp.integration.test.ts +1 -1
- package/src/parse.test.ts +14 -2
- package/src/paths/host.ts +11 -11
- package/src/paths/remove-empty-dir.ts +13 -0
- package/src/prompt.ts +10 -0
- package/src/schema.ts +9 -1
- package/src/skill/generate.ts +18 -4
- package/src/skill/install.ts +33 -6
- package/src/skill/naming.ts +28 -0
- package/src/types.ts +86 -7
- package/src/validate.ts +73 -9
- package/docs/templates/cursor/rules/cli-program.mdc +0 -31
- package/examples/config-app/main.ts +0 -20
- package/examples/config-app/program.ts +0 -81
- package/examples/config-app/schema.ts +0 -37
- package/examples/config-app/types.ts +0 -19
- package/examples/consumer-app/README.md +0 -57
- package/examples/consumer-app/src/main.ts +0 -15
- package/examples/consumer-app/src/program.ts +0 -108
- package/src/install/binary.ts +0 -94
- package/src/install/completions.ts +0 -56
- package/src/install/update.test.ts +0 -108
- package/src/install/update.ts +0 -57
- /package/examples/{consumer-app → full-example}/schemas/configSchemas.ts +0 -0
- /package/examples/{consumer-app → full-example}/schemas/outputSchemas.ts +0 -0
- /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.test.ts +0 -0
- /package/examples/{consumer-app → full-example}/scripts/schemagen/discover-schema-roots.ts +0 -0
- /package/examples/{consumer-app → full-example}/scripts/schemagen/naming.ts +0 -0
- /package/examples/{consumer-app → full-example}/scripts/schemagen.ts +0 -0
- /package/examples/{consumer-app → full-example}/tsconfig.json +0 -0
package/index.d.ts
CHANGED
|
@@ -1,5 +1,9 @@
|
|
|
1
1
|
// Generated by dts-bundle-generator v9.5.1
|
|
2
2
|
|
|
3
|
+
/** Resolved absolute path to the app JSON config file (`~/.local/lib/<key>/config.json`). */
|
|
4
|
+
export declare function resolveAppConfigPath(program: CliProgram): string;
|
|
5
|
+
/** Human-readable config path for error messages (`~/…` when under home). */
|
|
6
|
+
export declare function displayAppConfigPath(program: CliProgram): string;
|
|
3
7
|
export type ResolvedConfig = Record<string, unknown>;
|
|
4
8
|
declare class EmptyAppConfigSnapshot {
|
|
5
9
|
private readonly program;
|
|
@@ -22,7 +26,7 @@ declare class AppConfigSnapshot {
|
|
|
22
26
|
require(key: string): unknown;
|
|
23
27
|
set(key: string, value: unknown): void;
|
|
24
28
|
read(): ResolvedConfig;
|
|
25
|
-
/** Resolved absolute path to the app JSON config file (
|
|
29
|
+
/** Resolved absolute path to the app JSON config file (`~/.local/lib/<key>/config.json`). */
|
|
26
30
|
get path(): string;
|
|
27
31
|
/** Resolved absolute directory containing the config file. */
|
|
28
32
|
get dir(): string;
|
|
@@ -194,6 +198,10 @@ export interface CliMcpBundleConfig {
|
|
|
194
198
|
export interface CliMcpServerConfig {
|
|
195
199
|
/** When `true`, enables the `mcp` built-in and MCP stdio server. */
|
|
196
200
|
enabled: boolean;
|
|
201
|
+
/** When `true`, `mcp bundle` writes `dist/<key>.mcpb` for Claude Desktop. Default false. */
|
|
202
|
+
mcpd?: boolean;
|
|
203
|
+
/** When `true`, `mcp bundle` also writes `dist/claude-plugin/<name>.zip`. Default false. */
|
|
204
|
+
claudePlugin?: boolean;
|
|
197
205
|
/** Resource URI for schema export (default: `<sanitized root key>://schema`). */
|
|
198
206
|
schemaResourceUri?: string;
|
|
199
207
|
/**
|
|
@@ -241,21 +249,24 @@ export interface CliMcpToolConfig {
|
|
|
241
249
|
*/
|
|
242
250
|
outputSchema?: Record<string, unknown>;
|
|
243
251
|
}
|
|
252
|
+
/** Context passed to {@link CliAppConfigEntry.resolve} for one config key. */
|
|
253
|
+
export interface CliAppConfigResolveContext {
|
|
254
|
+
/** Schema key being resolved. */
|
|
255
|
+
key: string;
|
|
256
|
+
/** Entry metadata for this key. */
|
|
257
|
+
entry: CliAppConfigEntry;
|
|
258
|
+
/** Program root (read-only). */
|
|
259
|
+
program: CliProgram;
|
|
260
|
+
/** Raw value from the config file, if any. */
|
|
261
|
+
fileValue: unknown;
|
|
262
|
+
/** Non-empty host env string when `entry.env` is set; otherwise `undefined`. */
|
|
263
|
+
envValue: string | undefined;
|
|
264
|
+
}
|
|
244
265
|
/**
|
|
245
|
-
*
|
|
266
|
+
* Optional fallback resolver for one config key (e.g. `gh auth token` when `GH_TOKEN` is unset).
|
|
267
|
+
* Return `undefined` to continue resolution (env, then default).
|
|
246
268
|
*/
|
|
247
|
-
export
|
|
248
|
-
/** Path to an executable binary to copy into the install location. */
|
|
249
|
-
path: string;
|
|
250
|
-
/** Release version of `path` (used for already-current checks and success messages). */
|
|
251
|
-
version?: string;
|
|
252
|
-
/** Called after reinstall completes (e.g. remove a temp download directory). */
|
|
253
|
-
cleanup?: () => void | Promise<void>;
|
|
254
|
-
}
|
|
255
|
-
/** Fetches the latest release binary for `install --update`. */
|
|
256
|
-
export type CliUpdateGetLatest = (ctx: {
|
|
257
|
-
version: string;
|
|
258
|
-
}) => Promise<CliUpdateArtifact>;
|
|
269
|
+
export type CliAppConfigResolveFn = (ctx: CliAppConfigResolveContext) => unknown;
|
|
259
270
|
/**
|
|
260
271
|
* Metadata overlay for one key in {@link CliAppConfig.entries}.
|
|
261
272
|
* Types and validation come from {@link CliAppConfig.jsonSchema} when set; otherwise all values are strings.
|
|
@@ -276,13 +287,16 @@ export interface CliAppConfigEntry {
|
|
|
276
287
|
sensitive?: boolean;
|
|
277
288
|
/** When set: non-empty `process.env[env]` overrides file; value exported after resolve. */
|
|
278
289
|
env?: string;
|
|
290
|
+
/**
|
|
291
|
+
* Optional fallback after file when env is empty.
|
|
292
|
+
* Return `undefined` to fall back to `env` (if set) and schema defaults.
|
|
293
|
+
*/
|
|
294
|
+
resolve?: CliAppConfigResolveFn;
|
|
279
295
|
}
|
|
280
296
|
/**
|
|
281
297
|
* App configuration block on the program root ({@link CliProgram.appConfig}).
|
|
282
298
|
*/
|
|
283
299
|
export interface CliAppConfig {
|
|
284
|
-
/** Default: `~/.config/<sanitized-key>/config` (or `%APPDATA%/<key>/config` on Windows). */
|
|
285
|
-
path?: string;
|
|
286
300
|
/** Built-in `config get` / `config set`. Default: enabled when `appConfig` is set. */
|
|
287
301
|
commands?: boolean | {
|
|
288
302
|
enabled?: boolean;
|
|
@@ -296,13 +310,59 @@ export interface CliAppConfig {
|
|
|
296
310
|
export interface CliInstallConfig {
|
|
297
311
|
/** When `false`, hide/disable `install` (default: enabled). */
|
|
298
312
|
enabled?: boolean;
|
|
299
|
-
/** Default bin directory (default: `~/.local/bin`). Overridden by `INSTALL_PREFIX` env and `--prefix`. */
|
|
300
|
-
prefix?: string;
|
|
301
313
|
/**
|
|
302
|
-
*
|
|
303
|
-
*
|
|
314
|
+
* Default agent integration for full install (`install --all`).
|
|
315
|
+
* - `'mcp'` when `mcpServer.enabled` (default): MCP targets in `--all`; paired skills excluded.
|
|
316
|
+
* - `'skill'` when MCP is off (default): skill targets in `--all`; paired MCP excluded.
|
|
317
|
+
* - `'both'`: install MCP and skill for the same host when both are available.
|
|
304
318
|
*/
|
|
305
|
-
|
|
319
|
+
agentIntegration?: InstallAgentIntegration;
|
|
320
|
+
/** Per-artifact gates for full install/uninstall. See {@link resolveEffectiveInstallTargets}. */
|
|
321
|
+
targets?: CliInstallTargets;
|
|
322
|
+
}
|
|
323
|
+
/** Agent integration mode for install — MCP vs shell skill per host. */
|
|
324
|
+
export type InstallAgentIntegration = "mcp" | "skill" | "both";
|
|
325
|
+
/** Boolean or structured gate for one install artifact. */
|
|
326
|
+
export type InstallTargetSpec = boolean | {
|
|
327
|
+
/** When false, artifact is never installed (even with scoped CLI flags). Default true. */
|
|
328
|
+
enabled?: boolean;
|
|
329
|
+
/** When true, included in bare `install` / `install --all`. Default varies by key. */
|
|
330
|
+
includedInAll?: boolean;
|
|
331
|
+
};
|
|
332
|
+
export interface ResolvedInstallTarget {
|
|
333
|
+
enabled: boolean;
|
|
334
|
+
includedInAll: boolean;
|
|
335
|
+
}
|
|
336
|
+
/** Per-artifact gates for full install/uninstall. See {@link resolveEffectiveInstallTargets}. */
|
|
337
|
+
export interface CliInstallTargets {
|
|
338
|
+
/** App binary status only (Homebrew PATH); no self-install. */
|
|
339
|
+
app?: InstallTargetSpec;
|
|
340
|
+
/** ChatGPT desktop MCP. Default false. */
|
|
341
|
+
chatgptMcp?: InstallTargetSpec;
|
|
342
|
+
/** Claude Code MCP (`~/.claude.json`). Default false. */
|
|
343
|
+
claudeCodeMcp?: InstallTargetSpec;
|
|
344
|
+
/** Claude Desktop MCP. Default false. */
|
|
345
|
+
claudeDesktopMcp?: InstallTargetSpec;
|
|
346
|
+
/** Claude Code skill. Default false. */
|
|
347
|
+
claudeSkill?: InstallTargetSpec;
|
|
348
|
+
/** Codex MCP (`codex mcp add`). Default false. */
|
|
349
|
+
codexMcp?: InstallTargetSpec;
|
|
350
|
+
/** Codex skill. Default false. */
|
|
351
|
+
codexSkill?: InstallTargetSpec;
|
|
352
|
+
/** App config: wizard via install --configure only. Default not in --all. */
|
|
353
|
+
configure?: InstallTargetSpec;
|
|
354
|
+
/** Cursor MCP. Default false. */
|
|
355
|
+
cursorMcp?: InstallTargetSpec;
|
|
356
|
+
/** Cursor skill. Default false. */
|
|
357
|
+
cursorSkill?: InstallTargetSpec;
|
|
358
|
+
/** OpenClaw MCP. Default false. */
|
|
359
|
+
openclawMcp?: InstallTargetSpec;
|
|
360
|
+
/** OpenClaw skill. Default false. */
|
|
361
|
+
openclawSkill?: InstallTargetSpec;
|
|
362
|
+
/** OpenCode MCP. Default false. */
|
|
363
|
+
opencodeMcp?: InstallTargetSpec;
|
|
364
|
+
/** OpenCode skill. Default false. */
|
|
365
|
+
opencodeSkill?: InstallTargetSpec;
|
|
306
366
|
}
|
|
307
367
|
/**
|
|
308
368
|
* One bundled documentation topic for the `docs` built-in (program root only).
|
|
@@ -410,7 +470,6 @@ export interface CliCapabilities {
|
|
|
410
470
|
mcp: boolean;
|
|
411
471
|
install: boolean;
|
|
412
472
|
docs: boolean;
|
|
413
|
-
update: boolean;
|
|
414
473
|
configCommands: boolean;
|
|
415
474
|
}
|
|
416
475
|
/** JSON-safe command node (no handlers). */
|
|
@@ -492,49 +551,6 @@ export declare function shouldRunHeadlessWithYes(ctx: HeadlessContext, opts: {
|
|
|
492
551
|
export declare function requireYesInNonTty(yes: boolean, hint: string, dryRun?: boolean, interactive?: boolean): void;
|
|
493
552
|
/** Prefixes a success message when running in dry-run mode. */
|
|
494
553
|
export declare function formatDryRunMessage(message: string, dryRun: boolean): string;
|
|
495
|
-
/** Config for {@link ghReleaseUpdateGetLatest}. */
|
|
496
|
-
export interface GhReleaseUpdateConfig {
|
|
497
|
-
/** GitHub `owner/repo` slug. */
|
|
498
|
-
repo: string;
|
|
499
|
-
/** Release asset filename (e.g. `myapp`). */
|
|
500
|
-
asset: string;
|
|
501
|
-
/** Temp directory name prefix for downloads. */
|
|
502
|
-
tempPrefix: string;
|
|
503
|
-
/** Path to the on-disk version-check cache JSON file. */
|
|
504
|
-
cachePath: string;
|
|
505
|
-
/** Optional hint when `gh auth` fails or no releases exist. */
|
|
506
|
-
repoEnvHint?: string;
|
|
507
|
-
}
|
|
508
|
-
/** Config for {@link createGhVersionCheck}. */
|
|
509
|
-
export interface GhVersionCheckConfig {
|
|
510
|
-
/** Installed semver string. */
|
|
511
|
-
currentVersion: string;
|
|
512
|
-
/** CLI command name for update notices (e.g. `qa`). */
|
|
513
|
-
commandName: string;
|
|
514
|
-
/** Path to the on-disk version-check cache JSON file. */
|
|
515
|
-
cachePath: string;
|
|
516
|
-
/** Cache TTL in milliseconds (default 24h). */
|
|
517
|
-
ttlMs?: number;
|
|
518
|
-
/** When true, skip background refresh (e.g. test subprocess). */
|
|
519
|
-
skipRefresh?: () => boolean;
|
|
520
|
-
/** When true, skip refresh because `gh` is unavailable. */
|
|
521
|
-
ghAvailable?: () => boolean;
|
|
522
|
-
/** Fetches latest release version via `gh`. */
|
|
523
|
-
fetchLatest: () => Promise<string>;
|
|
524
|
-
}
|
|
525
|
-
/** Returns whether the installed version matches the latest release. */
|
|
526
|
-
export declare function isAlreadyCurrent(current: string, latest: string): boolean;
|
|
527
|
-
/** Strips a leading `v` from a release tag. */
|
|
528
|
-
export declare function parseReleaseTag(tag: string): string;
|
|
529
|
-
/** Builds a `CliUpdateGetLatest` hook that downloads a release via `gh`. */
|
|
530
|
-
export declare function ghReleaseUpdateGetLatest(config: GhReleaseUpdateConfig): CliUpdateGetLatest;
|
|
531
|
-
/** Version-check cache helpers for summary notices and background refresh. */
|
|
532
|
-
export declare function createGhVersionCheck(config: GhVersionCheckConfig): {
|
|
533
|
-
getUpdateNotice: () => string | null;
|
|
534
|
-
refreshIfStale: () => void;
|
|
535
|
-
};
|
|
536
|
-
/** Shared `gh release view` fetcher for hooks and version-check refresh. */
|
|
537
|
-
export declare function createGhFetchLatest(config: Pick<GhReleaseUpdateConfig, "repo" | "repoEnvHint">): () => Promise<string>;
|
|
538
554
|
/** Resolved paths for `mcp bundle`. */
|
|
539
555
|
export interface McpBundlePaths {
|
|
540
556
|
binaryPath: string;
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "argsbarg",
|
|
3
|
-
"version": "4.
|
|
3
|
+
"version": "4.1.1",
|
|
4
4
|
"type": "module",
|
|
5
5
|
"engines": {
|
|
6
6
|
"bun": ">=1.3"
|
|
@@ -12,7 +12,7 @@
|
|
|
12
12
|
"module": "./src/index.ts",
|
|
13
13
|
"types": "./index.d.ts",
|
|
14
14
|
"bin": {
|
|
15
|
-
"argsbarg": "src/
|
|
15
|
+
"argsbarg": "src/cli-tool/main.ts"
|
|
16
16
|
},
|
|
17
17
|
"exports": {
|
|
18
18
|
".": {
|
|
@@ -1,10 +1,12 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
+
import { ParseKind, parse, postParseValidate } from "../parse.ts";
|
|
2
3
|
import type { CliProgram } from "../types.ts";
|
|
3
4
|
import { exportPresentationBuiltins } from "./export.ts";
|
|
4
5
|
import { completionBashScript, completionFishScript, completionZshScript } from "./index.ts";
|
|
5
6
|
import { cliBuiltinInstallCommand, installBuiltinOptions } from "./install.ts";
|
|
6
7
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
7
|
-
import { cliPresentationRoot } from "./presentation.ts";
|
|
8
|
+
import { cliParseRoot, cliPresentationRoot } from "./presentation.ts";
|
|
9
|
+
import { cliBuiltinUninstallCommand, uninstallBuiltinOptions } from "./uninstall.ts";
|
|
8
10
|
|
|
9
11
|
const fixture: CliProgram = {
|
|
10
12
|
key: "myapp",
|
|
@@ -21,14 +23,39 @@ const fixture: CliProgram = {
|
|
|
21
23
|
};
|
|
22
24
|
|
|
23
25
|
describe("builtins help copy", () => {
|
|
24
|
-
test("install command includes description
|
|
26
|
+
test("install command includes Homebrew-oriented description", () => {
|
|
25
27
|
const install = cliBuiltinInstallCommand(fixture);
|
|
26
|
-
expect(install.description).toContain("
|
|
27
|
-
expect(install.notes).toContain("install
|
|
28
|
+
expect(install.description).toContain("agent skills");
|
|
29
|
+
expect(install.notes).toContain("brew install");
|
|
28
30
|
const names = installBuiltinOptions(fixture).map((o) => o.name);
|
|
29
31
|
expect(names).toContain("all");
|
|
30
32
|
expect(names).toContain("mcp");
|
|
31
|
-
expect(names).toContain("
|
|
33
|
+
expect(names).not.toContain("app");
|
|
34
|
+
expect(names).not.toContain("completions");
|
|
35
|
+
expect(names).not.toContain("update");
|
|
36
|
+
expect(names.indexOf("all")).toBeLessThan(names.indexOf("mcp"));
|
|
37
|
+
expect(names.indexOf("mcp")).toBeLessThan(names.indexOf("status"));
|
|
38
|
+
expect(names).not.toContain("uninstall");
|
|
39
|
+
const yesOpt = installBuiltinOptions(fixture).find((o) => o.name === "yes");
|
|
40
|
+
expect(yesOpt?.shortName).toBe("y");
|
|
41
|
+
});
|
|
42
|
+
|
|
43
|
+
test("uninstall command includes removal guidance", () => {
|
|
44
|
+
const uninstall = cliBuiltinUninstallCommand(fixture);
|
|
45
|
+
expect(uninstall.notes).toContain("brew uninstall");
|
|
46
|
+
const names = uninstallBuiltinOptions(fixture).map((o) => o.name);
|
|
47
|
+
expect(names).toContain("all");
|
|
48
|
+
expect(names).not.toContain("status");
|
|
49
|
+
expect(names).not.toContain("reinstall");
|
|
50
|
+
});
|
|
51
|
+
|
|
52
|
+
test("install -y parses as --yes", () => {
|
|
53
|
+
const root = cliParseRoot(fixture);
|
|
54
|
+
const pr = postParseValidate(root, parse(root, ["install", "-y"]));
|
|
55
|
+
expect(pr.kind).toBe(ParseKind.Ok);
|
|
56
|
+
if (pr.kind === ParseKind.Ok) {
|
|
57
|
+
expect(pr.opts.yes).toBe("1");
|
|
58
|
+
}
|
|
32
59
|
});
|
|
33
60
|
|
|
34
61
|
test("install omits --mcp option when mcpServer unset", () => {
|
|
@@ -37,23 +64,10 @@ describe("builtins help copy", () => {
|
|
|
37
64
|
expect(names).not.toContain("mcp");
|
|
38
65
|
});
|
|
39
66
|
|
|
40
|
-
test("install
|
|
67
|
+
test("install notes mention brew upgrade", () => {
|
|
41
68
|
const install = cliBuiltinInstallCommand(fixture);
|
|
42
|
-
expect(
|
|
43
|
-
expect(install.notes).
|
|
44
|
-
expect(install.notes).toContain("Refresh after upgrading");
|
|
45
|
-
});
|
|
46
|
-
|
|
47
|
-
test("install notes include upgrade section when updateGetLatest is set", () => {
|
|
48
|
-
const withUpdate: CliProgram = {
|
|
49
|
-
...fixture,
|
|
50
|
-
install: { updateGetLatest: async () => ({ path: process.execPath }) },
|
|
51
|
-
};
|
|
52
|
-
const install = cliBuiltinInstallCommand(withUpdate);
|
|
53
|
-
const notes = install.notes ?? "";
|
|
54
|
-
expect(installBuiltinOptions(withUpdate).map((o) => o.name)).toContain("update");
|
|
55
|
-
expect(notes).toContain("Upgrade to latest release");
|
|
56
|
-
expect(notes.indexOf("install --reinstall")).toBeLessThan(notes.indexOf("install --update"));
|
|
69
|
+
expect(install.notes).toContain("brew upgrade");
|
|
70
|
+
expect(install.notes).toContain("install --configure");
|
|
57
71
|
});
|
|
58
72
|
|
|
59
73
|
test("mcp builtin description is user-facing", () => {
|
|
@@ -74,6 +88,7 @@ describe("presentation root", () => {
|
|
|
74
88
|
const keys = root.commands?.map((c) => c.key) ?? [];
|
|
75
89
|
expect(keys).toContain("mcp");
|
|
76
90
|
expect(keys).toContain("install");
|
|
91
|
+
expect(keys).toContain("uninstall");
|
|
77
92
|
});
|
|
78
93
|
|
|
79
94
|
test("omits install when install.enabled is false", () => {
|
|
@@ -143,6 +158,6 @@ describe("schema export builtins", () => {
|
|
|
143
158
|
test("exportPresentationBuiltins includes install options", () => {
|
|
144
159
|
const builtins = exportPresentationBuiltins(fixture);
|
|
145
160
|
const install = builtins.find((b) => b.key === "install");
|
|
146
|
-
expect(install?.options?.find((o) => o.name === "all")?.description).toContain("
|
|
161
|
+
expect(install?.options?.find((o) => o.name === "all")?.description).toContain("agent");
|
|
147
162
|
});
|
|
148
163
|
});
|
|
@@ -1,13 +1,12 @@
|
|
|
1
1
|
import { resolveCapabilities } from "../capabilities.ts";
|
|
2
|
-
import type { CliProgram
|
|
2
|
+
import type { CliProgram } from "../types.ts";
|
|
3
3
|
|
|
4
4
|
/**
|
|
5
5
|
* Builds the static `completion` / `bash` / `zsh` / `fish` command subtree (merged into the program root at runtime).
|
|
6
6
|
*/
|
|
7
|
-
export function cliBuiltinCompletionGroup(program: CliProgram): CliRouter {
|
|
7
|
+
export function cliBuiltinCompletionGroup(program: CliProgram): import("../types.ts").CliRouter {
|
|
8
8
|
const appName = program.key;
|
|
9
|
-
const
|
|
10
|
-
const router: CliRouter = {
|
|
9
|
+
const router: import("../types.ts").CliRouter = {
|
|
11
10
|
key: "completion",
|
|
12
11
|
description: "Generate the autocompletion script for shells.",
|
|
13
12
|
commands: [
|
|
@@ -15,9 +14,9 @@ export function cliBuiltinCompletionGroup(program: CliProgram): CliRouter {
|
|
|
15
14
|
key: "bash",
|
|
16
15
|
description: "Print a bash tab-completion script.",
|
|
17
16
|
notes:
|
|
18
|
-
"
|
|
19
|
-
|
|
20
|
-
|
|
17
|
+
"Homebrew installs completions during `brew install` via generate_completions_from_executable.\n\n" +
|
|
18
|
+
"Ensure your shell loads Homebrew completions:\n" +
|
|
19
|
+
" https://docs.brew.sh/Shell-Completion\n\n" +
|
|
21
20
|
"Try this session only:\n\n" +
|
|
22
21
|
` source <(${appName} completion bash)`,
|
|
23
22
|
handler: () => {},
|
|
@@ -26,9 +25,9 @@ export function cliBuiltinCompletionGroup(program: CliProgram): CliRouter {
|
|
|
26
25
|
key: "zsh",
|
|
27
26
|
description: "Print a zsh tab-completion script.",
|
|
28
27
|
notes:
|
|
29
|
-
"
|
|
30
|
-
|
|
31
|
-
"
|
|
28
|
+
"Homebrew installs completions to $(brew --prefix)/share/zsh/site-functions.\n\n" +
|
|
29
|
+
"Ensure brew shellenv + compinit are configured:\n" +
|
|
30
|
+
" https://docs.brew.sh/Shell-Completion\n\n" +
|
|
32
31
|
"Try this session only:\n\n" +
|
|
33
32
|
` eval "$(${appName} completion zsh)"`,
|
|
34
33
|
handler: () => {},
|
|
@@ -37,15 +36,18 @@ export function cliBuiltinCompletionGroup(program: CliProgram): CliRouter {
|
|
|
37
36
|
key: "fish",
|
|
38
37
|
description: "Print a fish tab-completion script.",
|
|
39
38
|
notes:
|
|
40
|
-
"
|
|
41
|
-
|
|
42
|
-
"
|
|
39
|
+
"Homebrew installs completions to $(brew --prefix)/share/fish/vendor_completions.d.\n\n" +
|
|
40
|
+
"See: https://docs.brew.sh/Shell-Completion\n\n" +
|
|
41
|
+
"Try this session only:\n\n" +
|
|
42
|
+
` ${appName} completion fish | source`,
|
|
43
43
|
handler: () => {},
|
|
44
44
|
},
|
|
45
45
|
],
|
|
46
46
|
};
|
|
47
|
-
if (
|
|
48
|
-
router.notes =
|
|
47
|
+
if (resolveCapabilities(program).install) {
|
|
48
|
+
router.notes =
|
|
49
|
+
"Completions are installed by Homebrew during formula install.\n\n" +
|
|
50
|
+
"See: https://docs.brew.sh/Shell-Completion";
|
|
49
51
|
}
|
|
50
52
|
return router;
|
|
51
53
|
}
|
|
@@ -1,16 +1,16 @@
|
|
|
1
1
|
import { describe, expect, test } from "bun:test";
|
|
2
|
-
import { mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
2
|
+
import { mkdirSync, mkdtempSync, rmSync, writeFileSync } from "node:fs";
|
|
3
3
|
import { tmpdir } from "node:os";
|
|
4
|
-
import { join } from "node:path";
|
|
4
|
+
import { dirname, join } from "node:path";
|
|
5
|
+
import { resolveAppConfigPath, writeAppConfigFile } from "../config/file.ts";
|
|
5
6
|
import { Cli, type CliProgram } from "../index.ts";
|
|
6
7
|
|
|
7
|
-
function configFixture(
|
|
8
|
+
function configFixture(): CliProgram {
|
|
8
9
|
return {
|
|
9
10
|
key: "cfg-app",
|
|
10
11
|
version: "1.0.0",
|
|
11
12
|
description: "Config builtin test.",
|
|
12
13
|
appConfig: {
|
|
13
|
-
path: configPath,
|
|
14
14
|
entries: {
|
|
15
15
|
apiToken: { description: "Token.", env: "API_TOKEN", sensitive: true },
|
|
16
16
|
port: { description: "Port.", required: false },
|
|
@@ -23,15 +23,21 @@ function configFixture(configPath: string): CliProgram {
|
|
|
23
23
|
describe("builtins/config", () => {
|
|
24
24
|
test("config get redacts sensitive values", async () => {
|
|
25
25
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
26
|
-
const
|
|
27
|
-
|
|
26
|
+
const prevHome = process.env.HOME;
|
|
27
|
+
process.env.HOME = dir;
|
|
28
28
|
const prev = process.env.API_TOKEN;
|
|
29
29
|
delete process.env.API_TOKEN;
|
|
30
30
|
try {
|
|
31
|
-
const
|
|
31
|
+
const program = configFixture();
|
|
32
|
+
const configPath = resolveAppConfigPath(program);
|
|
33
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
34
|
+
writeFileSync(configPath, `${JSON.stringify({ apiToken: "secret" })}\n`);
|
|
35
|
+
const result = await new Cli(program).invoke(["config", "get", "apiToken"]);
|
|
32
36
|
expect(result.exitCode).toBe(0);
|
|
33
37
|
expect(result.stdout.trim()).toBe("REDACTED");
|
|
34
38
|
} finally {
|
|
39
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
40
|
+
else process.env.HOME = prevHome;
|
|
35
41
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
36
42
|
rmSync(dir, { recursive: true, force: true });
|
|
37
43
|
}
|
|
@@ -39,20 +45,21 @@ describe("builtins/config", () => {
|
|
|
39
45
|
|
|
40
46
|
test("config get --json redacts sensitive as { set: true }", async () => {
|
|
41
47
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
42
|
-
const
|
|
43
|
-
|
|
48
|
+
const prevHome = process.env.HOME;
|
|
49
|
+
process.env.HOME = dir;
|
|
44
50
|
const prev = process.env.API_TOKEN;
|
|
45
51
|
delete process.env.API_TOKEN;
|
|
46
52
|
try {
|
|
47
|
-
const
|
|
48
|
-
|
|
49
|
-
|
|
50
|
-
|
|
51
|
-
|
|
52
|
-
]);
|
|
53
|
+
const program = configFixture();
|
|
54
|
+
const configPath = resolveAppConfigPath(program);
|
|
55
|
+
mkdirSync(dirname(configPath), { recursive: true });
|
|
56
|
+
writeFileSync(configPath, `${JSON.stringify({ apiToken: "secret" })}\n`);
|
|
57
|
+
const result = await new Cli(program).invoke(["config", "get", "apiToken", "--json"]);
|
|
53
58
|
expect(result.exitCode).toBe(0);
|
|
54
|
-
expect(JSON.parse(result.stdout
|
|
59
|
+
expect(JSON.parse(result.stdout)).toEqual({ set: true });
|
|
55
60
|
} finally {
|
|
61
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
62
|
+
else process.env.HOME = prevHome;
|
|
56
63
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
57
64
|
rmSync(dir, { recursive: true, force: true });
|
|
58
65
|
}
|
|
@@ -60,21 +67,20 @@ describe("builtins/config", () => {
|
|
|
60
67
|
|
|
61
68
|
test("config set writes and resolves without required exit", async () => {
|
|
62
69
|
const dir = mkdtempSync(join(tmpdir(), "cfg-builtin-"));
|
|
63
|
-
const
|
|
64
|
-
|
|
70
|
+
const prevHome = process.env.HOME;
|
|
71
|
+
process.env.HOME = dir;
|
|
65
72
|
const prev = process.env.API_TOKEN;
|
|
66
73
|
delete process.env.API_TOKEN;
|
|
67
74
|
try {
|
|
68
|
-
const
|
|
69
|
-
|
|
70
|
-
|
|
71
|
-
"port",
|
|
72
|
-
"9090",
|
|
73
|
-
]);
|
|
75
|
+
const program = configFixture();
|
|
76
|
+
writeAppConfigFile(program, { apiToken: "seed" });
|
|
77
|
+
const result = await new Cli(program).invoke(["config", "set", "port", "9090"]);
|
|
74
78
|
expect(result.exitCode).toBe(0);
|
|
75
|
-
const get = await new Cli(
|
|
79
|
+
const get = await new Cli(program).invoke(["config", "get", "port"]);
|
|
76
80
|
expect(get.stdout.trim()).toBe("9090");
|
|
77
81
|
} finally {
|
|
82
|
+
if (prevHome === undefined) delete process.env.HOME;
|
|
83
|
+
else process.env.HOME = prevHome;
|
|
78
84
|
if (prev !== undefined) process.env.API_TOKEN = prev;
|
|
79
85
|
rmSync(dir, { recursive: true, force: true });
|
|
80
86
|
}
|
package/src/builtins/config.ts
CHANGED
|
@@ -5,7 +5,7 @@ Built-in `config get` / `config set` commands.
|
|
|
5
5
|
import { bootstrapAppConfig } from "../config/bootstrap.ts";
|
|
6
6
|
import { configEntrySensitive, defaultConfigEntryTitle } from "../config/entry.ts";
|
|
7
7
|
import { writeAppConfigFile } from "../config/file.ts";
|
|
8
|
-
import { exportConfigToEnv, resolveAppConfig } from "../config/resolve.ts";
|
|
8
|
+
import { captureMappedHostEnv, exportConfigToEnv, resolveAppConfig } from "../config/resolve.ts";
|
|
9
9
|
import { configPropertySchema, effectiveJsonSchema } from "../config/schema.ts";
|
|
10
10
|
import { parseConfigSetValue } from "../config/validate.ts";
|
|
11
11
|
import type { CliLeaf, CliOption, CliProgram, CliRouter } from "../types.ts";
|
|
@@ -128,11 +128,12 @@ function configSetRun(program: CliProgram, key: string, rawValue: string, useJso
|
|
|
128
128
|
process.exit(1);
|
|
129
129
|
}
|
|
130
130
|
|
|
131
|
+
const hostEnv = captureMappedHostEnv(program);
|
|
131
132
|
const { fileData } = bootstrapAppConfig(program, { validateFile: true });
|
|
132
133
|
const next = { ...fileData, [key]: parsed };
|
|
133
134
|
writeAppConfigFile(program, next);
|
|
134
|
-
const resolved = resolveAppConfig(program, next);
|
|
135
|
-
exportConfigToEnv(program, resolved);
|
|
135
|
+
const resolved = resolveAppConfig(program, next, hostEnv);
|
|
136
|
+
exportConfigToEnv(program, resolved, hostEnv);
|
|
136
137
|
}
|
|
137
138
|
|
|
138
139
|
function configGetLeaf(program: CliProgram): CliLeaf {
|
package/src/builtins/dispatch.ts
CHANGED
|
@@ -1,7 +1,7 @@
|
|
|
1
1
|
import { capabilityDeniedMessage, resolveCapabilities } from "../capabilities.ts";
|
|
2
2
|
import { Cli } from "../cli.ts";
|
|
3
3
|
import { cliBuiltinDocsGroupIfEnabled } from "../docs/builtin.ts";
|
|
4
|
-
import { cliInstall } from "../install/index.ts";
|
|
4
|
+
import { cliInstall, cliUninstall } from "../install/index.ts";
|
|
5
5
|
import { runMcpBundle } from "../mcp/bundle.ts";
|
|
6
6
|
import type { ParseResult } from "../parse.ts";
|
|
7
7
|
import { ParseKind } from "../parse.ts";
|
|
@@ -15,6 +15,7 @@ import { cliBuiltinConfigGroupIfEnabled } from "./config.ts";
|
|
|
15
15
|
import { cliBuiltinInstallCommand } from "./install.ts";
|
|
16
16
|
import { cliBuiltinMcpCommand } from "./mcp.ts";
|
|
17
17
|
import { cliPresentationRoot } from "./presentation.ts";
|
|
18
|
+
import { cliBuiltinUninstallCommand } from "./uninstall.ts";
|
|
18
19
|
import { cliBuiltinVersionCommand } from "./version.ts";
|
|
19
20
|
|
|
20
21
|
export interface DispatchBuiltinOpts {
|
|
@@ -103,6 +104,18 @@ export async function dispatchBuiltin(
|
|
|
103
104
|
}
|
|
104
105
|
await cliInstall(program, pr.opts);
|
|
105
106
|
}
|
|
107
|
+
|
|
108
|
+
if (pr.path[0] === "uninstall") {
|
|
109
|
+
if (!caps.install) {
|
|
110
|
+
process.stderr.write(capabilityDeniedMessage("install"));
|
|
111
|
+
process.exit(1);
|
|
112
|
+
}
|
|
113
|
+
if (pr.path.length !== 1) {
|
|
114
|
+
process.stderr.write(`Unknown subcommand: uninstall ${pr.path.slice(1).join(" ")}\n`);
|
|
115
|
+
process.exit(1);
|
|
116
|
+
}
|
|
117
|
+
await cliUninstall(program, pr.opts);
|
|
118
|
+
}
|
|
106
119
|
}
|
|
107
120
|
|
|
108
121
|
/** Built-in intercept roots for leaf programs. */
|
|
@@ -139,6 +152,17 @@ export function builtinInterceptRoot(
|
|
|
139
152
|
};
|
|
140
153
|
}
|
|
141
154
|
|
|
155
|
+
if (first === "uninstall" && caps.install) {
|
|
156
|
+
return {
|
|
157
|
+
parseRoot: {
|
|
158
|
+
key: program.key,
|
|
159
|
+
description: program.description,
|
|
160
|
+
commands: [cliBuiltinUninstallCommand(program)],
|
|
161
|
+
},
|
|
162
|
+
isLeafCompletionIntercept: false,
|
|
163
|
+
};
|
|
164
|
+
}
|
|
165
|
+
|
|
142
166
|
if (first === "mcp" && caps.mcp) {
|
|
143
167
|
return {
|
|
144
168
|
parseRoot: {
|