dryai 2.1.0 → 3.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.
Files changed (47) hide show
  1. package/README.md +140 -122
  2. package/dest/cli.d.ts +68 -0
  3. package/dest/cli.js +147 -0
  4. package/dest/commands/skills/add.d.ts +4 -3
  5. package/dest/commands/skills/add.js +44 -12
  6. package/dest/commands/skills/index.d.ts +3 -3
  7. package/dest/commands/skills/index.js +19 -12
  8. package/dest/commands/skills/list.d.ts +2 -2
  9. package/dest/commands/skills/list.js +4 -3
  10. package/dest/commands/skills/rehash-all.d.ts +2 -2
  11. package/dest/commands/skills/rehash-all.js +6 -5
  12. package/dest/commands/skills/rehash.d.ts +2 -2
  13. package/dest/commands/skills/rehash.js +3 -2
  14. package/dest/commands/skills/remove.d.ts +2 -2
  15. package/dest/commands/skills/remove.js +3 -2
  16. package/dest/commands/skills/update-all.d.ts +2 -2
  17. package/dest/commands/skills/update-all.js +8 -7
  18. package/dest/commands/skills/update.d.ts +2 -2
  19. package/dest/commands/skills/update.js +6 -5
  20. package/dest/commands/sync.d.ts +6 -0
  21. package/dest/commands/sync.js +8 -0
  22. package/dest/lib/agent-definition-helpers.d.ts +74 -0
  23. package/dest/lib/agent-definition-helpers.js +68 -0
  24. package/dest/lib/agent-definitions.d.ts +333 -0
  25. package/dest/lib/agent-definitions.js +301 -0
  26. package/dest/lib/agent-types.d.ts +46 -0
  27. package/dest/lib/agent-types.js +1 -0
  28. package/dest/lib/agents.d.ts +81 -0
  29. package/dest/lib/agents.js +301 -0
  30. package/dest/lib/command-options.d.ts +1 -1
  31. package/dest/lib/command-options.js +1 -1
  32. package/dest/lib/context.d.ts +8 -25
  33. package/dest/lib/context.js +8 -26
  34. package/dest/lib/frontmatter.d.ts +27 -70
  35. package/dest/lib/frontmatter.js +23 -42
  36. package/dest/lib/object-helpers.d.ts +5 -0
  37. package/dest/lib/object-helpers.js +6 -0
  38. package/dest/lib/skills.d.ts +35 -93
  39. package/dest/lib/skills.js +66 -8
  40. package/dest/lib/sync.d.ts +7 -0
  41. package/dest/lib/sync.js +503 -0
  42. package/dest/main.js +6 -86
  43. package/package.json +3 -3
  44. package/dest/commands/install.d.ts +0 -3
  45. package/dest/commands/install.js +0 -4
  46. package/dest/lib/install.d.ts +0 -8
  47. package/dest/lib/install.js +0 -380
package/README.md CHANGED
@@ -1,22 +1,24 @@
1
1
  # Share AI config CLI
2
2
 
3
- Installs command, rule, and skill sources from `~/.config/dryai` by default into Copilot and Cursor targets.
3
+ Syncs command, rule, and skill sources from `~/.config/dryai` by default into supported agent targets.
4
4
 
5
- Pass `--config-root <path>` to read configs from a different root such as `./config`.
5
+ Global CLI options:
6
6
 
7
- Pass `--output-root <path>` to write generated output somewhere other than your home directory.
7
+ - `--config-root <path>` reads configs from a different root such as `./config`.
8
+ - `--output-root <path>` writes generated output somewhere other than your home directory.
9
+ - `--test` is a shortcut for `--output-root ./output-test`, and if both are provided, `--output-root` wins.
8
10
 
9
- `--test` is a shortcut for `--output-root ./output-test`, and if both are provided, `--output-root` wins.
11
+ These are root-level options for the CLI. They modify command behavior for any given command.
10
12
 
11
- ## Input
13
+ ## Config Layout
12
14
 
13
- Input config files live under the selected input root:
15
+ Input config files live under the selected config root:
14
16
 
15
17
  - `commands`
16
18
  - `rules`
17
19
  - `skills`
18
20
 
19
- Live output is written to:
21
+ The current built-in agent config writes live output to these default roots:
20
22
 
21
23
  - `~/.copilot/prompts`
22
24
  - `~/.copilot/instructions`
