@doow/cli 0.1.6 → 0.1.7
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/dist/cjs/auth/device-flow.js +32 -1
- package/dist/cjs/auth/device-flow.js.map +1 -1
- package/dist/cjs/config/store.js +3 -0
- package/dist/cjs/config/store.js.map +1 -1
- package/dist/cjs/index.js +1 -1
- package/dist/cli.cjs +449 -135
- package/dist/cli.cjs.map +1 -1
- package/dist/esm/auth/device-flow.js +32 -1
- package/dist/esm/auth/device-flow.js.map +1 -1
- package/dist/esm/config/store.js +3 -0
- package/dist/esm/config/store.js.map +1 -1
- package/dist/esm/index.js +1 -1
- package/package.json +1 -1
package/dist/cli.cjs
CHANGED
|
@@ -184,6 +184,9 @@ async function deleteProfile(name) {
|
|
|
184
184
|
if (config.activeProfile === name) {
|
|
185
185
|
throw new Error(`Cannot delete the active profile "${name}". Switch to another profile first.`);
|
|
186
186
|
}
|
|
187
|
+
if (!config.profiles[name]) {
|
|
188
|
+
throw new Error(`Profile "${name}" does not exist.`);
|
|
189
|
+
}
|
|
187
190
|
delete config.profiles[name];
|
|
188
191
|
await writeConfig(config);
|
|
189
192
|
await clearProfileCredentials(name);
|
|
@@ -791,6 +794,33 @@ function isLegacyPendingPollResponse(status, body) {
|
|
|
791
794
|
legacyBody.message === 'Bad Request Exception' &&
|
|
792
795
|
legacyBody.response === 'Bad Request Exception');
|
|
793
796
|
}
|
|
797
|
+
function isThrottledPollResponse(status, body) {
|
|
798
|
+
const legacyBody = body;
|
|
799
|
+
return (status === 429 ||
|
|
800
|
+
legacyBody.status === 429 ||
|
|
801
|
+
legacyBody.statusCode === 429 ||
|
|
802
|
+
legacyBody.message?.toLowerCase().includes('too many requests') === true);
|
|
803
|
+
}
|
|
804
|
+
function getRetryAfterSeconds(headers) {
|
|
805
|
+
const headerValue = headers.get('retry-after-auth') ?? headers.get('retry-after');
|
|
806
|
+
if (!headerValue)
|
|
807
|
+
return undefined;
|
|
808
|
+
const seconds = Number.parseInt(headerValue, 10);
|
|
809
|
+
return Number.isFinite(seconds) && seconds > 0 ? seconds : undefined;
|
|
810
|
+
}
|
|
811
|
+
function formatPollFailure(status, body) {
|
|
812
|
+
if (typeof body.error === 'string' && body.error.length > 0) {
|
|
813
|
+
return body.error_description ? `${body.error} — ${body.error_description}` : body.error;
|
|
814
|
+
}
|
|
815
|
+
const legacyBody = body;
|
|
816
|
+
if (typeof legacyBody.message === 'string' && legacyBody.message.length > 0) {
|
|
817
|
+
return legacyBody.message;
|
|
818
|
+
}
|
|
819
|
+
if (typeof legacyBody.response === 'string' && legacyBody.response.length > 0) {
|
|
820
|
+
return legacyBody.response;
|
|
821
|
+
}
|
|
822
|
+
return `HTTP ${status}`;
|
|
823
|
+
}
|
|
794
824
|
// ---------------------------------------------------------------------------
|
|
795
825
|
// Core function
|
|
796
826
|
// ---------------------------------------------------------------------------
|
|
@@ -893,7 +923,11 @@ async function executeDeviceFlow(options = {}) {
|
|
|
893
923
|
if (isLegacyPendingPollResponse(tokenRes.status, errBody)) {
|
|
894
924
|
continue;
|
|
895
925
|
}
|
|
896
|
-
|
|
926
|
+
if (isThrottledPollResponse(tokenRes.status, errBody)) {
|
|
927
|
+
pollInterval = getRetryAfterSeconds(tokenRes.headers) ?? Math.max(pollInterval + 5, 30);
|
|
928
|
+
continue;
|
|
929
|
+
}
|
|
930
|
+
throw new Error(`Token polling failed: ${formatPollFailure(tokenRes.status, errBody)}`);
|
|
897
931
|
}
|
|
898
932
|
}
|
|
899
933
|
}
|
|
@@ -1562,7 +1596,11 @@ function getValue(row, key) {
|
|
|
1562
1596
|
function truncate(s, max) {
|
|
1563
1597
|
if (s.length <= max)
|
|
1564
1598
|
return s;
|
|
1565
|
-
|
|
1599
|
+
if (max <= 0)
|
|
1600
|
+
return '';
|
|
1601
|
+
if (max <= 3)
|
|
1602
|
+
return '.'.repeat(max);
|
|
1603
|
+
return s.slice(0, max - 3) + '...';
|
|
1566
1604
|
}
|
|
1567
1605
|
/**
|
|
1568
1606
|
* Calculate the display width for a column, taking into account the header
|
|
@@ -1595,9 +1633,9 @@ function printTable(rows, columns) {
|
|
|
1595
1633
|
})
|
|
1596
1634
|
.join(' ');
|
|
1597
1635
|
process.stderr.write(header + '\n');
|
|
1598
|
-
// Separator (
|
|
1636
|
+
// Separator (ASCII-only hyphen chars)
|
|
1599
1637
|
const totalWidth = widths.reduce((sum, w) => sum + w, 0) + (columns.length - 1) * 2;
|
|
1600
|
-
const separator = '
|
|
1638
|
+
const separator = '-'.repeat(Math.min(totalWidth, termWidth));
|
|
1601
1639
|
process.stderr.write(separator + '\n');
|
|
1602
1640
|
// Data rows
|
|
1603
1641
|
for (const row of rows) {
|
|
@@ -2858,9 +2896,15 @@ function registerAppsCommands(program) {
|
|
|
2858
2896
|
.command('add-manager <appId> <memberId>')
|
|
2859
2897
|
.description('Add a manager to an application')
|
|
2860
2898
|
.action(async (appId, memberId) => {
|
|
2861
|
-
const
|
|
2899
|
+
const globalOpts = program.opts();
|
|
2900
|
+
const format = resolveFormat(globalOpts);
|
|
2862
2901
|
try {
|
|
2863
|
-
const
|
|
2902
|
+
const variables = { input: { application_id: appId, member_id: memberId } };
|
|
2903
|
+
if (isDryRun(globalOpts)) {
|
|
2904
|
+
printDryRun({ operation: 'mutation', name: 'UpdateAppManager', variables });
|
|
2905
|
+
return;
|
|
2906
|
+
}
|
|
2907
|
+
const data = await gqlFetch(ADD_APP_MANAGER, variables, clientOpts$4(program));
|
|
2864
2908
|
const result = data.updateAppManager
|
|
2865
2909
|
?? data.addAppManager
|
|
2866
2910
|
?? { success: false, message: 'No response', appManagerId: null };
|
|
@@ -4208,13 +4252,23 @@ function registerLicensesCommands(program) {
|
|
|
4208
4252
|
.command('assign <licenseId> <memberId>')
|
|
4209
4253
|
.description('Assign a license to a user')
|
|
4210
4254
|
.action(async (licenseId, memberId) => {
|
|
4211
|
-
const
|
|
4255
|
+
const globalOpts = program.opts();
|
|
4256
|
+
const format = resolveFormat(globalOpts);
|
|
4212
4257
|
try {
|
|
4213
|
-
const
|
|
4258
|
+
const variables = {
|
|
4214
4259
|
input: { license_id: licenseId, user_id: memberId },
|
|
4215
4260
|
licenseId,
|
|
4216
4261
|
memberId,
|
|
4217
|
-
}
|
|
4262
|
+
};
|
|
4263
|
+
if (isDryRun(globalOpts)) {
|
|
4264
|
+
printDryRun({
|
|
4265
|
+
operation: 'mutation',
|
|
4266
|
+
name: 'AddUserToLicense',
|
|
4267
|
+
variables: { input: variables.input },
|
|
4268
|
+
});
|
|
4269
|
+
return;
|
|
4270
|
+
}
|
|
4271
|
+
const data = await gqlFetch(ASSIGN_LICENSE, variables, clientOpts$2(program));
|
|
4218
4272
|
const legacy = data;
|
|
4219
4273
|
const license = normalizeLicense(legacy.addUserToLicense?.data ?? legacy.assignLicense);
|
|
4220
4274
|
if (format === 'json') {
|
|
@@ -4918,7 +4972,7 @@ async function cardsListHandler(opts, override) {
|
|
|
4918
4972
|
{ header: 'APPS', key: 'applications', width: 24 },
|
|
4919
4973
|
]);
|
|
4920
4974
|
if (payload.cursor) {
|
|
4921
|
-
process.stderr.write(`\n
|
|
4975
|
+
process.stderr.write(`\n ... more results available (cursor: ${payload.cursor})\n`);
|
|
4922
4976
|
}
|
|
4923
4977
|
}
|
|
4924
4978
|
async function cardGetHandler(id, opts, override) {
|
|
@@ -4991,7 +5045,7 @@ async function cardTransactionsHandler(id, opts, override) {
|
|
|
4991
5045
|
{ header: 'DATE', key: 'date', width: 24 },
|
|
4992
5046
|
]);
|
|
4993
5047
|
if (payload.cursor !== undefined && payload.cursor !== null) {
|
|
4994
|
-
process.stderr.write(`\n
|
|
5048
|
+
process.stderr.write(`\n ... more results available (cursor: ${payload.cursor})\n`);
|
|
4995
5049
|
}
|
|
4996
5050
|
}
|
|
4997
5051
|
async function cardsCreateHandler(opts, override) {
|
|
@@ -7030,10 +7084,10 @@ function printMoreResultsHint$2(nextPageAvailable, cursor) {
|
|
|
7030
7084
|
if (!nextPageAvailable)
|
|
7031
7085
|
return;
|
|
7032
7086
|
if (cursor) {
|
|
7033
|
-
process.stderr.write(`\n
|
|
7087
|
+
process.stderr.write(`\n ... more results available (cursor: ${cursor})\n`);
|
|
7034
7088
|
return;
|
|
7035
7089
|
}
|
|
7036
|
-
process.stderr.write('\n
|
|
7090
|
+
process.stderr.write('\n ... more results available\n');
|
|
7037
7091
|
}
|
|
7038
7092
|
function getConnectResultSummary(result) {
|
|
7039
7093
|
const name = result.integration.integration?.name ?? '(unknown)';
|
|
@@ -7326,6 +7380,55 @@ function registerIntegrationsCommands(program) {
|
|
|
7326
7380
|
});
|
|
7327
7381
|
}
|
|
7328
7382
|
|
|
7383
|
+
/**
|
|
7384
|
+
* ASCII-only bar chart renderer for human-readable terminal summaries.
|
|
7385
|
+
*
|
|
7386
|
+
* All output goes to stderr so stdout stays clean for JSON / piping.
|
|
7387
|
+
*/
|
|
7388
|
+
function truncateAscii(s, max) {
|
|
7389
|
+
if (s.length <= max)
|
|
7390
|
+
return s;
|
|
7391
|
+
if (max <= 0)
|
|
7392
|
+
return '';
|
|
7393
|
+
if (max <= 3)
|
|
7394
|
+
return '.'.repeat(max);
|
|
7395
|
+
return s.slice(0, max - 3) + '...';
|
|
7396
|
+
}
|
|
7397
|
+
function resolveBarWidth(rows, options, labelWidth) {
|
|
7398
|
+
if (options.barWidth !== undefined)
|
|
7399
|
+
return Math.max(8, options.barWidth);
|
|
7400
|
+
const termWidth = process.stderr.columns ?? 120;
|
|
7401
|
+
const suffixWidth = rows.reduce((max, row) => {
|
|
7402
|
+
const valueText = options.valueFormatter
|
|
7403
|
+
? options.valueFormatter(row.value, row)
|
|
7404
|
+
: String(row.value);
|
|
7405
|
+
const detailText = row.detail ? ` ${row.detail}` : '';
|
|
7406
|
+
return Math.max(max, (valueText + detailText).length);
|
|
7407
|
+
}, 0);
|
|
7408
|
+
return Math.max(8, Math.min(40, termWidth - labelWidth - suffixWidth - 5));
|
|
7409
|
+
}
|
|
7410
|
+
function printBarChart(rows, options = {}) {
|
|
7411
|
+
if (rows.length === 0)
|
|
7412
|
+
return;
|
|
7413
|
+
const widestLabel = rows.reduce((max, row) => Math.max(max, row.label.length), 0);
|
|
7414
|
+
const labelWidth = options.labelWidth ??
|
|
7415
|
+
Math.min(options.maxLabelWidth ?? 24, widestLabel);
|
|
7416
|
+
const barWidth = resolveBarWidth(rows, options, labelWidth);
|
|
7417
|
+
const maxValue = rows.reduce((max, row) => Math.max(max, row.value), 0);
|
|
7418
|
+
for (const row of rows) {
|
|
7419
|
+
const label = truncateAscii(row.label, labelWidth).padEnd(labelWidth);
|
|
7420
|
+
const valueText = options.valueFormatter
|
|
7421
|
+
? options.valueFormatter(row.value, row)
|
|
7422
|
+
: String(row.value);
|
|
7423
|
+
const detailText = row.detail ? ` ${row.detail}` : '';
|
|
7424
|
+
const barLength = row.value <= 0 || maxValue <= 0
|
|
7425
|
+
? 0
|
|
7426
|
+
: Math.max(1, Math.round((row.value / maxValue) * barWidth));
|
|
7427
|
+
const bar = '#'.repeat(barLength).padEnd(barWidth, ' ');
|
|
7428
|
+
process.stderr.write(`${label} | ${bar} ${valueText}${detailText}\n`);
|
|
7429
|
+
}
|
|
7430
|
+
}
|
|
7431
|
+
|
|
7329
7432
|
/**
|
|
7330
7433
|
* gql/operations/insights.ts
|
|
7331
7434
|
*
|
|
@@ -7438,7 +7541,7 @@ function isRead(notification) {
|
|
|
7438
7541
|
return notification.status !== 'UNREAD' || notification.read_at !== null;
|
|
7439
7542
|
}
|
|
7440
7543
|
async function insightsListHandler(opts) {
|
|
7441
|
-
const format = resolveFormat(opts);
|
|
7544
|
+
const format = opts.chart && !opts.json ? 'table' : resolveFormat(opts);
|
|
7442
7545
|
const data = await gqlFetch(GET_NOTIFICATION_STATS, undefined, buildClientOpts(opts));
|
|
7443
7546
|
const stats = data.getAppNotificationStats;
|
|
7444
7547
|
if (format === 'json') {
|
|
@@ -7453,6 +7556,16 @@ async function insightsListHandler(opts) {
|
|
|
7453
7556
|
['Actionable', String(stats.actionable)],
|
|
7454
7557
|
['Persistent', String(stats.persistent)],
|
|
7455
7558
|
]);
|
|
7559
|
+
if (opts.chart) {
|
|
7560
|
+
process.stderr.write('\n--- Notification Mix (ASCII Chart) ---\n');
|
|
7561
|
+
printBarChart([
|
|
7562
|
+
{ label: 'Unread', value: stats.unread },
|
|
7563
|
+
{ label: 'Read', value: stats.read },
|
|
7564
|
+
{ label: 'Dismissed', value: stats.dismissed },
|
|
7565
|
+
{ label: 'Actionable', value: stats.actionable },
|
|
7566
|
+
{ label: 'Persistent', value: stats.persistent },
|
|
7567
|
+
]);
|
|
7568
|
+
}
|
|
7456
7569
|
}
|
|
7457
7570
|
async function needsAttentionHandler(opts) {
|
|
7458
7571
|
const format = resolveFormat(opts);
|
|
@@ -7500,7 +7613,7 @@ async function notificationsListHandler(opts) {
|
|
|
7500
7613
|
{ header: 'CREATED', key: 'created_at', width: 20 },
|
|
7501
7614
|
]);
|
|
7502
7615
|
if (page < total_pages) {
|
|
7503
|
-
process.stderr.write(`\n
|
|
7616
|
+
process.stderr.write(`\n ... more results available (page ${page} of ${total_pages})\n`);
|
|
7504
7617
|
}
|
|
7505
7618
|
}
|
|
7506
7619
|
async function notificationsReadHandler(id, opts) {
|
|
@@ -7569,10 +7682,11 @@ function registerInsightsCommands(program) {
|
|
|
7569
7682
|
.command('stats')
|
|
7570
7683
|
.alias('list')
|
|
7571
7684
|
.description('Show notification summary stats')
|
|
7572
|
-
.
|
|
7685
|
+
.option('--chart', 'Render ASCII charts in human output')
|
|
7686
|
+
.action(async (opts) => {
|
|
7573
7687
|
const globalOpts = program.opts();
|
|
7574
7688
|
try {
|
|
7575
|
-
await insightsListHandler(globalOpts);
|
|
7689
|
+
await insightsListHandler({ ...globalOpts, ...opts });
|
|
7576
7690
|
}
|
|
7577
7691
|
catch (err) {
|
|
7578
7692
|
const msg = err instanceof Error ? err.message : String(err);
|
|
@@ -8179,7 +8293,7 @@ async function uploadFile(options) {
|
|
|
8179
8293
|
const fileBuffer = fs$1.readFileSync(options.filePath);
|
|
8180
8294
|
const fileName = node_path.basename(options.filePath);
|
|
8181
8295
|
const formData = new FormData();
|
|
8182
|
-
formData.append('
|
|
8296
|
+
formData.append('files', new Blob([fileBuffer], { type: 'application/octet-stream' }), fileName);
|
|
8183
8297
|
if (options.chatId !== undefined) {
|
|
8184
8298
|
formData.append('chat_id', options.chatId);
|
|
8185
8299
|
}
|
|
@@ -8824,8 +8938,18 @@ function formatAmount(value) {
|
|
|
8824
8938
|
function formatPercent(value) {
|
|
8825
8939
|
return `${value.toFixed(1)}%`;
|
|
8826
8940
|
}
|
|
8941
|
+
function shouldRenderChart(opts, format) {
|
|
8942
|
+
return opts.chart === true && format !== 'json';
|
|
8943
|
+
}
|
|
8944
|
+
function formatDelta(changeAmount, changePercentage, valueFormatter = (value) => String(value)) {
|
|
8945
|
+
if (changeAmount === undefined || changePercentage === undefined)
|
|
8946
|
+
return undefined;
|
|
8947
|
+
const amountPrefix = changeAmount > 0 ? '+' : '';
|
|
8948
|
+
const percentPrefix = changePercentage > 0 ? '+' : '';
|
|
8949
|
+
return `${amountPrefix}${valueFormatter(changeAmount)} (${percentPrefix}${changePercentage.toFixed(1)}%)`;
|
|
8950
|
+
}
|
|
8827
8951
|
async function dashboardOverviewHandler(opts) {
|
|
8828
|
-
const format = resolveFormat(opts);
|
|
8952
|
+
const format = opts.chart && !opts.json ? 'table' : resolveFormat(opts);
|
|
8829
8953
|
const clientOpts = buildClientOptions(opts);
|
|
8830
8954
|
const period = parseDashboardPeriod(opts.period);
|
|
8831
8955
|
const [spendData, topAppsData, renewalsData] = await Promise.all([
|
|
@@ -8849,6 +8973,16 @@ async function dashboardOverviewHandler(opts) {
|
|
|
8849
8973
|
['Period', overview.period],
|
|
8850
8974
|
['Total spend', formatAmount(overview.spend.totalSpend)],
|
|
8851
8975
|
]);
|
|
8976
|
+
if (shouldRenderChart(opts, format) && overview.top_apps.length > 0) {
|
|
8977
|
+
process.stderr.write('\n--- Top Applications (ASCII Chart) ---\n');
|
|
8978
|
+
printBarChart(overview.top_apps.map((app) => ({
|
|
8979
|
+
label: app.name,
|
|
8980
|
+
value: app.totalCost,
|
|
8981
|
+
detail: `${app.userCount} users`,
|
|
8982
|
+
})), {
|
|
8983
|
+
valueFormatter: (value) => formatAmount(value),
|
|
8984
|
+
});
|
|
8985
|
+
}
|
|
8852
8986
|
if (overview.top_apps.length > 0) {
|
|
8853
8987
|
process.stderr.write('\n--- Top Applications ---\n');
|
|
8854
8988
|
printTable(overview.top_apps, [
|
|
@@ -8866,6 +9000,16 @@ async function dashboardOverviewHandler(opts) {
|
|
|
8866
9000
|
},
|
|
8867
9001
|
]);
|
|
8868
9002
|
}
|
|
9003
|
+
if (shouldRenderChart(opts, format) && overview.upcoming_renewals.length > 0) {
|
|
9004
|
+
process.stderr.write('\n--- Upcoming Renewals (ASCII Chart) ---\n');
|
|
9005
|
+
printBarChart(overview.upcoming_renewals.map((renewal) => ({
|
|
9006
|
+
label: renewal.appName,
|
|
9007
|
+
value: renewal.amount,
|
|
9008
|
+
detail: `${renewal.daysUntilRenewal}d ${renewal.renewalDate}`,
|
|
9009
|
+
})), {
|
|
9010
|
+
valueFormatter: (value) => formatAmount(value),
|
|
9011
|
+
});
|
|
9012
|
+
}
|
|
8869
9013
|
if (overview.upcoming_renewals.length > 0) {
|
|
8870
9014
|
process.stderr.write('\n--- Upcoming Renewals (30 days) ---\n');
|
|
8871
9015
|
printTable(overview.upcoming_renewals, [
|
|
@@ -8886,7 +9030,7 @@ async function dashboardOverviewHandler(opts) {
|
|
|
8886
9030
|
}
|
|
8887
9031
|
}
|
|
8888
9032
|
async function dashboardSpendHandler(opts) {
|
|
8889
|
-
const format = resolveFormat(opts);
|
|
9033
|
+
const format = opts.chart && !opts.json ? 'table' : resolveFormat(opts);
|
|
8890
9034
|
const clientOpts = buildClientOptions(opts);
|
|
8891
9035
|
const period = parseDashboardPeriod(opts.period);
|
|
8892
9036
|
const [overviewData, categoryData] = await Promise.all([
|
|
@@ -8908,6 +9052,16 @@ async function dashboardSpendHandler(opts) {
|
|
|
8908
9052
|
['Total spend', formatAmount(spend.overview.totalSpend)],
|
|
8909
9053
|
['Category total', formatAmount(spend.category_total)],
|
|
8910
9054
|
]);
|
|
9055
|
+
if (shouldRenderChart(opts, format) && spend.by_category.length > 0) {
|
|
9056
|
+
process.stderr.write('\n--- Spend by Category (ASCII Chart) ---\n');
|
|
9057
|
+
printBarChart(spend.by_category.map((category) => ({
|
|
9058
|
+
label: category.name,
|
|
9059
|
+
value: category.amount,
|
|
9060
|
+
detail: formatPercent(category.percentage),
|
|
9061
|
+
})), {
|
|
9062
|
+
valueFormatter: (value) => formatAmount(value),
|
|
9063
|
+
});
|
|
9064
|
+
}
|
|
8911
9065
|
if (spend.by_category.length > 0) {
|
|
8912
9066
|
process.stderr.write('\n--- Spend by Category ---\n');
|
|
8913
9067
|
printTable(spend.by_category, [
|
|
@@ -8926,7 +9080,7 @@ async function dashboardSpendHandler(opts) {
|
|
|
8926
9080
|
}
|
|
8927
9081
|
}
|
|
8928
9082
|
async function dashboardRenewalsHandler(opts) {
|
|
8929
|
-
const format = resolveFormat(opts);
|
|
9083
|
+
const format = opts.chart && !opts.json ? 'table' : resolveFormat(opts);
|
|
8930
9084
|
const clientOpts = buildClientOptions(opts);
|
|
8931
9085
|
const days = opts.days ? Number.parseInt(opts.days, 10) : undefined;
|
|
8932
9086
|
const data = await gqlFetch(GET_UPCOMING_RENEWALS, days !== undefined ? { days } : undefined, clientOpts);
|
|
@@ -8939,6 +9093,17 @@ async function dashboardRenewalsHandler(opts) {
|
|
|
8939
9093
|
process.stderr.write('No upcoming renewals found.\n');
|
|
8940
9094
|
return;
|
|
8941
9095
|
}
|
|
9096
|
+
if (shouldRenderChart(opts, format)) {
|
|
9097
|
+
process.stderr.write('\n--- Upcoming Renewals (ASCII Chart) ---\n');
|
|
9098
|
+
printBarChart(renewals.map((renewal) => ({
|
|
9099
|
+
label: renewal.appName,
|
|
9100
|
+
value: renewal.amount,
|
|
9101
|
+
detail: `${renewal.daysUntilRenewal}d ${renewal.renewalDate}`,
|
|
9102
|
+
})), {
|
|
9103
|
+
valueFormatter: (value) => formatAmount(value),
|
|
9104
|
+
});
|
|
9105
|
+
process.stderr.write('\n');
|
|
9106
|
+
}
|
|
8942
9107
|
printTable(renewals, [
|
|
8943
9108
|
{ header: 'APP', key: 'appName', width: 24 },
|
|
8944
9109
|
{
|
|
@@ -8957,7 +9122,7 @@ async function dashboardRenewalsHandler(opts) {
|
|
|
8957
9122
|
]);
|
|
8958
9123
|
}
|
|
8959
9124
|
async function dashboardMetricsHandler(opts) {
|
|
8960
|
-
const format = resolveFormat(opts);
|
|
9125
|
+
const format = opts.chart && !opts.json ? 'table' : resolveFormat(opts);
|
|
8961
9126
|
const clientOpts = buildClientOptions(opts);
|
|
8962
9127
|
const period = parseDashboardPeriod(opts.period);
|
|
8963
9128
|
const data = await gqlFetch(GET_DASHBOARD_METRICS, { input: buildDashboardMetricsInput(opts.period) }, clientOpts);
|
|
@@ -8970,29 +9135,91 @@ async function dashboardMetricsHandler(opts) {
|
|
|
8970
9135
|
const applicationMetrics = metrics.applicationMetrics;
|
|
8971
9136
|
const userMetrics = metrics.userMetrics;
|
|
8972
9137
|
const subscriptionMetrics = metrics.subscriptionMetrics;
|
|
8973
|
-
|
|
8974
|
-
|
|
8975
|
-
|
|
8976
|
-
['Paid apps', String(
|
|
8977
|
-
|
|
8978
|
-
|
|
8979
|
-
['
|
|
8980
|
-
|
|
8981
|
-
|
|
8982
|
-
['Active
|
|
8983
|
-
|
|
8984
|
-
|
|
8985
|
-
|
|
8986
|
-
|
|
8987
|
-
|
|
8988
|
-
]
|
|
8989
|
-
|
|
9138
|
+
const rows = [['Period', period.label], ['Filter', metrics.filter]];
|
|
9139
|
+
const paidApps = allMetrics?.paidApplications.current;
|
|
9140
|
+
if (paidApps !== undefined)
|
|
9141
|
+
rows.push(['Paid apps', String(paidApps)]);
|
|
9142
|
+
const freeApps = allMetrics?.freeApplications.current;
|
|
9143
|
+
if (freeApps !== undefined)
|
|
9144
|
+
rows.push(['Free apps', String(freeApps)]);
|
|
9145
|
+
const activeApps = applicationMetrics?.activeApplications.current;
|
|
9146
|
+
if (activeApps !== undefined)
|
|
9147
|
+
rows.push(['Active apps', String(activeApps)]);
|
|
9148
|
+
const inactiveApps = applicationMetrics?.inactiveApplications.current;
|
|
9149
|
+
if (inactiveApps !== undefined)
|
|
9150
|
+
rows.push(['Inactive apps', String(inactiveApps)]);
|
|
9151
|
+
const users = allMetrics?.totalUsers.current ?? userMetrics?.activeUsers.current;
|
|
9152
|
+
if (users !== undefined)
|
|
9153
|
+
rows.push(['Users', String(users)]);
|
|
9154
|
+
const activeSubscriptions = subscriptionMetrics?.activeSubscriptions.currentCount;
|
|
9155
|
+
if (activeSubscriptions !== undefined) {
|
|
9156
|
+
rows.push(['Active subscriptions', String(activeSubscriptions)]);
|
|
9157
|
+
}
|
|
9158
|
+
const activeLicenses = subscriptionMetrics?.activeLicenses.currentCount;
|
|
9159
|
+
if (activeLicenses !== undefined) {
|
|
9160
|
+
rows.push(['Active licenses', String(activeLicenses)]);
|
|
9161
|
+
}
|
|
9162
|
+
const totalSpend = allMetrics?.totalSubscriptionSpend.current ??
|
|
9163
|
+
subscriptionMetrics?.totalSubscriptionValue.currentValue;
|
|
9164
|
+
if (totalSpend !== undefined)
|
|
9165
|
+
rows.push(['Total spend', formatAmount(totalSpend)]);
|
|
9166
|
+
printKeyValue(rows);
|
|
9167
|
+
if (shouldRenderChart(opts, format)) {
|
|
9168
|
+
const metricRows = [];
|
|
9169
|
+
if (paidApps !== undefined) {
|
|
9170
|
+
metricRows.push({
|
|
9171
|
+
label: 'Paid apps',
|
|
9172
|
+
value: paidApps,
|
|
9173
|
+
detail: formatDelta(allMetrics?.paidApplications.changeAmount, allMetrics?.paidApplications.changePercentage),
|
|
9174
|
+
});
|
|
9175
|
+
}
|
|
9176
|
+
if (freeApps !== undefined) {
|
|
9177
|
+
metricRows.push({
|
|
9178
|
+
label: 'Free apps',
|
|
9179
|
+
value: freeApps,
|
|
9180
|
+
detail: formatDelta(allMetrics?.freeApplications.changeAmount, allMetrics?.freeApplications.changePercentage),
|
|
9181
|
+
});
|
|
9182
|
+
}
|
|
9183
|
+
if (activeApps !== undefined) {
|
|
9184
|
+
metricRows.push({
|
|
9185
|
+
label: 'Active apps',
|
|
9186
|
+
value: activeApps,
|
|
9187
|
+
detail: formatDelta(applicationMetrics?.activeApplications.changeAmount, applicationMetrics?.activeApplications.changePercentage),
|
|
9188
|
+
});
|
|
9189
|
+
}
|
|
9190
|
+
if (users !== undefined) {
|
|
9191
|
+
metricRows.push({
|
|
9192
|
+
label: 'Users',
|
|
9193
|
+
value: users,
|
|
9194
|
+
detail: formatDelta(allMetrics?.totalUsers.changeAmount ?? userMetrics?.activeUsers.changeAmount, allMetrics?.totalUsers.changePercentage ?? userMetrics?.activeUsers.changePercentage),
|
|
9195
|
+
});
|
|
9196
|
+
}
|
|
9197
|
+
if (activeSubscriptions !== undefined) {
|
|
9198
|
+
metricRows.push({
|
|
9199
|
+
label: 'Active subscriptions',
|
|
9200
|
+
value: activeSubscriptions,
|
|
9201
|
+
detail: formatDelta(subscriptionMetrics?.activeSubscriptions.changeAmount, subscriptionMetrics?.activeSubscriptions.changePercentage),
|
|
9202
|
+
});
|
|
9203
|
+
}
|
|
9204
|
+
if (activeLicenses !== undefined) {
|
|
9205
|
+
metricRows.push({
|
|
9206
|
+
label: 'Active licenses',
|
|
9207
|
+
value: activeLicenses,
|
|
9208
|
+
detail: formatDelta(subscriptionMetrics?.activeLicenses.changeAmount, subscriptionMetrics?.activeLicenses.changePercentage),
|
|
9209
|
+
});
|
|
9210
|
+
}
|
|
9211
|
+
if (metricRows.length > 0) {
|
|
9212
|
+
process.stderr.write('\n--- Metrics (ASCII Chart) ---\n');
|
|
9213
|
+
printBarChart(metricRows);
|
|
9214
|
+
}
|
|
9215
|
+
}
|
|
8990
9216
|
}
|
|
8991
9217
|
function registerDashboardCommands(program) {
|
|
8992
9218
|
const dashboard = program
|
|
8993
9219
|
.command('dashboard')
|
|
8994
9220
|
.description('View dashboard overview and key metrics')
|
|
8995
9221
|
.option('--period <period>', 'Time period (e.g. 7d, 30d, 90d, 12m, all)')
|
|
9222
|
+
.option('--chart', 'Render ASCII charts in human output')
|
|
8996
9223
|
.action(async (opts) => {
|
|
8997
9224
|
const globalOpts = program.opts();
|
|
8998
9225
|
try {
|
|
@@ -9013,6 +9240,7 @@ function registerDashboardCommands(program) {
|
|
|
9013
9240
|
.command('overview')
|
|
9014
9241
|
.description('View dashboard overview')
|
|
9015
9242
|
.option('--period <period>', 'Time period (e.g. 7d, 30d, 90d, 12m, all)')
|
|
9243
|
+
.option('--chart', 'Render ASCII charts in human output')
|
|
9016
9244
|
.action(async (opts) => {
|
|
9017
9245
|
const globalOpts = program.opts();
|
|
9018
9246
|
try {
|
|
@@ -9033,6 +9261,7 @@ function registerDashboardCommands(program) {
|
|
|
9033
9261
|
.command('spend')
|
|
9034
9262
|
.description('View spend breakdown')
|
|
9035
9263
|
.option('--period <period>', 'Time period (e.g. 7d, 30d, 90d, 12m, all)')
|
|
9264
|
+
.option('--chart', 'Render ASCII charts in human output')
|
|
9036
9265
|
.action(async (opts) => {
|
|
9037
9266
|
const globalOpts = program.opts();
|
|
9038
9267
|
try {
|
|
@@ -9053,6 +9282,7 @@ function registerDashboardCommands(program) {
|
|
|
9053
9282
|
.command('renewals')
|
|
9054
9283
|
.description('View upcoming renewals')
|
|
9055
9284
|
.option('--days <n>', 'Number of days ahead to look (default: 30)')
|
|
9285
|
+
.option('--chart', 'Render ASCII charts in human output')
|
|
9056
9286
|
.action(async (opts) => {
|
|
9057
9287
|
const globalOpts = program.opts();
|
|
9058
9288
|
try {
|
|
@@ -9073,6 +9303,7 @@ function registerDashboardCommands(program) {
|
|
|
9073
9303
|
.command('metrics')
|
|
9074
9304
|
.description('View key dashboard metrics')
|
|
9075
9305
|
.option('--period <period>', 'Time period (e.g. 7d, 30d, 90d, 12m, all)')
|
|
9306
|
+
.option('--chart', 'Render ASCII charts in human output')
|
|
9076
9307
|
.action(async (opts) => {
|
|
9077
9308
|
const globalOpts = program.opts();
|
|
9078
9309
|
try {
|
|
@@ -9496,10 +9727,10 @@ function printMoreResultsHint$1(pagination) {
|
|
|
9496
9727
|
if (!pagination.has_more)
|
|
9497
9728
|
return;
|
|
9498
9729
|
if (pagination.cursor) {
|
|
9499
|
-
process.stderr.write(`\n
|
|
9730
|
+
process.stderr.write(`\n ... more results available (cursor: ${pagination.cursor})\n`);
|
|
9500
9731
|
return;
|
|
9501
9732
|
}
|
|
9502
|
-
process.stderr.write('\n
|
|
9733
|
+
process.stderr.write('\n ... more results available\n');
|
|
9503
9734
|
}
|
|
9504
9735
|
async function expensesListHandler(opts) {
|
|
9505
9736
|
const format = resolveFormat(opts);
|
|
@@ -9960,10 +10191,10 @@ function printMoreResultsHint(pagination) {
|
|
|
9960
10191
|
if (!pagination.has_more)
|
|
9961
10192
|
return;
|
|
9962
10193
|
if (pagination.cursor) {
|
|
9963
|
-
process.stderr.write(`\n
|
|
10194
|
+
process.stderr.write(`\n ... more results available (cursor: ${pagination.cursor})\n`);
|
|
9964
10195
|
return;
|
|
9965
10196
|
}
|
|
9966
|
-
process.stderr.write('\n
|
|
10197
|
+
process.stderr.write('\n ... more results available\n');
|
|
9967
10198
|
}
|
|
9968
10199
|
async function overagesListHandler(opts) {
|
|
9969
10200
|
const format = resolveFormat(opts);
|
|
@@ -10467,6 +10698,56 @@ function decodeReportBuffer$1(data, reportFormat) {
|
|
|
10467
10698
|
? Buffer.from(data, 'base64')
|
|
10468
10699
|
: Buffer.from(data, 'utf-8');
|
|
10469
10700
|
}
|
|
10701
|
+
const DEFAULT_EXPENSE_REPORT_COLUMNS = [
|
|
10702
|
+
'Total',
|
|
10703
|
+
'Month',
|
|
10704
|
+
'Year',
|
|
10705
|
+
'Date',
|
|
10706
|
+
'App ID',
|
|
10707
|
+
'Transaction ID',
|
|
10708
|
+
'Source',
|
|
10709
|
+
'Payment Channel',
|
|
10710
|
+
'Transaction Status',
|
|
10711
|
+
'Vendor',
|
|
10712
|
+
'App Name',
|
|
10713
|
+
'Description',
|
|
10714
|
+
];
|
|
10715
|
+
const EXPENSE_REPORT_COLUMN_LOOKUP = new Map(DEFAULT_EXPENSE_REPORT_COLUMNS.map((column) => [normalizeExpenseReportColumnToken(column), column]));
|
|
10716
|
+
function normalizeExpenseReportColumnToken(value) {
|
|
10717
|
+
return value.trim().toLowerCase().replace(/[\s_-]+/g, '');
|
|
10718
|
+
}
|
|
10719
|
+
function dedupe(values) {
|
|
10720
|
+
return Array.from(new Set(values));
|
|
10721
|
+
}
|
|
10722
|
+
function resolveExpenseReportColumns(raw) {
|
|
10723
|
+
if (Array.isArray(raw)) {
|
|
10724
|
+
return raw.length > 0 ? dedupe(raw) : [...DEFAULT_EXPENSE_REPORT_COLUMNS];
|
|
10725
|
+
}
|
|
10726
|
+
if (!raw || raw.trim().length === 0) {
|
|
10727
|
+
return [...DEFAULT_EXPENSE_REPORT_COLUMNS];
|
|
10728
|
+
}
|
|
10729
|
+
const requested = raw
|
|
10730
|
+
.split(',')
|
|
10731
|
+
.map((value) => value.trim())
|
|
10732
|
+
.filter(Boolean);
|
|
10733
|
+
if (requested.length === 0) {
|
|
10734
|
+
return [...DEFAULT_EXPENSE_REPORT_COLUMNS];
|
|
10735
|
+
}
|
|
10736
|
+
const resolved = [];
|
|
10737
|
+
const unknown = [];
|
|
10738
|
+
for (const token of requested) {
|
|
10739
|
+
const normalized = EXPENSE_REPORT_COLUMN_LOOKUP.get(normalizeExpenseReportColumnToken(token));
|
|
10740
|
+
if (!normalized) {
|
|
10741
|
+
unknown.push(token);
|
|
10742
|
+
continue;
|
|
10743
|
+
}
|
|
10744
|
+
resolved.push(normalized);
|
|
10745
|
+
}
|
|
10746
|
+
if (unknown.length > 0) {
|
|
10747
|
+
throw new Error(`Unknown expense report columns: ${unknown.join(', ')}. Allowed columns: ${DEFAULT_EXPENSE_REPORT_COLUMNS.join(', ')}`);
|
|
10748
|
+
}
|
|
10749
|
+
return dedupe(resolved);
|
|
10750
|
+
}
|
|
10470
10751
|
function buildExpenseReportInput(filters) {
|
|
10471
10752
|
const input = {};
|
|
10472
10753
|
if (filters.from)
|
|
@@ -10487,6 +10768,7 @@ function buildExpenseReportInput(filters) {
|
|
|
10487
10768
|
input['amount_min'] = filters.amountMin;
|
|
10488
10769
|
if (filters.amountMax !== undefined)
|
|
10489
10770
|
input['amount_max'] = filters.amountMax;
|
|
10771
|
+
input['columns'] = resolveExpenseReportColumns(filters.columns);
|
|
10490
10772
|
return input;
|
|
10491
10773
|
}
|
|
10492
10774
|
async function expenseReportHandler(opts) {
|
|
@@ -10609,6 +10891,7 @@ function registerReportsCommands(program) {
|
|
|
10609
10891
|
.option('--source <source>', 'Filter by source')
|
|
10610
10892
|
.option('--amount-min <number>', 'Minimum amount filter', parseFloat)
|
|
10611
10893
|
.option('--amount-max <number>', 'Maximum amount filter', parseFloat)
|
|
10894
|
+
.option('--columns <items>', 'Comma-separated columns (default: Total,Month,Year,Date,App ID,Transaction ID,Source,Payment Channel,Transaction Status,Vendor,App Name,Description)')
|
|
10612
10895
|
.option('--output <path>', 'Write report to file (required for PDF when not piped)')
|
|
10613
10896
|
.option('--email <addresses>', 'Comma-separated email addresses — send report by email instead of downloading')
|
|
10614
10897
|
.action(async (opts) => {
|
|
@@ -11705,38 +11988,63 @@ const GLOBAL_FLAGS = [
|
|
|
11705
11988
|
'--api-url',
|
|
11706
11989
|
'--debug',
|
|
11707
11990
|
];
|
|
11708
|
-
|
|
11709
|
-
|
|
11710
|
-
|
|
11711
|
-
|
|
11712
|
-
|
|
11713
|
-
|
|
11714
|
-
|
|
11715
|
-
|
|
11716
|
-
|
|
11717
|
-
|
|
11718
|
-
|
|
11719
|
-
|
|
11720
|
-
|
|
11721
|
-
|
|
11722
|
-
|
|
11723
|
-
|
|
11724
|
-
|
|
11725
|
-
|
|
11726
|
-
|
|
11727
|
-
|
|
11728
|
-
|
|
11729
|
-
|
|
11730
|
-
|
|
11731
|
-
const
|
|
11991
|
+
function unique(values) {
|
|
11992
|
+
return Array.from(new Set(values));
|
|
11993
|
+
}
|
|
11994
|
+
function visibleSubcommands(command) {
|
|
11995
|
+
return command.commands.filter((child) => child.name() !== 'help');
|
|
11996
|
+
}
|
|
11997
|
+
function commandNames(command) {
|
|
11998
|
+
return unique([command.name(), ...command.aliases()]);
|
|
11999
|
+
}
|
|
12000
|
+
function commandChildren(command) {
|
|
12001
|
+
const subcommands = visibleSubcommands(command);
|
|
12002
|
+
if (subcommands.length > 0) {
|
|
12003
|
+
return unique(subcommands.flatMap((child) => commandNames(child)));
|
|
12004
|
+
}
|
|
12005
|
+
return unique(command.options
|
|
12006
|
+
.map((option) => option.long)
|
|
12007
|
+
.filter((flag) => Boolean(flag) && flag !== '--help'));
|
|
12008
|
+
}
|
|
12009
|
+
function buildCompletionGraph(program) {
|
|
12010
|
+
const pathChildren = {};
|
|
12011
|
+
const topLevelCommands = unique(visibleSubcommands(program).flatMap((command) => commandNames(command)));
|
|
12012
|
+
function visit(command, pathVariants) {
|
|
12013
|
+
const children = commandChildren(command);
|
|
12014
|
+
for (const path of pathVariants) {
|
|
12015
|
+
pathChildren[path.join(' ')] = children;
|
|
12016
|
+
}
|
|
12017
|
+
for (const child of visibleSubcommands(command)) {
|
|
12018
|
+
const childVariants = pathVariants.flatMap((path) => commandNames(child).map((name) => [...path, name]));
|
|
12019
|
+
visit(child, childVariants);
|
|
12020
|
+
}
|
|
12021
|
+
}
|
|
12022
|
+
for (const command of visibleSubcommands(program)) {
|
|
12023
|
+
const variants = commandNames(command).map((name) => [name]);
|
|
12024
|
+
visit(command, variants);
|
|
12025
|
+
}
|
|
12026
|
+
const commandPaths = Object.keys(pathChildren).sort((a, b) => {
|
|
12027
|
+
const depth = a.split(' ').length - b.split(' ').length;
|
|
12028
|
+
return depth !== 0 ? depth : a.localeCompare(b);
|
|
12029
|
+
});
|
|
12030
|
+
return { commandPaths, pathChildren, topLevelCommands };
|
|
12031
|
+
}
|
|
12032
|
+
function shellSingleQuote(value) {
|
|
12033
|
+
return `'${value.replace(/'/g, `'\\''`)}'`;
|
|
12034
|
+
}
|
|
11732
12035
|
// ---------------------------------------------------------------------------
|
|
11733
12036
|
// Bash completion script generator
|
|
11734
12037
|
// ---------------------------------------------------------------------------
|
|
11735
|
-
function completionBashHandler() {
|
|
11736
|
-
const
|
|
11737
|
-
|
|
11738
|
-
|
|
11739
|
-
|
|
12038
|
+
function completionBashHandler(program) {
|
|
12039
|
+
const graph = buildCompletionGraph(program);
|
|
12040
|
+
const knownPaths = graph.commandPaths.map((path) => ` ${shellSingleQuote(path)}`).join('\n');
|
|
12041
|
+
const subcommandCases = graph.commandPaths
|
|
12042
|
+
.map((path) => {
|
|
12043
|
+
const words = unique([
|
|
12044
|
+
...(graph.pathChildren[path] ?? []),
|
|
12045
|
+
...GLOBAL_FLAGS,
|
|
12046
|
+
]).join(' ');
|
|
12047
|
+
return ` ${shellSingleQuote(path)}) COMPREPLY=( $(compgen -W "${words}" -- "$cur") ) ; return ;;`;
|
|
11740
12048
|
})
|
|
11741
12049
|
.join('\n');
|
|
11742
12050
|
const script = `# doow bash completion
|
|
@@ -11752,10 +12060,13 @@ _doow_completions() {
|
|
|
11752
12060
|
}
|
|
11753
12061
|
|
|
11754
12062
|
local global_flags="${GLOBAL_FLAGS.join(' ')}"
|
|
11755
|
-
local top_cmds="${
|
|
12063
|
+
local top_cmds="${graph.topLevelCommands.join(' ')}"
|
|
12064
|
+
local -a known_paths=(
|
|
12065
|
+
${knownPaths}
|
|
12066
|
+
)
|
|
11756
12067
|
|
|
11757
|
-
|
|
11758
|
-
local
|
|
12068
|
+
local command_path=""
|
|
12069
|
+
local candidate=""
|
|
11759
12070
|
local i
|
|
11760
12071
|
for (( i=1; i<COMP_CWORD; i++ )); do
|
|
11761
12072
|
local w="\${COMP_WORDS[i]}"
|
|
@@ -11763,22 +12074,31 @@ _doow_completions() {
|
|
|
11763
12074
|
--*) ;;
|
|
11764
12075
|
-*) ;;
|
|
11765
12076
|
*)
|
|
11766
|
-
|
|
11767
|
-
|
|
12077
|
+
candidate="\${candidate:+\$candidate }\$w"
|
|
12078
|
+
local matched=0
|
|
12079
|
+
local known
|
|
12080
|
+
for known in "\${known_paths[@]}"; do
|
|
12081
|
+
if [[ "$known" == "$candidate" ]]; then
|
|
12082
|
+
matched=1
|
|
12083
|
+
command_path="$candidate"
|
|
12084
|
+
break
|
|
12085
|
+
fi
|
|
12086
|
+
done
|
|
12087
|
+
if [[ $matched -eq 0 ]]; then
|
|
11768
12088
|
break
|
|
11769
12089
|
fi
|
|
11770
12090
|
;;
|
|
11771
12091
|
esac
|
|
11772
12092
|
done
|
|
11773
12093
|
|
|
11774
|
-
if [[ -z "$
|
|
12094
|
+
if [[ -z "$command_path" ]]; then
|
|
11775
12095
|
# Complete top-level subcommands and global flags
|
|
11776
12096
|
COMPREPLY=( $(compgen -W "$top_cmds $global_flags" -- "$cur") )
|
|
11777
12097
|
return
|
|
11778
12098
|
fi
|
|
11779
12099
|
|
|
11780
12100
|
# Complete within a subcommand
|
|
11781
|
-
case "$
|
|
12101
|
+
case "$command_path" in
|
|
11782
12102
|
${subcommandCases}
|
|
11783
12103
|
*) COMPREPLY=( $(compgen -W "$global_flags" -- "$cur") ) ; return ;;
|
|
11784
12104
|
esac
|
|
@@ -11791,67 +12111,61 @@ complete -F _doow_completions doow
|
|
|
11791
12111
|
// ---------------------------------------------------------------------------
|
|
11792
12112
|
// Zsh completion script generator
|
|
11793
12113
|
// ---------------------------------------------------------------------------
|
|
11794
|
-
function completionZshHandler() {
|
|
11795
|
-
const
|
|
11796
|
-
|
|
11797
|
-
|
|
11798
|
-
|
|
11799
|
-
|
|
11800
|
-
|
|
11801
|
-
|
|
11802
|
-
|
|
11803
|
-
|
|
11804
|
-
|
|
11805
|
-
if (isFlags) {
|
|
11806
|
-
const flagArgs = children.map((f) => `'${f}[${f}]'`).join(' ');
|
|
11807
|
-
return ` (${cmd})\n _arguments ${flagArgs}\n ;;`;
|
|
11808
|
-
}
|
|
11809
|
-
const subcmds = children.map((c) => `'${c}'`).join(' ');
|
|
11810
|
-
return ` (${cmd})\n local -a subcmds\n subcmds=(${subcmds})\n _describe 'subcommand' subcmds\n ;;`;
|
|
12114
|
+
function completionZshHandler(program) {
|
|
12115
|
+
const graph = buildCompletionGraph(program);
|
|
12116
|
+
const topLevelCommands = graph.topLevelCommands.map(shellSingleQuote).join(' ');
|
|
12117
|
+
const globalFlags = GLOBAL_FLAGS.map(shellSingleQuote).join(' ');
|
|
12118
|
+
const childAssignments = graph.commandPaths
|
|
12119
|
+
.map((path) => {
|
|
12120
|
+
const words = unique([
|
|
12121
|
+
...(graph.pathChildren[path] ?? []),
|
|
12122
|
+
...GLOBAL_FLAGS,
|
|
12123
|
+
]).join(' ');
|
|
12124
|
+
return ` completion_children[${shellSingleQuote(path)}]=${shellSingleQuote(words)}`;
|
|
11811
12125
|
})
|
|
11812
12126
|
.join('\n');
|
|
11813
|
-
const globalFlagArgs = GLOBAL_FLAGS.map((f) => `'${f}[${f}]'`).join(' ');
|
|
11814
12127
|
const script = `#compdef doow
|
|
11815
12128
|
# doow zsh completion
|
|
11816
12129
|
# Source this file or add the following line to ~/.zshrc:
|
|
11817
12130
|
# eval "$(doow completion zsh)"
|
|
11818
12131
|
|
|
11819
12132
|
_doow() {
|
|
11820
|
-
local
|
|
11821
|
-
|
|
11822
|
-
|
|
11823
|
-
|
|
11824
|
-
|
|
11825
|
-
|
|
11826
|
-
|
|
11827
|
-
|
|
11828
|
-
|
|
11829
|
-
|
|
11830
|
-
|
|
11831
|
-
|
|
11832
|
-
|
|
11833
|
-
|
|
11834
|
-
|
|
11835
|
-
|
|
11836
|
-
|
|
11837
|
-
|
|
11838
|
-
|
|
11839
|
-
|
|
11840
|
-
|
|
11841
|
-
|
|
11842
|
-
|
|
11843
|
-
|
|
11844
|
-
|
|
11845
|
-
|
|
11846
|
-
|
|
11847
|
-
|
|
11848
|
-
$
|
|
11849
|
-
|
|
11850
|
-
|
|
11851
|
-
|
|
11852
|
-
|
|
11853
|
-
|
|
11854
|
-
|
|
12133
|
+
local cur="\${words[CURRENT]}"
|
|
12134
|
+
local candidate=""
|
|
12135
|
+
local command_path=""
|
|
12136
|
+
local -a top_level global_flags
|
|
12137
|
+
typeset -A completion_children
|
|
12138
|
+
|
|
12139
|
+
top_level=(${topLevelCommands})
|
|
12140
|
+
global_flags=(${globalFlags})
|
|
12141
|
+
${childAssignments}
|
|
12142
|
+
|
|
12143
|
+
local i
|
|
12144
|
+
for (( i=2; i<CURRENT; i++ )); do
|
|
12145
|
+
local w="\${words[i]}"
|
|
12146
|
+
[[ "$w" == -* ]] && continue
|
|
12147
|
+
candidate="\${candidate:+\$candidate }\$w"
|
|
12148
|
+
if [[ -n "\${completion_children[$candidate]+x}" ]]; then
|
|
12149
|
+
command_path="$candidate"
|
|
12150
|
+
else
|
|
12151
|
+
break
|
|
12152
|
+
fi
|
|
12153
|
+
done
|
|
12154
|
+
|
|
12155
|
+
if [[ -z "$command_path" ]]; then
|
|
12156
|
+
compadd -- "\${top_level[@]}" "\${global_flags[@]}"
|
|
12157
|
+
return
|
|
12158
|
+
fi
|
|
12159
|
+
|
|
12160
|
+
local children="\${completion_children[$command_path]}"
|
|
12161
|
+
if [[ -n "$children" ]]; then
|
|
12162
|
+
local -a suggestions
|
|
12163
|
+
suggestions=(\${=children})
|
|
12164
|
+
compadd -- "\${suggestions[@]}"
|
|
12165
|
+
return
|
|
12166
|
+
fi
|
|
12167
|
+
|
|
12168
|
+
compadd -- "\${global_flags[@]}"
|
|
11855
12169
|
}
|
|
11856
12170
|
|
|
11857
12171
|
compdef _doow doow
|
|
@@ -11901,13 +12215,13 @@ function registerCompletionCommands(program) {
|
|
|
11901
12215
|
.command('bash')
|
|
11902
12216
|
.description('Output bash completion script')
|
|
11903
12217
|
.action(() => {
|
|
11904
|
-
completionBashHandler();
|
|
12218
|
+
completionBashHandler(program);
|
|
11905
12219
|
});
|
|
11906
12220
|
completion
|
|
11907
12221
|
.command('zsh')
|
|
11908
12222
|
.description('Output zsh completion script')
|
|
11909
12223
|
.action(() => {
|
|
11910
|
-
completionZshHandler();
|
|
12224
|
+
completionZshHandler(program);
|
|
11911
12225
|
});
|
|
11912
12226
|
completion
|
|
11913
12227
|
.command('install')
|
|
@@ -36634,7 +36948,7 @@ async function startStdioServer(options) {
|
|
|
36634
36948
|
await server.connect(transport);
|
|
36635
36949
|
}
|
|
36636
36950
|
|
|
36637
|
-
const VERSION = "0.1.
|
|
36951
|
+
const VERSION = "0.1.7" ;
|
|
36638
36952
|
const ASCII_LOGO = `
|
|
36639
36953
|
_
|
|
36640
36954
|
__| | ___ ___ __ __
|