@enactprotocol/cli 2.1.4 → 2.1.6

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/dist/index.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA;;;;;GAKG;AAEH,OAAO,EAAE,iBAAiB,EAAE,MAAM,uBAAuB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EACL,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,mBAAmB,EACnB,oBAAoB,EACpB,mBAAmB,EACnB,oBAAoB,EACpB,uBAAuB,EACvB,uBAAuB,EACvB,oBAAoB,EACpB,uBAAuB,EACvB,sBAAsB,EACtB,mBAAmB,EACnB,sBAAsB,EACtB,qBAAqB,EACrB,oBAAoB,EACpB,qBAAqB,EACrB,sBAAsB,EACtB,oBAAoB,GACrB,MAAM,YAAY,CAAC;AACpB,OAAO,EAAE,KAAK,EAAE,WAAW,EAAE,MAAM,SAAS,CAAC;AAE7C,MAAM,CAAC,MAAM,OAAO,GAAG,OAAO,CAAC;AAK/B,uBAAuB;AACvB,KAAK,UAAU,IAAI;IACjB,+CAA+C;IAC/C,iBAAiB,EAAE,CAAC;IAEpB,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,OAAO,CAAC;SACb,WAAW,CAAC,6DAA6D,CAAC;SAC1E,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB,yBAAyB;IACzB,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9B,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7B,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9B,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9B,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7B,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAC/B,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAEhC,8BAA8B;IAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,mBAAmB,CAAC,OAAO,CAAC,CAAC;IAC7B,uBAAuB,CAAC,OAAO,CAAC,CAAC;IACjC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9B,qBAAqB,CAAC,OAAO,CAAC,CAAC;IAE/B,wCAAwC;IACxC,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAChC,uBAAuB,CAAC,OAAO,CAAC,CAAC;IAEjC,4BAA4B;IAC5B,oBAAoB,CAAC,OAAO,CAAC,CAAC;IAC9B,sBAAsB,CAAC,OAAO,CAAC,CAAC;IAEhC,0EAA0E;IAC1E,OAAO,CAAC,YAAY,CAAC,CAAC,GAAG,EAAE,EAAE;QAC3B,wEAAwE;QACxE,gEAAgE;QAChE,IACE,GAAG,CAAC,IAAI,KAAK,gBAAgB;YAC7B,GAAG,CAAC,IAAI,KAAK,yBAAyB;YACtC,GAAG,CAAC,IAAI,KAAK,mBAAmB;YAChC,GAAG,CAAC,IAAI,KAAK,kCAAkC;YAC/C,GAAG,CAAC,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EACnC,CAAC;YACD,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,MAAM,GAAG,CAAC;IACZ,CAAC,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,OAAO,CAAC,UAAU,CAAC,OAAO,CAAC,IAAI,CAAC,CAAC;IACzC,CAAC;IAAC,OAAO,GAAY,EAAE,CAAC;QACtB,iEAAiE;QACjE,MAAM,MAAM,GAAG,GAA0C,CAAC;QAC1D,IAAI,MAAM,EAAE,IAAI,EAAE,UAAU,CAAC,YAAY,CAAC,IAAI,MAAM,EAAE,OAAO,EAAE,QAAQ,CAAC,YAAY,CAAC,EAAE,CAAC;YACtF,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;QAClB,CAAC;QACD,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;AACH,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC,KAAK,CAAC,CAAC,GAAG,EAAE,EAAE;QACnB,KAAK,CAAC,WAAW,CAAC,GAAG,CAAC,CAAC,CAAC;QACxB,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC,CAAC,CAAC;AACL,CAAC"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@enactprotocol/cli",
3
- "version": "2.1.4",
3
+ "version": "2.1.6",
4
4
  "description": "Command-line interface for Enact - the npm for AI tools",
5
5
  "type": "module",
6
6
  "main": "./dist/index.js",
@@ -34,10 +34,10 @@
34
34
  },
