@contextium/cli 1.7.1 → 1.8.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 +10 -27
- package/dist/commands/tags.js +23 -713
- package/dist/commands/tags.js.map +1 -1
- package/dist/commands/workflows.js +5 -67
- package/dist/commands/workflows.js.map +1 -1
- package/dist/index.js +1 -1
- package/package.json +1 -1
- package/skills/ium/idea.md +3 -5
- package/skills/ium/new-project.md +16 -19
package/dist/commands/tags.js
CHANGED
|
@@ -15,105 +15,12 @@ async function resolveWorkspace(client, workspaceName) {
|
|
|
15
15
|
return workspace;
|
|
16
16
|
}
|
|
17
17
|
export const tagsCommand = new Command('tags')
|
|
18
|
-
.description('Manage
|
|
19
|
-
// List tag types
|
|
20
|
-
tagsCommand
|
|
21
|
-
.command('types')
|
|
22
|
-
.description('List tag types in a workspace')
|
|
23
|
-
.option('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
24
|
-
.action(async (options) => {
|
|
25
|
-
const spinner = ora('Loading tag types...').start();
|
|
26
|
-
try {
|
|
27
|
-
const config = await loadConfig();
|
|
28
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
29
|
-
if (!options.workspace) {
|
|
30
|
-
spinner.fail('Error: Workspace is required. Use -w flag.');
|
|
31
|
-
process.exit(1);
|
|
32
|
-
}
|
|
33
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
34
|
-
const response = await client.get(`/workspaces/${workspace.id}/tag-types`);
|
|
35
|
-
const tagTypesData = response.data;
|
|
36
|
-
const tagTypes = tagTypesData.data || tagTypesData || [];
|
|
37
|
-
if (tagTypes.length === 0) {
|
|
38
|
-
spinner.stop();
|
|
39
|
-
console.log(chalk.yellow('No tag types found.'));
|
|
40
|
-
return;
|
|
41
|
-
}
|
|
42
|
-
spinner.stop();
|
|
43
|
-
console.log(chalk.bold(`\n${tagTypes.length} tag type(s):\n`));
|
|
44
|
-
tagTypes.forEach((type) => {
|
|
45
|
-
console.log(chalk.cyan(`${type.name} (${type.slug})`));
|
|
46
|
-
console.log(` ID: ${type.id}`);
|
|
47
|
-
console.log(` Color: ${type.color}`);
|
|
48
|
-
if (type.icon)
|
|
49
|
-
console.log(` Icon: ${type.icon}`);
|
|
50
|
-
if (type.description)
|
|
51
|
-
console.log(` Description: ${type.description}`);
|
|
52
|
-
console.log(` System: ${type.isSystem ? 'Yes' : 'No'}`);
|
|
53
|
-
console.log(` Display Order: ${type.displayOrder}`);
|
|
54
|
-
if (type.tagCount !== undefined)
|
|
55
|
-
console.log(` Tag Count: ${type.tagCount}`);
|
|
56
|
-
console.log('');
|
|
57
|
-
});
|
|
58
|
-
}
|
|
59
|
-
catch (error) {
|
|
60
|
-
spinner.fail(`Failed to load tag types: ${error.message}`);
|
|
61
|
-
process.exit(1);
|
|
62
|
-
}
|
|
63
|
-
});
|
|
64
|
-
// Create tag type
|
|
65
|
-
tagsCommand
|
|
66
|
-
.command('create-type')
|
|
67
|
-
.description('Create a new tag type')
|
|
68
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
69
|
-
.requiredOption('-n, --name <name>', 'Type name')
|
|
70
|
-
.option('-s, --slug <slug>', 'Type slug (auto-generated from name if not provided)')
|
|
71
|
-
.option('-c, --color <hex>', 'Hex color code')
|
|
72
|
-
.option('-i, --icon <icon>', 'Icon name')
|
|
73
|
-
.option('-d, --description <text>', 'Description')
|
|
74
|
-
.action(async (options) => {
|
|
75
|
-
const spinner = ora('Creating tag type...').start();
|
|
76
|
-
try {
|
|
77
|
-
const config = await loadConfig();
|
|
78
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
79
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
80
|
-
const slug = options.slug || options.name.toLowerCase().replace(/\s+/g, '-').replace(/[^a-z0-9-]/g, '').replace(/-+/g, '-').replace(/^-|-$/g, '');
|
|
81
|
-
// Validate slug format
|
|
82
|
-
if (!/^[a-z0-9-]+$/.test(slug)) {
|
|
83
|
-
spinner.fail('Error: Slug must contain only lowercase letters, numbers, and hyphens');
|
|
84
|
-
process.exit(1);
|
|
85
|
-
}
|
|
86
|
-
const data = {
|
|
87
|
-
name: options.name,
|
|
88
|
-
slug,
|
|
89
|
-
};
|
|
90
|
-
if (options.color)
|
|
91
|
-
data.color = options.color;
|
|
92
|
-
if (options.icon)
|
|
93
|
-
data.icon = options.icon;
|
|
94
|
-
if (options.description)
|
|
95
|
-
data.description = options.description;
|
|
96
|
-
const response = await client.post(`/workspaces/${workspace.id}/tag-types`, data);
|
|
97
|
-
const tagTypeData = response.data;
|
|
98
|
-
const tagType = tagTypeData.data || tagTypeData;
|
|
99
|
-
spinner.stop();
|
|
100
|
-
console.log(chalk.green('\n✓ Tag type created successfully!'));
|
|
101
|
-
console.log(` ID: ${tagType.id}`);
|
|
102
|
-
console.log(` Name: ${tagType.name}`);
|
|
103
|
-
console.log(` Slug: ${tagType.slug}`);
|
|
104
|
-
console.log(` Color: ${tagType.color}\n`);
|
|
105
|
-
}
|
|
106
|
-
catch (error) {
|
|
107
|
-
spinner.fail(`Failed to create tag type: ${error.message}`);
|
|
108
|
-
process.exit(1);
|
|
109
|
-
}
|
|
110
|
-
});
|
|
18
|
+
.description('Manage flat, untyped tags');
|
|
111
19
|
// List tags
|
|
112
20
|
tagsCommand
|
|
113
21
|
.command('list')
|
|
114
22
|
.description('List tags in a workspace')
|
|
115
23
|
.option('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
116
|
-
.option('-t, --type <typeId>', 'Filter by tag type ID')
|
|
117
24
|
.action(async (options) => {
|
|
118
25
|
const spinner = ora('Loading tags...').start();
|
|
119
26
|
try {
|
|
@@ -124,11 +31,7 @@ tagsCommand
|
|
|
124
31
|
process.exit(1);
|
|
125
32
|
}
|
|
126
33
|
const workspace = await resolveWorkspace(client, options.workspace);
|
|
127
|
-
|
|
128
|
-
if (options.type) {
|
|
129
|
-
url += `?typeId=${options.type}`;
|
|
130
|
-
}
|
|
131
|
-
const response = await client.get(url);
|
|
34
|
+
const response = await client.get(`/workspaces/${workspace.id}/tags`);
|
|
132
35
|
const tagsData = response.data;
|
|
133
36
|
const tags = tagsData.data || tagsData || [];
|
|
134
37
|
if (tags.length === 0) {
|
|
@@ -138,25 +41,13 @@ tagsCommand
|
|
|
138
41
|
}
|
|
139
42
|
spinner.stop();
|
|
140
43
|
console.log(chalk.bold(`\n${tags.length} tag(s):\n`));
|
|
141
|
-
// Group by type
|
|
142
|
-
const tagsByType = new Map();
|
|
143
44
|
tags.forEach((tag) => {
|
|
144
|
-
|
|
145
|
-
|
|
146
|
-
|
|
147
|
-
|
|
148
|
-
|
|
149
|
-
|
|
150
|
-
tagsByType.forEach((typeTags, typeName) => {
|
|
151
|
-
console.log(chalk.cyan(typeName + ':'));
|
|
152
|
-
typeTags.forEach((tag) => {
|
|
153
|
-
console.log(` ${tag.typeSlug}:${tag.value}`);
|
|
154
|
-
console.log(` ID: ${tag.id}`);
|
|
155
|
-
console.log(` Color: ${tag.color}`);
|
|
156
|
-
if (tag.usageCount !== undefined)
|
|
157
|
-
console.log(` Usage: ${tag.usageCount} files`);
|
|
158
|
-
console.log('');
|
|
159
|
-
});
|
|
45
|
+
console.log(chalk.cyan(` ${tag.value}`));
|
|
46
|
+
console.log(` ID: ${tag.id}`);
|
|
47
|
+
console.log(` Color: ${tag.color}`);
|
|
48
|
+
if (tag.usageCount !== undefined)
|
|
49
|
+
console.log(` Usage: ${tag.usageCount} files`);
|
|
50
|
+
console.log('');
|
|
160
51
|
});
|
|
161
52
|
}
|
|
162
53
|
catch (error) {
|
|
@@ -164,104 +55,20 @@ tagsCommand
|
|
|
164
55
|
process.exit(1);
|
|
165
56
|
}
|
|
166
57
|
});
|
|
167
|
-
//
|
|
168
|
-
|
|
169
|
-
|
|
170
|
-
.description('Show tag usage analytics (most-used tags first)')
|
|
171
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
172
|
-
.option('-l, --limit <n>', 'Max tags to show', parseInt)
|
|
173
|
-
.action(async (options) => {
|
|
174
|
-
const spinner = ora('Loading tag analytics...').start();
|
|
175
|
-
try {
|
|
176
|
-
const config = await loadConfig();
|
|
177
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
178
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
179
|
-
const response = await client.get(`/workspaces/${workspace.id}/tags/analytics`);
|
|
180
|
-
const data = response.data.data || response.data || [];
|
|
181
|
-
const list = options.limit ? data.slice(0, options.limit) : data;
|
|
182
|
-
spinner.stop();
|
|
183
|
-
if (list.length === 0) {
|
|
184
|
-
console.log(chalk.yellow('No tag usage data found.'));
|
|
185
|
-
return;
|
|
186
|
-
}
|
|
187
|
-
console.log(chalk.bold(`\nTag usage analytics (${list.length} tags):\n`));
|
|
188
|
-
list.forEach((item, i) => {
|
|
189
|
-
const count = item.usageCount ?? item.count ?? 0;
|
|
190
|
-
const label = item.typeSlug ? `${item.typeSlug}:${item.value}` : item.value;
|
|
191
|
-
console.log(` ${chalk.dim(`${i + 1}.`)} ${chalk.cyan(label)} — ${chalk.bold(count)} file${count !== 1 ? 's' : ''}`);
|
|
192
|
-
});
|
|
193
|
-
console.log();
|
|
194
|
-
}
|
|
195
|
-
catch (error) {
|
|
196
|
-
spinner.fail(`Failed to load analytics: ${error.message}`);
|
|
197
|
-
process.exit(1);
|
|
198
|
-
}
|
|
199
|
-
});
|
|
200
|
-
// Create tag
|
|
201
|
-
tagsCommand
|
|
202
|
-
.command('create')
|
|
203
|
-
.description('Create a new tag')
|
|
204
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
205
|
-
.requiredOption('-t, --type <typeId>', 'Tag type ID or slug')
|
|
206
|
-
.requiredOption('-v, --value <value>', 'Tag value (lowercase, hyphen-separated)')
|
|
207
|
-
.option('-c, --color <hex>', 'Hex color code')
|
|
208
|
-
.action(async (options) => {
|
|
209
|
-
const spinner = ora('Creating tag...').start();
|
|
210
|
-
try {
|
|
211
|
-
const config = await loadConfig();
|
|
212
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
213
|
-
// Validate value format
|
|
214
|
-
if (!/^[a-z0-9-]+$/.test(options.value)) {
|
|
215
|
-
spinner.fail('Error: Tag value must contain only lowercase letters, numbers, and hyphens');
|
|
216
|
-
process.exit(1);
|
|
217
|
-
}
|
|
218
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
219
|
-
// Resolve type by slug if not a UUID
|
|
220
|
-
let typeId = options.type;
|
|
221
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
222
|
-
if (!uuidRegex.test(typeId)) {
|
|
223
|
-
const typesResponse = await client.get(`/workspaces/${workspace.id}/tag-types`);
|
|
224
|
-
const typesData = typesResponse.data;
|
|
225
|
-
const types = typesData.data || typesData || [];
|
|
226
|
-
const matchingType = types.find((t) => t.slug === typeId || t.name.toLowerCase() === typeId.toLowerCase());
|
|
227
|
-
if (!matchingType) {
|
|
228
|
-
spinner.fail(`Tag type '${typeId}' not found. Use 'contextium tags types -w ${options.workspace}' to list types.`);
|
|
229
|
-
process.exit(1);
|
|
230
|
-
}
|
|
231
|
-
typeId = matchingType.id;
|
|
232
|
-
}
|
|
233
|
-
const data = {
|
|
234
|
-
typeId,
|
|
235
|
-
value: options.value,
|
|
236
|
-
};
|
|
237
|
-
if (options.color)
|
|
238
|
-
data.color = options.color;
|
|
239
|
-
const response = await client.post(`/workspaces/${workspace.id}/tags`, data);
|
|
240
|
-
const tagData = response.data;
|
|
241
|
-
const tag = tagData.data || tagData;
|
|
242
|
-
spinner.stop();
|
|
243
|
-
console.log(chalk.green('\n✓ Tag created successfully!'));
|
|
244
|
-
console.log(` ID: ${tag.id}`);
|
|
245
|
-
console.log(` Tag: ${tag.typeSlug}:${tag.value}`);
|
|
246
|
-
console.log(` Type: ${tag.typeName}`);
|
|
247
|
-
console.log(` Color: ${tag.color}\n`);
|
|
248
|
-
}
|
|
249
|
-
catch (error) {
|
|
250
|
-
spinner.fail(`Failed to create tag: ${error.message}`);
|
|
251
|
-
process.exit(1);
|
|
252
|
-
}
|
|
253
|
-
});
|
|
58
|
+
// Tags are authored inline: add a #<value> token to a file's content (e.g.
|
|
59
|
+
// `contextium edit-file <id> --content "… #urgent"`) and it's created on save.
|
|
60
|
+
// There is no standalone "create a bare tag" command.
|
|
254
61
|
// Apply tag to a file
|
|
255
62
|
tagsCommand
|
|
256
63
|
.command('apply')
|
|
257
64
|
.description('(deprecated) Tags are authored inline — add #<value> to the file content')
|
|
258
65
|
.option('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
259
|
-
.option('-t, --tag <tagId>', 'Tag ID or
|
|
66
|
+
.option('-t, --tag <tagId>', 'Tag ID or value')
|
|
260
67
|
.option('-f, --file <fileId>', 'File ID')
|
|
261
68
|
.action(() => {
|
|
262
69
|
console.log(chalk.yellow('\n⚠ "contextium tags apply" is deprecated.'));
|
|
263
70
|
console.log(chalk.dim('Tags are now authored inline: add a #<value> token to the file content (e.g. contextium edit <fileId> ...).'));
|
|
264
|
-
console.log(chalk.dim('File content is the source of truth for a file\'s tags
|
|
71
|
+
console.log(chalk.dim('File content is the source of truth for a file\'s tags.'));
|
|
265
72
|
process.exit(1);
|
|
266
73
|
});
|
|
267
74
|
// Apply tag to multiple files
|
|
@@ -269,12 +76,11 @@ tagsCommand
|
|
|
269
76
|
.command('apply-bulk')
|
|
270
77
|
.description('(deprecated) Tags are authored inline — add #<value> to file content')
|
|
271
78
|
.option('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
272
|
-
.option('-t, --tag <tagId>', 'Tag ID or
|
|
79
|
+
.option('-t, --tag <tagId>', 'Tag ID or value')
|
|
273
80
|
.option('-f, --files <fileIds>', 'Comma-separated file IDs')
|
|
274
81
|
.action(() => {
|
|
275
82
|
console.log(chalk.yellow('\n⚠ "contextium tags apply-bulk" is deprecated.'));
|
|
276
83
|
console.log(chalk.dim('Tags are now authored inline: add a #<value> token to each file\'s content.'));
|
|
277
|
-
console.log(chalk.dim('For pattern-based bulk tagging, use tag rules (contextium tags create-rule).'));
|
|
278
84
|
process.exit(1);
|
|
279
85
|
});
|
|
280
86
|
// Remove tag from a file
|
|
@@ -282,7 +88,7 @@ tagsCommand
|
|
|
282
88
|
.command('remove')
|
|
283
89
|
.description('(deprecated) Untag by removing the #<value> token from the file content')
|
|
284
90
|
.option('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
285
|
-
.option('-t, --tag <tagId>', 'Tag ID or
|
|
91
|
+
.option('-t, --tag <tagId>', 'Tag ID or value')
|
|
286
92
|
.option('-f, --file <fileId>', 'File ID')
|
|
287
93
|
.action(() => {
|
|
288
94
|
console.log(chalk.yellow('\n⚠ "contextium tags remove" is deprecated.'));
|
|
@@ -295,7 +101,7 @@ tagsCommand
|
|
|
295
101
|
.command('search')
|
|
296
102
|
.description('Search files by tags')
|
|
297
103
|
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
298
|
-
.requiredOption('-t, --tags <tagIds>', 'Comma-separated tag IDs or tag values (e.g., "md" or "
|
|
104
|
+
.requiredOption('-t, --tags <tagIds>', 'Comma-separated tag IDs or tag values (e.g., "md" or "draft")')
|
|
299
105
|
.option('-p, --library <libraryId>', 'Filter by context library ID')
|
|
300
106
|
.action(async (options) => {
|
|
301
107
|
const spinner = ora('Searching files...').start();
|
|
@@ -304,14 +110,14 @@ tagsCommand
|
|
|
304
110
|
const client = new ApiClient(config.api_key, config.api_url);
|
|
305
111
|
const workspace = await resolveWorkspace(client, options.workspace);
|
|
306
112
|
const workspaceId = workspace.id;
|
|
307
|
-
// Parse tag input - support both IDs and
|
|
113
|
+
// Parse tag input - support both IDs and values
|
|
308
114
|
let tagIds = [];
|
|
309
115
|
const tagInput = options.tags.split(',').map((t) => t.trim());
|
|
310
116
|
// Check if inputs are UUIDs or need to be resolved
|
|
311
117
|
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
312
118
|
const needsResolution = tagInput.some((t) => !uuidRegex.test(t));
|
|
313
119
|
if (needsResolution) {
|
|
314
|
-
// Fetch all tags to resolve
|
|
120
|
+
// Fetch all tags to resolve values to IDs
|
|
315
121
|
const tagsResponse = await client.get(`/workspaces/${workspaceId}/tags`);
|
|
316
122
|
const allTags = tagsResponse.data.data || tagsResponse.data || [];
|
|
317
123
|
for (const input of tagInput) {
|
|
@@ -319,14 +125,7 @@ tagsCommand
|
|
|
319
125
|
tagIds.push(input);
|
|
320
126
|
}
|
|
321
127
|
else {
|
|
322
|
-
|
|
323
|
-
const [typeSlug, value] = input.includes(':') ? input.split(':') : [null, input];
|
|
324
|
-
const matchingTag = allTags.find((tag) => {
|
|
325
|
-
if (typeSlug) {
|
|
326
|
-
return tag.typeSlug === typeSlug && tag.value === value;
|
|
327
|
-
}
|
|
328
|
-
return tag.value === value;
|
|
329
|
-
});
|
|
128
|
+
const matchingTag = allTags.find((tag) => tag.value === input);
|
|
330
129
|
if (matchingTag) {
|
|
331
130
|
tagIds.push(matchingTag.id);
|
|
332
131
|
}
|
|
@@ -437,7 +236,7 @@ tagsCommand
|
|
|
437
236
|
console.log(chalk.bold(`\n${tags.length} tag(s) on file:\n`));
|
|
438
237
|
tags.forEach((entry) => {
|
|
439
238
|
const tag = entry.tag || entry;
|
|
440
|
-
console.log(` ${chalk.cyan(tag.
|
|
239
|
+
console.log(` ${chalk.cyan(tag.value)}`);
|
|
441
240
|
console.log(chalk.dim(` ID: ${tag.id} Color: ${tag.color || 'default'}`));
|
|
442
241
|
});
|
|
443
242
|
console.log('');
|
|
@@ -447,496 +246,7 @@ tagsCommand
|
|
|
447
246
|
process.exit(1);
|
|
448
247
|
}
|
|
449
248
|
});
|
|
450
|
-
//
|
|
451
|
-
|
|
452
|
-
|
|
453
|
-
.description('Update an existing tag')
|
|
454
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
455
|
-
.requiredOption('-t, --tag <tagId>', 'Tag ID or slug:value')
|
|
456
|
-
.option('--value <value>', 'New tag value (lowercase, hyphens)')
|
|
457
|
-
.option('--type <typeId>', 'New tag type ID')
|
|
458
|
-
.option('-c, --color <hex>', 'New hex color code')
|
|
459
|
-
.action(async (options) => {
|
|
460
|
-
const spinner = ora('Updating tag...').start();
|
|
461
|
-
try {
|
|
462
|
-
const config = await loadConfig();
|
|
463
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
464
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
465
|
-
// Resolve tag by slug:value if not a UUID
|
|
466
|
-
let tagId = options.tag;
|
|
467
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
468
|
-
if (!uuidRegex.test(tagId)) {
|
|
469
|
-
const tagsResponse = await client.get(`/workspaces/${workspace.id}/tags`);
|
|
470
|
-
const allTags = tagsResponse.data.data || tagsResponse.data || [];
|
|
471
|
-
const [typeSlug, value] = tagId.includes(':') ? tagId.split(':') : [null, tagId];
|
|
472
|
-
const matchingTag = allTags.find((tag) => {
|
|
473
|
-
if (typeSlug)
|
|
474
|
-
return tag.typeSlug === typeSlug && tag.value === value;
|
|
475
|
-
return tag.value === value;
|
|
476
|
-
});
|
|
477
|
-
if (!matchingTag) {
|
|
478
|
-
spinner.fail(`Tag '${tagId}' not found.`);
|
|
479
|
-
process.exit(1);
|
|
480
|
-
}
|
|
481
|
-
tagId = matchingTag.id;
|
|
482
|
-
}
|
|
483
|
-
const data = {};
|
|
484
|
-
if (options.value)
|
|
485
|
-
data.value = options.value;
|
|
486
|
-
if (options.type)
|
|
487
|
-
data.typeId = options.type;
|
|
488
|
-
if (options.color)
|
|
489
|
-
data.color = options.color;
|
|
490
|
-
if (Object.keys(data).length === 0) {
|
|
491
|
-
spinner.fail('No fields to update. Use --value, --type, or --color.');
|
|
492
|
-
process.exit(1);
|
|
493
|
-
}
|
|
494
|
-
const response = await client.put(`/tags/${tagId}?workspaceId=${workspace.id}`, data);
|
|
495
|
-
const tag = response.data.data || response.data;
|
|
496
|
-
spinner.stop();
|
|
497
|
-
console.log(chalk.green('\n✓ Tag updated successfully!'));
|
|
498
|
-
console.log(` Tag: ${tag.typeSlug || ''}:${tag.value}`);
|
|
499
|
-
console.log(` ID: ${tag.id}`);
|
|
500
|
-
if (tag.color)
|
|
501
|
-
console.log(` Color: ${tag.color}`);
|
|
502
|
-
console.log('');
|
|
503
|
-
}
|
|
504
|
-
catch (error) {
|
|
505
|
-
spinner.fail(`Failed to update tag: ${error.message}`);
|
|
506
|
-
process.exit(1);
|
|
507
|
-
}
|
|
508
|
-
});
|
|
509
|
-
// Get tag type by ID
|
|
510
|
-
tagsCommand
|
|
511
|
-
.command('type-get')
|
|
512
|
-
.description('Get a tag type by ID')
|
|
513
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
514
|
-
.requiredOption('-t, --type <typeId>', 'Tag type ID or slug')
|
|
515
|
-
.action(async (options) => {
|
|
516
|
-
const spinner = ora('Loading tag type...').start();
|
|
517
|
-
try {
|
|
518
|
-
const config = await loadConfig();
|
|
519
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
520
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
521
|
-
// Resolve type by slug if not a UUID
|
|
522
|
-
let typeId = options.type;
|
|
523
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
524
|
-
if (!uuidRegex.test(typeId)) {
|
|
525
|
-
const typesResponse = await client.get(`/workspaces/${workspace.id}/tag-types`);
|
|
526
|
-
const types = typesResponse.data.data || typesResponse.data || [];
|
|
527
|
-
const match = types.find((t) => t.slug === typeId || t.name.toLowerCase() === typeId.toLowerCase());
|
|
528
|
-
if (!match) {
|
|
529
|
-
spinner.fail(`Tag type '${typeId}' not found.`);
|
|
530
|
-
process.exit(1);
|
|
531
|
-
}
|
|
532
|
-
typeId = match.id;
|
|
533
|
-
}
|
|
534
|
-
const response = await client.get(`/tag-types/${typeId}`, {
|
|
535
|
-
params: { workspaceId: workspace.id }
|
|
536
|
-
});
|
|
537
|
-
const tagType = response.data.data || response.data;
|
|
538
|
-
spinner.stop();
|
|
539
|
-
console.log(chalk.bold(`\nTag Type: ${tagType.name}\n`));
|
|
540
|
-
console.log(` ID: ${tagType.id}`);
|
|
541
|
-
console.log(` Slug: ${tagType.slug}`);
|
|
542
|
-
console.log(` Color: ${tagType.color || 'default'}`);
|
|
543
|
-
if (tagType.icon)
|
|
544
|
-
console.log(` Icon: ${tagType.icon}`);
|
|
545
|
-
if (tagType.description)
|
|
546
|
-
console.log(` Description: ${tagType.description}`);
|
|
547
|
-
console.log(` System: ${tagType.isSystem ? 'Yes' : 'No'}`);
|
|
548
|
-
console.log(` Display Order: ${tagType.displayOrder}`);
|
|
549
|
-
if (tagType.tagCount !== undefined)
|
|
550
|
-
console.log(` Tag Count: ${tagType.tagCount}`);
|
|
551
|
-
console.log('');
|
|
552
|
-
}
|
|
553
|
-
catch (error) {
|
|
554
|
-
spinner.fail(`Failed to load tag type: ${error.message}`);
|
|
555
|
-
process.exit(1);
|
|
556
|
-
}
|
|
557
|
-
});
|
|
558
|
-
// Update tag type
|
|
559
|
-
tagsCommand
|
|
560
|
-
.command('type-update')
|
|
561
|
-
.description('Update an existing tag type')
|
|
562
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
563
|
-
.requiredOption('-t, --type <typeId>', 'Tag type ID or slug')
|
|
564
|
-
.option('-n, --name <name>', 'New name')
|
|
565
|
-
.option('-s, --slug <slug>', 'New slug')
|
|
566
|
-
.option('-c, --color <hex>', 'New hex color code')
|
|
567
|
-
.option('-i, --icon <icon>', 'New icon name')
|
|
568
|
-
.option('-d, --description <text>', 'New description')
|
|
569
|
-
.option('--order <number>', 'New display order')
|
|
570
|
-
.action(async (options) => {
|
|
571
|
-
const spinner = ora('Updating tag type...').start();
|
|
572
|
-
try {
|
|
573
|
-
const config = await loadConfig();
|
|
574
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
575
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
576
|
-
// Resolve type by slug if not a UUID
|
|
577
|
-
let typeId = options.type;
|
|
578
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
579
|
-
if (!uuidRegex.test(typeId)) {
|
|
580
|
-
const typesResponse = await client.get(`/workspaces/${workspace.id}/tag-types`);
|
|
581
|
-
const types = typesResponse.data.data || typesResponse.data || [];
|
|
582
|
-
const match = types.find((t) => t.slug === typeId || t.name.toLowerCase() === typeId.toLowerCase());
|
|
583
|
-
if (!match) {
|
|
584
|
-
spinner.fail(`Tag type '${typeId}' not found.`);
|
|
585
|
-
process.exit(1);
|
|
586
|
-
}
|
|
587
|
-
typeId = match.id;
|
|
588
|
-
}
|
|
589
|
-
const data = {};
|
|
590
|
-
if (options.name)
|
|
591
|
-
data.name = options.name;
|
|
592
|
-
if (options.slug)
|
|
593
|
-
data.slug = options.slug;
|
|
594
|
-
if (options.color)
|
|
595
|
-
data.color = options.color;
|
|
596
|
-
if (options.icon)
|
|
597
|
-
data.icon = options.icon;
|
|
598
|
-
if (options.description)
|
|
599
|
-
data.description = options.description;
|
|
600
|
-
if (options.order !== undefined)
|
|
601
|
-
data.displayOrder = parseInt(options.order, 10);
|
|
602
|
-
if (Object.keys(data).length === 0) {
|
|
603
|
-
spinner.fail('No fields to update. Use --name, --slug, --color, --icon, --description, or --order.');
|
|
604
|
-
process.exit(1);
|
|
605
|
-
}
|
|
606
|
-
const response = await client.put(`/tag-types/${typeId}?workspaceId=${workspace.id}`, data);
|
|
607
|
-
const tagType = response.data.data || response.data;
|
|
608
|
-
spinner.stop();
|
|
609
|
-
console.log(chalk.green('\n✓ Tag type updated successfully!'));
|
|
610
|
-
console.log(` Name: ${tagType.name}`);
|
|
611
|
-
console.log(` Slug: ${tagType.slug}`);
|
|
612
|
-
console.log(` ID: ${tagType.id}`);
|
|
613
|
-
if (tagType.color)
|
|
614
|
-
console.log(` Color: ${tagType.color}`);
|
|
615
|
-
console.log('');
|
|
616
|
-
}
|
|
617
|
-
catch (error) {
|
|
618
|
-
spinner.fail(`Failed to update tag type: ${error.message}`);
|
|
619
|
-
process.exit(1);
|
|
620
|
-
}
|
|
621
|
-
});
|
|
622
|
-
// Delete tag type
|
|
623
|
-
tagsCommand
|
|
624
|
-
.command('delete-type')
|
|
625
|
-
.description('Delete a tag type')
|
|
626
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
627
|
-
.requiredOption('-t, --type <typeId>', 'Tag type ID or slug')
|
|
628
|
-
.option('--confirm', 'Skip confirmation prompt')
|
|
629
|
-
.action(async (options) => {
|
|
630
|
-
const spinner = ora('Resolving tag type...').start();
|
|
631
|
-
try {
|
|
632
|
-
const config = await loadConfig();
|
|
633
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
634
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
635
|
-
let typeId = options.type;
|
|
636
|
-
const uuidRegex = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
|
|
637
|
-
if (!uuidRegex.test(typeId)) {
|
|
638
|
-
const typesResponse = await client.get(`/workspaces/${workspace.id}/tag-types`);
|
|
639
|
-
const types = typesResponse.data.data || typesResponse.data || [];
|
|
640
|
-
const match = types.find((t) => t.slug === typeId || t.name.toLowerCase() === typeId.toLowerCase());
|
|
641
|
-
if (!match) {
|
|
642
|
-
spinner.fail(`Tag type '${typeId}' not found.`);
|
|
643
|
-
process.exit(1);
|
|
644
|
-
}
|
|
645
|
-
typeId = match.id;
|
|
646
|
-
}
|
|
647
|
-
spinner.stop();
|
|
648
|
-
if (!options.confirm) {
|
|
649
|
-
console.log(chalk.yellow(`\n⚠ This will permanently delete tag type ${options.type} and all its tags.`));
|
|
650
|
-
console.log(chalk.dim(' Use --confirm to skip this warning.\n'));
|
|
651
|
-
const readline = await import('readline');
|
|
652
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
653
|
-
const answer = await new Promise((resolve) => {
|
|
654
|
-
rl.question(' Continue? (y/N) ', resolve);
|
|
655
|
-
});
|
|
656
|
-
rl.close();
|
|
657
|
-
if (answer.toLowerCase() !== 'y') {
|
|
658
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
659
|
-
return;
|
|
660
|
-
}
|
|
661
|
-
}
|
|
662
|
-
const deleteSpinner = ora('Deleting tag type...').start();
|
|
663
|
-
await client.delete(`/tag-types/${typeId}?workspaceId=${workspace.id}`);
|
|
664
|
-
deleteSpinner.stop();
|
|
665
|
-
console.log(chalk.green('\n✓ Tag type deleted successfully.'));
|
|
666
|
-
console.log();
|
|
667
|
-
}
|
|
668
|
-
catch (error) {
|
|
669
|
-
spinner.fail(`Failed to delete tag type: ${error.message}`);
|
|
670
|
-
process.exit(1);
|
|
671
|
-
}
|
|
672
|
-
});
|
|
673
|
-
// Delete a tag
|
|
674
|
-
tagsCommand
|
|
675
|
-
.command('delete')
|
|
676
|
-
.description('Delete a tag')
|
|
677
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
678
|
-
.requiredOption('-i, --id <tagId>', 'Tag ID')
|
|
679
|
-
.option('--confirm', 'Skip confirmation prompt')
|
|
680
|
-
.action(async (options) => {
|
|
681
|
-
const spinner = ora('Resolving workspace...').start();
|
|
682
|
-
try {
|
|
683
|
-
const config = await loadConfig();
|
|
684
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
685
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
686
|
-
spinner.stop();
|
|
687
|
-
if (!options.confirm) {
|
|
688
|
-
console.log(chalk.yellow(`\n⚠ This will permanently delete tag ${options.id}.`));
|
|
689
|
-
const readline = await import('readline');
|
|
690
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
691
|
-
const answer = await new Promise((resolve) => { rl.question(' Continue? (y/N) ', resolve); });
|
|
692
|
-
rl.close();
|
|
693
|
-
if (answer.toLowerCase() !== 'y') {
|
|
694
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
}
|
|
698
|
-
const deleteSpinner = ora('Deleting tag...').start();
|
|
699
|
-
await client.delete(`/tags/${options.id}?workspaceId=${workspace.id}`);
|
|
700
|
-
deleteSpinner.stop();
|
|
701
|
-
console.log(chalk.green('\n✓ Tag deleted successfully.'));
|
|
702
|
-
}
|
|
703
|
-
catch (error) {
|
|
704
|
-
spinner.fail(`Failed to delete tag: ${error.message}`);
|
|
705
|
-
process.exit(1);
|
|
706
|
-
}
|
|
707
|
-
});
|
|
708
|
-
// List tag rules
|
|
709
|
-
tagsCommand
|
|
710
|
-
.command('list-rules')
|
|
711
|
-
.description('List tag rules in a workspace')
|
|
712
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
713
|
-
.option('--active-only', 'Return only active rules')
|
|
714
|
-
.action(async (options) => {
|
|
715
|
-
const spinner = ora('Loading tag rules...').start();
|
|
716
|
-
try {
|
|
717
|
-
const config = await loadConfig();
|
|
718
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
719
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
720
|
-
const url = `/workspaces/${workspace.id}/tag-rules${options.activeOnly ? '?activeOnly=true' : ''}`;
|
|
721
|
-
const response = await client.get(url);
|
|
722
|
-
const rules = response.data.data || response.data || [];
|
|
723
|
-
spinner.stop();
|
|
724
|
-
if (rules.length === 0) {
|
|
725
|
-
console.log(chalk.yellow('No tag rules found.'));
|
|
726
|
-
return;
|
|
727
|
-
}
|
|
728
|
-
console.log(chalk.bold(`\n${rules.length} tag rule(s):\n`));
|
|
729
|
-
rules.forEach((rule) => {
|
|
730
|
-
const status = rule.isActive ? chalk.green('active') : chalk.dim('inactive');
|
|
731
|
-
console.log(chalk.cyan(`${rule.name}`) + chalk.dim(` [${status}]`));
|
|
732
|
-
console.log(` ID: ${rule.id}`);
|
|
733
|
-
console.log(` Pattern: ${rule.patternType}${rule.pattern ? ` — ${rule.pattern}` : ''}`);
|
|
734
|
-
if (rule.tags?.length)
|
|
735
|
-
console.log(` Tags: ${rule.tags.map((t) => `${t.typeSlug}:${t.value}`).join(', ')}`);
|
|
736
|
-
if (rule.description)
|
|
737
|
-
console.log(` Description: ${rule.description}`);
|
|
738
|
-
console.log();
|
|
739
|
-
});
|
|
740
|
-
}
|
|
741
|
-
catch (error) {
|
|
742
|
-
spinner.fail(`Failed to list tag rules: ${error.message}`);
|
|
743
|
-
process.exit(1);
|
|
744
|
-
}
|
|
745
|
-
});
|
|
746
|
-
// Create a tag rule
|
|
747
|
-
tagsCommand
|
|
748
|
-
.command('create-rule')
|
|
749
|
-
.description('Create a new tag rule')
|
|
750
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
751
|
-
.requiredOption('-n, --name <name>', 'Rule name')
|
|
752
|
-
.requiredOption('-p, --pattern-type <type>', 'Pattern type: match_all | filename_contains | file_extension | path_contains | path_starts_with | filename_regex')
|
|
753
|
-
.requiredOption('-T, --tag-ids <ids>', 'Comma-separated tag IDs to apply')
|
|
754
|
-
.option('--pattern <pattern>', 'Pattern value (not required for match_all)')
|
|
755
|
-
.option('-d, --description <text>', 'Rule description')
|
|
756
|
-
.option('--inactive', 'Create rule as inactive (default: active)')
|
|
757
|
-
.option('--priority <n>', 'Rule priority (lower runs first)', parseInt)
|
|
758
|
-
.action(async (options) => {
|
|
759
|
-
const spinner = ora('Creating tag rule...').start();
|
|
760
|
-
try {
|
|
761
|
-
const config = await loadConfig();
|
|
762
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
763
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
764
|
-
const validTypes = ['match_all', 'filename_contains', 'file_extension', 'path_contains', 'path_starts_with', 'filename_regex'];
|
|
765
|
-
if (!validTypes.includes(options.patternType)) {
|
|
766
|
-
spinner.fail(`Invalid pattern type. Must be one of: ${validTypes.join(', ')}`);
|
|
767
|
-
process.exit(1);
|
|
768
|
-
}
|
|
769
|
-
const tagIds = options.tagIds.split(',').map((id) => id.trim()).filter(Boolean);
|
|
770
|
-
const body = { name: options.name, patternType: options.patternType, tagIds, isActive: !options.inactive };
|
|
771
|
-
if (options.pattern)
|
|
772
|
-
body.pattern = options.pattern;
|
|
773
|
-
if (options.description)
|
|
774
|
-
body.description = options.description;
|
|
775
|
-
if (options.priority !== undefined)
|
|
776
|
-
body.priority = options.priority;
|
|
777
|
-
const response = await client.post(`/workspaces/${workspace.id}/tag-rules`, body);
|
|
778
|
-
const rule = response.data.data || response.data;
|
|
779
|
-
spinner.stop();
|
|
780
|
-
console.log(chalk.green('\n✓ Tag rule created!'));
|
|
781
|
-
console.log(` ID: ${rule.id}`);
|
|
782
|
-
console.log(` Name: ${rule.name}`);
|
|
783
|
-
console.log(` Pattern: ${rule.patternType}${rule.pattern ? ` — ${rule.pattern}` : ''}`);
|
|
784
|
-
console.log(` Active: ${rule.isActive}\n`);
|
|
785
|
-
}
|
|
786
|
-
catch (error) {
|
|
787
|
-
spinner.fail(`Failed to create tag rule: ${error.message}`);
|
|
788
|
-
process.exit(1);
|
|
789
|
-
}
|
|
790
|
-
});
|
|
791
|
-
// Update a tag rule
|
|
792
|
-
tagsCommand
|
|
793
|
-
.command('update-rule')
|
|
794
|
-
.description('Update a tag rule')
|
|
795
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
796
|
-
.requiredOption('-i, --id <ruleId>', 'Rule ID')
|
|
797
|
-
.option('-n, --name <name>', 'New name')
|
|
798
|
-
.option('-d, --description <text>', 'New description')
|
|
799
|
-
.option('-p, --pattern-type <type>', 'New pattern type')
|
|
800
|
-
.option('--pattern <pattern>', 'New pattern value')
|
|
801
|
-
.option('--active', 'Set rule active')
|
|
802
|
-
.option('--inactive', 'Set rule inactive')
|
|
803
|
-
.option('-T, --tag-ids <ids>', 'New comma-separated tag IDs')
|
|
804
|
-
.option('--priority <n>', 'New priority', parseInt)
|
|
805
|
-
.action(async (options) => {
|
|
806
|
-
const spinner = ora('Updating tag rule...').start();
|
|
807
|
-
try {
|
|
808
|
-
const config = await loadConfig();
|
|
809
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
810
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
811
|
-
const body = {};
|
|
812
|
-
if (options.name)
|
|
813
|
-
body.name = options.name;
|
|
814
|
-
if (options.description)
|
|
815
|
-
body.description = options.description;
|
|
816
|
-
if (options.patternType)
|
|
817
|
-
body.patternType = options.patternType;
|
|
818
|
-
if (options.pattern !== undefined)
|
|
819
|
-
body.pattern = options.pattern;
|
|
820
|
-
if (options.active)
|
|
821
|
-
body.isActive = true;
|
|
822
|
-
if (options.inactive)
|
|
823
|
-
body.isActive = false;
|
|
824
|
-
if (options.tagIds)
|
|
825
|
-
body.tagIds = options.tagIds.split(',').map((id) => id.trim()).filter(Boolean);
|
|
826
|
-
if (options.priority !== undefined)
|
|
827
|
-
body.priority = options.priority;
|
|
828
|
-
await client.put(`/tag-rules/${options.id}?workspaceId=${workspace.id}`, body);
|
|
829
|
-
spinner.stop();
|
|
830
|
-
console.log(chalk.green('\n✓ Tag rule updated successfully.\n'));
|
|
831
|
-
}
|
|
832
|
-
catch (error) {
|
|
833
|
-
spinner.fail(`Failed to update tag rule: ${error.message}`);
|
|
834
|
-
process.exit(1);
|
|
835
|
-
}
|
|
836
|
-
});
|
|
837
|
-
// Delete a tag rule
|
|
838
|
-
tagsCommand
|
|
839
|
-
.command('delete-rule')
|
|
840
|
-
.description('Delete a tag rule')
|
|
841
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
842
|
-
.requiredOption('-i, --id <ruleId>', 'Rule ID')
|
|
843
|
-
.option('--confirm', 'Skip confirmation prompt')
|
|
844
|
-
.action(async (options) => {
|
|
845
|
-
const spinner = ora('Resolving workspace...').start();
|
|
846
|
-
try {
|
|
847
|
-
const config = await loadConfig();
|
|
848
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
849
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
850
|
-
spinner.stop();
|
|
851
|
-
if (!options.confirm) {
|
|
852
|
-
console.log(chalk.yellow(`\n⚠ This will permanently delete rule ${options.id}.`));
|
|
853
|
-
const readline = await import('readline');
|
|
854
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
855
|
-
const answer = await new Promise((resolve) => { rl.question(' Continue? (y/N) ', resolve); });
|
|
856
|
-
rl.close();
|
|
857
|
-
if (answer.toLowerCase() !== 'y') {
|
|
858
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
859
|
-
return;
|
|
860
|
-
}
|
|
861
|
-
}
|
|
862
|
-
const deleteSpinner = ora('Deleting tag rule...').start();
|
|
863
|
-
await client.delete(`/tag-rules/${options.id}?workspaceId=${workspace.id}`);
|
|
864
|
-
deleteSpinner.stop();
|
|
865
|
-
console.log(chalk.green('\n✓ Tag rule deleted successfully.'));
|
|
866
|
-
}
|
|
867
|
-
catch (error) {
|
|
868
|
-
spinner.fail(`Failed to delete tag rule: ${error.message}`);
|
|
869
|
-
process.exit(1);
|
|
870
|
-
}
|
|
871
|
-
});
|
|
872
|
-
// Test a tag rule pattern
|
|
873
|
-
tagsCommand
|
|
874
|
-
.command('test-pattern')
|
|
875
|
-
.description('Test a tag rule pattern against workspace files (dry run)')
|
|
876
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
877
|
-
.requiredOption('-p, --pattern-type <type>', 'Pattern type: match_all | filename_contains | file_extension | path_contains | path_starts_with | filename_regex')
|
|
878
|
-
.option('--pattern <pattern>', 'Pattern value')
|
|
879
|
-
.option('-l, --limit <n>', 'Max matching files to show (default 10)', parseInt)
|
|
880
|
-
.action(async (options) => {
|
|
881
|
-
const spinner = ora('Testing pattern...').start();
|
|
882
|
-
try {
|
|
883
|
-
const config = await loadConfig();
|
|
884
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
885
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
886
|
-
const body = { patternType: options.patternType, pattern: options.pattern || '', limit: options.limit || 10 };
|
|
887
|
-
const response = await client.post(`/workspaces/${workspace.id}/tag-rules/test`, body);
|
|
888
|
-
const result = response.data.data || response.data;
|
|
889
|
-
spinner.stop();
|
|
890
|
-
const matches = result.matchingPaths || result.paths || [];
|
|
891
|
-
if (matches.length === 0) {
|
|
892
|
-
console.log(chalk.yellow('\nNo files matched this pattern.\n'));
|
|
893
|
-
}
|
|
894
|
-
else {
|
|
895
|
-
console.log(chalk.bold(`\n${matches.length} matching file(s):\n`));
|
|
896
|
-
matches.forEach((p) => console.log(chalk.dim(` ${p}`)));
|
|
897
|
-
console.log();
|
|
898
|
-
}
|
|
899
|
-
}
|
|
900
|
-
catch (error) {
|
|
901
|
-
spinner.fail(`Failed to test pattern: ${error.message}`);
|
|
902
|
-
process.exit(1);
|
|
903
|
-
}
|
|
904
|
-
});
|
|
905
|
-
// Apply a tag rule retroactively to existing files
|
|
906
|
-
tagsCommand
|
|
907
|
-
.command('apply-rule')
|
|
908
|
-
.description('Apply a tag rule retroactively to all existing workspace files')
|
|
909
|
-
.requiredOption('-w, --workspace <id>', 'Workspace name, slug, or ID')
|
|
910
|
-
.requiredOption('-i, --id <ruleId>', 'Rule ID')
|
|
911
|
-
.option('--confirm', 'Skip confirmation prompt')
|
|
912
|
-
.action(async (options) => {
|
|
913
|
-
const spinner = ora('Resolving workspace...').start();
|
|
914
|
-
try {
|
|
915
|
-
const config = await loadConfig();
|
|
916
|
-
const client = new ApiClient(config.api_key, config.api_url);
|
|
917
|
-
const workspace = await resolveWorkspace(client, options.workspace);
|
|
918
|
-
spinner.stop();
|
|
919
|
-
if (!options.confirm) {
|
|
920
|
-
console.log(chalk.yellow('\n⚠ This will apply the rule to ALL existing files in the workspace.'));
|
|
921
|
-
const readline = await import('readline');
|
|
922
|
-
const rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
923
|
-
const answer = await new Promise((resolve) => { rl.question(' Continue? (y/N) ', resolve); });
|
|
924
|
-
rl.close();
|
|
925
|
-
if (answer.toLowerCase() !== 'y') {
|
|
926
|
-
console.log(chalk.dim(' Cancelled.'));
|
|
927
|
-
return;
|
|
928
|
-
}
|
|
929
|
-
}
|
|
930
|
-
const applySpinner = ora('Applying rule to existing files...').start();
|
|
931
|
-
const response = await client.post(`/tag-rules/${options.id}/apply?workspaceId=${workspace.id}`, {});
|
|
932
|
-
const result = response.data.data || response.data;
|
|
933
|
-
applySpinner.stop();
|
|
934
|
-
const applied = result.applied ?? result.count ?? '?';
|
|
935
|
-
console.log(chalk.green(`\n✓ Rule applied. Tagged ${applied} file(s).\n`));
|
|
936
|
-
}
|
|
937
|
-
catch (error) {
|
|
938
|
-
spinner.fail(`Failed to apply rule: ${error.message}`);
|
|
939
|
-
process.exit(1);
|
|
940
|
-
}
|
|
941
|
-
});
|
|
249
|
+
// Tag vocabulary is content-driven: a tag exists while some file's content
|
|
250
|
+
// contains its #value, and disappears when no file references it. There are no
|
|
251
|
+
// rename/recolor/delete-tag commands — edit the files' content instead.
|
|
942
252
|
//# sourceMappingURL=tags.js.map
|