@cxtms/cx-schema 1.6.1 → 1.6.4
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/.claude/skills/cx-core/SKILL.md +85 -0
- package/.claude/skills/cx-module/SKILL.md +26 -0
- package/.claude/skills/cx-workflow/SKILL.md +66 -3
- package/dist/cli.js +183 -149
- package/dist/cli.js.map +1 -1
- package/package.json +1 -1
package/dist/cli.js
CHANGED
|
@@ -115,7 +115,7 @@ ${chalk_1.default.bold.yellow('COMMANDS:')}
|
|
|
115
115
|
${chalk_1.default.green('pat')} Manage personal access tokens (create, list, revoke)
|
|
116
116
|
${chalk_1.default.green('orgs')} List, select, or set active organization
|
|
117
117
|
${chalk_1.default.green('appmodule')} Manage app modules on a CX server (push, delete)
|
|
118
|
-
${chalk_1.default.green('workflow')} Manage workflows on a CX server (push, delete, execute, logs)
|
|
118
|
+
${chalk_1.default.green('workflow')} Manage workflows on a CX server (push, delete, execute, logs, log)
|
|
119
119
|
${chalk_1.default.green('publish')} Publish all modules and workflows to a CX server
|
|
120
120
|
${chalk_1.default.green('schema')} Show JSON schema for a component or task
|
|
121
121
|
${chalk_1.default.green('example')} Show example YAML for a component or task
|
|
@@ -141,6 +141,11 @@ ${chalk_1.default.bold.yellow('OPTIONS:')}
|
|
|
141
141
|
${chalk_1.default.green('--copy')} Copy component instead of moving (source unchanged, target gets higher priority)
|
|
142
142
|
${chalk_1.default.green('--org <id>')} Organization ID for server commands
|
|
143
143
|
${chalk_1.default.green('--vars <json>')} JSON variables for workflow execute
|
|
144
|
+
${chalk_1.default.green('--from <date>')} Filter logs from date (YYYY-MM-DD)
|
|
145
|
+
${chalk_1.default.green('--to <date>')} Filter logs to date (YYYY-MM-DD)
|
|
146
|
+
${chalk_1.default.green('--output <file>')} Save workflow log to file (or -o)
|
|
147
|
+
${chalk_1.default.green('--console')} Print workflow log to stdout
|
|
148
|
+
${chalk_1.default.green('--json')} Download JSON log instead of text
|
|
144
149
|
|
|
145
150
|
${chalk_1.default.bold.yellow('VALIDATION EXAMPLES:')}
|
|
146
151
|
${chalk_1.default.gray('# Validate a module YAML file')}
|
|
@@ -210,10 +215,7 @@ ${chalk_1.default.bold.yellow('AUTH COMMANDS:')}
|
|
|
210
215
|
${chalk_1.default.gray('# Login to a CX environment')}
|
|
211
216
|
${chalk_1.default.cyan(`${PROGRAM_NAME} login https://qa.storevista.acuitive.net`)}
|
|
212
217
|
|
|
213
|
-
${chalk_1.default.gray('# Logout from
|
|
214
|
-
${chalk_1.default.cyan(`${PROGRAM_NAME} logout https://qa.storevista.acuitive.net`)}
|
|
215
|
-
|
|
216
|
-
${chalk_1.default.gray('# List stored sessions')}
|
|
218
|
+
${chalk_1.default.gray('# Logout from current session')}
|
|
217
219
|
${chalk_1.default.cyan(`${PROGRAM_NAME} logout`)}
|
|
218
220
|
|
|
219
221
|
${chalk_1.default.bold.yellow('PAT COMMANDS:')}
|
|
@@ -263,11 +265,16 @@ ${chalk_1.default.bold.yellow('WORKFLOW COMMANDS:')}
|
|
|
263
265
|
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow execute <workflowId|file.yaml>`)}
|
|
264
266
|
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow execute <workflowId> --vars '{"city":"London"}'`)}
|
|
265
267
|
|
|
266
|
-
${chalk_1.default.gray('# List
|
|
267
|
-
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow
|
|
268
|
+
${chalk_1.default.gray('# List execution logs for a workflow (sorted desc)')}
|
|
269
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow logs <workflowId|file.yaml>`)}
|
|
270
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow logs <workflowId> --from 2026-01-01 --to 2026-01-31`)}
|
|
268
271
|
|
|
269
|
-
${chalk_1.default.gray('#
|
|
270
|
-
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow
|
|
272
|
+
${chalk_1.default.gray('# Download a specific execution log')}
|
|
273
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow log <executionId>`)} ${chalk_1.default.gray('# save txt log to temp dir')}
|
|
274
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow log <executionId> --output log.txt`)} ${chalk_1.default.gray('# save to file')}
|
|
275
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow log <executionId> --console`)} ${chalk_1.default.gray('# print to stdout')}
|
|
276
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow log <executionId> --json`)} ${chalk_1.default.gray('# download JSON log (more detail)')}
|
|
277
|
+
${chalk_1.default.cyan(`${PROGRAM_NAME} workflow log <executionId> --json --console`)} ${chalk_1.default.gray('# JSON log to stdout')}
|
|
271
278
|
|
|
272
279
|
${chalk_1.default.bold.yellow('PUBLISH COMMANDS:')}
|
|
273
280
|
${chalk_1.default.gray('# Publish all modules and workflows from current project')}
|
|
@@ -1458,28 +1465,33 @@ function runSetupClaude() {
|
|
|
1458
1465
|
// ============================================================================
|
|
1459
1466
|
const AUTH_CALLBACK_PORT = 9000;
|
|
1460
1467
|
const AUTH_TIMEOUT_MS = 2 * 60 * 1000; // 2 minutes
|
|
1461
|
-
function
|
|
1462
|
-
|
|
1468
|
+
function getSessionDir() {
|
|
1469
|
+
const projectName = path.basename(process.cwd());
|
|
1470
|
+
return path.join(os.homedir(), '.cxtms', projectName);
|
|
1463
1471
|
}
|
|
1464
|
-
function
|
|
1465
|
-
|
|
1466
|
-
return path.join(getTokenDir(), `${hostname}.json`);
|
|
1472
|
+
function getSessionFilePath() {
|
|
1473
|
+
return path.join(getSessionDir(), '.session.json');
|
|
1467
1474
|
}
|
|
1468
|
-
function
|
|
1469
|
-
const filePath =
|
|
1475
|
+
function readSessionFile() {
|
|
1476
|
+
const filePath = getSessionFilePath();
|
|
1470
1477
|
if (!fs.existsSync(filePath))
|
|
1471
1478
|
return null;
|
|
1472
|
-
|
|
1479
|
+
try {
|
|
1480
|
+
return JSON.parse(fs.readFileSync(filePath, 'utf-8'));
|
|
1481
|
+
}
|
|
1482
|
+
catch {
|
|
1483
|
+
return null;
|
|
1484
|
+
}
|
|
1473
1485
|
}
|
|
1474
|
-
function
|
|
1475
|
-
const dir =
|
|
1486
|
+
function writeSessionFile(data) {
|
|
1487
|
+
const dir = getSessionDir();
|
|
1476
1488
|
if (!fs.existsSync(dir)) {
|
|
1477
1489
|
fs.mkdirSync(dir, { recursive: true });
|
|
1478
1490
|
}
|
|
1479
|
-
fs.writeFileSync(
|
|
1491
|
+
fs.writeFileSync(getSessionFilePath(), JSON.stringify(data, null, 2), 'utf-8');
|
|
1480
1492
|
}
|
|
1481
|
-
function
|
|
1482
|
-
const filePath =
|
|
1493
|
+
function deleteSessionFile() {
|
|
1494
|
+
const filePath = getSessionFilePath();
|
|
1483
1495
|
if (fs.existsSync(filePath)) {
|
|
1484
1496
|
fs.unlinkSync(filePath);
|
|
1485
1497
|
}
|
|
@@ -1619,7 +1631,7 @@ async function refreshTokens(stored) {
|
|
|
1619
1631
|
refresh_token: data.refresh_token || stored.refresh_token,
|
|
1620
1632
|
expires_at: Math.floor(Date.now() / 1000) + (data.expires_in || 3600),
|
|
1621
1633
|
};
|
|
1622
|
-
|
|
1634
|
+
writeSessionFile(updated);
|
|
1623
1635
|
return updated;
|
|
1624
1636
|
}
|
|
1625
1637
|
async function runLogin(domain) {
|
|
@@ -1663,51 +1675,30 @@ async function runLogin(domain) {
|
|
|
1663
1675
|
// Step 6: Exchange code for tokens
|
|
1664
1676
|
console.log(chalk_1.default.gray(' Exchanging authorization code...'));
|
|
1665
1677
|
const tokens = await exchangeCodeForTokens(domain, clientId, code, codeVerifier);
|
|
1666
|
-
// Step 7: Store
|
|
1667
|
-
|
|
1678
|
+
// Step 7: Store session locally
|
|
1679
|
+
writeSessionFile(tokens);
|
|
1668
1680
|
close();
|
|
1669
1681
|
console.log(chalk_1.default.green(` ✓ Logged in to ${new URL(domain).hostname}`));
|
|
1670
|
-
console.log(chalk_1.default.gray(`
|
|
1671
|
-
}
|
|
1672
|
-
async function runLogout(
|
|
1673
|
-
|
|
1674
|
-
|
|
1675
|
-
|
|
1676
|
-
|
|
1677
|
-
console.log(chalk_1.default.gray('\n No stored sessions.\n'));
|
|
1678
|
-
return;
|
|
1679
|
-
}
|
|
1680
|
-
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
1681
|
-
if (files.length === 0) {
|
|
1682
|
-
console.log(chalk_1.default.gray('\n No stored sessions.\n'));
|
|
1683
|
-
return;
|
|
1684
|
-
}
|
|
1685
|
-
console.log(chalk_1.default.bold.cyan('\n Stored sessions:\n'));
|
|
1686
|
-
for (const f of files) {
|
|
1687
|
-
const hostname = f.replace('.json', '');
|
|
1688
|
-
console.log(chalk_1.default.white(` ${hostname}`));
|
|
1689
|
-
}
|
|
1690
|
-
console.log(chalk_1.default.gray(`\n Usage: ${PROGRAM_NAME} logout <url>\n`));
|
|
1691
|
-
return;
|
|
1692
|
-
}
|
|
1693
|
-
// Normalize URL
|
|
1694
|
-
if (!domain.startsWith('http://') && !domain.startsWith('https://')) {
|
|
1695
|
-
domain = `https://${domain}`;
|
|
1696
|
-
}
|
|
1697
|
-
domain = domain.replace(/\/+$/, '');
|
|
1698
|
-
const stored = readTokenFile(domain);
|
|
1699
|
-
if (!stored) {
|
|
1700
|
-
console.log(chalk_1.default.gray(`\n No session found for ${new URL(domain).hostname}\n`));
|
|
1682
|
+
console.log(chalk_1.default.gray(` Session stored at: ${getSessionFilePath()}\n`));
|
|
1683
|
+
}
|
|
1684
|
+
async function runLogout(_domain) {
|
|
1685
|
+
const session = readSessionFile();
|
|
1686
|
+
if (!session) {
|
|
1687
|
+
console.log(chalk_1.default.gray('\n No active session in this project.\n'));
|
|
1688
|
+
console.log(chalk_1.default.gray(` Login first: ${PROGRAM_NAME} login <url>\n`));
|
|
1701
1689
|
return;
|
|
1702
1690
|
}
|
|
1703
1691
|
console.log(chalk_1.default.bold.cyan('\n CX CLI Logout\n'));
|
|
1692
|
+
console.log(chalk_1.default.gray(` Server: ${new URL(session.domain).hostname}`));
|
|
1704
1693
|
// Revoke tokens (non-fatal)
|
|
1705
|
-
|
|
1706
|
-
|
|
1707
|
-
|
|
1708
|
-
|
|
1709
|
-
|
|
1710
|
-
|
|
1694
|
+
if (session.client_id && session.refresh_token) {
|
|
1695
|
+
console.log(chalk_1.default.gray(' Revoking tokens...'));
|
|
1696
|
+
await revokeToken(session.domain, session.client_id, session.access_token);
|
|
1697
|
+
await revokeToken(session.domain, session.client_id, session.refresh_token);
|
|
1698
|
+
}
|
|
1699
|
+
// Delete local session file
|
|
1700
|
+
deleteSessionFile();
|
|
1701
|
+
console.log(chalk_1.default.green(` ✓ Logged out from ${new URL(session.domain).hostname}\n`));
|
|
1711
1702
|
}
|
|
1712
1703
|
// ============================================================================
|
|
1713
1704
|
// AppModule Commands
|
|
@@ -1720,7 +1711,7 @@ async function graphqlRequest(domain, token, query, variables) {
|
|
|
1720
1711
|
if (process.env.CXTMS_AUTH)
|
|
1721
1712
|
throw new Error('PAT token unauthorized (401). Check your CXTMS_AUTH token.');
|
|
1722
1713
|
// Try refresh for OAuth sessions
|
|
1723
|
-
const stored =
|
|
1714
|
+
const stored = readSessionFile();
|
|
1724
1715
|
if (!stored)
|
|
1725
1716
|
throw new Error('Session expired. Run `cx-cli login <url>` again.');
|
|
1726
1717
|
try {
|
|
@@ -1812,43 +1803,20 @@ function resolveSession() {
|
|
|
1812
1803
|
expires_at: 0,
|
|
1813
1804
|
};
|
|
1814
1805
|
}
|
|
1815
|
-
// 1. Check
|
|
1816
|
-
const
|
|
1817
|
-
if (
|
|
1818
|
-
|
|
1819
|
-
|
|
1820
|
-
|
|
1821
|
-
console.error(chalk_1.default.red(`Not logged in to ${appDomain}. Run \`cx-cli login ${appDomain}\` first.`));
|
|
1822
|
-
process.exit(2);
|
|
1823
|
-
}
|
|
1824
|
-
// 2. Check for single session
|
|
1825
|
-
const dir = getTokenDir();
|
|
1826
|
-
if (!fs.existsSync(dir)) {
|
|
1827
|
-
console.error(chalk_1.default.red('Not logged in. Run `cx-cli login <url>` first.'));
|
|
1828
|
-
process.exit(2);
|
|
1829
|
-
}
|
|
1830
|
-
const files = fs.readdirSync(dir).filter(f => f.endsWith('.json'));
|
|
1831
|
-
if (files.length === 0) {
|
|
1832
|
-
console.error(chalk_1.default.red('Not logged in. Run `cx-cli login <url>` first.'));
|
|
1833
|
-
process.exit(2);
|
|
1834
|
-
}
|
|
1835
|
-
if (files.length === 1) {
|
|
1836
|
-
return JSON.parse(fs.readFileSync(path.join(dir, files[0]), 'utf-8'));
|
|
1837
|
-
}
|
|
1838
|
-
// 3. Multiple sessions — error
|
|
1839
|
-
console.error(chalk_1.default.red('Multiple sessions found:'));
|
|
1840
|
-
for (const f of files) {
|
|
1841
|
-
console.error(chalk_1.default.white(` ${f.replace('.json', '')}`));
|
|
1842
|
-
}
|
|
1843
|
-
console.error(chalk_1.default.gray('Add `server` field to app.yaml or use a single login session.'));
|
|
1806
|
+
// 1. Check local .cxtms/.session.json
|
|
1807
|
+
const session = readSessionFile();
|
|
1808
|
+
if (session)
|
|
1809
|
+
return session;
|
|
1810
|
+
// 2. Not logged in
|
|
1811
|
+
console.error(chalk_1.default.red('Not logged in. Run `cx-cli login <url>` first.'));
|
|
1844
1812
|
process.exit(2);
|
|
1845
1813
|
}
|
|
1846
1814
|
async function resolveOrgId(domain, token, override) {
|
|
1847
1815
|
// 1. Explicit override
|
|
1848
1816
|
if (override !== undefined)
|
|
1849
1817
|
return override;
|
|
1850
|
-
// 2. Cached in
|
|
1851
|
-
const stored =
|
|
1818
|
+
// 2. Cached in session file
|
|
1819
|
+
const stored = readSessionFile();
|
|
1852
1820
|
if (stored?.organization_id)
|
|
1853
1821
|
return stored.organization_id;
|
|
1854
1822
|
// 3. Query server
|
|
@@ -1864,7 +1832,7 @@ async function resolveOrgId(domain, token, override) {
|
|
|
1864
1832
|
// Cache it
|
|
1865
1833
|
if (stored) {
|
|
1866
1834
|
stored.organization_id = orgId;
|
|
1867
|
-
|
|
1835
|
+
writeSessionFile(stored);
|
|
1868
1836
|
}
|
|
1869
1837
|
return orgId;
|
|
1870
1838
|
}
|
|
@@ -2072,9 +2040,9 @@ async function runOrgsUse(orgIdStr) {
|
|
|
2072
2040
|
console.error('');
|
|
2073
2041
|
process.exit(2);
|
|
2074
2042
|
}
|
|
2075
|
-
// Save to
|
|
2043
|
+
// Save to session file
|
|
2076
2044
|
session.organization_id = orgId;
|
|
2077
|
-
|
|
2045
|
+
writeSessionFile(session);
|
|
2078
2046
|
console.log(chalk_1.default.green(`\n ✓ Context set to: ${match.companyName} (${orgId})\n`));
|
|
2079
2047
|
}
|
|
2080
2048
|
async function runOrgsSelect() {
|
|
@@ -2113,7 +2081,7 @@ async function runOrgsSelect() {
|
|
|
2113
2081
|
}
|
|
2114
2082
|
const selected = orgs[idx];
|
|
2115
2083
|
session.organization_id = selected.organizationId;
|
|
2116
|
-
|
|
2084
|
+
writeSessionFile(session);
|
|
2117
2085
|
console.log(chalk_1.default.green(`\n ✓ Context set to: ${selected.companyName} (${selected.organizationId})\n`));
|
|
2118
2086
|
}
|
|
2119
2087
|
// ============================================================================
|
|
@@ -2287,57 +2255,85 @@ async function runWorkflowExecute(workflowIdOrFile, orgOverride, variables) {
|
|
|
2287
2255
|
}
|
|
2288
2256
|
console.log('');
|
|
2289
2257
|
}
|
|
2290
|
-
|
|
2291
|
-
if (!workflowIdOrFile) {
|
|
2292
|
-
console.error(chalk_1.default.red('Error: Workflow ID or YAML file required'));
|
|
2293
|
-
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} workflow executions <workflowId|file.yaml> [--org <id>]`));
|
|
2294
|
-
process.exit(2);
|
|
2295
|
-
}
|
|
2296
|
-
const session = resolveSession();
|
|
2297
|
-
const domain = session.domain;
|
|
2298
|
-
const token = session.access_token;
|
|
2299
|
-
const orgId = await resolveOrgId(domain, token, orgOverride);
|
|
2300
|
-
// Resolve workflowId — accept UUID directly or extract from YAML file
|
|
2301
|
-
let workflowId = workflowIdOrFile;
|
|
2258
|
+
function resolveWorkflowId(workflowIdOrFile) {
|
|
2302
2259
|
if (workflowIdOrFile.endsWith('.yaml') || workflowIdOrFile.endsWith('.yml')) {
|
|
2303
2260
|
if (!fs.existsSync(workflowIdOrFile)) {
|
|
2304
2261
|
console.error(chalk_1.default.red(`Error: File not found: ${workflowIdOrFile}`));
|
|
2305
2262
|
process.exit(2);
|
|
2306
2263
|
}
|
|
2307
2264
|
const parsed = yaml_1.default.parse(fs.readFileSync(workflowIdOrFile, 'utf-8'));
|
|
2308
|
-
|
|
2309
|
-
if (!
|
|
2265
|
+
const id = parsed?.workflow?.workflowId;
|
|
2266
|
+
if (!id) {
|
|
2310
2267
|
console.error(chalk_1.default.red('Error: Workflow YAML is missing workflow.workflowId'));
|
|
2311
2268
|
process.exit(2);
|
|
2312
2269
|
}
|
|
2270
|
+
return id;
|
|
2271
|
+
}
|
|
2272
|
+
return workflowIdOrFile;
|
|
2273
|
+
}
|
|
2274
|
+
async function runWorkflowLogs(workflowIdOrFile, orgOverride, fromDate, toDate) {
|
|
2275
|
+
if (!workflowIdOrFile) {
|
|
2276
|
+
console.error(chalk_1.default.red('Error: Workflow ID or YAML file required'));
|
|
2277
|
+
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} workflow logs <workflowId|file.yaml> [--from <date>] [--to <date>]`));
|
|
2278
|
+
process.exit(2);
|
|
2279
|
+
}
|
|
2280
|
+
const session = resolveSession();
|
|
2281
|
+
const { domain, access_token: token } = session;
|
|
2282
|
+
const orgId = await resolveOrgId(domain, token, orgOverride);
|
|
2283
|
+
const workflowId = resolveWorkflowId(workflowIdOrFile);
|
|
2284
|
+
// Parse date filters
|
|
2285
|
+
const fromTs = fromDate ? new Date(fromDate).getTime() : 0;
|
|
2286
|
+
const toTs = toDate ? new Date(toDate + 'T23:59:59').getTime() : Infinity;
|
|
2287
|
+
if (fromDate && isNaN(fromTs)) {
|
|
2288
|
+
console.error(chalk_1.default.red(`Invalid --from date: ${fromDate}. Use YYYY-MM-DD format.`));
|
|
2289
|
+
process.exit(2);
|
|
2290
|
+
}
|
|
2291
|
+
if (toDate && isNaN(toTs)) {
|
|
2292
|
+
console.error(chalk_1.default.red(`Invalid --to date: ${toDate}. Use YYYY-MM-DD format.`));
|
|
2293
|
+
process.exit(2);
|
|
2313
2294
|
}
|
|
2314
2295
|
const data = await graphqlRequest(domain, token, `
|
|
2315
2296
|
query ($organizationId: Int!, $workflowId: UUID!) {
|
|
2316
|
-
workflowExecutions(organizationId: $organizationId, workflowId: $workflowId, take:
|
|
2297
|
+
workflowExecutions(organizationId: $organizationId, workflowId: $workflowId, take: 100) {
|
|
2317
2298
|
totalCount
|
|
2318
|
-
items { executionId executionStatus executedAt durationMs
|
|
2299
|
+
items { executionId executionStatus executedAt durationMs txtLogUrl user { fullName email } }
|
|
2319
2300
|
}
|
|
2320
2301
|
}
|
|
2321
2302
|
`, { organizationId: orgId, workflowId });
|
|
2322
|
-
|
|
2303
|
+
let items = data?.workflowExecutions?.items || [];
|
|
2323
2304
|
const total = data?.workflowExecutions?.totalCount || 0;
|
|
2324
|
-
|
|
2305
|
+
// Filter by date range
|
|
2306
|
+
if (fromDate || toDate) {
|
|
2307
|
+
items = items.filter((ex) => {
|
|
2308
|
+
const t = new Date(ex.executedAt).getTime();
|
|
2309
|
+
return t >= fromTs && t <= toTs;
|
|
2310
|
+
});
|
|
2311
|
+
}
|
|
2312
|
+
// Sort descending
|
|
2313
|
+
items.sort((a, b) => new Date(b.executedAt).getTime() - new Date(a.executedAt).getTime());
|
|
2314
|
+
console.log(chalk_1.default.bold.cyan('\n Workflow Logs\n'));
|
|
2325
2315
|
console.log(chalk_1.default.gray(` Server: ${new URL(domain).hostname}`));
|
|
2326
2316
|
console.log(chalk_1.default.gray(` Workflow: ${workflowId}`));
|
|
2327
|
-
console.log(chalk_1.default.gray(` Total: ${total}
|
|
2317
|
+
console.log(chalk_1.default.gray(` Total: ${total}`));
|
|
2318
|
+
if (fromDate || toDate) {
|
|
2319
|
+
console.log(chalk_1.default.gray(` Filter: ${fromDate || '...'} → ${toDate || '...'}`));
|
|
2320
|
+
}
|
|
2321
|
+
console.log(chalk_1.default.gray(` Showing: ${items.length}\n`));
|
|
2328
2322
|
if (items.length === 0) {
|
|
2329
2323
|
console.log(chalk_1.default.gray(' No executions found.\n'));
|
|
2330
2324
|
return;
|
|
2331
2325
|
}
|
|
2332
|
-
// Sort descending by executedAt (API may not support ordering)
|
|
2333
|
-
items.sort((a, b) => new Date(b.executedAt).getTime() - new Date(a.executedAt).getTime());
|
|
2334
2326
|
for (const ex of items) {
|
|
2335
2327
|
const date = new Date(ex.executedAt).toLocaleString();
|
|
2336
2328
|
const duration = ex.durationMs != null ? `${(ex.durationMs / 1000).toFixed(1)}s` : '?';
|
|
2337
2329
|
const statusColor = ex.executionStatus === 'Success' ? chalk_1.default.green : ex.executionStatus === 'Failed' ? chalk_1.default.red : chalk_1.default.yellow;
|
|
2338
|
-
|
|
2330
|
+
const logIcon = ex.txtLogUrl ? chalk_1.default.green('●') : chalk_1.default.gray('○');
|
|
2331
|
+
const user = ex.user?.fullName || ex.user?.email || '';
|
|
2332
|
+
console.log(` ${logIcon} ${chalk_1.default.white(ex.executionId)} ${statusColor(ex.executionStatus.padEnd(10))} ${date} ${chalk_1.default.gray(duration)}${user ? ' ' + chalk_1.default.gray(user) : ''}`);
|
|
2339
2333
|
}
|
|
2340
|
-
console.log(
|
|
2334
|
+
console.log();
|
|
2335
|
+
console.log(chalk_1.default.gray(` ${chalk_1.default.green('●')} log available ${chalk_1.default.gray('○')} no log`));
|
|
2336
|
+
console.log(chalk_1.default.gray(` Download: ${PROGRAM_NAME} workflow log <executionId> [--output <file>] [--console]\n`));
|
|
2341
2337
|
}
|
|
2342
2338
|
function fetchGzipText(url) {
|
|
2343
2339
|
const zlib = require('zlib');
|
|
@@ -2361,23 +2357,22 @@ function fetchGzipText(url) {
|
|
|
2361
2357
|
if (err) {
|
|
2362
2358
|
resolve(raw.toString('utf-8'));
|
|
2363
2359
|
return;
|
|
2364
|
-
}
|
|
2360
|
+
}
|
|
2365
2361
|
resolve(result.toString('utf-8'));
|
|
2366
2362
|
});
|
|
2367
2363
|
});
|
|
2368
2364
|
}).on('error', reject);
|
|
2369
2365
|
});
|
|
2370
2366
|
}
|
|
2371
|
-
async function
|
|
2367
|
+
async function runWorkflowLog(executionId, orgOverride, outputFile, toConsole, useJson) {
|
|
2372
2368
|
if (!executionId) {
|
|
2373
2369
|
console.error(chalk_1.default.red('Error: Execution ID required'));
|
|
2374
|
-
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} workflow
|
|
2370
|
+
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} workflow log <executionId> [--output <file>] [--console] [--json]`));
|
|
2375
2371
|
process.exit(2);
|
|
2376
2372
|
}
|
|
2377
2373
|
const session = resolveSession();
|
|
2378
2374
|
const { domain, access_token: token } = session;
|
|
2379
2375
|
const orgId = await resolveOrgId(domain, token, orgOverride);
|
|
2380
|
-
// Fetch execution details with log URLs
|
|
2381
2376
|
const data = await graphqlRequest(domain, token, `
|
|
2382
2377
|
query ($organizationId: Int!, $executionId: UUID!) {
|
|
2383
2378
|
workflowExecution(organizationId: $organizationId, executionId: $executionId) {
|
|
@@ -2392,33 +2387,60 @@ async function runWorkflowLogs(executionId, orgOverride) {
|
|
|
2392
2387
|
console.error(chalk_1.default.red(`Execution not found: ${executionId}`));
|
|
2393
2388
|
process.exit(2);
|
|
2394
2389
|
}
|
|
2390
|
+
const logUrl = useJson ? ex.jsonLogUrl : ex.txtLogUrl;
|
|
2391
|
+
const logType = useJson ? 'json' : 'txt';
|
|
2392
|
+
const ext = useJson ? '.json' : '.log';
|
|
2393
|
+
if (!logUrl) {
|
|
2394
|
+
console.error(chalk_1.default.yellow(`No ${logType} log available for this execution.`));
|
|
2395
|
+
process.exit(0);
|
|
2396
|
+
}
|
|
2395
2397
|
const date = new Date(ex.executedAt).toLocaleString();
|
|
2396
2398
|
const duration = ex.durationMs != null ? `${(ex.durationMs / 1000).toFixed(1)}s` : '?';
|
|
2397
2399
|
const statusColor = ex.executionStatus === 'Success' ? chalk_1.default.green : ex.executionStatus === 'Failed' ? chalk_1.default.red : chalk_1.default.yellow;
|
|
2398
2400
|
const userName = ex.user?.fullName || ex.user?.email || '';
|
|
2399
|
-
|
|
2400
|
-
|
|
2401
|
-
|
|
2402
|
-
|
|
2403
|
-
|
|
2404
|
-
|
|
2405
|
-
|
|
2406
|
-
|
|
2407
|
-
|
|
2408
|
-
if
|
|
2401
|
+
// Download log
|
|
2402
|
+
let logText;
|
|
2403
|
+
try {
|
|
2404
|
+
logText = await fetchGzipText(logUrl);
|
|
2405
|
+
}
|
|
2406
|
+
catch (e) {
|
|
2407
|
+
console.error(chalk_1.default.red(`Failed to download log: ${e.message}`));
|
|
2408
|
+
process.exit(2);
|
|
2409
|
+
}
|
|
2410
|
+
// Pretty-print JSON if it's valid JSON
|
|
2411
|
+
if (useJson) {
|
|
2409
2412
|
try {
|
|
2410
|
-
const
|
|
2411
|
-
|
|
2412
|
-
|
|
2413
|
-
}
|
|
2414
|
-
|
|
2415
|
-
|
|
2416
|
-
|
|
2417
|
-
}
|
|
2413
|
+
const parsed = JSON.parse(logText);
|
|
2414
|
+
logText = JSON.stringify(parsed, null, 2);
|
|
2415
|
+
}
|
|
2416
|
+
catch { /* keep as-is */ }
|
|
2417
|
+
}
|
|
2418
|
+
if (toConsole) {
|
|
2419
|
+
console.log(chalk_1.default.bold.cyan('\n Workflow Execution\n'));
|
|
2420
|
+
console.log(chalk_1.default.white(` ID: ${ex.executionId}`));
|
|
2421
|
+
console.log(chalk_1.default.white(` Workflow: ${ex.workflowId}`));
|
|
2422
|
+
console.log(chalk_1.default.white(` Status: ${statusColor(ex.executionStatus)}`));
|
|
2423
|
+
console.log(chalk_1.default.white(` Executed: ${date}`));
|
|
2424
|
+
console.log(chalk_1.default.white(` Duration: ${duration}`));
|
|
2425
|
+
if (userName)
|
|
2426
|
+
console.log(chalk_1.default.white(` User: ${userName}`));
|
|
2427
|
+
console.log(chalk_1.default.gray(`\n --- ${logType.toUpperCase()} Log ---\n`));
|
|
2428
|
+
console.log(logText);
|
|
2429
|
+
return;
|
|
2430
|
+
}
|
|
2431
|
+
// Save to file
|
|
2432
|
+
let filePath;
|
|
2433
|
+
if (outputFile) {
|
|
2434
|
+
filePath = path.resolve(outputFile);
|
|
2418
2435
|
}
|
|
2419
2436
|
else {
|
|
2420
|
-
|
|
2437
|
+
const tmpDir = os.tmpdir();
|
|
2438
|
+
const dateStr = new Date(ex.executedAt).toISOString().slice(0, 10);
|
|
2439
|
+
filePath = path.join(tmpDir, `workflow-${ex.workflowId}-${dateStr}-${executionId}${ext}`);
|
|
2421
2440
|
}
|
|
2441
|
+
fs.writeFileSync(filePath, logText, 'utf-8');
|
|
2442
|
+
console.log(chalk_1.default.green(` ✓ ${logType.toUpperCase()} log saved: ${filePath}`));
|
|
2443
|
+
console.log(chalk_1.default.gray(` Execution: ${executionId} ${statusColor(ex.executionStatus)} ${date} ${duration}`));
|
|
2422
2444
|
}
|
|
2423
2445
|
// ============================================================================
|
|
2424
2446
|
// Publish Command
|
|
@@ -3033,6 +3055,18 @@ function parseArgs(args) {
|
|
|
3033
3055
|
else if (arg === '--vars') {
|
|
3034
3056
|
options.vars = args[++i];
|
|
3035
3057
|
}
|
|
3058
|
+
else if (arg === '--from') {
|
|
3059
|
+
options.from = args[++i];
|
|
3060
|
+
}
|
|
3061
|
+
else if (arg === '--to') {
|
|
3062
|
+
options.to = args[++i];
|
|
3063
|
+
}
|
|
3064
|
+
else if (arg === '--output' || arg === '-o') {
|
|
3065
|
+
options.output = args[++i];
|
|
3066
|
+
}
|
|
3067
|
+
else if (arg === '--console') {
|
|
3068
|
+
options.console = true;
|
|
3069
|
+
}
|
|
3036
3070
|
else if (!arg.startsWith('-')) {
|
|
3037
3071
|
files.push(arg);
|
|
3038
3072
|
}
|
|
@@ -3916,15 +3950,15 @@ async function main() {
|
|
|
3916
3950
|
else if (sub === 'execute') {
|
|
3917
3951
|
await runWorkflowExecute(files[1], options.orgId, options.vars);
|
|
3918
3952
|
}
|
|
3919
|
-
else if (sub === 'executions') {
|
|
3920
|
-
await runWorkflowExecutions(files[1], options.orgId);
|
|
3921
|
-
}
|
|
3922
3953
|
else if (sub === 'logs') {
|
|
3923
|
-
await runWorkflowLogs(files[1], options.orgId);
|
|
3954
|
+
await runWorkflowLogs(files[1], options.orgId, options.from, options.to);
|
|
3955
|
+
}
|
|
3956
|
+
else if (sub === 'log') {
|
|
3957
|
+
await runWorkflowLog(files[1], options.orgId, options.output, options.console, options.format === 'json');
|
|
3924
3958
|
}
|
|
3925
3959
|
else {
|
|
3926
3960
|
console.error(chalk_1.default.red(`Unknown workflow subcommand: ${sub || '(none)'}`));
|
|
3927
|
-
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} workflow <push|delete|execute|
|
|
3961
|
+
console.error(chalk_1.default.gray(`Usage: ${PROGRAM_NAME} workflow <push|delete|execute|logs|log> ...`));
|
|
3928
3962
|
process.exit(2);
|
|
3929
3963
|
}
|
|
3930
3964
|
process.exit(0);
|