@@ -24,9 +26,7 @@ Live output is written to:
24
26
  - `~/.cursor/rules`
25
27
  - `~/.cursor/skills`
26
28
 
27
- ## Example Configs
28
-
29
- One input root can contain all three source types:
29
+ One config root can contain all three source types:
30
30
 
31
31
  ```text
32
32
  ~/.config/dryai/
@@ -39,25 +39,128 @@ One input root can contain all three source types:
39
39
  └── SKILL.md
40
40
  ```
41
41
 
42
+ See VS Code editor setup note below.
43
+
44
+ ## Commands
45
+
46
+ ### `sync`
47
+
48
+ - Purpose: Sync commands, rules, and skills from the selected config root into the configured agent target directories.
49
+ - Input roots: Reads from `commands`, `rules`, and `skills` under the selected config root.
50
+ - Output roots: Writes to the live output paths listed in Config Layout by default.
51
+ - Pruning: Removes stale dryai-managed outputs that were written by earlier sync runs but are no longer present in the selected config root.
52
+ - Safety: Only prunes outputs tracked in `sync-manifest.json`; unrelated user files in target roots are left alone.
53
+
54
+ ### `skills add`
55
+
56
+ - Purpose: Import one or more managed skills from a remote repository.
57
+ - Repository argument: `<repo>` may be a full git remote URL or a GitHub `owner/repo` shorthand such as `anthropics/skills`.
58
+ - Storage: Imported skills are copied into `config/skills/<name>/` and tracked in `skills.lock.json`.
59
+ - Config root: Local skill directories and `skills.lock.json` are read from and written to the selected config root.
60
+ - Required: `--skill <name>` is required at least once.
61
+ - Default resolution: Each requested skill resolves from `<repo root>/skills/<name>`.
62
+ - `--path <repoPath>`: Resolves each requested skill from a different base directory.
63
+ - `--path .`: Resolves each requested skill from the repository root itself.
64
+ - `--as <name>`: Stores the imported skill under a different local managed name when importing exactly one skill.
65
+ - `--ref <gitRef>`: Fetches a specific branch, tag, or commit instead of the remote default branch.
66
+ - `--pin`: Stores the resolved commit instead of tracking a moving ref.
67
+ - Examples:
68
+
69
+ ```sh
70
+ # Resolves from <repo root>/skills/skill-creator
71
+ dryai skills add anthropics/skills --skill skill-creator
72
+
73
+ # Resolves from <repo root>/review-helper
74
+ dryai skills add anthropics/skills --path . --skill review-helper
75
+
76
+ # Resolves from <repo root>/tools/review-helper
77
+ dryai skills add anthropics/skills --path tools --skill review-helper
78
+
79
+ # Resolves from <repo root>/skills/pr-review and <repo root>/skills/commit
80
+ dryai skills add vercel-labs/agent-skills --skill pr-review commit
81
+
82
+ # Resolves from <repo root>/skills/pr-review and <repo root>/skills/commit
83
+ dryai skills add https://github.com/vercel-labs/agent-skills.git --skill pr-review commit
84
+ ```
85
+
86
+ By default, imports track the requested ref. With no `--ref`, that means the remote default branch `HEAD` is tracked.
87
+
88
+ ### `skills update`
89
+
90
+ - Purpose: Re-fetch one managed skill from its tracked source and replace the local copied directory.
91
+ - `--force`: Overwrites local edits instead of skipping the update.
92
+
93
+ ### `skills update-all`
94
+
95
+ - Purpose: Re-fetch all managed skills from their tracked sources and replace the local copied directories.
96
+ - `--force`: Overwrites local edits instead of skipping the update.
97
+
98
+ ### `skills rehash`
99
+
100
+ - Purpose: Refresh the stored file hashes for one managed skill using its current local contents.
101
+
102
+ ### `skills rehash-all`
103
+
104
+ - Purpose: Refresh the stored file hashes for every managed skill using their current local contents.
105
+ - Behavior: Skips managed entries whose local directory is missing.
106
+
107
+ ### `skills remove`
108
+
109
+ - Purpose: Delete a managed skill's local copied directory and remove its lockfile entry.
110
+
111
+ ### `skills list`
112
+
113
+ - Purpose: Report local skill directories, annotate managed entries from the lockfile, and flag managed entries whose local directory is missing.
114
+
115
+ ### `skills` lockfile
116
+
117
+ The lockfile records:
118
+
119
+ - the local skill name
120
+ - the source repository
121
+ - the source path within that repository
122
+ - the requested git ref, when one was provided
123
+ - the resolved commit that was imported
124
+ - the last installed content hash for each file in the managed skill directory
125
+
126
+ Before replacing a managed skill, `dryai` compares the current local files against the hashes stored in `skills.lock.json`. If any file was added, removed, or edited locally, the update is skipped and a warning is printed so you do not lose your customizations by accident.
127
+
128
+ For skills imported before hash tracking existed, use these commands to store hashes from the current local directory without fetching from the remote source:
129
+
130
+ ```sh
131
+ dryai skills rehash review-helper
132
+ dryai skills rehash-all
133
+ ```
134
+
135
+ Use these commands to intentionally overwrite local edits:
136
+
137
+ ```sh
138
+ dryai skills update review-helper --force
139
+ dryai skills update-all --force
140
+ ```
141
+
142
+ ## Example Configs
143
+
42
144
  ### Example Rule
