@enactprotocol/cli 1.2.13 → 2.0.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/package.json +34 -38
- package/src/commands/auth/index.ts +940 -0
- package/src/commands/cache/index.ts +361 -0
- package/src/commands/config/README.md +239 -0
- package/src/commands/config/index.ts +164 -0
- package/src/commands/env/README.md +197 -0
- package/src/commands/env/index.ts +392 -0
- package/src/commands/exec/README.md +110 -0
- package/src/commands/exec/index.ts +195 -0
- package/src/commands/get/index.ts +198 -0
- package/src/commands/index.ts +30 -0
- package/src/commands/inspect/index.ts +264 -0
- package/src/commands/install/README.md +146 -0
- package/src/commands/install/index.ts +682 -0
- package/src/commands/list/README.md +115 -0
- package/src/commands/list/index.ts +138 -0
- package/src/commands/publish/index.ts +350 -0
- package/src/commands/report/index.ts +366 -0
- package/src/commands/run/README.md +124 -0
- package/src/commands/run/index.ts +686 -0
- package/src/commands/search/index.ts +368 -0
- package/src/commands/setup/index.ts +274 -0
- package/src/commands/sign/index.ts +652 -0
- package/src/commands/trust/README.md +214 -0
- package/src/commands/trust/index.ts +453 -0
- package/src/commands/unyank/index.ts +107 -0
- package/src/commands/yank/index.ts +143 -0
- package/src/index.ts +96 -0
- package/src/types.ts +81 -0
- package/src/utils/errors.ts +409 -0
- package/src/utils/exit-codes.ts +159 -0
- package/src/utils/ignore.ts +147 -0
- package/src/utils/index.ts +107 -0
- package/src/utils/output.ts +242 -0
- package/src/utils/spinner.ts +214 -0
- package/tests/commands/auth.test.ts +217 -0
- package/tests/commands/cache.test.ts +286 -0
- package/tests/commands/config.test.ts +277 -0
- package/tests/commands/env.test.ts +293 -0
- package/tests/commands/exec.test.ts +112 -0
- package/tests/commands/get.test.ts +179 -0
- package/tests/commands/inspect.test.ts +201 -0
- package/tests/commands/install-integration.test.ts +343 -0
- package/tests/commands/install.test.ts +288 -0
- package/tests/commands/list.test.ts +160 -0
- package/tests/commands/publish.test.ts +186 -0
- package/tests/commands/report.test.ts +194 -0
- package/tests/commands/run.test.ts +231 -0
- package/tests/commands/search.test.ts +131 -0
- package/tests/commands/sign.test.ts +164 -0
- package/tests/commands/trust.test.ts +236 -0
- package/tests/commands/unyank.test.ts +114 -0
- package/tests/commands/yank.test.ts +154 -0
- package/tests/e2e.test.ts +554 -0
- package/tests/fixtures/calculator/enact.yaml +34 -0
- package/tests/fixtures/echo-tool/enact.md +31 -0
- package/tests/fixtures/env-tool/enact.yaml +19 -0
- package/tests/fixtures/greeter/enact.yaml +18 -0
- package/tests/fixtures/invalid-tool/enact.yaml +4 -0
- package/tests/index.test.ts +8 -0
- package/tests/types.test.ts +84 -0
- package/tests/utils/errors.test.ts +303 -0
- package/tests/utils/exit-codes.test.ts +189 -0
- package/tests/utils/ignore.test.ts +461 -0
- package/tests/utils/output.test.ts +126 -0
- package/tsconfig.json +17 -0
- package/tsconfig.tsbuildinfo +1 -0
- package/dist/index.js +0 -231612
- package/dist/index.js.bak +0 -231611
- package/dist/web/static/app.js +0 -663
- package/dist/web/static/index.html +0 -117
- package/dist/web/static/style.css +0 -291
|
@@ -0,0 +1,115 @@
|
|
|
1
|
+
# enact list
|
|
2
|
+
|
|
3
|
+
List installed tools.
|
|
4
|
+
|
|
5
|
+
## Synopsis
|
|
6
|
+
|
|
7
|
+
```bash
|
|
8
|
+
enact list [options]
|
|
9
|
+
```
|
|
10
|
+
|
|
11
|
+
## Description
|
|
12
|
+
|
|
13
|
+
The `list` command displays installed tools, showing their name and version. By default, it shows project tools. Use `-g` to show global tools.
|
|
14
|
+
|
|
15
|
+
## Options
|
|
16
|
+
|
|
17
|
+
| Option | Description |
|
|
18
|
+
|--------|-------------|
|
|
19
|
+
| `-g, --global` | List global tools (`~/.enact/tools/`) |
|
|
20
|
+
| `-v, --verbose` | Show detailed output including file paths |
|
|
21
|
+
| `--json` | Output as JSON |
|
|
22
|
+
|
|
23
|
+
## Examples
|
|
24
|
+
|
|
25
|
+
### Basic usage
|
|
26
|
+
|
|
27
|
+
```bash
|
|
28
|
+
# List project tools (default)
|
|
29
|
+
enact list
|
|
30
|
+
|
|
31
|
+
# List global tools
|
|
32
|
+
enact list -g
|
|
33
|
+
|
|
34
|
+
# List with full paths
|
|
35
|
+
enact list --verbose
|
|
36
|
+
```
|
|
37
|
+
|
|
38
|
+
### JSON output
|
|
39
|
+
|
|
40
|
+
```bash
|
|
41
|
+
# Get JSON for scripting
|
|
42
|
+
enact list --json
|
|
43
|
+
|
|
44
|
+
# Global tools as JSON
|
|
45
|
+
enact list -g --json
|
|
46
|
+
```
|
|
47
|
+
|
|
48
|
+
## Output Format
|
|
49
|
+
|
|
50
|
+
### Table Output (Default)
|
|
51
|
+
|
|
52
|
+
```
|
|
53
|
+
Project Tools
|
|
54
|
+
─────────────
|
|
55
|
+
|
|
56
|
+
Name Version
|
|
57
|
+
────────────────────────────────────────
|
|
58
|
+
alice/utils/greeter 1.0.0
|
|
59
|
+
company/internal-tool 0.5.0
|
|
60
|
+
|
|
61
|
+
Total: 2 tool(s)
|
|
62
|
+
```
|
|
63
|
+
|
|
64
|
+
### Global Tools
|
|
65
|
+
|
|
66
|
+
```
|
|
67
|
+
Global Tools
|
|
68
|
+
────────────
|
|
69
|
+
|
|
70
|
+
Name Version
|
|
71
|
+
────────────────────────────────────────
|
|
72
|
+
EnactProtocol/pdf-extract 2.1.0
|
|
73
|
+
|
|
74
|
+
Total: 1 tool(s)
|
|
75
|
+
```
|
|
76
|
+
|
|
77
|
+
### Verbose Output
|
|
78
|
+
|
|
79
|
+
```
|
|
80
|
+
Name Version Location
|
|
81
|
+
──────────────────────────────────────────────────────────────────────
|
|
82
|
+
alice/utils/greeter 1.0.0 /path/to/project/.enact/tools/alice/utils/greeter
|
|
83
|
+
```
|
|
84
|
+
|
|
85
|
+
### JSON Output
|
|
86
|
+
|
|
87
|
+
```json
|
|
88
|
+
[
|
|
89
|
+
{
|
|
90
|
+
"name": "alice/utils/greeter",
|
|
91
|
+
"version": "1.0.0",
|
|
92
|
+
"location": "/path/to/project/.enact/tools/alice/utils/greeter",
|
|
93
|
+
"scope": "project"
|
|
94
|
+
}
|
|
95
|
+
]
|
|
96
|
+
```
|
|
97
|
+
|
|
98
|
+
## Tool Locations
|
|
99
|
+
|
|
100
|
+
| Scope | Directory | Description |
|
|
101
|
+
|-------|-----------|-------------|
|
|
102
|
+
| `project` | `.enact/tools/` | Tools installed for the current project |
|
|
103
|
+
| `global` | `~/.enact/tools/` | Tools installed globally for the user |
|
|
104
|
+
|
|
105
|
+
## Exit Codes
|
|
106
|
+
|
|
107
|
+
| Code | Description |
|
|
108
|
+
|------|-------------|
|
|
109
|
+
| `0` | Success |
|
|
110
|
+
| `1` | Error reading directories |
|
|
111
|
+
|
|
112
|
+
## See Also
|
|
113
|
+
|
|
114
|
+
- [enact install](../install/README.md) - Install tools
|
|
115
|
+
- [enact run](../run/README.md) - Execute tools
|
|
@@ -0,0 +1,138 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enact list command
|
|
3
|
+
*
|
|
4
|
+
* List installed tools from tools.json registries.
|
|
5
|
+
* - Default: project tools (via .enact/tools.json)
|
|
6
|
+
* - --global/-g: global tools (via ~/.enact/tools.json)
|
|
7
|
+
*
|
|
8
|
+
* All tools are stored in ~/.enact/cache/{tool}/{version}/
|
|
9
|
+
*/
|
|
10
|
+
|
|
11
|
+
import { listInstalledTools, tryLoadManifestFromDir } from "@enactprotocol/shared";
|
|
12
|
+
import type { Command } from "commander";
|
|
13
|
+
import type { CommandContext, GlobalOptions } from "../../types";
|
|
14
|
+
import {
|
|
15
|
+
type TableColumn,
|
|
16
|
+
dim,
|
|
17
|
+
error,
|
|
18
|
+
formatError,
|
|
19
|
+
header,
|
|
20
|
+
info,
|
|
21
|
+
json,
|
|
22
|
+
newline,
|
|
23
|
+
table,
|
|
24
|
+
} from "../../utils";
|
|
25
|
+
|
|
26
|
+
interface ListOptions extends GlobalOptions {
|
|
27
|
+
global?: boolean;
|
|
28
|
+
}
|
|
29
|
+
|
|
30
|
+
interface ToolInfo {
|
|
31
|
+
name: string;
|
|
32
|
+
description: string;
|
|
33
|
+
version: string;
|
|
34
|
+
location: string;
|
|
35
|
+
scope: string;
|
|
36
|
+
[key: string]: string; // Index signature for table compatibility
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
/**
|
|
40
|
+
* List tools from tools.json registry
|
|
41
|
+
*/
|
|
42
|
+
function listToolsFromRegistry(scope: "global" | "project", cwd?: string): ToolInfo[] {
|
|
43
|
+
const tools: ToolInfo[] = [];
|
|
44
|
+
const installedTools = listInstalledTools(scope, cwd);
|
|
45
|
+
|
|
46
|
+
for (const tool of installedTools) {
|
|
47
|
+
// Load manifest from cache to get description
|
|
48
|
+
const loaded = tryLoadManifestFromDir(tool.cachePath);
|
|
49
|
+
tools.push({
|
|
50
|
+
name: tool.name,
|
|
51
|
+
description: loaded?.manifest.description ?? "-",
|
|
52
|
+
version: tool.version,
|
|
53
|
+
location: tool.cachePath,
|
|
54
|
+
scope,
|
|
55
|
+
});
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
return tools;
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
/**
|
|
62
|
+
* List command handler
|
|
63
|
+
*/
|
|
64
|
+
async function listHandler(options: ListOptions, ctx: CommandContext): Promise<void> {
|
|
65
|
+
const allTools: ToolInfo[] = [];
|
|
66
|
+
|
|
67
|
+
if (options.global) {
|
|
68
|
+
// Global tools (via ~/.enact/tools.json)
|
|
69
|
+
const globalTools = listToolsFromRegistry("global");
|
|
70
|
+
allTools.push(...globalTools);
|
|
71
|
+
} else {
|
|
72
|
+
// Project tools (via .enact/tools.json)
|
|
73
|
+
const projectTools = listToolsFromRegistry("project", ctx.cwd);
|
|
74
|
+
allTools.push(...projectTools);
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
// Output
|
|
78
|
+
if (options.json) {
|
|
79
|
+
json(allTools);
|
|
80
|
+
return;
|
|
81
|
+
}
|
|
82
|
+
|
|
83
|
+
if (allTools.length === 0) {
|
|
84
|
+
if (options.global) {
|
|
85
|
+
info("No global tools installed.");
|
|
86
|
+
dim("Install globally with 'enact install <tool> -g'");
|
|
87
|
+
} else {
|
|
88
|
+
info("No project tools installed.");
|
|
89
|
+
dim("Install with 'enact install <tool>' or use '-g' for global");
|
|
90
|
+
}
|
|
91
|
+
return;
|
|
92
|
+
}
|
|
93
|
+
|
|
94
|
+
header(options.global ? "Global Tools" : "Project Tools");
|
|
95
|
+
newline();
|
|
96
|
+
|
|
97
|
+
const columns: TableColumn[] = [
|
|
98
|
+
{ key: "name", header: "Name", width: 28 },
|
|
99
|
+
{ key: "description", header: "Description", width: 50 },
|
|
100
|
+
];
|
|
101
|
+
|
|
102
|
+
if (options.verbose) {
|
|
103
|
+
columns.push({ key: "version", header: "Version", width: 10 });
|
|
104
|
+
columns.push({ key: "location", header: "Location", width: 40 });
|
|
105
|
+
}
|
|
106
|
+
|
|
107
|
+
table(allTools, columns);
|
|
108
|
+
newline();
|
|
109
|
+
dim(`Total: ${allTools.length} tool(s)`);
|
|
110
|
+
}
|
|
111
|
+
|
|
112
|
+
/**
|
|
113
|
+
* Configure the list command
|
|
114
|
+
*/
|
|
115
|
+
export function configureListCommand(program: Command): void {
|
|
116
|
+
program
|
|
117
|
+
.command("list")
|
|
118
|
+
.alias("ls")
|
|
119
|
+
.description("List installed tools")
|
|
120
|
+
.option("-g, --global", "List global tools (via ~/.enact/tools.json)")
|
|
121
|
+
.option("-v, --verbose", "Show detailed output including paths")
|
|
122
|
+
.option("--json", "Output as JSON")
|
|
123
|
+
.action(async (options: ListOptions) => {
|
|
124
|
+
const ctx: CommandContext = {
|
|
125
|
+
cwd: process.cwd(),
|
|
126
|
+
options,
|
|
127
|
+
isCI: Boolean(process.env.CI),
|
|
128
|
+
isInteractive: process.stdout.isTTY ?? false,
|
|
129
|
+
};
|
|
130
|
+
|
|
131
|
+
try {
|
|
132
|
+
await listHandler(options, ctx);
|
|
133
|
+
} catch (err) {
|
|
134
|
+
error(formatError(err));
|
|
135
|
+
process.exit(1);
|
|
136
|
+
}
|
|
137
|
+
});
|
|
138
|
+
}
|
|
@@ -0,0 +1,350 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enact publish command
|
|
3
|
+
*
|
|
4
|
+
* Publish a tool to the Enact registry using v2 multipart upload.
|
|
5
|
+
*/
|
|
6
|
+
|
|
7
|
+
import { existsSync, readFileSync, readdirSync, statSync } from "node:fs";
|
|
8
|
+
import { dirname, join, relative, resolve } from "node:path";
|
|
9
|
+
import { createApiClient, publishTool } from "@enactprotocol/api";
|
|
10
|
+
import { getSecret } from "@enactprotocol/secrets";
|
|
11
|
+
import {
|
|
12
|
+
type LoadedManifest,
|
|
13
|
+
type ToolManifest,
|
|
14
|
+
loadConfig,
|
|
15
|
+
loadManifest,
|
|
16
|
+
loadManifestFromDir,
|
|
17
|
+
validateManifest,
|
|
18
|
+
} from "@enactprotocol/shared";
|
|
19
|
+
import type { Command } from "commander";
|
|
20
|
+
import type { CommandContext, GlobalOptions } from "../../types";
|
|
21
|
+
import {
|
|
22
|
+
dim,
|
|
23
|
+
error,
|
|
24
|
+
formatError,
|
|
25
|
+
header,
|
|
26
|
+
info,
|
|
27
|
+
json,
|
|
28
|
+
keyValue,
|
|
29
|
+
newline,
|
|
30
|
+
success,
|
|
31
|
+
warning,
|
|
32
|
+
withSpinner,
|
|
33
|
+
} from "../../utils";
|
|
34
|
+
import { loadGitignore, shouldIgnore } from "../../utils/ignore";
|
|
35
|
+
|
|
36
|
+
/** Auth namespace for token storage */
|
|
37
|
+
const AUTH_NAMESPACE = "enact:auth";
|
|
38
|
+
const ACCESS_TOKEN_KEY = "access_token";
|
|
39
|
+
|
|
40
|
+
interface PublishOptions extends GlobalOptions {
|
|
41
|
+
dryRun?: boolean;
|
|
42
|
+
tag?: string;
|
|
43
|
+
skipAuth?: boolean;
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
/**
|
|
47
|
+
* Recursively collect all files in a directory
|
|
48
|
+
*/
|
|
49
|
+
function collectFiles(
|
|
50
|
+
dir: string,
|
|
51
|
+
baseDir: string,
|
|
52
|
+
files: Array<{ path: string; relativePath: string }> = [],
|
|
53
|
+
ignorePatterns: string[] = []
|
|
54
|
+
): Array<{ path: string; relativePath: string }> {
|
|
55
|
+
const entries = readdirSync(dir, { withFileTypes: true });
|
|
56
|
+
|
|
57
|
+
for (const entry of entries) {
|
|
58
|
+
const fullPath = join(dir, entry.name);
|
|
59
|
+
const relativePath = relative(baseDir, fullPath);
|
|
60
|
+
|
|
61
|
+
// Check if file should be ignored
|
|
62
|
+
if (shouldIgnore(relativePath, entry.name, ignorePatterns)) {
|
|
63
|
+
continue;
|
|
64
|
+
}
|
|
65
|
+
|
|
66
|
+
if (entry.isDirectory()) {
|
|
67
|
+
collectFiles(fullPath, baseDir, files, ignorePatterns);
|
|
68
|
+
} else if (entry.isFile()) {
|
|
69
|
+
files.push({ path: fullPath, relativePath });
|
|
70
|
+
}
|
|
71
|
+
}
|
|
72
|
+
|
|
73
|
+
return files;
|
|
74
|
+
}
|
|
75
|
+
|
|
76
|
+
/**
|
|
77
|
+
* Create a tar.gz bundle from the tool directory
|
|
78
|
+
*/
|
|
79
|
+
async function createBundleFromDir(toolDir: string): Promise<Uint8Array> {
|
|
80
|
+
// Load gitignore patterns (ALWAYS_IGNORE is already checked in shouldIgnore)
|
|
81
|
+
const gitignorePatterns = loadGitignore(toolDir);
|
|
82
|
+
|
|
83
|
+
// Collect all files to include (respecting ignore patterns)
|
|
84
|
+
const files = collectFiles(toolDir, toolDir, [], gitignorePatterns);
|
|
85
|
+
|
|
86
|
+
// Use tar to create the bundle (available on all supported platforms)
|
|
87
|
+
const tempDir = join(process.env.TMPDIR ?? "/tmp", `enact-bundle-${Date.now()}`);
|
|
88
|
+
const tempBundle = join(tempDir, "bundle.tar.gz");
|
|
89
|
+
|
|
90
|
+
const { mkdirSync, rmSync } = await import("node:fs");
|
|
91
|
+
mkdirSync(tempDir, { recursive: true });
|
|
92
|
+
|
|
93
|
+
try {
|
|
94
|
+
// Create tar.gz using tar command
|
|
95
|
+
const fileList = files.map((f) => f.relativePath).join("\n");
|
|
96
|
+
const fileListPath = join(tempDir, "files.txt");
|
|
97
|
+
const { writeFileSync } = await import("node:fs");
|
|
98
|
+
writeFileSync(fileListPath, fileList);
|
|
99
|
+
|
|
100
|
+
// Use COPYFILE_DISABLE=1 to prevent macOS from adding AppleDouble (._) files
|
|
101
|
+
const proc = Bun.spawn(["tar", "-czf", tempBundle, "-C", toolDir, "-T", fileListPath], {
|
|
102
|
+
stdout: "pipe",
|
|
103
|
+
stderr: "pipe",
|
|
104
|
+
env: {
|
|
105
|
+
...process.env,
|
|
106
|
+
COPYFILE_DISABLE: "1", // Prevents macOS extended attributes/resource forks
|
|
107
|
+
},
|
|
108
|
+
});
|
|
109
|
+
|
|
110
|
+
const exitCode = await proc.exited;
|
|
111
|
+
|
|
112
|
+
if (exitCode !== 0) {
|
|
113
|
+
const stderr = await new Response(proc.stderr).text();
|
|
114
|
+
throw new Error(`Failed to create bundle: ${stderr}`);
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
// Read the bundle
|
|
118
|
+
const bundleData = readFileSync(tempBundle);
|
|
119
|
+
return new Uint8Array(bundleData);
|
|
120
|
+
} finally {
|
|
121
|
+
// Clean up temp files
|
|
122
|
+
try {
|
|
123
|
+
rmSync(tempDir, { recursive: true, force: true });
|
|
124
|
+
} catch {
|
|
125
|
+
// Ignore cleanup errors
|
|
126
|
+
}
|
|
127
|
+
}
|
|
128
|
+
}
|
|
129
|
+
|
|
130
|
+
/**
|
|
131
|
+
* Load README from tool directory
|
|
132
|
+
*/
|
|
133
|
+
function loadReadme(toolDir: string): string | undefined {
|
|
134
|
+
const readmeNames = ["README.md", "readme.md", "README", "readme"];
|
|
135
|
+
|
|
136
|
+
for (const name of readmeNames) {
|
|
137
|
+
const readmePath = join(toolDir, name);
|
|
138
|
+
if (existsSync(readmePath)) {
|
|
139
|
+
return readFileSync(readmePath, "utf-8");
|
|
140
|
+
}
|
|
141
|
+
}
|
|
142
|
+
|
|
143
|
+
return undefined;
|
|
144
|
+
}
|
|
145
|
+
|
|
146
|
+
/**
|
|
147
|
+
* Load and validate manifest from file or directory
|
|
148
|
+
*/
|
|
149
|
+
async function loadAndValidateManifest(
|
|
150
|
+
pathArg: string,
|
|
151
|
+
ctx: CommandContext
|
|
152
|
+
): Promise<{ manifest: ToolManifest; toolDir: string }> {
|
|
153
|
+
const fullPath = resolve(ctx.cwd, pathArg);
|
|
154
|
+
|
|
155
|
+
if (!existsSync(fullPath)) {
|
|
156
|
+
throw new Error(`Path not found: ${fullPath}`);
|
|
157
|
+
}
|
|
158
|
+
|
|
159
|
+
// Load manifest - handle both files and directories
|
|
160
|
+
let loaded: LoadedManifest | undefined;
|
|
161
|
+
const stats = statSync(fullPath);
|
|
162
|
+
if (stats.isDirectory()) {
|
|
163
|
+
loaded = loadManifestFromDir(fullPath);
|
|
164
|
+
} else {
|
|
165
|
+
loaded = loadManifest(fullPath);
|
|
166
|
+
}
|
|
167
|
+
|
|
168
|
+
if (!loaded) {
|
|
169
|
+
throw new Error(`Could not load manifest from: ${fullPath}`);
|
|
170
|
+
}
|
|
171
|
+
|
|
172
|
+
// Validate manifest
|
|
173
|
+
const validation = validateManifest(loaded.manifest);
|
|
174
|
+
if (!validation.valid) {
|
|
175
|
+
const errors = validation.errors?.map((e) => ` - ${e}`).join("\n");
|
|
176
|
+
throw new Error(`Invalid manifest:\n${errors}`);
|
|
177
|
+
}
|
|
178
|
+
|
|
179
|
+
return {
|
|
180
|
+
manifest: loaded.manifest,
|
|
181
|
+
toolDir: dirname(loaded.filePath),
|
|
182
|
+
};
|
|
183
|
+
}
|
|
184
|
+
|
|
185
|
+
/**
|
|
186
|
+
* Publish command handler
|
|
187
|
+
*/
|
|
188
|
+
async function publishHandler(
|
|
189
|
+
pathArg: string,
|
|
190
|
+
options: PublishOptions,
|
|
191
|
+
ctx: CommandContext
|
|
192
|
+
): Promise<void> {
|
|
193
|
+
// Load and validate manifest
|
|
194
|
+
const { manifest, toolDir } = await loadAndValidateManifest(pathArg, ctx);
|
|
195
|
+
|
|
196
|
+
const toolName = manifest.name;
|
|
197
|
+
const version = manifest.version ?? "0.0.0";
|
|
198
|
+
|
|
199
|
+
header(`Publishing ${toolName}@${version}`);
|
|
200
|
+
newline();
|
|
201
|
+
|
|
202
|
+
// Show what we're publishing
|
|
203
|
+
keyValue("Name", toolName);
|
|
204
|
+
keyValue("Version", version);
|
|
205
|
+
keyValue("Description", manifest.description);
|
|
206
|
+
if (manifest.tags && manifest.tags.length > 0) {
|
|
207
|
+
keyValue("Tags", manifest.tags.join(", "));
|
|
208
|
+
}
|
|
209
|
+
newline();
|
|
210
|
+
|
|
211
|
+
// Get registry URL from config or environment
|
|
212
|
+
const config = loadConfig();
|
|
213
|
+
const registryUrl =
|
|
214
|
+
process.env.ENACT_REGISTRY_URL ??
|
|
215
|
+
config.registry?.url ??
|
|
216
|
+
"https://siikwkfgsmouioodghho.supabase.co/functions/v1";
|
|
217
|
+
|
|
218
|
+
if (options.verbose) {
|
|
219
|
+
keyValue("Registry", registryUrl);
|
|
220
|
+
}
|
|
221
|
+
|
|
222
|
+
// Check for auth token from keyring (skip in local dev mode)
|
|
223
|
+
let authToken: string | undefined;
|
|
224
|
+
if (options.skipAuth) {
|
|
225
|
+
warning("Skipping authentication (local development mode)");
|
|
226
|
+
// For local dev, use the Supabase anon key from environment or default local key
|
|
227
|
+
authToken =
|
|
228
|
+
process.env.SUPABASE_ANON_KEY ??
|
|
229
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZS1kZW1vIiwicm9sZSI6ImFub24iLCJleHAiOjE5ODM4MTI5OTZ9.CRXP1A7WOeoJeXxjNni43kdQwgnWNReilDMblYTn_I0";
|
|
230
|
+
} else {
|
|
231
|
+
const secretToken = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
|
|
232
|
+
authToken = secretToken ?? undefined;
|
|
233
|
+
if (!authToken) {
|
|
234
|
+
// Check config registry authToken
|
|
235
|
+
authToken = config.registry?.authToken;
|
|
236
|
+
}
|
|
237
|
+
if (!authToken) {
|
|
238
|
+
// Also check environment variable for CI/local dev
|
|
239
|
+
authToken = process.env.ENACT_AUTH_TOKEN;
|
|
240
|
+
}
|
|
241
|
+
if (!authToken) {
|
|
242
|
+
// Fallback to official registry anon key if using official registry
|
|
243
|
+
if (registryUrl.includes("siikwkfgsmouioodghho.supabase.co")) {
|
|
244
|
+
authToken =
|
|
245
|
+
"eyJhbGciOiJIUzI1NiIsInR5cCI6IkpXVCJ9.eyJpc3MiOiJzdXBhYmFzZSIsInJlZiI6InNpaWt3a2Znc21vdWlvb2RnaGhvIiwicm9sZSI6ImFub24iLCJpYXQiOjE3NjQ2MTkzMzksImV4cCI6MjA4MDE5NTMzOX0.kxnx6-IPFhmGx6rzNx36vbyhFMFZKP_jFqaDbKnJ_E0";
|
|
246
|
+
}
|
|
247
|
+
}
|
|
248
|
+
if (!authToken) {
|
|
249
|
+
error("Not authenticated. Please run: enact auth login");
|
|
250
|
+
dim("Or set ENACT_AUTH_TOKEN environment variable");
|
|
251
|
+
dim("Or use --skip-auth for local development");
|
|
252
|
+
process.exit(1);
|
|
253
|
+
}
|
|
254
|
+
}
|
|
255
|
+
|
|
256
|
+
const client = createApiClient({ baseUrl: registryUrl });
|
|
257
|
+
if (authToken) {
|
|
258
|
+
client.setAuthToken(authToken);
|
|
259
|
+
}
|
|
260
|
+
|
|
261
|
+
// Dry run mode
|
|
262
|
+
if (options.dryRun) {
|
|
263
|
+
warning("Dry run mode - not actually publishing");
|
|
264
|
+
newline();
|
|
265
|
+
info("Would publish to registry:");
|
|
266
|
+
keyValue("Tool", toolName);
|
|
267
|
+
keyValue("Version", version);
|
|
268
|
+
keyValue("Source", toolDir);
|
|
269
|
+
|
|
270
|
+
// Show files that would be bundled
|
|
271
|
+
const files = collectFiles(toolDir, toolDir);
|
|
272
|
+
info(`Would bundle ${files.length} files`);
|
|
273
|
+
if (options.verbose) {
|
|
274
|
+
for (const file of files.slice(0, 10)) {
|
|
275
|
+
dim(` ${file.relativePath}`);
|
|
276
|
+
}
|
|
277
|
+
if (files.length > 10) {
|
|
278
|
+
dim(` ... and ${files.length - 10} more`);
|
|
279
|
+
}
|
|
280
|
+
}
|
|
281
|
+
return;
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Load README if available
|
|
285
|
+
const readme = loadReadme(toolDir);
|
|
286
|
+
if (readme) {
|
|
287
|
+
info("Found README.md");
|
|
288
|
+
}
|
|
289
|
+
|
|
290
|
+
// Create bundle
|
|
291
|
+
const bundle = await withSpinner("Creating bundle...", async () => {
|
|
292
|
+
return await createBundleFromDir(toolDir);
|
|
293
|
+
});
|
|
294
|
+
|
|
295
|
+
info(`Bundle size: ${(bundle.length / 1024).toFixed(1)} KB`);
|
|
296
|
+
|
|
297
|
+
// Publish to registry using v2 multipart API
|
|
298
|
+
const result = await withSpinner("Publishing to registry...", async () => {
|
|
299
|
+
return await publishTool(client, {
|
|
300
|
+
name: toolName,
|
|
301
|
+
manifest: manifest as unknown as Record<string, unknown>,
|
|
302
|
+
bundle,
|
|
303
|
+
readme,
|
|
304
|
+
});
|
|
305
|
+
});
|
|
306
|
+
|
|
307
|
+
// JSON output
|
|
308
|
+
if (options.json) {
|
|
309
|
+
json(result);
|
|
310
|
+
return;
|
|
311
|
+
}
|
|
312
|
+
|
|
313
|
+
// Success output
|
|
314
|
+
newline();
|
|
315
|
+
success(`Published ${result.name}@${result.version}`);
|
|
316
|
+
keyValue("Bundle Hash", result.bundleHash);
|
|
317
|
+
keyValue("Published At", result.publishedAt.toISOString());
|
|
318
|
+
newline();
|
|
319
|
+
dim(`Install with: enact install ${toolName}`);
|
|
320
|
+
}
|
|
321
|
+
|
|
322
|
+
/**
|
|
323
|
+
* Configure the publish command
|
|
324
|
+
*/
|
|
325
|
+
export function configurePublishCommand(program: Command): void {
|
|
326
|
+
program
|
|
327
|
+
.command("publish [path]")
|
|
328
|
+
.description("Publish a tool to the Enact registry")
|
|
329
|
+
.option("-n, --dry-run", "Show what would be published without publishing")
|
|
330
|
+
.option("-t, --tag <tag>", "Add a release tag (e.g., latest, beta)")
|
|
331
|
+
.option("-v, --verbose", "Show detailed output")
|
|
332
|
+
.option("--skip-auth", "Skip authentication (for local development)")
|
|
333
|
+
.option("--json", "Output as JSON")
|
|
334
|
+
.action(async (pathArg: string | undefined, options: PublishOptions) => {
|
|
335
|
+
const resolvedPath = pathArg ?? ".";
|
|
336
|
+
const ctx: CommandContext = {
|
|
337
|
+
cwd: process.cwd(),
|
|
338
|
+
options,
|
|
339
|
+
isCI: Boolean(process.env.CI),
|
|
340
|
+
isInteractive: process.stdout.isTTY ?? false,
|
|
341
|
+
};
|
|
342
|
+
|
|
343
|
+
try {
|
|
344
|
+
await publishHandler(resolvedPath, options, ctx);
|
|
345
|
+
} catch (err) {
|
|
346
|
+
error(formatError(err));
|
|
347
|
+
process.exit(1);
|
|
348
|
+
}
|
|
349
|
+
});
|
|
350
|
+
}
|