35
35
  "dependencies": {
36
36
  "@clack/prompts": "^0.11.0",
37
- "@enactprotocol/api": "2.1.4",
38
- "@enactprotocol/execution": "2.1.4",
39
- "@enactprotocol/secrets": "2.1.4",
40
- "@enactprotocol/shared": "2.1.4",
37
+ "@enactprotocol/api": "2.1.6",
38
+ "@enactprotocol/execution": "2.1.6",
39
+ "@enactprotocol/secrets": "2.1.6",
40
+ "@enactprotocol/shared": "2.1.6",
41
41
  "commander": "^12.1.0",
42
42
  "picocolors": "^1.1.1"
43
43
  },
@@ -17,6 +17,7 @@ export { configureConfigCommand } from "./config";
17
17
  // Registry commands (Phase 8)
18
18
  export { configureSearchCommand } from "./search";
19
19
  export { configureGetCommand } from "./get";
20
+ export { configureLearnCommand } from "./learn";
20
21
  export { configurePublishCommand } from "./publish";
21
22
  export { configureAuthCommand } from "./auth";
22
23
  export { configureCacheCommand } from "./cache";
@@ -72,62 +72,220 @@ Edit this file to create your own tool:
72
72
  - [Tool Manifest Reference](https://enact.dev/docs/manifest)
73
73
  `,
74
74
 
75
- "tool-agents.md": `# AGENTS.md
75
+ "tool-agents.md": `# Enact Tool Development Guide
76
76
 
77
- Enact tool: containerized, signed executable. Manifest: \`enact.md\` (YAML frontmatter + Markdown docs).
77
+ Enact tools are containerized, cryptographically-signed executables. Each tool is defined by an \`enact.md\` file (YAML frontmatter + Markdown docs).
78
78
 
79
- ## Commands
80
- \`\`\`bash
81
- enact run ./ --args '{"name": "Test"}' # Run locally
82
- enact run ./ --args '{}' --dry-run # Preview execution
83
- enact sign ./ && enact publish ./ # Sign and publish
84
- \`\`\`
79
+ ## Quick Reference
80
+
81
+ | Task | Command |
82
+ |------|---------|
83
+ | Run with JSON | \`enact run ./ --args '{"key": "value"}'\` |
84
+ | Run from file | \`enact run ./ --input-file inputs.json\` |
85
+ | Dry run | \`enact run ./ --args '{}' --dry-run\` |
86
+ | Sign & publish | \`enact sign ./ && enact publish ./\` |
85
87
 
86
88
  ## enact.md Structure
89
+
87
90
  \`\`\`yaml
88
91
  ---
89
- name: {{TOOL_NAME}} # org/category/tool format
90
- description: What it does
91
- version: 1.0.0 # semver
92
- from: python:3.12-slim # pin versions, not :latest
93
- build: pip install requests # cached by Dagger
92
+ name: {{TOOL_NAME}}
93
+ description: What the tool does
94
+ version: 1.0.0
95
+ enact: "2.0.0"
96
+
97
+ from: python:3.12-slim # Docker image (pin versions, not :latest)
98
+ build: pip install requests # Build steps (cached by Dagger)
94
99
  command: python /work/main.py \${input}
95
100
  timeout: 30s
101
+
96
102
  inputSchema:
97
103
  type: object
98
104
  properties:
99
- input: { type: string }
105
+ input:
106
+ type: string
107
+ description: "Input to process"
100
108
  required: [input]
109
+
110
+ outputSchema:
111
+ type: object
112
+ properties:
113
+ result:
114
+ type: string
115
+
101
116
  env:
102
- API_KEY: # declare secrets (set via: enact env set API_KEY --secret)
117
+ API_KEY:
118
+ description: "External API key"
119
+ secret: true # Set via: enact env set API_KEY --secret
103
120
  ---
104
121
  # Tool Name
105
- Documentation here (usage examples, etc.)
122
+ Documentation here.
106
123
  \`\`\`
107
124
 
125
+ ## Field Reference
126
+
127
+ | Field | Description |
128
+ |-------|-------------|
129
+ | \`name\` | Hierarchical ID: \`org/category/tool\` |
130
+ | \`description\` | What the tool does |
131
+ | \`version\` | Semver version |
132
+ | \`from\` | Docker image |
133
+ | \`build\` | Build commands (string or array, cached) |
134
+ | \`command\` | Shell command with \`\${param}\` substitution |
135
+ | \`timeout\` | Max execution time (e.g., "30s", "5m") |
136
+ | \`inputSchema\` | JSON Schema for inputs |
137
+ | \`outputSchema\` | JSON Schema for outputs |
138
+ | \`env\` | Environment variables and secrets |
139
+
108
140
  ## Parameter Substitution
141
+
142
+ Enact auto-quotes parameters. **Never manually quote:**
143
+
144
+ \`\`\`yaml
145
+ # WRONG - causes double-quoting
146
+ command: python /work/main.py "\${input}"
147
+
148
+ # RIGHT - Enact handles quoting
149
+ command: python /work/main.py \${input}
150
+ \`\`\`
151
+
152
+ **Optional params:** Missing optional params become empty strings. Always provide defaults:
153
+ \`\`\`yaml
154
+ inputSchema:
155
+ properties:
156
+ greeting:
157
+ type: string
158
+ default: "Hello" # Recommended for optional params
159
+ \`\`\`
160
+
161
+ Or handle empty in shell:
162
+ \`\`\`yaml
163
+ command: "echo \${greeting:-Hello} \${name}"
164
+ \`\`\`
165
+
166
+ Modifiers:
109
167
  - \`\${param}\` — auto-quoted (handles spaces, JSON, special chars)
110
- - \`\${param:raw}\` — unquoted (use carefully)
111
- - **Never manually quote**: \`"\${param}"\` causes double-quoting
168
+ - \`\${param:raw}\` — raw, no quoting (use carefully)
112
169
 
113
170
  ## Output
114
- Always output valid JSON when \`outputSchema\` is defined:
171
+
172
+ Output valid JSON to stdout when \`outputSchema\` is defined:
173
+
115
174
  \`\`\`python
116
175
  import json, sys
117
- print(json.dumps({"result": data})) # stdout = tool output
118
- sys.exit(1) # non-zero = error
176
+
177
+ try:
178
+ result = do_work()
179
+ print(json.dumps({"status": "success", "result": result}))
180
+ except Exception as e:
181
+ print(json.dumps({"status": "error", "message": str(e)}))
182
+ sys.exit(1) # non-zero = error
119
183
  \`\`\`
120
184
 
121
- ## File Access
122
- Tool runs in container with \`/work\` as working directory. Source files copied there.
185
+ ## Build Steps by Language
186
+
187
+ **Python:**
188
+ \`\`\`yaml
189
+ from: python:3.12-slim
190
+ build: pip install requests pandas
191
+ \`\`\`
192
+
193
+ **Node.js:**
194
+ \`\`\`yaml
195
+ from: node:20-alpine
196
+ build:
197
+ - npm install
198
+ - npm run build
199
+ \`\`\`
200
+
201
+ **Rust:**
202
+ \`\`\`yaml
203
+ from: rust:1.83-slim
204
+ build: rustc /work/main.rs -o /work/tool
205
+ command: /work/tool \${input}
206
+ \`\`\`
123
207
 
124
- ## Adding Dependencies
125
- - Python: \`build: pip install package1 package2\`
126
- - Node: \`build: ["npm install", "npm run build"]\`
127
- - System: \`build: apt-get update && apt-get install -y libfoo\`
128
- - Compiled: \`build: rustc /work/main.rs -o /work/tool\`
208
+ **Go:**
209
+ \`\`\`yaml
210
+ from: golang:1.22-alpine
211
+ build: cd /work && go build -o tool main.go
212
+ command: /work/tool \${input}
213
+ \`\`\`
214
+
215
+ **System packages:**
216
+ \`\`\`yaml
217
+ build: apt-get update && apt-get install -y libfoo-dev
218
+ \`\`\`
129
219
 
130
220
  Build steps are cached — first run slow, subsequent runs instant.
221
+
222
+ ## File Access
223
+
224
+ Tools run in a container with \`/work\` as the working directory. All source files are copied there.
225
+
226
+ ## Secrets
227
+
228
+ Declare in \`enact.md\`:
229
+ \`\`\`yaml
230
+ env:
231
+ API_KEY:
232
+ description: "API key for service"
233
+ secret: true
234
+ \`\`\`
235
+
236
+ Set before running:
237
+ \`\`\`bash
238
+ enact env set API_KEY --secret --namespace {{TOOL_NAME}}
239
+ \`\`\`
240
+
241
+ Access in code:
242
+ \`\`\`python
243
+ import os
244
+ api_key = os.environ.get('API_KEY')
245
+ \`\`\`
246
+
247
+ ## LLM Instruction Tools
248
+
249
+ Tools without a \`command\` field are interpreted by LLMs:
250
+
251
+ \`\`\`yaml
252
+ ---
253
+ name: myorg/ai/reviewer
254
+ description: AI-powered code review
255
+ inputSchema:
256
+ type: object
257
+ properties:
258
+ code: { type: string }
259
+ required: [code]
260
+ outputSchema:
261
+ type: object
262
+ properties:
263
+ issues: { type: array }
264
+ score: { type: number }
265
+ ---
266
+ # Code Reviewer
267
+
268
+ You are a senior engineer. Review the code for bugs, style, and security.
269
+ Return JSON: {"issues": [...], "score": 75}
270
+ \`\`\`
271
+
272
+ ## Publishing Checklist
273
+
274
+ - [ ] \`name\` follows \`namespace/category/tool\` pattern
275
+ - [ ] \`version\` set (semver)
276
+ - [ ] \`description\` is clear and searchable
277
+ - [ ] \`inputSchema\` / \`outputSchema\` defined
278
+ - [ ] \`from\` uses pinned image version
279
+ - [ ] \`timeout\` set appropriately
280
+ - [ ] Tool tested locally with \`enact run ./\`
281
+
282
+ ## Troubleshooting
283
+
284
+ \`\`\`bash
285
+ enact run ./ --args '{"x": "y"}' --verbose # Verbose output
286
+ enact run ./ --args '{}' --dry-run # Preview command
287
+ enact list # List installed tools
288
+ \`\`\`
131
289
  `,
132
290
 
133
291
  "agent-agents.md": `# AGENTS.md
@@ -0,0 +1,107 @@
1
+ /**
2
+ * enact learn command
3
+ *
4
+ * Display the documentation (enact.md) for a tool.
5
+ * Fetches and displays the raw manifest content for easy reading.
6
+ */
7
+
8
+ import { createApiClient, getToolInfo, getToolVersion } from "@enactprotocol/api";
9
+ import { loadConfig } from "@enactprotocol/shared";
10
+ import type { Command } from "commander";
11
+ import type { CommandContext, GlobalOptions } from "../../types";
12
+ import { dim, error, formatError, header, json, newline } from "../../utils";
13
+
14
+ interface LearnOptions extends GlobalOptions {
15
+ ver?: string;
16
+ }
17
+
18
+ /**
19
+ * Learn command handler
20
+ */
21
+ async function learnHandler(
22
+ toolName: string,
23
+ options: LearnOptions,
24
+ _ctx: CommandContext
25
+ ): Promise<void> {
26
+ const config = loadConfig();
27
+ const registryUrl =
28
+ process.env.ENACT_REGISTRY_URL ??
29
+ config.registry?.url ??
30
+ "https://siikwkfgsmouioodghho.supabase.co/functions/v1";
31
+ const authToken = config.registry?.authToken;
32
+ const client = createApiClient({
33
+ baseUrl: registryUrl,
34
+ authToken: authToken,
35
+ });
36
+
37
+ try {
38
+ // Get the version to fetch - either specified or latest
39
+ let version = options.ver;
40
+ if (!version) {
41
+ const toolInfo = await getToolInfo(client, toolName);
42
+ version = toolInfo.latestVersion;
43
+ }
44
+
45
+ // Get the version info which includes rawManifest
46
+ const versionInfo = await getToolVersion(client, toolName, version);
47
+
48
+ if (options.json) {
49
+ json({
50
+ name: toolName,
51
+ version: versionInfo.version,
52
+ documentation: versionInfo.rawManifest ?? null,
53
+ });
54
+ return;
55
+ }
56
+
57
+ if (!versionInfo.rawManifest) {
58
+ error(`No documentation found for ${toolName}@${version}`);
59
+ dim("This tool may not have an enact.md file.");
60
+ process.exit(1);
61
+ }
62
+
63
+ // Display the documentation
64
+ header(`${toolName}@${version}`);
65
+ newline();
66
+ console.log(versionInfo.rawManifest);
67
+ } catch (err) {
68
+ if (err instanceof Error) {
69
+ if (err.message.includes("not_found") || err.message.includes("404")) {
70
+ error(`Tool not found: ${toolName}`);
71
+ dim("Check the tool name or search with: enact search <query>");
72
+ process.exit(1);
73
+ }
74
+ if (err.message.includes("fetch")) {
75
+ error("Unable to connect to registry. Check your internet connection.");
76
+ process.exit(1);
77
+ }
78
+ }
79
+ throw err;
80
+ }
81
+ }
82
+
83
+ /**
84
+ * Configure the learn command
85
+ */
86
+ export function configureLearnCommand(program: Command): void {
87
+ program
88
+ .command("learn <tool>")
89
+ .description("Display documentation (enact.md) for a tool")
90
+ .option("--ver <version>", "Show documentation for a specific version")
91
+ .option("--json", "Output as JSON")
92
+ .action(async (toolName: string, options: LearnOptions) => {
93
+ const ctx: CommandContext = {
94
+ cwd: process.cwd(),
95
+ options,
96
+ isCI: Boolean(process.env.CI),
97
+ isInteractive: process.stdout.isTTY ?? false,
98
+ };
99
+
100
+ try {
101
+ await learnHandler(toolName, options, ctx);
102
+ } catch (err) {
103
+ error(formatError(err));
104
+ process.exit(1);
105
+ }
106
+ });
107
+ }
@@ -61,6 +61,7 @@ import {
61
61
 
62
62
  interface RunOptions extends GlobalOptions {
63
63
  args?: string;
64
+ inputFile?: string;
64
65
  input?: string[];
65
66
  timeout?: string;
66
67
  noCache?: boolean;
@@ -70,14 +71,46 @@ interface RunOptions extends GlobalOptions {
70
71
 
71
72
  /**
72
73
  * Parse input arguments from various formats
74
+ *
75
+ * Priority order (later sources override earlier):
76
+ * 1. --input-file (JSON file)
77
+ * 2. --args (inline JSON)
78
+ * 3. --input (key=value pairs)
79
+ *
80
+ * Recommended for agents: Use --args or --input-file with JSON
73
81
  */
74
82
  function parseInputArgs(
75
83
  argsJson: string | undefined,
84
+ inputFile: string | undefined,
76
85
  inputFlags: string[] | undefined
77
86
  ): Record<string, unknown> {
78
87
  const inputs: Record<string, unknown> = {};
79
88
 
80
- // Parse --args JSON
89
+ // Parse --input-file JSON file (loaded first, can be overridden)
90
+ if (inputFile) {
91
+ try {
92
+ const { readFileSync, existsSync } = require("node:fs");
93
+ const { resolve } = require("node:path");
94
+ const filePath = resolve(inputFile);
95
+
96
+ if (!existsSync(filePath)) {
97
+ throw new Error(`Input file not found: ${inputFile}`);
98
+ }
99
+
100
+ const content = readFileSync(filePath, "utf-8");
101
+ const parsed = JSON.parse(content);
102
+ if (typeof parsed === "object" && parsed !== null) {
103
+ Object.assign(inputs, parsed);
104
+ }
105
+ } catch (err) {
106
+ if (err instanceof Error && err.message.startsWith("Input file not found")) {
107
+ throw err;
108
+ }
109
+ throw new Error(`Invalid JSON in input file: ${formatError(err)}`);
110
+ }
111
+ }
112
+
113
+ // Parse --args JSON (overrides file)
81
114
  if (argsJson) {
82
115
  try {
83
116
  const parsed = JSON.parse(argsJson);
@@ -89,7 +122,7 @@ function parseInputArgs(
89
122
  }
90
123
  }
91
124
 
92
- // Parse --input key=value pairs
125
+ // Parse --input key=value pairs (overrides both)
93
126
  if (inputFlags) {
94
127
  for (const input of inputFlags) {
95
128
  const eqIndex = input.indexOf("=");
@@ -501,7 +534,7 @@ async function runHandler(tool: string, options: RunOptions, ctx: CommandContext
501
534
  const manifest = resolution.manifest;
502
535
 
503
536
  // Parse inputs
504
- const inputs = parseInputArgs(options.args, options.input);
537
+ const inputs = parseInputArgs(options.args, options.inputFile, options.input);
505
538
 
506
539
  // Apply defaults from schema
507
540
  const inputsWithDefaults = manifest.inputSchema
@@ -669,8 +702,9 @@ export function configureRunCommand(program: Command): void {
669
702
  .command("run")
670
703
  .description("Execute a tool with its manifest-defined command")
671
704
  .argument("<tool>", "Tool to run (name, path, or '.' for current directory)")
672
- .option("-a, --args <json>", "Input arguments as JSON")
673
- .option("-i, --input <key=value...>", "Input arguments as key=value pairs")
705
+ .option("-a, --args <json>", "Input arguments as JSON string (recommended)")
706
+ .option("-f, --input-file <path>", "Load input arguments from JSON file")
707
+ .option("-i, --input <key=value...>", "Input arguments as key=value pairs (simple values only)")
674
708
  .option("-t, --timeout <duration>", "Execution timeout (e.g., 30s, 5m)")
675
709
  .option("--no-cache", "Disable container caching")
676
710
  .option("--local", "Only resolve from local sources")
package/src/index.ts CHANGED
@@ -19,6 +19,7 @@ import {
19
19
  configureInitCommand,
20
20
  configureInspectCommand,
21
21
  configureInstallCommand,
22
+ configureLearnCommand,
22
23
  configureListCommand,
23
24
  configurePublishCommand,
24
25
  configureReportCommand,
@@ -32,7 +33,7 @@ import {
32
33
  } from "./commands";
33
34
  import { error, formatError } from "./utils";
34
35
 
35
- export const version = "2.1.4";
36
+ export const version = "2.1.6";
36
37
 
37
38
  // Export types for external use
38
39
  export type { GlobalOptions, CommandContext } from "./types";
@@ -47,7 +48,7 @@ async function main() {
47
48
  program
48
49
  .name("enact")
49
50
  .description("Enact - Verified, portable protocol for AI-executable tools")
50
- .version(version);
51
+ .version(version, "-v, --version", "output the version number");
51
52
 
52
53
  // Configure all commands
53
54
  configureSetupCommand(program);
@@ -63,6 +64,7 @@ async function main() {
63
64
  // Registry commands (Phase 8)
64
65
  configureSearchCommand(program);
65
66
  configureGetCommand(program);
67
+ configureLearnCommand(program);
66
68
  configurePublishCommand(program);
67
69
  configureAuthCommand(program);
68
70
  configureCacheCommand(program);
@@ -352,7 +352,7 @@ describe("init command", () => {
352
352
  rmSync(testDir, { recursive: true });
353
353
  });
354
354
 
355
- test("AGENTS.md does not contain unnecessary verbosity", async () => {
355
+ test("AGENTS.md is comprehensive but reasonably sized", async () => {
356
356
  const program = new Command();
357
357
  program.exitOverride();
358
358
  configureInitCommand(program);
@@ -376,9 +376,10 @@ describe("init command", () => {
376
376
 
377
377
  const content = readFileSync(join(testDir, "AGENTS.md"), "utf-8");
378
378
 
379
- // Should be concise - check line count is reasonable
379
+ // Should be comprehensive but not excessive
380
380
  const lines = content.split("\n").length;
381
- expect(lines).toBeLessThan(100); // Should be concise
381
+ expect(lines).toBeLessThan(250); // Comprehensive guide under 250 lines
382
+ expect(lines).toBeGreaterThan(100); // But not too sparse
382
383
 
383
384
  // Clean up
384
385
  rmSync(testDir, { recursive: true });