43
145
 
44
146
  Rules are markdown files under `rules/`. `dryai` recognizes these rule frontmatter fields:
45
147
 
46
148
  - `description`
47
- - `copilot.applyTo`
48
- - `cursor.alwaysApply`
49
- - `cursor.globs`
149
+ - `agents.copilot.applyTo`
150
+ - `agents.cursor.alwaysApply`
151
+ - `agents.cursor.globs`
50
152
 
51
- `cursor.globs` should be provided as one comma-separated glob string.
153
+ `agents.cursor.globs` should be provided as one comma-separated glob string.
52
154
 
53
155
  ```md
54
156
  ---
55
157
  description: Reply with "Yes, Captain!" before answering when the user says "Make it so" or "Engage".
56
- copilot:
57
- applyTo: '**/*.tsx, **/*.ts, src/**/*.ts, src/**/*.tsx, src/**/*.js, src/**/*.jsx'
58
- cursor:
59
- alwaysApply: false
60
- globs: '**/*.tsx, **/*.ts, src/**/*.ts, src/**/*.tsx, src/**/*.js, src/**/*.jsx'
158
+ agents:
159
+ copilot:
160
+ applyTo: '**/*.tsx, **/*.ts, src/**/*.ts, src/**/*.tsx, src/**/*.js, src/**/*.jsx'
161
+ cursor:
162
+ alwaysApply: false
163
+ globs: '**/*.tsx, **/*.ts, src/**/*.ts, src/**/*.tsx, src/**/*.js, src/**/*.jsx'
61
164
  ---
62
165
 
63
166
  # Say Yes Captain
@@ -71,14 +174,15 @@ Commands are markdown files under `commands/`. `dryai` recognizes these command
71
174
 
72
175
  - `name`
73
176
  - `description`
74
- - `cursor.disable-model-invocation`
177
+ - `agents.cursor.disable-model-invocation`
75
178
 
76
179
  ```md
77
180
  ---
78
181
  name: gen-commit-msg
79
182
  description: Generate a conventional commit message from the current staged git diff.
80
- cursor:
81
- disable-model-invocation: true
183
+ agents:
184
+ cursor:
185
+ disable-model-invocation: true
82
186
  ---
83
187
 
84
188
  # Generate Commit Message
@@ -88,9 +192,9 @@ Read the staged diff and produce a conventional commit message with a concise su
88
192
 
89
193
  ### Example Skill
90
194
 
91
- Skills live in directories under `skills/`. The directory is copied as-is into the Copilot and Cursor skills targets.
195
+ Skills live in directories under `skills/`. The directory is copied as-is into the configured agent skill targets.
92
196
 
93
- Unlike commands and rules, `dryai` does not define or validate a fixed skill frontmatter schema. Skill files are passed through unchanged, so the allowed frontmatter fields depend on the skill format expected by the target editor or agent.
197
+ Unlike commands and rules, `dryai` does not define or validate a fixed skill frontmatter schema. Skill files are passed through unchanged, so the allowed frontmatter fields depend on the skill format expected by the target agent.
94
198
 
95
199
  ```text
96
200
  skills/
@@ -112,77 +216,9 @@ Focus on findings first.
112
216
  - Keep the overview brief unless the user asks for a deeper walkthrough.
113
217
  ```
114
218
 
