@constraint/cli 0.2.0 → 0.3.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 CHANGED
@@ -129,13 +129,14 @@ constraint skills update --all --global
129
129
  Create or edit a standard Agent Skill folder locally, then upload it:
130
130
 
131
131
  ```bash
132
- constraint skills upload ./account-research
132
+ constraint skills upload ./account-research --domain sales_pipeline
133
133
  ```
134
134
 
135
135
  The folder must contain a root `SKILL.md` with `name` and `description` YAML
136
- frontmatter. If the user can manage more than one Constraint domain, the CLI
137
- asks which domain owns the skill. Skill versions are immutable; uploading
138
- changed content creates the next version.
136
+ frontmatter. Pass the owning domain with `--domain`; if it is omitted in an
137
+ interactive terminal, the CLI asks which permitted domain owns the skill.
138
+ Skill versions are immutable; uploading changed content creates the next
139
+ version.
139
140
 
140
141
  ## Report skill runs
141
142
 
package/package.json CHANGED
@@ -1,10 +1,16 @@
1
1
  {
2
2
  "name": "@constraint/cli",
3
- "version": "0.2.0",
3
+ "version": "0.3.0",
4
4
  "description": "Install, publish, and report Agent Skills with Constraint",
5
5
  "type": "module",
6
- "bin": { "constraint": "bin/constraint.js" },
7
- "files": ["bin", "src", "skills"],
6
+ "bin": {
7
+ "constraint": "bin/constraint.js"
8
+ },
9
+ "files": [
10
+ "bin",
11
+ "src",
12
+ "skills"
13
+ ],
8
14
  "scripts": {
9
15
  "check": "node --check bin/constraint.js && node --check src/cli.js",
10
16
  "test": "node --test"
@@ -13,7 +19,12 @@
13
19
  "@clack/prompts": "1.7.0",
14
20
  "picocolors": "1.1.1"
15
21
  },
16
- "engines": { "node": ">=20" },
22
+ "engines": {
23
+ "node": ">=20"
24
+ },
17
25
  "license": "SEE LICENSE IN LICENSE",
18
- "publishConfig": { "access": "public", "provenance": true }
26
+ "publishConfig": {
27
+ "access": "public",
28
+ "provenance": true
29
+ }
19
30
  }
package/src/cli.js CHANGED
@@ -29,7 +29,7 @@ import {
29
29
  uploadSkill,
30
30
  } from './skills.js';
31
31
 
32
- const VERSION = '0.2.0';
32
+ const VERSION = '0.2.2';
33
33
 
