@enactprotocol/cli 2.3.5 → 2.3.8
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/commands/index.d.ts +1 -0
- package/dist/commands/index.d.ts.map +1 -1
- package/dist/commands/index.js +2 -0
- package/dist/commands/index.js.map +1 -1
- package/dist/commands/init/index.d.ts.map +1 -1
- package/dist/commands/init/index.js +94 -46
- package/dist/commands/init/index.js.map +1 -1
- package/dist/commands/init/templates/agent-agents.d.ts +1 -1
- package/dist/commands/init/templates/agent-agents.d.ts.map +1 -1
- package/dist/commands/init/templates/agent-agents.js +1 -1
- package/dist/commands/init/templates/default-skill-package.d.ts +5 -0
- package/dist/commands/init/templates/default-skill-package.d.ts.map +1 -0
- package/dist/commands/init/templates/default-skill-package.js +13 -0
- package/dist/commands/init/templates/default-skill-package.js.map +1 -0
- package/dist/commands/init/templates/default-skill-script.d.ts +5 -0
- package/dist/commands/init/templates/default-skill-script.d.ts.map +1 -0
- package/dist/commands/init/templates/default-skill-script.js +11 -0
- package/dist/commands/init/templates/default-skill-script.js.map +1 -0
- package/dist/commands/init/templates/index.d.ts +2 -0
- package/dist/commands/init/templates/index.d.ts.map +1 -1
- package/dist/commands/init/templates/index.js +2 -0
- package/dist/commands/init/templates/index.js.map +1 -1
- package/dist/commands/install/index.d.ts +2 -2
- package/dist/commands/install/index.js +18 -18
- package/dist/commands/install/index.js.map +1 -1
- package/dist/commands/list/index.d.ts +2 -2
- package/dist/commands/list/index.js +3 -3
- package/dist/commands/list/index.js.map +1 -1
- package/dist/commands/org/index.d.ts +16 -0
- package/dist/commands/org/index.d.ts.map +1 -0
- package/dist/commands/org/index.js +203 -0
- package/dist/commands/org/index.js.map +1 -0
- package/dist/commands/publish/index.d.ts.map +1 -1
- package/dist/commands/publish/index.js +5 -1
- package/dist/commands/publish/index.js.map +1 -1
- package/dist/commands/search/index.js +2 -2
- package/dist/commands/search/index.js.map +1 -1
- package/dist/index.d.ts +1 -1
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +4 -2
- package/dist/index.js.map +1 -1
- package/dist/utils/errors.js +2 -2
- package/dist/utils/errors.js.map +1 -1
- package/package.json +6 -6
- package/src/commands/env/README.md +1 -1
- package/src/commands/index.ts +3 -0
- package/src/commands/init/index.ts +103 -46
- package/src/commands/init/templates/agent-agents.ts +1 -1
- package/src/commands/init/templates/default-skill-package.ts +12 -0
- package/src/commands/init/templates/default-skill-script.ts +10 -0
- package/src/commands/init/templates/index.ts +2 -0
- package/src/commands/install/README.md +2 -2
- package/src/commands/install/index.ts +18 -18
- package/src/commands/list/index.ts +3 -3
- package/src/commands/org/index.ts +263 -0
- package/src/commands/publish/index.ts +4 -1
- package/src/commands/run/README.md +1 -1
- package/src/commands/search/index.ts +2 -2
- package/src/index.ts +5 -1
- package/src/utils/errors.ts +2 -2
- package/tests/commands/cache.test.ts +2 -2
- package/tests/commands/init.test.ts +66 -55
- package/tests/commands/install-integration.test.ts +11 -11
- package/tests/commands/publish.test.ts +5 -3
- package/tests/commands/sign.test.ts +1 -1
- package/tests/e2e.test.ts +13 -11
- package/tests/utils/ignore.test.ts +2 -1
- package/tsconfig.tsbuildinfo +1 -1
- /package/tests/fixtures/calculator/{skill.yaml → skill.package.yaml} +0 -0
- /package/tests/fixtures/env-tool/{skill.yaml → skill.package.yaml} +0 -0
- /package/tests/fixtures/greeter/{skill.yaml → skill.package.yaml} +0 -0
- /package/tests/fixtures/invalid-tool/{skill.yaml → skill.package.yaml} +0 -0
|
@@ -0,0 +1,263 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* enact org command
|
|
3
|
+
*
|
|
4
|
+
* Manage organizations for @org scoped tool namespaces.
|
|
5
|
+
*
|
|
6
|
+
* Subcommands:
|
|
7
|
+
* - create: Create a new organization
|
|
8
|
+
* - info: Show organization details
|
|
9
|
+
* - list: List your organizations (TODO: requires user orgs endpoint)
|
|
10
|
+
* - add-member: Add a member to an organization
|
|
11
|
+
* - remove-member: Remove a member from an organization
|
|
12
|
+
* - set-role: Change a member's role
|
|
13
|
+
*/
|
|
14
|
+
|
|
15
|
+
import {
|
|
16
|
+
addOrgMember,
|
|
17
|
+
createApiClient,
|
|
18
|
+
createOrg,
|
|
19
|
+
getOrg,
|
|
20
|
+
listOrgMembers,
|
|
21
|
+
removeOrgMember,
|
|
22
|
+
updateOrgMemberRole,
|
|
23
|
+
} from "@enactprotocol/api";
|
|
24
|
+
import { getSecret } from "@enactprotocol/secrets";
|
|
25
|
+
import { loadConfig } from "@enactprotocol/shared";
|
|
26
|
+
import type { OrgRole } from "@enactprotocol/shared";
|
|
27
|
+
import type { Command } from "commander";
|
|
28
|
+
import type { GlobalOptions } from "../../types";
|
|
29
|
+
import {
|
|
30
|
+
type TableColumn,
|
|
31
|
+
dim,
|
|
32
|
+
error,
|
|
33
|
+
formatError,
|
|
34
|
+
header,
|
|
35
|
+
json,
|
|
36
|
+
keyValue,
|
|
37
|
+
newline,
|
|
38
|
+
success,
|
|
39
|
+
table,
|
|
40
|
+
} from "../../utils";
|
|
41
|
+
|
|
42
|
+
const AUTH_NAMESPACE = "enact:auth";
|
|
43
|
+
const ACCESS_TOKEN_KEY = "access_token";
|
|
44
|
+
|
|
45
|
+
const VALID_ROLES = ["owner", "admin", "member"] as const;
|
|
46
|
+
|
|
47
|
+
async function getClient() {
|
|
48
|
+
const config = loadConfig();
|
|
49
|
+
const token = await getSecret(AUTH_NAMESPACE, ACCESS_TOKEN_KEY);
|
|
50
|
+
return createApiClient({
|
|
51
|
+
baseUrl: config.registry?.url,
|
|
52
|
+
authToken: token ?? undefined,
|
|
53
|
+
});
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
// ---------------------------------------------------------------------------
|
|
57
|
+
// Handlers
|
|
58
|
+
// ---------------------------------------------------------------------------
|
|
59
|
+
|
|
60
|
+
async function createHandler(
|
|
61
|
+
name: string,
|
|
62
|
+
options: { displayName?: string; description?: string } & GlobalOptions
|
|
63
|
+
) {
|
|
64
|
+
try {
|
|
65
|
+
const client = await getClient();
|
|
66
|
+
const org = await createOrg(client, {
|
|
67
|
+
name,
|
|
68
|
+
displayName: options.displayName,
|
|
69
|
+
description: options.description,
|
|
70
|
+
});
|
|
71
|
+
|
|
72
|
+
if (options.json) {
|
|
73
|
+
json(org);
|
|
74
|
+
return;
|
|
75
|
+
}
|
|
76
|
+
|
|
77
|
+
success(`Organization "@${org.name}" created`);
|
|
78
|
+
newline();
|
|
79
|
+
keyValue("Name", `@${org.name}`);
|
|
80
|
+
if (org.display_name) keyValue("Display Name", org.display_name);
|
|
81
|
+
if (org.description) keyValue("Description", org.description);
|
|
82
|
+
newline();
|
|
83
|
+
dim("You are the owner. Publish tools with:");
|
|
84
|
+
dim(` enact publish (with name: "@${org.name}/your-tool" in SKILL.md)`);
|
|
85
|
+
} catch (err) {
|
|
86
|
+
error(formatError(err));
|
|
87
|
+
process.exit(1);
|
|
88
|
+
}
|
|
89
|
+
}
|
|
90
|
+
|
|
91
|
+
async function infoHandler(name: string, options: GlobalOptions) {
|
|
92
|
+
try {
|
|
93
|
+
// Strip @ prefix if provided
|
|
94
|
+
const orgName = name.startsWith("@") ? name.substring(1) : name;
|
|
95
|
+
const client = await getClient();
|
|
96
|
+
|
|
97
|
+
const org = await getOrg(client, orgName);
|
|
98
|
+
const members = await listOrgMembers(client, orgName);
|
|
99
|
+
|
|
100
|
+
if (options.json) {
|
|
101
|
+
json({ ...org, members });
|
|
102
|
+
return;
|
|
103
|
+
}
|
|
104
|
+
|
|
105
|
+
header(`@${org.name}`);
|
|
106
|
+
if (org.display_name) keyValue("Display Name", org.display_name);
|
|
107
|
+
if (org.description) keyValue("Description", org.description);
|
|
108
|
+
keyValue("Tools", String(org.tool_count));
|
|
109
|
+
keyValue("Members", String(org.member_count));
|
|
110
|
+
keyValue("Created", org.created_at);
|
|
111
|
+
|
|
112
|
+
newline();
|
|
113
|
+
header("Members");
|
|
114
|
+
const columns: TableColumn[] = [
|
|
115
|
+
{ header: "Username", key: "username", width: 20 },
|
|
116
|
+
{ header: "Role", key: "role", width: 10 },
|
|
117
|
+
{ header: "Added", key: "added_at", width: 20 },
|
|
118
|
+
];
|
|
119
|
+
table(
|
|
120
|
+
members.map((m) => ({
|
|
121
|
+
username: m.username,
|
|
122
|
+
role: m.role,
|
|
123
|
+
added_at: m.added_at,
|
|
124
|
+
})),
|
|
125
|
+
columns
|
|
126
|
+
);
|
|
127
|
+
} catch (err) {
|
|
128
|
+
error(formatError(err));
|
|
129
|
+
process.exit(1);
|
|
130
|
+
}
|
|
131
|
+
}
|
|
132
|
+
|
|
133
|
+
async function addMemberHandler(
|
|
134
|
+
orgName: string,
|
|
135
|
+
username: string,
|
|
136
|
+
options: { role?: string } & GlobalOptions
|
|
137
|
+
) {
|
|
138
|
+
try {
|
|
139
|
+
const org = orgName.startsWith("@") ? orgName.substring(1) : orgName;
|
|
140
|
+
const role = (options.role ?? "member") as OrgRole;
|
|
141
|
+
|
|
142
|
+
if (!VALID_ROLES.includes(role as (typeof VALID_ROLES)[number])) {
|
|
143
|
+
error(`Invalid role: ${role}. Must be one of: ${VALID_ROLES.join(", ")}`);
|
|
144
|
+
process.exit(1);
|
|
145
|
+
}
|
|
146
|
+
|
|
147
|
+
const client = await getClient();
|
|
148
|
+
await addOrgMember(client, org, { username, role });
|
|
149
|
+
|
|
150
|
+
if (options.json) {
|
|
151
|
+
json({ username, role, org });
|
|
152
|
+
return;
|
|
153
|
+
}
|
|
154
|
+
|
|
155
|
+
success(`Added "${username}" to @${org} as ${role}`);
|
|
156
|
+
} catch (err) {
|
|
157
|
+
error(formatError(err));
|
|
158
|
+
process.exit(1);
|
|
159
|
+
}
|
|
160
|
+
}
|
|
161
|
+
|
|
162
|
+
async function removeMemberHandler(orgName: string, username: string, options: GlobalOptions) {
|
|
163
|
+
try {
|
|
164
|
+
const org = orgName.startsWith("@") ? orgName.substring(1) : orgName;
|
|
165
|
+
const client = await getClient();
|
|
166
|
+
await removeOrgMember(client, org, username);
|
|
167
|
+
|
|
168
|
+
if (options.json) {
|
|
169
|
+
json({ removed: username, org });
|
|
170
|
+
return;
|
|
171
|
+
}
|
|
172
|
+
|
|
173
|
+
success(`Removed "${username}" from @${org}`);
|
|
174
|
+
} catch (err) {
|
|
175
|
+
error(formatError(err));
|
|
176
|
+
process.exit(1);
|
|
177
|
+
}
|
|
178
|
+
}
|
|
179
|
+
|
|
180
|
+
async function setRoleHandler(
|
|
181
|
+
orgName: string,
|
|
182
|
+
username: string,
|
|
183
|
+
role: string,
|
|
184
|
+
options: GlobalOptions
|
|
185
|
+
) {
|
|
186
|
+
try {
|
|
187
|
+
const org = orgName.startsWith("@") ? orgName.substring(1) : orgName;
|
|
188
|
+
|
|
189
|
+
if (!VALID_ROLES.includes(role as (typeof VALID_ROLES)[number])) {
|
|
190
|
+
error(`Invalid role: ${role}. Must be one of: ${VALID_ROLES.join(", ")}`);
|
|
191
|
+
process.exit(1);
|
|
192
|
+
}
|
|
193
|
+
|
|
194
|
+
const client = await getClient();
|
|
195
|
+
await updateOrgMemberRole(client, org, username, role as OrgRole);
|
|
196
|
+
|
|
197
|
+
if (options.json) {
|
|
198
|
+
json({ username, role, org });
|
|
199
|
+
return;
|
|
200
|
+
}
|
|
201
|
+
|
|
202
|
+
success(`Set "${username}" role to ${role} in @${org}`);
|
|
203
|
+
} catch (err) {
|
|
204
|
+
error(formatError(err));
|
|
205
|
+
process.exit(1);
|
|
206
|
+
}
|
|
207
|
+
}
|
|
208
|
+
|
|
209
|
+
// ---------------------------------------------------------------------------
|
|
210
|
+
// Command Configuration
|
|
211
|
+
// ---------------------------------------------------------------------------
|
|
212
|
+
|
|
213
|
+
export function configureOrgCommand(program: Command): void {
|
|
214
|
+
const org = program.command("org").description("Manage organizations (@org scoped namespaces)");
|
|
215
|
+
|
|
216
|
+
// enact org create <name>
|
|
217
|
+
org
|
|
218
|
+
.command("create <name>")
|
|
219
|
+
.description("Create a new organization")
|
|
220
|
+
.option("--display-name <name>", "Display name for the organization")
|
|
221
|
+
.option("--description <desc>", "Organization description")
|
|
222
|
+
.option("--json", "Output as JSON")
|
|
223
|
+
.action(async (name: string, options) => {
|
|
224
|
+
await createHandler(name, options);
|
|
225
|
+
});
|
|
226
|
+
|
|
227
|
+
// enact org info <name>
|
|
228
|
+
org
|
|
229
|
+
.command("info <name>")
|
|
230
|
+
.description("Show organization details and members")
|
|
231
|
+
.option("--json", "Output as JSON")
|
|
232
|
+
.action(async (name: string, options) => {
|
|
233
|
+
await infoHandler(name, options);
|
|
234
|
+
});
|
|
235
|
+
|
|
236
|
+
// enact org add-member <org> <username>
|
|
237
|
+
org
|
|
238
|
+
.command("add-member <org> <username>")
|
|
239
|
+
.description("Add a member to an organization")
|
|
240
|
+
.option("--role <role>", "Member role (owner, admin, member)", "member")
|
|
241
|
+
.option("--json", "Output as JSON")
|
|
242
|
+
.action(async (orgName: string, username: string, options) => {
|
|
243
|
+
await addMemberHandler(orgName, username, options);
|
|
244
|
+
});
|
|
245
|
+
|
|
246
|
+
// enact org remove-member <org> <username>
|
|
247
|
+
org
|
|
248
|
+
.command("remove-member <org> <username>")
|
|
249
|
+
.description("Remove a member from an organization")
|
|
250
|
+
.option("--json", "Output as JSON")
|
|
251
|
+
.action(async (orgName: string, username: string, options) => {
|
|
252
|
+
await removeMemberHandler(orgName, username, options);
|
|
253
|
+
});
|
|
254
|
+
|
|
255
|
+
// enact org set-role <org> <username> <role>
|
|
256
|
+
org
|
|
257
|
+
.command("set-role <org> <username> <role>")
|
|
258
|
+
.description("Change a member's role (owner, admin, member)")
|
|
259
|
+
.option("--json", "Output as JSON")
|
|
260
|
+
.action(async (orgName: string, username: string, role: string, options) => {
|
|
261
|
+
await setRoleHandler(orgName, username, role, options);
|
|
262
|
+
});
|
|
263
|
+
}
|
|
@@ -320,7 +320,10 @@ async function publishHandler(
|
|
|
320
320
|
const currentUsername = await getCurrentUsername();
|
|
321
321
|
if (currentUsername) {
|
|
322
322
|
const toolNamespace = extractNamespace(toolName);
|
|
323
|
-
if (toolNamespace
|
|
323
|
+
if (toolNamespace.startsWith("@")) {
|
|
324
|
+
// Org-scoped tool — server will enforce org membership
|
|
325
|
+
dim(`Publishing to organization namespace "${toolNamespace}"...`);
|
|
326
|
+
} else if (toolNamespace !== currentUsername) {
|
|
324
327
|
error(
|
|
325
328
|
`Namespace mismatch: Tool namespace "${toolNamespace}" does not match your username "${currentUsername}".`
|
|
326
329
|
);
|
|
@@ -10,7 +10,7 @@ enact run <tool> [options]
|
|
|
10
10
|
|
|
11
11
|
## Description
|
|
12
12
|
|
|
13
|
-
The `run` command executes a tool using the command defined in its manifest (`skill.
|
|
13
|
+
The `run` command executes a tool using the command defined in its manifest (`skill.package.yml` or `SKILL.md`). The tool runs in an isolated container environment with:
|
|
14
14
|
|
|
15
15
|
- Input validation against the tool's JSON Schema
|
|
16
16
|
- Automatic secret resolution from the OS keyring
|
|
@@ -379,8 +379,8 @@ export function configureSearchCommand(program: Command): void {
|
|
|
379
379
|
program
|
|
380
380
|
.command("search <query>")
|
|
381
381
|
.description("Search the Enact registry for tools")
|
|
382
|
-
.option("--local", "Search project
|
|
383
|
-
.option("-g, --global", "Search global
|
|
382
|
+
.option("--local", "Search project skills (agents/skills/) instead of registry")
|
|
383
|
+
.option("-g, --global", "Search global skills (~/.agents/skills/) instead of registry")
|
|
384
384
|
.option("-t, --tags <tags>", "Filter by tags (comma-separated, registry only)")
|
|
385
385
|
.option("-l, --limit <number>", "Maximum results to return (default: 20, registry only)")
|
|
386
386
|
.option("-o, --offset <number>", "Pagination offset (default: 0, registry only)")
|
package/src/index.ts
CHANGED
|
@@ -22,6 +22,7 @@ import {
|
|
|
22
22
|
configureLearnCommand,
|
|
23
23
|
configureListCommand,
|
|
24
24
|
configureMcpCommand,
|
|
25
|
+
configureOrgCommand,
|
|
25
26
|
configurePublishCommand,
|
|
26
27
|
configureReportCommand,
|
|
27
28
|
configureRunCommand,
|
|
@@ -37,7 +38,7 @@ import {
|
|
|
37
38
|
} from "./commands";
|
|
38
39
|
import { error, formatError } from "./utils";
|
|
39
40
|
|
|
40
|
-
export const version = "2.3.
|
|
41
|
+
export const version = "2.3.8";
|
|
41
42
|
|
|
42
43
|
// Export types for external use
|
|
43
44
|
export type { GlobalOptions, CommandContext } from "./types";
|
|
@@ -93,6 +94,9 @@ async function main() {
|
|
|
93
94
|
// Self-hosted registry
|
|
94
95
|
configureServeCommand(program);
|
|
95
96
|
|
|
97
|
+
// Organizations
|
|
98
|
+
configureOrgCommand(program);
|
|
99
|
+
|
|
96
100
|
// Global error handler - handle Commander's help/version exits gracefully
|
|
97
101
|
program.exitOverride((err) => {
|
|
98
102
|
// Commander throws errors for help, version, and other "exit" scenarios
|
package/src/utils/errors.ts
CHANGED
|
@@ -84,7 +84,7 @@ export class ManifestError extends CliError {
|
|
|
84
84
|
super(
|
|
85
85
|
fullMessage,
|
|
86
86
|
EXIT_MANIFEST_ERROR,
|
|
87
|
-
"Ensure the directory contains a valid skill.yaml or SKILL.md file."
|
|
87
|
+
"Ensure the directory contains a valid skill.package.yaml, skill.package.yml, or SKILL.md file."
|
|
88
88
|
);
|
|
89
89
|
this.name = "ManifestError";
|
|
90
90
|
}
|
|
@@ -377,7 +377,7 @@ export const ErrorMessages = {
|
|
|
377
377
|
message: `No manifest found in ${dir}`,
|
|
378
378
|
suggestions: [
|
|
379
379
|
`Create a manifest: ${colors.command("enact init")}`,
|
|
380
|
-
"Ensure the directory contains skill.yaml or SKILL.md",
|
|
380
|
+
"Ensure the directory contains skill.package.yaml, skill.package.yml, or SKILL.md",
|
|
381
381
|
],
|
|
382
382
|
}),
|
|
383
383
|
|
|
@@ -271,14 +271,14 @@ describe("cache command", () => {
|
|
|
271
271
|
}
|
|
272
272
|
|
|
273
273
|
const info: CacheInfo = {
|
|
274
|
-
path: "/Users/test/.
|
|
274
|
+
path: "/Users/test/.agents/skills",
|
|
275
275
|
totalSize: 10_485_760, // 10 MB
|
|
276
276
|
toolCount: 25,
|
|
277
277
|
oldestEntry: "2024-01-01T00:00:00Z",
|
|
278
278
|
newestEntry: "2024-01-20T12:00:00Z",
|
|
279
279
|
};
|
|
280
280
|
|
|
281
|
-
expect(info.path).toContain(".
|
|
281
|
+
expect(info.path).toContain(".agents");
|
|
282
282
|
expect(info.totalSize).toBeGreaterThan(0);
|
|
283
283
|
expect(info.toolCount).toBeGreaterThanOrEqual(0);
|
|
284
284
|
});
|
|
@@ -107,7 +107,7 @@ describe("init command", () => {
|
|
|
107
107
|
}
|
|
108
108
|
});
|
|
109
109
|
|
|
110
|
-
test("default mode creates
|
|
110
|
+
test("default mode creates skill.package.yaml", async () => {
|
|
111
111
|
const program = new Command();
|
|
112
112
|
program.exitOverride();
|
|
113
113
|
configureInitCommand(program);
|
|
@@ -116,27 +116,23 @@ describe("init command", () => {
|
|
|
116
116
|
process.chdir(testDir);
|
|
117
117
|
|
|
118
118
|
try {
|
|
119
|
-
await program.parseAsync(["node", "test", "init"]);
|
|
119
|
+
await program.parseAsync(["node", "test", "init", "--name", "test/my-tool"]);
|
|
120
120
|
} catch {
|
|
121
121
|
// Command may throw due to exitOverride
|
|
122
122
|
} finally {
|
|
123
123
|
process.chdir(originalCwd);
|
|
124
124
|
}
|
|
125
125
|
|
|
126
|
-
const
|
|
127
|
-
expect(existsSync(
|
|
126
|
+
const packagePath = join(testDir, "skill.package.yaml");
|
|
127
|
+
expect(existsSync(packagePath)).toBe(true);
|
|
128
128
|
|
|
129
|
-
const content = readFileSync(
|
|
130
|
-
expect(content).toContain("
|
|
131
|
-
expect(content).toContain("enact
|
|
132
|
-
expect(content).toContain("
|
|
133
|
-
|
|
134
|
-
// Should NOT create enact.md in default mode
|
|
135
|
-
const manifestPath = join(testDir, "enact.md");
|
|
136
|
-
expect(existsSync(manifestPath)).toBe(false);
|
|
129
|
+
const content = readFileSync(packagePath, "utf-8");
|
|
130
|
+
expect(content).toContain("name: test/my-tool");
|
|
131
|
+
expect(content).toContain("enact:");
|
|
132
|
+
expect(content).toContain("scripts:");
|
|
137
133
|
});
|
|
138
134
|
|
|
139
|
-
test("default mode creates .
|
|
135
|
+
test("default mode creates hello.py", async () => {
|
|
140
136
|
const program = new Command();
|
|
141
137
|
program.exitOverride();
|
|
142
138
|
configureInitCommand(program);
|
|
@@ -145,18 +141,19 @@ describe("init command", () => {
|
|
|
145
141
|
process.chdir(testDir);
|
|
146
142
|
|
|
147
143
|
try {
|
|
148
|
-
await program.parseAsync(["node", "test", "init"]);
|
|
144
|
+
await program.parseAsync(["node", "test", "init", "--name", "test/my-tool"]);
|
|
149
145
|
} catch {
|
|
150
146
|
// Command may throw due to exitOverride
|
|
151
147
|
} finally {
|
|
152
148
|
process.chdir(originalCwd);
|
|
153
149
|
}
|
|
154
150
|
|
|
155
|
-
const
|
|
156
|
-
expect(existsSync(
|
|
151
|
+
const scriptPath = join(testDir, "hello.py");
|
|
152
|
+
expect(existsSync(scriptPath)).toBe(true);
|
|
157
153
|
|
|
158
|
-
const content =
|
|
159
|
-
expect(content).
|
|
154
|
+
const content = readFileSync(scriptPath, "utf-8");
|
|
155
|
+
expect(content).toContain("json.dumps");
|
|
156
|
+
expect(content).toContain("Hello");
|
|
160
157
|
});
|
|
161
158
|
|
|
162
159
|
test("--tool mode creates SKILL.md", async () => {
|
|
@@ -267,7 +264,7 @@ describe("init command", () => {
|
|
|
267
264
|
expect(existsSync(join(testDir, "AGENTS.md"))).toBe(false);
|
|
268
265
|
});
|
|
269
266
|
|
|
270
|
-
test("--agent mode creates
|
|
267
|
+
test("--agent mode creates agents/skills.json", async () => {
|
|
271
268
|
const program = new Command();
|
|
272
269
|
program.exitOverride();
|
|
273
270
|
configureInitCommand(program);
|
|
@@ -283,14 +280,14 @@ describe("init command", () => {
|
|
|
283
280
|
process.chdir(originalCwd);
|
|
284
281
|
}
|
|
285
282
|
|
|
286
|
-
const
|
|
287
|
-
expect(existsSync(
|
|
283
|
+
const skillsJsonPath = join(testDir, "agents", "skills.json");
|
|
284
|
+
expect(existsSync(skillsJsonPath)).toBe(true);
|
|
288
285
|
|
|
289
|
-
const content = JSON.parse(readFileSync(
|
|
286
|
+
const content = JSON.parse(readFileSync(skillsJsonPath, "utf-8"));
|
|
290
287
|
expect(content).toEqual({ tools: {} });
|
|
291
288
|
});
|
|
292
289
|
|
|
293
|
-
test("--claude mode creates
|
|
290
|
+
test("--claude mode creates agents/skills.json", async () => {
|
|
294
291
|
const program = new Command();
|
|
295
292
|
program.exitOverride();
|
|
296
293
|
configureInitCommand(program);
|
|
@@ -306,20 +303,20 @@ describe("init command", () => {
|
|
|
306
303
|
process.chdir(originalCwd);
|
|
307
304
|
}
|
|
308
305
|
|
|
309
|
-
const
|
|
310
|
-
expect(existsSync(
|
|
306
|
+
const skillsJsonPath = join(testDir, "agents", "skills.json");
|
|
307
|
+
expect(existsSync(skillsJsonPath)).toBe(true);
|
|
311
308
|
|
|
312
|
-
const content = JSON.parse(readFileSync(
|
|
309
|
+
const content = JSON.parse(readFileSync(skillsJsonPath, "utf-8"));
|
|
313
310
|
expect(content).toEqual({ tools: {} });
|
|
314
311
|
});
|
|
315
312
|
|
|
316
|
-
test("--agent mode with --force overwrites existing
|
|
317
|
-
// Create existing
|
|
318
|
-
const
|
|
319
|
-
mkdirSync(
|
|
320
|
-
const
|
|
313
|
+
test("--agent mode with --force overwrites existing agents/skills.json", async () => {
|
|
314
|
+
// Create existing agents/skills.json with some content
|
|
315
|
+
const agentsDir = join(testDir, "agents");
|
|
316
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
317
|
+
const skillsJsonPath = join(agentsDir, "skills.json");
|
|
321
318
|
const existingContent = { tools: { "some/tool": "1.0.0" } };
|
|
322
|
-
writeFileSync(
|
|
319
|
+
writeFileSync(skillsJsonPath, JSON.stringify(existingContent));
|
|
323
320
|
|
|
324
321
|
const program = new Command();
|
|
325
322
|
program.exitOverride();
|
|
@@ -336,17 +333,17 @@ describe("init command", () => {
|
|
|
336
333
|
process.chdir(originalCwd);
|
|
337
334
|
}
|
|
338
335
|
|
|
339
|
-
const content = JSON.parse(readFileSync(
|
|
336
|
+
const content = JSON.parse(readFileSync(skillsJsonPath, "utf-8"));
|
|
340
337
|
expect(content).toEqual({ tools: {} });
|
|
341
338
|
});
|
|
342
339
|
|
|
343
|
-
test("--agent mode preserves existing
|
|
344
|
-
// Create existing
|
|
345
|
-
const
|
|
346
|
-
mkdirSync(
|
|
347
|
-
const
|
|
340
|
+
test("--agent mode preserves existing agents/skills.json without --force", async () => {
|
|
341
|
+
// Create existing agents/skills.json with some content
|
|
342
|
+
const agentsDir = join(testDir, "agents");
|
|
343
|
+
mkdirSync(agentsDir, { recursive: true });
|
|
344
|
+
const skillsJsonPath = join(agentsDir, "skills.json");
|
|
348
345
|
const existingContent = { tools: { "some/tool": "1.0.0" } };
|
|
349
|
-
writeFileSync(
|
|
346
|
+
writeFileSync(skillsJsonPath, JSON.stringify(existingContent));
|
|
350
347
|
|
|
351
348
|
// Also create AGENTS.md so the command doesn't fail early
|
|
352
349
|
writeFileSync(join(testDir, "AGENTS.md"), "existing");
|
|
@@ -359,10 +356,6 @@ describe("init command", () => {
|
|
|
359
356
|
process.chdir(testDir);
|
|
360
357
|
|
|
361
358
|
try {
|
|
362
|
-
// Without --force, AGENTS.md check will fail and return early
|
|
363
|
-
// So we need to test with --force on AGENTS.md but not tools.json
|
|
364
|
-
// Actually the --force flag applies to both, so let's just verify
|
|
365
|
-
// tools.json is preserved when it exists and no --force
|
|
366
359
|
await program.parseAsync(["node", "test", "init", "--agent"]);
|
|
367
360
|
} catch {
|
|
368
361
|
// Command may throw due to exitOverride or warning about existing file
|
|
@@ -370,12 +363,12 @@ describe("init command", () => {
|
|
|
370
363
|
process.chdir(originalCwd);
|
|
371
364
|
}
|
|
372
365
|
|
|
373
|
-
//
|
|
374
|
-
const content = JSON.parse(readFileSync(
|
|
366
|
+
// skills.json should be preserved since AGENTS.md existed and no --force was used
|
|
367
|
+
const content = JSON.parse(readFileSync(skillsJsonPath, "utf-8"));
|
|
375
368
|
expect(content).toEqual(existingContent);
|
|
376
369
|
});
|
|
377
370
|
|
|
378
|
-
test("--tool mode does NOT create
|
|
371
|
+
test("--tool mode does NOT create agents/skills.json", async () => {
|
|
379
372
|
const program = new Command();
|
|
380
373
|
program.exitOverride();
|
|
381
374
|
configureInitCommand(program);
|
|
@@ -391,8 +384,8 @@ describe("init command", () => {
|
|
|
391
384
|
process.chdir(originalCwd);
|
|
392
385
|
}
|
|
393
386
|
|
|
394
|
-
const
|
|
395
|
-
expect(existsSync(
|
|
387
|
+
const skillsJsonPath = join(testDir, "agents", "skills.json");
|
|
388
|
+
expect(existsSync(skillsJsonPath)).toBe(false);
|
|
396
389
|
});
|
|
397
390
|
|
|
398
391
|
test("SKILL.md contains valid YAML frontmatter", async () => {
|
|
@@ -481,21 +474,39 @@ describe("init command", () => {
|
|
|
481
474
|
expect(content).toContain("enact search");
|
|
482
475
|
expect(content).toContain("enact install");
|
|
483
476
|
expect(content).toContain("enact list");
|
|
484
|
-
expect(content).toContain("
|
|
477
|
+
expect(content).toContain("agents/skills.json");
|
|
485
478
|
});
|
|
486
479
|
});
|
|
487
480
|
|
|
488
481
|
describe("option conflicts", () => {
|
|
489
|
-
test("
|
|
482
|
+
test("default mode creates a skill when no mode specified", async () => {
|
|
490
483
|
const program = new Command();
|
|
484
|
+
program.exitOverride();
|
|
491
485
|
configureInitCommand(program);
|
|
492
486
|
|
|
493
|
-
const
|
|
494
|
-
|
|
495
|
-
|
|
487
|
+
const testDir = join(import.meta.dir, ".test-init-default");
|
|
488
|
+
if (existsSync(testDir)) {
|
|
489
|
+
rmSync(testDir, { recursive: true });
|
|
490
|
+
}
|
|
491
|
+
mkdirSync(testDir, { recursive: true });
|
|
492
|
+
|
|
493
|
+
const originalCwd = process.cwd();
|
|
494
|
+
process.chdir(testDir);
|
|
495
|
+
|
|
496
|
+
try {
|
|
497
|
+
await program.parseAsync(["node", "test", "init", "--name", "test/tool"]);
|
|
498
|
+
} catch {
|
|
499
|
+
// Command may throw due to exitOverride
|
|
500
|
+
} finally {
|
|
501
|
+
process.chdir(originalCwd);
|
|
502
|
+
}
|
|
503
|
+
|
|
504
|
+
// Default should create skill files, not AGENTS.md
|
|
505
|
+
expect(existsSync(join(testDir, "skill.package.yaml"))).toBe(true);
|
|
506
|
+
expect(existsSync(join(testDir, "hello.py"))).toBe(true);
|
|
507
|
+
expect(existsSync(join(testDir, "AGENTS.md"))).toBe(false);
|
|
496
508
|
|
|
497
|
-
|
|
498
|
-
expect(agentOpt?.description).toContain("default");
|
|
509
|
+
rmSync(testDir, { recursive: true });
|
|
499
510
|
});
|
|
500
511
|
});
|
|
501
512
|
|
|
@@ -46,7 +46,7 @@ describe("install integration", () => {
|
|
|
46
46
|
mkdirSync(join(TEST_GLOBAL_HOME, ".enact", "cache"), { recursive: true });
|
|
47
47
|
|
|
48
48
|
// Create sample tool source
|
|
49
|
-
writeFileSync(join(TEST_TOOL_SRC, "skill.
|
|
49
|
+
writeFileSync(join(TEST_TOOL_SRC, "skill.package.yml"), SAMPLE_MANIFEST);
|
|
50
50
|
});
|
|
51
51
|
|
|
52
52
|
beforeEach(() => {
|
|
@@ -92,7 +92,7 @@ describe("install integration", () => {
|
|
|
92
92
|
|
|
93
93
|
// Verify installation
|
|
94
94
|
expect(existsSync(destPath)).toBe(true);
|
|
95
|
-
expect(existsSync(join(destPath, "skill.
|
|
95
|
+
expect(existsSync(join(destPath, "skill.package.yml"))).toBe(true);
|
|
96
96
|
|
|
97
97
|
// Verify manifest can be loaded from destination
|
|
98
98
|
const installedManifest = loadManifestFromDir(destPath);
|
|
@@ -118,7 +118,7 @@ describe("install integration", () => {
|
|
|
118
118
|
// Create v2 source
|
|
119
119
|
const v2Source = join(TEST_BASE, "source-tool-v2");
|
|
120
120
|
mkdirSync(v2Source, { recursive: true });
|
|
121
|
-
writeFileSync(join(v2Source, "skill.
|
|
121
|
+
writeFileSync(join(v2Source, "skill.package.yml"), SAMPLE_MANIFEST_V2);
|
|
122
122
|
|
|
123
123
|
// Simulate force overwrite
|
|
124
124
|
rmSync(destPath, { recursive: true, force: true });
|
|
@@ -167,9 +167,9 @@ describe("install integration", () => {
|
|
|
167
167
|
test("global install extracts to skills path", async () => {
|
|
168
168
|
const { getToolCachePath } = await import("@enactprotocol/shared");
|
|
169
169
|
|
|
170
|
-
// Verify skill path structure (~/.
|
|
170
|
+
// Verify skill path structure (~/.agents/skills/{name}/, no version subdir)
|
|
171
171
|
const skillPath = getToolCachePath("test/sample-tool", "1.0.0");
|
|
172
|
-
expect(skillPath).toContain(".
|
|
172
|
+
expect(skillPath).toContain(".agents");
|
|
173
173
|
expect(skillPath).toContain("skills");
|
|
174
174
|
expect(skillPath).toContain("test/sample-tool");
|
|
175
175
|
});
|
|
@@ -240,7 +240,7 @@ describe("install integration", () => {
|
|
|
240
240
|
const cachePath = join(TEST_GLOBAL_HOME, ".enact", "cache", "test", "cached-tool", "v1.0.0");
|
|
241
241
|
mkdirSync(cachePath, { recursive: true });
|
|
242
242
|
writeFileSync(
|
|
243
|
-
join(cachePath, "skill.
|
|
243
|
+
join(cachePath, "skill.package.yml"),
|
|
244
244
|
`
|
|
245
245
|
name: test/cached-tool
|
|
246
246
|
version: 1.0.0
|
|
@@ -251,7 +251,7 @@ command: echo "cached"
|
|
|
251
251
|
|
|
252
252
|
// The resolver should be able to find this tool once it's registered
|
|
253
253
|
// Note: Full resolver testing is in resolver.test.ts
|
|
254
|
-
expect(existsSync(join(cachePath, "skill.
|
|
254
|
+
expect(existsSync(join(cachePath, "skill.package.yml"))).toBe(true);
|
|
255
255
|
});
|
|
256
256
|
});
|
|
257
257
|
});
|
|
@@ -260,7 +260,7 @@ describe("tools.json edge cases", () => {
|
|
|
260
260
|
const EDGE_TEST_DIR = join(TEST_BASE, "edge-cases");
|
|
261
261
|
|
|
262
262
|
beforeAll(() => {
|
|
263
|
-
mkdirSync(join(EDGE_TEST_DIR, "
|
|
263
|
+
mkdirSync(join(EDGE_TEST_DIR, "agents"), { recursive: true });
|
|
264
264
|
});
|
|
265
265
|
|
|
266
266
|
afterAll(() => {
|
|
@@ -270,10 +270,10 @@ describe("tools.json edge cases", () => {
|
|
|
270
270
|
});
|
|
271
271
|
|
|
272
272
|
beforeEach(() => {
|
|
273
|
-
// Ensure
|
|
274
|
-
mkdirSync(join(EDGE_TEST_DIR, "
|
|
273
|
+
// Ensure agents directory exists
|
|
274
|
+
mkdirSync(join(EDGE_TEST_DIR, "agents"), { recursive: true });
|
|
275
275
|
|
|
276
|
-
const jsonPath = join(EDGE_TEST_DIR, "
|
|
276
|
+
const jsonPath = join(EDGE_TEST_DIR, "agents", "skills.json");
|
|
277
277
|
if (existsSync(jsonPath)) {
|
|
278
278
|
rmSync(jsonPath);
|
|
279
279
|
}
|