115
- ## Commands
116
-
117
- ```sh
118
- dryai install
119
- dryai skills list List local skills
120
- dryai skills add [options] <repo> Add managed skills from a remote repository
121
- dryai skills remove <name> Remove a managed skill
122
- dryai skills rehash <name> Refresh stored file hashes for one managed skill
123
- dryai skills rehash-all Refresh stored file hashes for all managed skills
124
- dryai skills update [options] <name> Update a managed skill from its tracked source
125
- dryai skills update-all [options] Update all managed skills from their tracked sources
126
- ```
127
-
128
- `<repo>` may be a full git remote URL or a GitHub `owner/repo` shorthand such as `anthropics/skills`.
129
-
130
- ## Managed Skills
131
-
132
- Imported skills are copied into `config/skills/<name>/` and tracked in `skills.lock.json`.
133
-
134
- When you run `skills` commands, local skill directories and `skills.lock.json` are read from and written to the selected config root.
135
-
136
- `skills add` requires at least one `--skill <name>` value. Each requested skill is always resolved from `<repo root>/skills/<name>`.
137
-
138
- Use `--as <name>` to choose a different local managed skill name when importing exactly one skill.
139
-
140
- Examples:
141
-
142
- ```sh
143
- dryai skills add anthropics/skills --skill skill-creator
144
- dryai skills add vercel-labs/agent-skills --skill pr-review commit
145
- dryai skills add https://github.com/vercel-labs/agent-skills.git --skill pr-review commit
146
- ```
147
-
148
- By default, imports track the requested ref. With no `--ref`, that means the remote default branch `HEAD` is tracked. Use `--pin` to store the currently resolved commit instead, so later `skills update` operations stay pinned to that commit.
149
-
150
- The lockfile records:
151
-
152
- - the local skill name
153
- - the source repository
154
- - the source path within that repository
155
- - the requested git ref, when one was provided
156
- - the resolved commit that was imported
157
- - the last installed content hash for each file in the managed skill directory
158
-
159
- `skills update` and `skills update-all` re-fetch the tracked repository snapshot and replace the local copied skill directory.
160
-
161
- Before replacing a managed skill, `dryai` compares the current local files against the hashes stored in `skills.lock.json`. If any file was added, removed, or edited locally, the update is skipped and a warning is printed so you do not lose your customizations by accident.
162
-
163
- For skills imported before hash tracking existed, use `rehash` to store hashes from the current local directory without fetching from the remote source:
164
-
165
- ```sh
166
- dryai skills rehash review-helper
167
- dryai skills rehash-all
168
- ```
169
-
170
- Use `--force` to intentionally overwrite local edits:
171
-
172
- ```sh
173
- dryai skills update review-helper --force
174
- dryai skills update-all --force
175
- ```
176
-
177
- `skills remove` deletes the local copied skill directory and removes its lockfile entry.
178
-
179
- `skills list` reports local skill directories, annotating managed entries from the lockfile and flagging managed entries whose local directory is missing.
180
-
181
- `skills rehash` updates the stored file hashes for one managed skill using its current local contents. `skills rehash-all` does the same for every managed skill and skips any managed entry whose local directory is missing.
182
-
183
219
  ## Development
184
220
 
185
- For development, use `pnpm dev` to rebuild into `dest/` on change and `pnpm dev:dryai --test install` to run the built CLI.
221
+ For development, use `pnpm dev` to rebuild into `dest/` on change and `pnpm dev:dryai <...>` to run the built CLI.
186
222
 
187
223
  Run `pnpm run setup:editor` after installing dependencies if you want the Effect language service workspace patch applied locally.
188
224
 
@@ -193,39 +229,21 @@ pnpm run dev
193
229
  pnpm run test
194
230
  pnpm run test:watch
195
231
 
