@zalosai/zalos-cli 0.2.13

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.
@@ -0,0 +1,213 @@
1
+ import { Image } from '@tiptap/extension-image';
2
+ import { Heading } from '@tiptap/extension-heading';
3
+ import { TaskItem, TaskList } from '@tiptap/extension-list';
4
+ import { Paragraph } from '@tiptap/extension-paragraph';
5
+ import { MarkdownManager } from '@tiptap/markdown';
6
+ import { renderToHTMLString } from '@tiptap/static-renderer/pm/html-string';
7
+ import { StarterKit } from '@tiptap/starter-kit';
8
+ import { ValidationError } from '../utils/errors.js';
9
+ function escapeBlockMarkers(value) {
10
+ const escapedMarkers = value.replace(/(^|\n)(?=(?:#{1,6}\s|```|!\[|[-+*]\s|>\s?|---\s*(?:\n|$)|\*\*\*\s*(?:\n|$)|___\s*(?:\n|$)))/g, '$1\\');
11
+ return escapedMarkers.replace(/(^|\n)(\d+)([.)])(?=\s)/g, '$1$2\\$3');
12
+ }
13
+ const MarkdownParagraph = Paragraph.extend({
14
+ renderMarkdown: (node, helpers) => {
15
+ const content = Array.isArray(node.content) ? node.content : [];
16
+ return escapeBlockMarkers(helpers.renderChildren(content));
17
+ },
18
+ });
19
+ function splitHeadingBreaks(content) {
20
+ return content.flatMap((node) => {
21
+ if (node.type !== 'text' || typeof node.text !== 'string' || !node.text.includes('<br>'))
22
+ return [node];
23
+ return node.text.split('<br>').flatMap((text, index, parts) => {
24
+ const output = text ? [{ ...node, text }] : [];
25
+ if (index < parts.length - 1)
26
+ output.push({ type: 'hardBreak' });
27
+ return output;
28
+ });
29
+ });
30
+ }
31
+ const MarkdownHeading = Heading.extend({
32
+ parseMarkdown: (token, helpers) => helpers.createNode('heading', { level: token.depth || 1 }, splitHeadingBreaks(helpers.parseInline(token.tokens || []))),
33
+ renderMarkdown: (node, helpers) => {
34
+ const level = typeof node.attrs?.level === 'number' ? node.attrs.level : 1;
35
+ const content = helpers.renderChildren(node.content || []).replace(/ {2}\n/g, '<br>');
36
+ return `${'#'.repeat(level)} ${content}`;
37
+ },
38
+ });
39
+ function escapeImageAlt(value) {
40
+ return value.replace(/[\\[\]]/g, '\\$&');
41
+ }
42
+ const MarkdownImage = Image.extend({
43
+ renderMarkdown: (node) => {
44
+ const src = typeof node.attrs?.src === 'string' ? node.attrs.src : '';
45
+ const alt = typeof node.attrs?.alt === 'string' ? escapeImageAlt(node.attrs.alt) : '';
46
+ const title = typeof node.attrs?.title === 'string' ? node.attrs.title : '';
47
+ return title ? `![${alt}](${src} "${title}")` : `![${alt}](${src})`;
48
+ },
49
+ });
50
+ const pageExtensions = [
51
+ StarterKit.configure({
52
+ link: { openOnClick: false },
53
+ heading: false,
54
+ paragraph: false,
55
+ }),
56
+ MarkdownParagraph,
57
+ MarkdownHeading,
58
+ TaskList,
59
+ TaskItem.configure({ nested: true }),
60
+ MarkdownImage,
61
+ ];
62
+ const markdownManager = new MarkdownManager({ extensions: pageExtensions });
63
+ const markdownNodeTypes = new Set([
64
+ 'doc',
65
+ 'paragraph',
66
+ 'text',
67
+ 'hardBreak',
68
+ 'heading',
69
+ 'blockquote',
70
+ 'bulletList',
71
+ 'orderedList',
72
+ 'listItem',
73
+ 'taskList',
74
+ 'taskItem',
75
+ 'codeBlock',
76
+ 'horizontalRule',
77
+ 'image',
78
+ ]);
79
+ const markdownMarkTypes = new Set(['bold', 'italic', 'code', 'link', 'strike', 'underline']);
80
+ function isNode(value) {
81
+ return typeof value === 'object' && value !== null && !Array.isArray(value);
82
+ }
83
+ function isPageContentNode(value) {
84
+ return typeof value === 'object' && value !== null;
85
+ }
86
+ function childNodes(node) {
87
+ if (!Array.isArray(node.content))
88
+ return [];
89
+ return node.content.filter(isPageContentNode);
90
+ }
91
+ function nodeText(node) {
92
+ if (typeof node.text === 'string')
93
+ return node.text;
94
+ if (typeof node.attrs?.alt === 'string')
95
+ return node.attrs.alt;
96
+ return childNodes(node)
97
+ .map((child) => nodeText(child).trim())
98
+ .filter(Boolean)
99
+ .join('\n');
100
+ }
101
+ function searchableText(contentJson) {
102
+ const content = Array.isArray(contentJson.content) ? contentJson.content : [];
103
+ return content
104
+ .filter(isPageContentNode)
105
+ .map((node) => nodeText(node).trim())
106
+ .filter(Boolean)
107
+ .join('\n\n');
108
+ }
109
+ function pageDocument(value) {
110
+ if (!isNode(value) || value.type !== 'doc')
111
+ throw new ValidationError('contentJson', 'Page content must be a Tiptap document');
112
+ return value;
113
+ }
114
+ function supportedUrl(value) {
115
+ if (typeof value !== 'string')
116
+ throw new ValidationError('markdown', 'Link URL is required');
117
+ if (value.startsWith('/') && !value.startsWith('//'))
118
+ return;
119
+ try {
120
+ const url = new URL(value);
121
+ if (!['http:', 'https:', 'mailto:'].includes(url.protocol))
122
+ throw new Error();
123
+ }
124
+ catch {
125
+ throw new ValidationError('markdown', `Unsupported link URL: ${value}`);
126
+ }
127
+ }
128
+ function validateMarkdownContent(value) {
129
+ if (!isNode(value) || typeof value.type !== 'string')
130
+ throw new ValidationError('contentJson', 'Page content contains an invalid Tiptap node');
131
+ if (!markdownNodeTypes.has(value.type))
132
+ throw new ValidationError('contentJson', `Tiptap node is not supported by Markdown: ${value.type}`);
133
+ if (value.type === 'image') {
134
+ if (!isNode(value.attrs) || typeof value.attrs.src !== 'string')
135
+ throw new ValidationError('contentJson', 'Image is missing its source URL');
136
+ if (Array.isArray(value.content) && value.content.length > 0)
137
+ throw new ValidationError('contentJson', 'Image captions are not supported by Markdown export');
138
+ supportedUrl(value.attrs.src);
139
+ }
140
+ if (Array.isArray(value.marks)) {
141
+ for (const mark of value.marks) {
142
+ if (!isNode(mark) || typeof mark.type !== 'string' || !markdownMarkTypes.has(mark.type))
143
+ throw new ValidationError('contentJson', `Tiptap mark is not supported by Markdown: ${isNode(mark) ? String(mark.type) : 'unknown'}`);
144
+ if (mark.type === 'link') {
145
+ if (!isNode(mark.attrs) || typeof mark.attrs.href !== 'string')
146
+ throw new ValidationError('contentJson', 'Link URL is required');
147
+ supportedUrl(mark.attrs.href);
148
+ }
149
+ }
150
+ }
151
+ if (!Array.isArray(value.content))
152
+ return;
153
+ for (const child of value.content)
154
+ validateMarkdownContent(child);
155
+ }
156
+ function metadataFromMarkdown(markdown) {
157
+ const revision = /^<!-- zl-page (\{[^\n]+\}) -->\s*\n?/.exec(markdown);
158
+ if (!revision)
159
+ return { metadata: {}, source: markdown };
160
+ try {
161
+ const parsed = JSON.parse(revision[1]);
162
+ if (!isNode(parsed))
163
+ throw new Error();
164
+ return { metadata: parsed, source: markdown.slice(revision[0].length) };
165
+ }
166
+ catch {
167
+ throw new ValidationError('markdown', 'Invalid zl-page revision marker');
168
+ }
169
+ }
170
+ function titleAndBody(markdown) {
171
+ const firstHeading = /^#\s+(.+?)\s*(?:\n|$)/.exec(markdown);
172
+ return {
173
+ title: firstHeading?.[1] ?? 'Untitled',
174
+ body: firstHeading ? markdown.slice(firstHeading[0].length) : markdown,
175
+ };
176
+ }
177
+ export function importPageMarkdown(markdown) {
178
+ const { metadata, source } = metadataFromMarkdown(markdown);
179
+ const { title, body } = titleAndBody(source);
180
+ try {
181
+ const contentJson = markdownManager.parse(body);
182
+ validateMarkdownContent(contentJson);
183
+ return {
184
+ title,
185
+ contentJson: contentJson,
186
+ contentHtml: renderToHTMLString({
187
+ content: contentJson,
188
+ extensions: pageExtensions,
189
+ }),
190
+ searchableText: searchableText(contentJson),
191
+ ...(typeof metadata.id === 'string' ? { pageId: metadata.id } : {}),
192
+ ...(typeof metadata.expectedUpdatedAt === 'string'
193
+ ? { expectedUpdatedAt: metadata.expectedUpdatedAt }
194
+ : {}),
195
+ };
196
+ }
197
+ catch (error) {
198
+ if (error instanceof ValidationError)
199
+ throw error;
200
+ const message = error instanceof Error ? error.message : 'unknown Markdown conversion error';
201
+ throw new ValidationError('markdown', `Unable to convert Markdown: ${message}`);
202
+ }
203
+ }
204
+ export function exportPageMarkdown(input) {
205
+ const contentJson = pageDocument(input.contentJson);
206
+ validateMarkdownContent(input.contentJson);
207
+ const body = markdownManager.serialize(contentJson).trim();
208
+ const marker = input.id && input.expectedUpdatedAt
209
+ ? `<!-- zl-page ${JSON.stringify({ id: input.id, expectedUpdatedAt: input.expectedUpdatedAt })} -->\n`
210
+ : '';
211
+ return `${marker}# ${input.title}${body ? `\n\n${body}` : ''}\n`;
212
+ }
213
+ //# sourceMappingURL=page-markdown.js.map
@@ -0,0 +1,178 @@
1
+ import { readFile, writeFile } from 'node:fs/promises';
2
+ import { client } from '../client.js';
3
+ import { exportPageMarkdown, importPageMarkdown } from './page-markdown.js';
4
+ import { ValidationError } from '../utils/errors.js';
5
+ import { printJson } from '../utils/output.js';
6
+ import { action, dataRow, fields, isRow, printPage, resourceId, } from '../utils/product-api.js';
7
+ function pageUpdatedAt(page) {
8
+ if (typeof page.updatedAt !== 'string' || !page.updatedAt) {
9
+ throw new ValidationError('updatedAt', 'Page response is missing its revision timestamp');
10
+ }
11
+ return page.updatedAt;
12
+ }
13
+ function pageContent(page) {
14
+ if (!isRow(page.contentJson))
15
+ throw new ValidationError('contentJson', 'Page response is missing Tiptap content');
16
+ return page.contentJson;
17
+ }
18
+ async function readPageMarkdown(path) {
19
+ try {
20
+ return importPageMarkdown(await readFile(path, 'utf8'));
21
+ }
22
+ catch (error) {
23
+ if (error instanceof ValidationError)
24
+ throw error;
25
+ throw new ValidationError('input', `Unable to read Markdown file: ${path}`);
26
+ }
27
+ }
28
+ export async function listPagesAction(options) {
29
+ const result = dataRow(await client.get('/pages'));
30
+ if (!Array.isArray(result.pages) || !result.pages.every(isRow)) {
31
+ throw new ValidationError('pages', 'Invalid Page list response');
32
+ }
33
+ printPage(result.pages, options);
34
+ }
35
+ export async function viewPageAction(id, options) {
36
+ const page = dataRow(await client.get(`/pages/${resourceId(id)}`));
37
+ if (options.fields) {
38
+ const selected = {};
39
+ for (const field of options.fields.split(',').map((value) => value.trim())) {
40
+ if (Object.hasOwn(page, field))
41
+ selected[field] = page[field];
42
+ }
43
+ printJson(selected);
44
+ return;
45
+ }
46
+ printJson(page);
47
+ }
48
+ export async function exportPageAction(id, options) {
49
+ const page = dataRow(await client.get(`/pages/${resourceId(id)}`));
50
+ const pageId = typeof page.id === 'string' ? resourceId(page.id) : resourceId(id);
51
+ if (typeof page.title !== 'string')
52
+ throw new ValidationError('title', 'Page response is missing title');
53
+ const markdown = exportPageMarkdown({
54
+ id: pageId,
55
+ title: page.title,
56
+ contentJson: pageContent(page),
57
+ expectedUpdatedAt: pageUpdatedAt(page),
58
+ });
59
+ if (options.output) {
60
+ await writeFile(options.output, markdown, { flag: 'wx', mode: 0o600 });
61
+ printJson({ output: options.output, pageId });
62
+ return;
63
+ }
64
+ process.stdout.write(markdown);
65
+ }
66
+ export async function createPageAction(options) {
67
+ const imported = await readPageMarkdown(options.input);
68
+ const payload = {
69
+ title: imported.title,
70
+ parentPageId: options.parentPageId ? resourceId(options.parentPageId) : null,
71
+ folderId: options.folderId ? resourceId(options.folderId) : null,
72
+ };
73
+ if (!options.apply || options.dryRun) {
74
+ printJson({
75
+ dryRun: true,
76
+ requests: [
77
+ { method: 'POST', path: '/pages', payload },
78
+ {
79
+ method: 'PUT',
80
+ path: '/pages/<created-page-id>/content',
81
+ payload: {
82
+ contentJson: imported.contentJson,
83
+ contentHtml: imported.contentHtml,
84
+ searchableText: imported.searchableText,
85
+ },
86
+ },
87
+ ],
88
+ });
89
+ return;
90
+ }
91
+ const created = dataRow(await client.post('/pages', payload));
92
+ if (typeof created.id !== 'string')
93
+ throw new ValidationError('id', 'Page create response is missing ID');
94
+ const pageId = resourceId(created.id);
95
+ const saved = dataRow(await client.put(`/pages/${pageId}/content`, {
96
+ contentJson: imported.contentJson,
97
+ contentHtml: imported.contentHtml,
98
+ searchableText: imported.searchableText,
99
+ expectedUpdatedAt: pageUpdatedAt(created),
100
+ }));
101
+ printJson(saved);
102
+ }
103
+ export async function updatePageAction(id, options) {
104
+ const pageId = resourceId(id);
105
+ const imported = await readPageMarkdown(options.input);
106
+ if (imported.pageId && imported.pageId !== pageId) {
107
+ throw new ValidationError('input', 'Markdown export belongs to a different Page');
108
+ }
109
+ if (!options.apply || options.dryRun) {
110
+ printJson({
111
+ dryRun: true,
112
+ requests: [
113
+ { method: 'PATCH', path: `/pages/${pageId}`, payload: { title: imported.title } },
114
+ {
115
+ method: 'PUT',
116
+ path: `/pages/${pageId}/content`,
117
+ payload: {
118
+ contentJson: imported.contentJson,
119
+ contentHtml: imported.contentHtml,
120
+ searchableText: imported.searchableText,
121
+ },
122
+ },
123
+ ],
124
+ });
125
+ return;
126
+ }
127
+ if (!imported.expectedUpdatedAt) {
128
+ throw new ValidationError('input', 'Update requires a current Markdown export with its revision marker');
129
+ }
130
+ const current = dataRow(await client.get(`/pages/${pageId}`));
131
+ if (pageUpdatedAt(current) !== imported.expectedUpdatedAt) {
132
+ throw new ValidationError('input', 'Page changed since export; export it again before updating');
133
+ }
134
+ let latest = current;
135
+ if (current.title !== imported.title) {
136
+ latest = dataRow(await client.patch(`/pages/${pageId}`, {
137
+ title: imported.title,
138
+ expectedUpdatedAt: imported.expectedUpdatedAt,
139
+ }));
140
+ }
141
+ const saved = dataRow(await client.put(`/pages/${pageId}/content`, {
142
+ contentJson: imported.contentJson,
143
+ contentHtml: imported.contentHtml,
144
+ searchableText: imported.searchableText,
145
+ expectedUpdatedAt: pageUpdatedAt(latest),
146
+ }));
147
+ printJson(saved);
148
+ }
149
+ export function registerPageCommand(program) {
150
+ const page = program.command('page').description('Manage Team Pages as Markdown documents');
151
+ page
152
+ .command('list')
153
+ .option('--limit <n>', 'Maximum results', '100')
154
+ .option('--offset <n>', 'Result offset', '0')
155
+ .action(async (options) => action(() => listPagesAction({ ...options, ...fields(program) })));
156
+ page
157
+ .command('view <id>')
158
+ .action(async (id) => action(() => viewPageAction(id, fields(program))));
159
+ page
160
+ .command('export <id>')
161
+ .option('--output <path>', 'Markdown output file (must not exist)')
162
+ .action(async (id, options) => action(() => exportPageAction(id, options)));
163
+ page
164
+ .command('create')
165
+ .requiredOption('--input <file>', 'Markdown document')
166
+ .option('--parent-page-id <id>', 'Parent Page ID')
167
+ .option('--folder-id <id>', 'Page folder ID')
168
+ .option('--apply', 'Create the Page')
169
+ .option('--dry-run', 'Preview creation')
170
+ .action(async (options) => action(() => createPageAction(options)));
171
+ page
172
+ .command('update <id>')
173
+ .requiredOption('--input <file>', 'Markdown export')
174
+ .option('--apply', 'Apply the Page update')
175
+ .option('--dry-run', 'Preview update')
176
+ .action(async (id, options) => action(() => updatePageAction(id, options)));
177
+ }
178
+ //# sourceMappingURL=page.js.map
@@ -0,0 +1,97 @@
1
+ import { client } from '../client.js';
2
+ import { handleError } from '../utils/errors.js';
3
+ import { filterSingle, printJson, printList } from '../utils/output.js';
4
+ import { parseJson, validateId } from '../utils/validate.js';
5
+ export async function listProjectsAction(options) {
6
+ const query = {};
7
+ if (options.teamId)
8
+ query['teamId'] = options.teamId;
9
+ if (options.cursor)
10
+ query['cursor'] = options.cursor;
11
+ const response = await client.get('/projects', Object.keys(query).length > 0 ? query : undefined);
12
+ const projects = options.includeArchived
13
+ ? response.data
14
+ : response.data.filter((project) => project['status'] !== 'archived');
15
+ printList(projects, options.fields, response.nextCursor);
16
+ }
17
+ export async function viewProjectAction(projectId, options) {
18
+ validateId(projectId, 'projectId');
19
+ const project = await client.get(`/projects/${projectId}`);
20
+ printJson(filterSingle(project, options.fields));
21
+ }
22
+ export async function createProjectAction(options) {
23
+ const payload = parseJson(options.json);
24
+ if (options.dryRun) {
25
+ printJson({ dryRun: true, method: 'POST', path: '/projects', payload });
26
+ return;
27
+ }
28
+ const result = await client.post('/projects', payload);
29
+ printJson(result);
30
+ }
31
+ export async function updateProjectAction(projectId, options) {
32
+ validateId(projectId, 'projectId');
33
+ const payload = parseJson(options.json);
34
+ if (options.dryRun) {
35
+ printJson({ dryRun: true, method: 'PATCH', path: `/projects/${projectId}`, payload });
36
+ return;
37
+ }
38
+ const result = await client.patch(`/projects/${projectId}`, payload);
39
+ printJson(result);
40
+ }
41
+ export function registerProjectCommand(program) {
42
+ const project = program.command('project').description('Manage projects');
43
+ project
44
+ .command('list')
45
+ .description('List projects')
46
+ .option('--team-id <id>', 'Filter by team ID')
47
+ .option('--cursor <token>', 'Pagination cursor from previous response')
48
+ .option('--include-archived', 'Include archived projects')
49
+ .action(async (options) => {
50
+ try {
51
+ const globalOpts = program.optsWithGlobals();
52
+ await listProjectsAction({ ...options, fields: globalOpts.fields });
53
+ }
54
+ catch (error) {
55
+ handleError(error);
56
+ }
57
+ });
58
+ project
59
+ .command('view <project-id>')
60
+ .description('View a project by ID')
61
+ .action(async (projectId) => {
62
+ try {
63
+ const globalOpts = program.optsWithGlobals();
64
+ await viewProjectAction(projectId, { fields: globalOpts.fields });
65
+ }
66
+ catch (error) {
67
+ handleError(error);
68
+ }
69
+ });
70
+ project
71
+ .command('create')
72
+ .description('Create a project')
73
+ .requiredOption('--json <payload>', 'Project fields as JSON')
74
+ .option('--dry-run', 'Preview without creating', false)
75
+ .action(async (options) => {
76
+ try {
77
+ await createProjectAction(options);
78
+ }
79
+ catch (error) {
80
+ handleError(error);
81
+ }
82
+ });
83
+ project
84
+ .command('update <project-id>')
85
+ .description('Update a project')
86
+ .requiredOption('--json <payload>', 'Fields to update as JSON')
87
+ .option('--dry-run', 'Preview without updating', false)
88
+ .action(async (projectId, options) => {
89
+ try {
90
+ await updateProjectAction(projectId, options);
91
+ }
92
+ catch (error) {
93
+ handleError(error);
94
+ }
95
+ });
96
+ }
97
+ //# sourceMappingURL=project.js.map
@@ -0,0 +1,178 @@
1
+ import { handleError, ValidationError } from '../utils/errors.js';
2
+ import { printJson } from '../utils/output.js';
3
+ const SCHEMAS = {
4
+ task: {
5
+ resource: 'task',
6
+ endpoints: {
7
+ list: 'GET /api/projects/:projectId/tasks',
8
+ view: 'GET /api/tasks/:taskId',
9
+ create: 'POST /api/projects/:projectId/tasks',
10
+ update: 'PATCH /api/tasks/:taskId',
11
+ delete: 'DELETE /api/tasks/:taskId',
12
+ move: 'POST /api/projects/:projectId/tasks/:taskId/move',
13
+ uploadAttachment: 'POST /api/tasks/:taskId/attachments (multipart form field: file)',
14
+ },
15
+ fields: {
16
+ id: 'string (uuid)',
17
+ projectId: 'string (uuid)',
18
+ cycleId: 'string (uuid) | null',
19
+ title: 'string',
20
+ description: 'string | null',
21
+ stateId: 'string (uuid) — get valid IDs from: zl state list --project-id <id>',
22
+ priority: 'enum: critical | high | medium | low',
23
+ category: 'string | null',
24
+ assigneeId: 'string (uuid) | null',
25
+ dueDate: 'string (ISO date YYYY-MM-DD) | null',
26
+ sequenceId: 'number',
27
+ sortOrder: 'number',
28
+ linkedWorkflowId: 'string (uuid) | null',
29
+ url: 'string (canonical Motion task URL; included in create, view, update, and move responses)',
30
+ reviewRequired: 'boolean',
31
+ customProperties: 'object',
32
+ createdById: 'string (uuid)',
33
+ createdAt: 'string (ISO datetime)',
34
+ updatedAt: 'string (ISO datetime)',
35
+ label_ids: 'string[] — get valid IDs from: zl label list --project-id <id>',
36
+ },
37
+ createFields: {
38
+ title: 'string (required)',
39
+ stateId: 'string (uuid, optional — defaults to project default state)',
40
+ description: 'string (optional)',
41
+ priority: 'enum: critical | high | medium | low (optional)',
42
+ assigneeId: 'string (uuid, optional)',
43
+ dueDate: 'string YYYY-MM-DD (optional)',
44
+ cycleId: 'string (uuid, optional)',
45
+ },
46
+ updateFields: {
47
+ title: 'string',
48
+ description: 'string | null',
49
+ stateId: 'string (uuid)',
50
+ priority: 'enum: critical | high | medium | low',
51
+ assigneeId: 'string (uuid) | null',
52
+ dueDate: 'string YYYY-MM-DD | null',
53
+ cycleId: 'string (uuid) | null',
54
+ sortOrder: 'number',
55
+ },
56
+ moveFields: {
57
+ stateName: 'string (optional — target state name; defaults to an equivalent state)',
58
+ },
59
+ },
60
+ project: {
61
+ resource: 'project',
62
+ endpoints: {
63
+ list: 'GET /api/projects',
64
+ view: 'GET /api/projects/:id',
65
+ create: 'POST /api/projects',
66
+ update: 'PATCH /api/projects/:id',
67
+ },
68
+ fields: {
69
+ id: 'string (uuid)',
70
+ teamId: 'string (uuid)',
71
+ name: 'string',
72
+ description: 'string | null',
73
+ status: 'string',
74
+ startDate: 'string (ISO date) | null',
75
+ targetDate: 'string (ISO date) | null',
76
+ createdAt: 'string (ISO datetime)',
77
+ updatedAt: 'string (ISO datetime)',
78
+ },
79
+ createFields: {
80
+ name: 'string (required)',
81
+ description: 'string (optional)',
82
+ status: 'string (optional)',
83
+ startDate: 'string YYYY-MM-DD (optional)',
84
+ targetDate: 'string YYYY-MM-DD (optional)',
85
+ },
86
+ },
87
+ 'api-token': {
88
+ resource: 'api-token',
89
+ endpoints: {
90
+ create: 'Browser-authorized CLI session: GET /api/auth/cli-login then POST /api/auth/cli-token',
91
+ },
92
+ fields: {
93
+ id: 'string (uuid)',
94
+ label: 'string',
95
+ teamId: 'string (uuid)',
96
+ scopes: 'string[]',
97
+ createdAt: 'string (ISO datetime)',
98
+ lastUsedAt: 'string (ISO datetime) | null',
99
+ expiresAt: 'string (ISO datetime) | null',
100
+ },
101
+ createFields: {
102
+ label: 'string (required, 1-64 characters)',
103
+ scopes: 'string[] (required — least-privilege scopes)',
104
+ expiresAt: 'string (future ISO datetime, required)',
105
+ team: 'selected in the authenticated browser from the user active memberships',
106
+ },
107
+ },
108
+ state: {
109
+ resource: 'state',
110
+ endpoints: { list: 'GET /api/projects/:projectId/states' },
111
+ fields: {
112
+ id: 'string (uuid)',
113
+ projectId: 'string (uuid)',
114
+ name: 'string',
115
+ color: 'string (hex)',
116
+ type: 'enum: backlog | unstarted | started | completed | cancelled',
117
+ isDefault: 'boolean',
118
+ },
119
+ },
120
+ label: {
121
+ resource: 'label',
122
+ endpoints: { list: 'GET /api/projects/:projectId/labels' },
123
+ fields: {
124
+ id: 'string (uuid)',
125
+ name: 'string',
126
+ color: 'string (hex)',
127
+ },
128
+ },
129
+ cycle: {
130
+ resource: 'cycle',
131
+ endpoints: {
132
+ list: 'GET /api/projects/:projectId/cycles',
133
+ view: 'GET /api/cycles/:cycleId',
134
+ },
135
+ fields: {
136
+ id: 'string (uuid)',
137
+ projectId: 'string (uuid)',
138
+ name: 'string',
139
+ status: 'enum: draft | active | completed',
140
+ startDate: 'string (ISO date) | null',
141
+ endDate: 'string (ISO date) | null',
142
+ },
143
+ },
144
+ member: {
145
+ resource: 'member',
146
+ endpoints: { list: 'GET /api/team/members' },
147
+ fields: {
148
+ id: 'string (uuid)',
149
+ name: 'string | null',
150
+ email: 'string',
151
+ role: 'string',
152
+ joinedAt: 'string (ISO datetime)',
153
+ status: 'active',
154
+ },
155
+ },
156
+ };
157
+ export function registerSchemaCommand(program) {
158
+ program
159
+ .command('schema [resource]')
160
+ .description('Print field schema for a Zalos resource (no network call)')
161
+ .action((resource) => {
162
+ try {
163
+ if (!resource) {
164
+ printJson({ resources: Object.keys(SCHEMAS) });
165
+ return;
166
+ }
167
+ const schema = SCHEMAS[resource];
168
+ if (!schema) {
169
+ throw new ValidationError('resource', `Unknown resource: ${resource}. Available: ${Object.keys(SCHEMAS).join(', ')}`);
170
+ }
171
+ printJson(schema);
172
+ }
173
+ catch (error) {
174
+ handleError(error);
175
+ }
176
+ });
177
+ }
178
+ //# sourceMappingURL=schema.js.map