34
34
  const HELP = `Constraint Skills CLI
35
35
 
@@ -45,7 +45,7 @@ Usage:
45
45
  constraint skills update <skill> [--agent <agent>]... [--project|--global]
46
46
  [--copy] [--yes]
47
47
  constraint skills update --all [--agent <agent>]... [--project|--global] [--yes]
48
- constraint skills upload <path>
48
+ constraint skills upload <path> --domain <domain>
49
49
  constraint skills run start <skill> --client <client> --session <session-id>
50
50
  constraint skills run complete <run-id> <transcript-path>
51
51
  constraint skills run list [--team <team>]... [--user <user>]... [--skill <skill>]...
@@ -78,6 +78,59 @@ function interactive(out) {
78
78
  return Boolean(process.stdin.isTTY && process.stdout.isTTY && !out.json && !out.quiet);
79
79
  }
80
80
 
81
+ function humanize(value) {
82
+ return String(value || '').replaceAll('_', ' ').replace(/\b\w/g, (letter) => letter.toUpperCase());
83
+ }
84
+
85
+ export function skillListPresentation(items, { installed = false, search = null, scope = 'project' } = {}) {
86
+ if (items.length === 0) {
87
+ if (installed) {
88
+ return {
89
+ title: 'No installed skills',
90
+ message: `Nothing is installed in ${scope} scope yet.\n\nBrowse available skills:\nconstraint skills list`,
91
+ };
92
+ }
93
+ if (search) {
94
+ return {
95
+ title: 'No matching skills',
96
+ message: `Nothing matched “${search}”. Try a broader search.`,
97
+ };
98
+ }
99
+ return {
100
+ title: 'No skills yet',
101
+ message: 'Your organization has not published any skills.\n\nPublish the first one:\nconstraint skills upload <path>',
102
+ };
103
+ }
104
+
105
+ if (installed) {
106
+ return {
107
+ title: `${items.length} installed skill${items.length === 1 ? '' : 's'}`,
108
+ message: items.map((item) => [
109
+ `${item.slug} · v${item.version} · ${item.agent}`,
110
+ `${humanize(item.scope)} · ${humanize(item.mode)} · ${item.path}`,
111
+ ].join('\n')).join('\n\n'),
112
+ };
113
+ }
114
+
115
+ return {
116
+ title: `${items.length} available skill${items.length === 1 ? '' : 's'}`,
117
+ message: items.map((item) => [
118
+ `${item.name} (${item.slug}) · v${item.latest_version} · ${humanize(item.domain)}`,
119
+ item.description,
120
+ ].join('\n')).join('\n\n'),
121
+ };
122
+ }
123
+
124
+ function printSkillList(items, options, out) {
125
+ if (out.json) {
126
+ out.data(items);
127
+ return;
128
+ }
129
+ const presentation = skillListPresentation(items, options);
130
+ if (interactive(out)) note(presentation.message, presentation.title);
131
+ else out.line(`${presentation.title}\n${presentation.message}`);
132
+ }
133
+
81
134
  function takeScope(args) {
82
135
  const project = takeFlag(args, '--project');
83
136
  const global = takeFlag(args, '--global');
@@ -111,6 +164,15 @@ async function chooseDomain(domains) {
111
164
  return selected;
112
165
  }
113
166
 
167
+ export function resolveUploadDomain(value, domains) {
168
+ if (!value) return null;
169
+ const normalized = String(value).trim().toLowerCase().replaceAll('-', '_').replaceAll(' ', '_');
170
+ if (!domains.includes(normalized)) {
171
+ throw new Error(`You do not have permission to upload skills to ${normalized}. Available domains: ${domains.join(', ') || 'none'}.`);
172
+ }
173
+ return normalized;
174
+ }
175
+
114
176
  async function authCommand(command, args, config, out) {
115
177
  rejectUnknown(args);
116
178
  if (args.length) throw new Error(`${command} does not accept positional arguments`);
@@ -182,12 +244,15 @@ async function listSkills(args, config, out) {
182
244
  if (args.length) throw new Error('skills list does not accept positional arguments');
183
245
  if (installed) {
184
246
  if (search) throw new Error('--search cannot be combined with --installed.');
185
- out.data(await installedSkills({ scope: scopeFlag || 'project', agents }));
247
+ const scope = scopeFlag || 'project';
248
+ const skills = await installedSkills({ scope, agents });
249
+ printSkillList(skills, { installed: true, scope }, out);
186
250
  return;
187
251
  }
188
252
  if (scopeFlag || agents.length) throw new Error('--project, --global, and --agent require --installed.');
189
253
  const query = search ? `?search=${encodeURIComponent(search)}` : '';
190
- out.data(await apiRequest(config, `/skills${query}`));
254
+ const skills = await apiRequest(config, `/skills${query}`);
255
+ printSkillList(skills, { search }, out);
191
256
  }
192
257
 
193
258
  async function inspectSkill(args, config, out) {
@@ -295,9 +360,11 @@ async function updateCommand(args, config, out) {
295
360
  }
296
361
 
297
362
  async function uploadCommand(args, config, out) {
363
+ const requestedDomain = takeOption(args, '--domain');
298
364
  rejectUnknown(args);
299
- if (args.length !== 1) throw new Error('Usage: constraint skills upload <path>');
300
- const domain = await chooseDomain(await manageableDomains(config));
365
+ if (args.length !== 1) throw new Error('Usage: constraint skills upload <path> --domain <domain>');
366
+ const domains = await manageableDomains(config);
367
+ const domain = resolveUploadDomain(requestedDomain, domains) || await chooseDomain(domains);
301
368
  const detail = await uploadSkill(config, args[0], domain);
302
369
  out.data({ skill_id: detail.skill_id, slug: detail.slug, domain: detail.domain, version: detail.version.version });
303
370
  }
package/src/skills.js CHANGED
@@ -24,11 +24,6 @@ import {
24
24
  workspaceId,
25
25
  } from './paths.js';
26
26
 
27
- export const DOMAINS = [
28
- 'sales_pipeline', 'content_audience', 'marketing_demand', 'offers_monetization',
29
- 'operations_team', 'partnerships_bd',
30
- ];
31
-
32
27
  function filesFromDetail(detail) {
33
28
  return detail.version.files.map((file) => ({
34
29
  path: file.path,
@@ -71,9 +66,10 @@ export async function uploadSkill(config, localPath, domain) {
71
66
  }
72
67
 
73
68
  export async function manageableDomains(config) {
74
- const actor = await apiRequest(config, '/me');
75
- if (actor.permissions.includes('org.manage')) return DOMAINS;
76
- return DOMAINS.filter((domain) => actor.permissions.includes(`${domain}.skill.manage`));
69
+ const domains = await apiRequest(config, '/domains');
70
+ return domains
71
+ .filter((domain) => domain.permissions.includes('domain:skill_manage'))
72
+ .map((domain) => domain.slug);
77
73
  }
78
74
 
79
75
  export async function configuredAgents() {