196
- pnpm dev:dryai install
197
- pnpm dev:dryai --test install
198
- pnpm dev:dryai --output-root ./tmp/install-root install
199
- pnpm dev:dryai --config-root ./config install
232
+ pnpm dev:dryai <...>
200
233
  ```
201
234
 
202
- ## CI and Release
203
-
204
- - On pull request open or update
205
- - Run CI validation with build, test, and `npm pack --dry-run`.
206
- - On changes landing on `main`
207
- - Run the same CI validation with build, test, and `npm pack --dry-run`.
208
-
209
- - On `v*` tag pushed to `main`, the release workflow will:
210
- - Verify the tag matches the checked-in `package.json` version.
211
- - Verify the tagged commit is on `main`.
212
- - Build and test the CLI.
213
- - Create a tarball with `npm pack`.
214
- - Create or update the matching GitHub Release.
215
- - Upload the tarball as a release asset.
216
- - After the release workflow succeeds, the publish workflow will:
217
- - Download the tarball artifact produced by the release workflow.
218
- - Publish that exact tarball to npm using npm trusted publishing.
235
+ ---
219
236
 
220
- Example release flow:
237
+ ## VS Code Setup
221
238
 
222
- ```sh
223
- git tag v0.1.0
224
- git push origin v0.1.0
225
- ```
239
+ One current editor-specific note: VS Code does not automatically discover prompt files from the Copilot prompt target at `~/.copilot/prompts`.
226
240
 
227
- Install from the release tarball with:
241
+ Add this to your VS Code user settings if you want prompt files installed by `dryai` into that target to be picked up:
228
242
 
229
- ```sh
230
- npm install -g https://github.com/willmruzek/share-ai-config/releases/download/v0.1.0/share-ai-config-0.1.0.tgz
243
+ ```json
244
+ {
245
+ "chat.promptFilesLocations": {
246
+ "~/.copilot/prompts": true
247
+ }
248
+ }
231
249
  ```
package/dest/cli.d.ts ADDED
@@ -0,0 +1,68 @@
1
+ import { Command } from 'commander';
2
+ import { z } from 'zod';
3
+ import { type AgentsContext } from './lib/context.js';
4
+ declare const rootOptionsSchema: z.ZodObject<{
5
+ test: z.ZodDefault<z.ZodOptional<z.ZodBoolean>>;
6
+ configRoot: z.ZodOptional<z.ZodString>;
7
+ outputRoot: z.ZodOptional<z.ZodString>;
8
+ }, z.core.$strip>;
9
+ export type RootOptions = z.output<typeof rootOptionsSchema>;
10
+ /**
11
+ * Raw stdout/stderr write functions, without newline conventions. These are
12
+ * the CLI-layer primitive used by Commander for help and version output; they
13
+ * also back the higher-level {@link CLIRuntime}.
14
+ */
15
+ export type StdioWriters = {
16
+ writeOut: (output: string) => void;
17
+ writeErr: (output: string) => void;
18
+ };
19
+ /**
20
+ * The line-oriented output interface available to every command action.
21
+ *
22
+ * Both methods append a trailing newline. The `log*` prefix is used to make
23
+ * call sites trivially greppable ("where do we emit CLI output?") without
24
+ * colliding with `console.log` / `console.warn` or arbitrary variables.
25
+ *
26
+ * Commands should not reach for the underlying {@link StdioWriters.writeOut}
27
+ * / {@link StdioWriters.writeErr} stream writes; that raw byte-level stream
28
+ * access is confined to the CLI layer (Commander help/version output).
29
+ */
30
+ export type CLIRuntime = {
31
+ /** Writes an informational message to stdout, with an appended newline. */
32
+ logInfo: (message: string) => void;
33
+ /** Writes a warning message to stderr, with an appended newline. */
34
+ logWarn: (message: string) => void;
35
+ };
36
+ /**
37
+ * The shared environment passed into every command action: the
38
+ * resolved domain context plus the runtime used for CLI output.
39
+ */
40
+ export type CommandEnv = {
41
+ context: AgentsContext;
42
+ runtime: CLIRuntime;
43
+ };
44
+ export type CLIOptions = {
45
+ executableName?: string;
46
+ version: string;
47
+ stdioWriters?: StdioWriters;
48
+ };
49
+ /**
50
+ * Builds an AgentsContext from the parsed root options, expanding ~ in paths and applying --test path defaults.
51
+ */
52
+ export declare function resolveActiveContext(rootOptions: RootOptions): AgentsContext;
53
+ /**
54
+ * Creates the production stdio writers backed by the real process streams.
55
+ */
56
+ export declare function createProductionStdioWriters(): StdioWriters;
57
+ /**
58
+ * Builds and returns the Commander program with all subcommands and global flags registered.
59
+ */
60
+ export declare function createCLI(options: CLIOptions): Command;
61
+ /**
62
+ * Parses argv and runs the matching command, returning the Commander program after completion.
63
+ */
64
+ export declare function runCLI(input: {
65
+ argv: string[];
66
+ } & CLIOptions): Promise<Command>;
67
+ export {};
68
+ //# sourceMappingURL=cli.d.ts.map
package/dest/cli.js ADDED
@@ -0,0 +1,147 @@
1
+ import { Command } from 'commander';
2
+ import { z } from 'zod';
3
+ import { addSkillsCommand } from './commands/skills/index.js';
4
+ import { runSyncCommand } from './commands/sync.js';
5
+ import { describeSupportedAgents } from './lib/agents.js';
6
+ import { nonEmptyOptionStringSchema, parseOptionValue, parseOptionsObject, } from './lib/command-options.js';
7
+ import { createAgentsContext, resolveRequestedConfigRoot, resolveRequestedOutputRoot, } from './lib/context.js';
8
+ const rootOptionsSchema = z.object({
9
+ test: z.boolean().optional().default(false),
10
+ configRoot: nonEmptyOptionStringSchema.optional(),
11
+ outputRoot: nonEmptyOptionStringSchema.optional(),
12
+ });
13
+ /**
14
+ * Parses the top-level CLI options into a validated shape.
15
+ */
16
+ function getRootOptions(program) {
17
+ return parseOptionsObject({
18
+ schema: rootOptionsSchema,
19
+ options: program.opts(),
20
+ optionsLabel: 'root options',
21
+ });
22
+ }
23
+ /**
24
+ * Returns true if --test or --output-root was passed.
25
+ */
26
+ function requestedOutputRootWasUsed(rootOptions) {
27
+ return rootOptions.test || rootOptions.outputRoot !== undefined;
28
+ }
29
+ /**
30
+ * Builds an AgentsContext from the parsed root options, expanding ~ in paths and applying --test path defaults.
31
+ */
32
+ export function resolveActiveContext(rootOptions) {
33
+ const requestedConfigRoot = resolveRequestedConfigRoot({
34
+ ...(rootOptions.configRoot ? { configRoot: rootOptions.configRoot } : {}),
35
+ });
36
+ const requestedOutputRoot = resolveRequestedOutputRoot({
37
+ test: rootOptions.test,
38
+ ...(rootOptions.outputRoot ? { outputRoot: rootOptions.outputRoot } : {}),
39
+ });
40
+ return createAgentsContext({
41
+ ...(requestedConfigRoot ? { inputRoot: requestedConfigRoot } : {}),
42
+ ...(requestedOutputRoot ? { outputRoot: requestedOutputRoot } : {}),
43
+ });
44
+ }
45
+ /**
46
+ * Derives a {@link CLIRuntime} from a pair of raw stdio writers by wrapping
47
+ * each writer with the line-oriented newline convention.
48
+ */
49
+ function wrapStdioWriters(stdioWriters) {
50
+ return {
51
+ logInfo(message) {
52
+ stdioWriters.writeOut(`${message}\n`);
53
+ },
54
+ logWarn(message) {
55
+ stdioWriters.writeErr(`${message}\n`);
56
+ },
57
+ };
58
+ }
59
+ /**
60
+ * Creates the production stdio writers backed by the real process streams.
61
+ */
62
+ export function createProductionStdioWriters() {
63
+ return {
64
+ writeOut(output) {
65
+ process.stdout.write(output);
66
+ },
67
+ writeErr(output) {
68
+ process.stderr.write(output);
69
+ },
70
+ };
71
+ }
72
+ /**
73
+ * Merges the provided CLIOptions with production defaults, returning a fully resolved options object.
74
+ */
75
+ function resolveCLIOptions(options) {
76
+ return {
77
+ executableName: options.executableName ?? 'dryai',
78
+ version: options.version,
79
+ stdioWriters: options.stdioWriters ?? createProductionStdioWriters(),
80
+ };
81
+ }
82
+ /**
83
+ * Builds and returns the Commander program with all subcommands and global flags registered.
84
+ */
85
+ export function createCLI(options) {
86
+ const resolvedOptions = resolveCLIOptions(options);
87
+ const program = new Command();
88
+ const executableName = resolvedOptions.executableName;
89
+ const stdioWriters = resolvedOptions.stdioWriters;
90
+ const runtime = wrapStdioWriters(stdioWriters);
91
+ const resolveEnv = () => ({
92
+ context: resolveActiveContext(getRootOptions(program)),
93
+ runtime,
94
+ });
95
+ program.configureOutput({
96
+ writeOut: (output) => {
97
+ stdioWriters.writeOut(output);
98
+ },
99
+ writeErr: (output) => {
100
+ stdioWriters.writeErr(output);
101
+ },
102
+ });
103
+ program
104
+ .name(executableName)
105
+ .usage('[options] <command> [args]')
106
+ .helpOption('-h, --help', 'Display this message')
107
+ .version(resolvedOptions.version, '-v, --version', 'Display the current version')
108
+ .option('--test', 'Shortcut for writing generated output into ./output-test unless --output-root is also provided')
109
+ .option('--config-root <path>', 'Read configs from a different root instead of ~/.config/dryai', parseOptionValue({
110
+ schema: nonEmptyOptionStringSchema,
111
+ optionLabel: '--config-root',
112
+ }))
113
+ .option('--output-root <path>', 'Write generated output under a different root instead of the default home directory', parseOptionValue({
114
+ schema: nonEmptyOptionStringSchema,
115
+ optionLabel: '--output-root',
116
+ }))
117
+ .helpCommand(false)
118
+ .action(() => {
119
+ program.outputHelp();
120
+ });
121
+ program
122
+ .command('sync')
123
+ .description(`Sync generated output into ${describeSupportedAgents()} targets`)
124
+ .action(async () => {
125
+ const rootOptions = getRootOptions(program);
126
+ const env = resolveEnv();
127
+ await runSyncCommand(env);
128
+ if (requestedOutputRootWasUsed(rootOptions)) {
129
+ runtime.logInfo(`Generated output written to ${env.context.outputRoot}`);
130
+ }
131
+ });
132
+ addSkillsCommand({
133
+ program,
134
+ commandName: `${executableName} skills`,
135
+ resolveEnv,
136
+ });
137
+ return program;
138
+ }
139
+ /**
140
+ * Parses argv and runs the matching command, returning the Commander program after completion.
141
+ */
142
+ export async function runCLI(input) {
143
+ const { argv, ...options } = input;
144
+ const program = createCLI(options);
145
+ await program.parseAsync(argv, { from: 'user' });
146
+ return program;
147
+ }
@@ -1,9 +1,10 @@
1
- import type { AgentsContext } from '../../lib/context.js';
1
+ import type { CommandEnv } from '../../cli.js';
2
2
  /**
3
- * Imports one or more managed skills from a remote repository `skills/` directory into the local skills directory.
3
+ * Imports one or more managed skills from a remote repository into the local skills directory.
4
4
  */
5
- export declare function runSkillsAddCommand(context: AgentsContext, input: {
5
+ export declare function runSkillsAddCommand(env: CommandEnv, input: {
6
6
  repo: string;
7
+ repoPath: string | undefined;
7
8
  skillNames: string[];
8
9
  asName: string | undefined;
9
10
  pin: boolean;
@@ -1,5 +1,5 @@
1
1
  import fs from 'fs-extra';
2
- import { cloneRemoteRepo, computeDirectoryHashes, createImportedSkillRecord, ensureSkillsLockfile, ensureSkillsRoot, findManagedSkill, formatManagedSkillSummary, getManagedSkillDirectory, loadSkillsLockfile, normalizeRemoteRepo, replaceManagedSkillDirectory, resolveManagedSkillImportPath, resolveSkillSourceDir, saveSkillsLockfile, timestampNow, upsertManagedSkill, } from '../../lib/skills.js';
2
+ import { cleanupRemoteRepoCheckout, cloneRemoteRepo, computeDirectoryHashes, createImportedSkillRecord, deriveSkillName, ensureSkillsLockfile, ensureSkillsRoot, findManagedSkill, formatManagedSkillSummary, getManagedSkillDirectory, loadSkillsLockfile, normalizeImportedSkillPath, normalizeRemoteRepo, replaceManagedSkillDirectory, resolveManagedSkillImportPath, resolveManagedSkillImportPathFromBase, resolveSkillSourceDirByPath, saveSkillsLockfile, timestampNow, upsertManagedSkill, } from '../../lib/skills.js';
3
3
  /**
4
4
  * Normalizes and de-duplicates requested skill names while preserving their input order.
5
5
  */
@@ -20,10 +20,37 @@ function normalizeRequestedSkillNames(skillNames) {
20
20
  return uniqueSkillNames;
21
21
  }
22
22
  /**
23
- * Imports one or more managed skills from a remote repository `skills/` directory into the local skills directory.
23
+ * Returns the resolved repository-relative import path for a single requested skill.
24
+ *
25
+ * @example
26
+ * // No base path — defaults to the repository `skills/` directory
27
+ * resolveRequestedImportPath({ basePath: undefined, requestedSkillName: 'my-skill' });
28
+ * // → 'skills/my-skill'
29
+ *
30
+ * @example
31
+ * // With a base path — skill is resolved relative to that directory
32
+ * resolveRequestedImportPath({ basePath: 'tools', requestedSkillName: 'my-skill' });
33
+ * // → 'tools/my-skill'
34
+ *
35
+ * @example
36
+ * // Base path of '.' — skill is resolved from the repository root
37
+ * resolveRequestedImportPath({ basePath: '.', requestedSkillName: 'my-skill' });
38
+ * // → 'my-skill'
24
39
  */
25
- export async function runSkillsAddCommand(context, input) {
40
+ function resolveRequestedImportPath(input) {
41
+ const importPath = resolveManagedSkillImportPathFromBase({
42
+ basePath: input.basePath,
43
+ skillName: input.requestedSkillName,
44
+ });
45
+ return normalizeImportedSkillPath(importPath) ?? importPath;
46
+ }
47
+ /**
48
+ * Imports one or more managed skills from a remote repository into the local skills directory.
49
+ */
50
+ export async function runSkillsAddCommand(env, input) {
51
+ const { context, runtime } = env;
26
52
  const repo = normalizeRemoteRepo(input.repo);
53
+ const normalizedBasePath = normalizeImportedSkillPath(input.repoPath);
27
54
  if (input.skillNames.length === 0) {
28
55
  throw new Error('At least one skill name must be provided with --skill');
29
56
  }
@@ -45,9 +72,14 @@ export async function runSkillsAddCommand(context, input) {
45
72
  const importedSkillSummaries = [];
46
73
  try {
47
74
  for (const requestedSkillName of requestedSkillNames) {
48
- const skillName = input.asName ?? requestedSkillName;
49
- const importedSkillPath = resolveManagedSkillImportPath({
50
- skillName: requestedSkillName,
75
+ const importedSkillPath = resolveRequestedImportPath({
76
+ basePath: normalizedBasePath,
77
+ requestedSkillName,
78
+ });
79
+ const skillName = deriveSkillName({
80
+ repo,
81
+ skillPath: importedSkillPath,
82
+ explicitName: input.asName,
51
83
  });
52
84
  const existingManagedSkill = findManagedSkill(lockfile, {
53
85
  name: skillName,
@@ -60,10 +92,10 @@ export async function runSkillsAddCommand(context, input) {
60
92
  if (await fs.pathExists(targetDir)) {
61
93
  throw new Error(`A local skill directory already exists: ${targetDir}`);
62
94
  }
63
- const sourceDir = await resolveSkillSourceDir({
95
+ const sourceDir = await resolveSkillSourceDirByPath({
64
96
  checkoutDir: checkout.checkoutDir,
65
97
  repo,
66
- skillName: requestedSkillName,
98
+ skillPath: importedSkillPath,
67
99
  });
68
100
  await replaceManagedSkillDirectory({
69
101
  targetDir,
@@ -87,15 +119,15 @@ export async function runSkillsAddCommand(context, input) {
87
119
  }
88
120
  }
89
121
  finally {
90
- await checkout.cleanup();
122
+ await cleanupRemoteRepoCheckout(checkout);
91
123
  }
92
124
  for (const importedSkillSummary of importedSkillSummaries) {
93
- console.log(`Imported ${importedSkillSummary}`);
125
+ runtime.logInfo(`Imported ${importedSkillSummary}`);
94
126
  }
95
127
  if (skippedSkillNames.length > 0) {
96
- console.warn(`Skipped already-imported skills: ${skippedSkillNames.join(', ')}`);
128
+ runtime.logWarn(`Skipped already-imported skills: ${skippedSkillNames.join(', ')}`);
97
129
  }
98
130
  if (importedSkillSummaries.length === 0) {
99
- console.log('No skills were imported.');
131
+ runtime.logInfo('No skills were imported.');
100
132
  }
101
133
  }