noxctl 0.7.4 → 0.9.0
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +50 -4
- package/dist/auth.d.ts +18 -1
- package/dist/auth.d.ts.map +1 -1
- package/dist/auth.js +129 -15
- package/dist/auth.js.map +1 -1
- package/dist/cli.d.ts.map +1 -1
- package/dist/cli.js +144 -33
- package/dist/cli.js.map +1 -1
- package/dist/credentials-store.d.ts +3 -0
- package/dist/credentials-store.d.ts.map +1 -1
- package/dist/credentials-store.js +54 -10
- package/dist/credentials-store.js.map +1 -1
- package/dist/embedded.d.ts +15 -0
- package/dist/embedded.d.ts.map +1 -0
- package/dist/embedded.js +11 -0
- package/dist/embedded.js.map +1 -0
- package/dist/fortnox-client.d.ts +40 -25
- package/dist/fortnox-client.d.ts.map +1 -1
- package/dist/fortnox-client.js +165 -51
- package/dist/fortnox-client.js.map +1 -1
- package/dist/index.d.ts +7 -2
- package/dist/index.d.ts.map +1 -1
- package/dist/index.js +50 -35
- package/dist/index.js.map +1 -1
- package/dist/keychain-target.d.ts +3 -0
- package/dist/keychain-target.d.ts.map +1 -1
- package/dist/keychain-target.js +28 -7
- package/dist/keychain-target.js.map +1 -1
- package/dist/operations/absencetransactions.d.ts +9 -6
- package/dist/operations/absencetransactions.d.ts.map +1 -1
- package/dist/operations/absencetransactions.js +42 -33
- package/dist/operations/absencetransactions.js.map +1 -1
- package/dist/operations/accounts.d.ts +6 -3
- package/dist/operations/accounts.d.ts.map +1 -1
- package/dist/operations/accounts.js +28 -24
- package/dist/operations/accounts.js.map +1 -1
- package/dist/operations/analytics.d.ts +35 -0
- package/dist/operations/analytics.d.ts.map +1 -1
- package/dist/operations/analytics.js +59 -35
- package/dist/operations/analytics.js.map +1 -1
- package/dist/operations/articles.d.ts +9 -6
- package/dist/operations/articles.d.ts.map +1 -1
- package/dist/operations/articles.js +36 -32
- package/dist/operations/articles.js.map +1 -1
- package/dist/operations/attendancetransactions.d.ts +9 -6
- package/dist/operations/attendancetransactions.d.ts.map +1 -1
- package/dist/operations/attendancetransactions.js +45 -36
- package/dist/operations/attendancetransactions.js.map +1 -1
- package/dist/operations/company.d.ts +5 -1
- package/dist/operations/company.d.ts.map +1 -1
- package/dist/operations/company.js +8 -4
- package/dist/operations/company.js.map +1 -1
- package/dist/operations/contracts.d.ts +12 -9
- package/dist/operations/contracts.d.ts.map +1 -1
- package/dist/operations/contracts.js +59 -47
- package/dist/operations/contracts.js.map +1 -1
- package/dist/operations/costcenters.d.ts +10 -7
- package/dist/operations/costcenters.d.ts.map +1 -1
- package/dist/operations/costcenters.js +39 -35
- package/dist/operations/costcenters.js.map +1 -1
- package/dist/operations/customers.d.ts +9 -6
- package/dist/operations/customers.d.ts.map +1 -1
- package/dist/operations/customers.js +46 -42
- package/dist/operations/customers.js.map +1 -1
- package/dist/operations/employees.d.ts +9 -6
- package/dist/operations/employees.d.ts.map +1 -1
- package/dist/operations/employees.js +35 -31
- package/dist/operations/employees.js.map +1 -1
- package/dist/operations/financial-reports.d.ts +7 -2
- package/dist/operations/financial-reports.d.ts.map +1 -1
- package/dist/operations/financial-reports.js +169 -165
- package/dist/operations/financial-reports.js.map +1 -1
- package/dist/operations/financial-years.d.ts +8 -5
- package/dist/operations/financial-years.d.ts.map +1 -1
- package/dist/operations/financial-years.js +33 -29
- package/dist/operations/financial-years.js.map +1 -1
- package/dist/operations/index.d.ts +311 -0
- package/dist/operations/index.d.ts.map +1 -0
- package/dist/operations/index.js +64 -0
- package/dist/operations/index.js.map +1 -0
- package/dist/operations/invoice-payments.d.ts +10 -7
- package/dist/operations/invoice-payments.d.ts.map +1 -1
- package/dist/operations/invoice-payments.js +45 -35
- package/dist/operations/invoice-payments.js.map +1 -1
- package/dist/operations/invoices.d.ts +34 -30
- package/dist/operations/invoices.d.ts.map +1 -1
- package/dist/operations/invoices.js +225 -113
- package/dist/operations/invoices.js.map +1 -1
- package/dist/operations/offers.d.ts +11 -8
- package/dist/operations/offers.d.ts.map +1 -1
- package/dist/operations/offers.js +54 -43
- package/dist/operations/offers.js.map +1 -1
- package/dist/operations/orders.d.ts +10 -7
- package/dist/operations/orders.d.ts.map +1 -1
- package/dist/operations/orders.js +43 -39
- package/dist/operations/orders.js.map +1 -1
- package/dist/operations/pricelists.d.ts +16 -10
- package/dist/operations/pricelists.d.ts.map +1 -1
- package/dist/operations/pricelists.js +56 -48
- package/dist/operations/pricelists.js.map +1 -1
- package/dist/operations/projects.d.ts +9 -6
- package/dist/operations/projects.d.ts.map +1 -1
- package/dist/operations/projects.js +34 -30
- package/dist/operations/projects.js.map +1 -1
- package/dist/operations/recurrings.d.ts +14 -10
- package/dist/operations/recurrings.d.ts.map +1 -1
- package/dist/operations/recurrings.js +74 -59
- package/dist/operations/recurrings.js.map +1 -1
- package/dist/operations/salarytransactions.d.ts +9 -6
- package/dist/operations/salarytransactions.d.ts.map +1 -1
- package/dist/operations/salarytransactions.js +41 -32
- package/dist/operations/salarytransactions.js.map +1 -1
- package/dist/operations/scheduletimes.d.ts +7 -3
- package/dist/operations/scheduletimes.d.ts.map +1 -1
- package/dist/operations/scheduletimes.js +26 -22
- package/dist/operations/scheduletimes.js.map +1 -1
- package/dist/operations/supplier-invoice-payments.d.ts +9 -6
- package/dist/operations/supplier-invoice-payments.d.ts.map +1 -1
- package/dist/operations/supplier-invoice-payments.js +38 -29
- package/dist/operations/supplier-invoice-payments.js.map +1 -1
- package/dist/operations/supplier-invoices.d.ts +9 -6
- package/dist/operations/supplier-invoices.d.ts.map +1 -1
- package/dist/operations/supplier-invoices.js +41 -32
- package/dist/operations/supplier-invoices.js.map +1 -1
- package/dist/operations/suppliers.d.ts +9 -6
- package/dist/operations/suppliers.d.ts.map +1 -1
- package/dist/operations/suppliers.js +36 -32
- package/dist/operations/suppliers.js.map +1 -1
- package/dist/operations/tax.d.ts +5 -1
- package/dist/operations/tax.d.ts.map +1 -1
- package/dist/operations/tax.js +43 -39
- package/dist/operations/tax.js.map +1 -1
- package/dist/operations/taxreductions.d.ts +8 -5
- package/dist/operations/taxreductions.d.ts.map +1 -1
- package/dist/operations/taxreductions.js +28 -24
- package/dist/operations/taxreductions.js.map +1 -1
- package/dist/operations/vouchers.d.ts +26 -8
- package/dist/operations/vouchers.d.ts.map +1 -1
- package/dist/operations/vouchers.js +174 -122
- package/dist/operations/vouchers.js.map +1 -1
- package/dist/schema-audit-mappings.d.ts +9 -0
- package/dist/schema-audit-mappings.d.ts.map +1 -0
- package/dist/schema-audit-mappings.js +45 -0
- package/dist/schema-audit-mappings.js.map +1 -0
- package/dist/schema-audit.d.ts +45 -0
- package/dist/schema-audit.d.ts.map +1 -0
- package/dist/schema-audit.js +132 -0
- package/dist/schema-audit.js.map +1 -0
- package/dist/scope-probes.d.ts.map +1 -1
- package/dist/scope-probes.js +1 -0
- package/dist/scope-probes.js.map +1 -1
- package/dist/strict-mcp-server.d.ts +17 -0
- package/dist/strict-mcp-server.d.ts.map +1 -0
- package/dist/strict-mcp-server.js +35 -0
- package/dist/strict-mcp-server.js.map +1 -0
- package/dist/tools/absencetransactions.d.ts +2 -1
- package/dist/tools/absencetransactions.d.ts.map +1 -1
- package/dist/tools/absencetransactions.js +3 -2
- package/dist/tools/absencetransactions.js.map +1 -1
- package/dist/tools/analytics.d.ts +2 -1
- package/dist/tools/analytics.d.ts.map +1 -1
- package/dist/tools/analytics.js +3 -2
- package/dist/tools/analytics.js.map +1 -1
- package/dist/tools/articles.d.ts +2 -1
- package/dist/tools/articles.d.ts.map +1 -1
- package/dist/tools/articles.js +3 -2
- package/dist/tools/articles.js.map +1 -1
- package/dist/tools/attendancetransactions.d.ts +2 -1
- package/dist/tools/attendancetransactions.d.ts.map +1 -1
- package/dist/tools/attendancetransactions.js +3 -2
- package/dist/tools/attendancetransactions.js.map +1 -1
- package/dist/tools/bookkeeping.d.ts +2 -1
- package/dist/tools/bookkeeping.d.ts.map +1 -1
- package/dist/tools/bookkeeping.js +83 -5
- package/dist/tools/bookkeeping.js.map +1 -1
- package/dist/tools/company.d.ts +2 -1
- package/dist/tools/company.d.ts.map +1 -1
- package/dist/tools/company.js +3 -2
- package/dist/tools/company.js.map +1 -1
- package/dist/tools/contracts.d.ts +2 -1
- package/dist/tools/contracts.d.ts.map +1 -1
- package/dist/tools/contracts.js +3 -2
- package/dist/tools/contracts.js.map +1 -1
- package/dist/tools/costcenters.d.ts +2 -1
- package/dist/tools/costcenters.d.ts.map +1 -1
- package/dist/tools/costcenters.js +3 -2
- package/dist/tools/costcenters.js.map +1 -1
- package/dist/tools/customers.d.ts +2 -1
- package/dist/tools/customers.d.ts.map +1 -1
- package/dist/tools/customers.js +3 -2
- package/dist/tools/customers.js.map +1 -1
- package/dist/tools/employees.d.ts +2 -1
- package/dist/tools/employees.d.ts.map +1 -1
- package/dist/tools/employees.js +3 -2
- package/dist/tools/employees.js.map +1 -1
- package/dist/tools/financial-reports.d.ts +2 -1
- package/dist/tools/financial-reports.d.ts.map +1 -1
- package/dist/tools/financial-reports.js +3 -2
- package/dist/tools/financial-reports.js.map +1 -1
- package/dist/tools/financial-years.d.ts +2 -1
- package/dist/tools/financial-years.d.ts.map +1 -1
- package/dist/tools/financial-years.js +3 -2
- package/dist/tools/financial-years.js.map +1 -1
- package/dist/tools/invoice-payments.d.ts +2 -1
- package/dist/tools/invoice-payments.d.ts.map +1 -1
- package/dist/tools/invoice-payments.js +3 -2
- package/dist/tools/invoice-payments.js.map +1 -1
- package/dist/tools/invoices.d.ts +2 -1
- package/dist/tools/invoices.d.ts.map +1 -1
- package/dist/tools/invoices.js +100 -21
- package/dist/tools/invoices.js.map +1 -1
- package/dist/tools/offers.d.ts +2 -1
- package/dist/tools/offers.d.ts.map +1 -1
- package/dist/tools/offers.js +50 -19
- package/dist/tools/offers.js.map +1 -1
- package/dist/tools/orders.d.ts +2 -1
- package/dist/tools/orders.d.ts.map +1 -1
- package/dist/tools/orders.js +58 -18
- package/dist/tools/orders.js.map +1 -1
- package/dist/tools/pricelists.d.ts +2 -1
- package/dist/tools/pricelists.d.ts.map +1 -1
- package/dist/tools/pricelists.js +3 -2
- package/dist/tools/pricelists.js.map +1 -1
- package/dist/tools/projects.d.ts +2 -1
- package/dist/tools/projects.d.ts.map +1 -1
- package/dist/tools/projects.js +3 -2
- package/dist/tools/projects.js.map +1 -1
- package/dist/tools/recurrings.d.ts +2 -1
- package/dist/tools/recurrings.d.ts.map +1 -1
- package/dist/tools/recurrings.js +3 -2
- package/dist/tools/recurrings.js.map +1 -1
- package/dist/tools/salarytransactions.d.ts +2 -1
- package/dist/tools/salarytransactions.d.ts.map +1 -1
- package/dist/tools/salarytransactions.js +3 -2
- package/dist/tools/salarytransactions.js.map +1 -1
- package/dist/tools/scheduletimes.d.ts +2 -1
- package/dist/tools/scheduletimes.d.ts.map +1 -1
- package/dist/tools/scheduletimes.js +3 -2
- package/dist/tools/scheduletimes.js.map +1 -1
- package/dist/tools/status.d.ts.map +1 -1
- package/dist/tools/status.js +5 -4
- package/dist/tools/status.js.map +1 -1
- package/dist/tools/supplier-invoice-payments.d.ts +2 -1
- package/dist/tools/supplier-invoice-payments.d.ts.map +1 -1
- package/dist/tools/supplier-invoice-payments.js +3 -2
- package/dist/tools/supplier-invoice-payments.js.map +1 -1
- package/dist/tools/supplier-invoices.d.ts +2 -1
- package/dist/tools/supplier-invoices.d.ts.map +1 -1
- package/dist/tools/supplier-invoices.js +47 -6
- package/dist/tools/supplier-invoices.js.map +1 -1
- package/dist/tools/suppliers.d.ts +2 -1
- package/dist/tools/suppliers.d.ts.map +1 -1
- package/dist/tools/suppliers.js +3 -2
- package/dist/tools/suppliers.js.map +1 -1
- package/dist/tools/tax.d.ts +2 -1
- package/dist/tools/tax.d.ts.map +1 -1
- package/dist/tools/tax.js +3 -2
- package/dist/tools/tax.js.map +1 -1
- package/dist/tools/taxreductions.d.ts +2 -1
- package/dist/tools/taxreductions.d.ts.map +1 -1
- package/dist/tools/taxreductions.js +3 -2
- package/dist/tools/taxreductions.js.map +1 -1
- package/dist/version.d.ts +2 -0
- package/dist/version.d.ts.map +1 -0
- package/dist/version.js +3 -0
- package/dist/version.js.map +1 -0
- package/dist/views.d.ts +2 -0
- package/dist/views.d.ts.map +1 -1
- package/dist/views.js +11 -0
- package/dist/views.js.map +1 -1
- package/docs/embedded.md +60 -0
- package/package.json +23 -4
package/dist/cli.js
CHANGED
|
@@ -7,7 +7,8 @@ import { readActivePointer, readActivePointerOutcome, writeActivePointer, delete
|
|
|
7
7
|
import { setResolvedProfile } from './auth.js';
|
|
8
8
|
import { applyPeriod } from './date-periods.js';
|
|
9
9
|
import { DEFAULT_PROFILE, InvalidProfileNameError } from './profile-name.js';
|
|
10
|
-
import {
|
|
10
|
+
import { VERSION } from './version.js';
|
|
11
|
+
import { invoiceListColumns, invoiceDetailColumns, invoiceConfirmColumns, invoiceAttachmentColumns, invoiceAttachmentListColumns, customerListColumns, customerDetailColumns, voucherListColumns, voucherDetailColumns, voucherRowColumns, accountListColumns, companyDetailColumns, articleListColumns, articleDetailColumns, supplierListColumns, supplierDetailColumns, supplierInvoiceListColumns, supplierInvoiceDetailColumns, supplierInvoiceConfirmColumns, invoicePaymentListColumns, invoicePaymentDetailColumns, supplierInvoicePaymentListColumns, supplierInvoicePaymentDetailColumns, offerListColumns, offerDetailColumns, orderListColumns, orderDetailColumns, projectListColumns, projectDetailColumns, costCenterListColumns, costCenterDetailColumns, taxReductionListColumns, taxReductionDetailColumns, priceListListColumns, priceListDetailColumns, priceListColumns, priceDetailColumns, financialYearListColumns, financialYearDetailColumns, lockedPeriodDetailColumns, contractListColumns, contractDetailColumns, recurringListColumns, recurringDetailColumns, invoiceRequestListColumns, invoiceRequestDetailColumns, topCustomerColumns, monthlyRevenueColumns, voucherAttachmentColumns, employeeListColumns, employeeDetailColumns, salaryTransactionListColumns, salaryTransactionDetailColumns, attendanceTransactionListColumns, attendanceTransactionDetailColumns, absenceTransactionListColumns, absenceTransactionDetailColumns, scheduleTimeDetailColumns, } from './views.js';
|
|
11
12
|
const program = new Command();
|
|
12
13
|
// A closed downstream pipe (`noxctl ... | head`, or a reader that exits early)
|
|
13
14
|
// makes stdout emit EPIPE. That is normal for a CLI, and without this listener
|
|
@@ -22,7 +23,7 @@ process.stdout.on('error', (err) => {
|
|
|
22
23
|
program
|
|
23
24
|
.name('noxctl')
|
|
24
25
|
.description('CLI and MCP server for Fortnox accounting')
|
|
25
|
-
.version(
|
|
26
|
+
.version(VERSION)
|
|
26
27
|
.addOption(new Option('-o, --output <format>', 'Output format (default: table on TTY, json when piped)')
|
|
27
28
|
.choices(['json', 'table'])
|
|
28
29
|
.default(undefined))
|
|
@@ -105,7 +106,7 @@ program.hook('preAction', async (thisCommand, actionCommand) => {
|
|
|
105
106
|
}
|
|
106
107
|
throw err;
|
|
107
108
|
}
|
|
108
|
-
setResolvedProfile(resolvedProfileInfo.name);
|
|
109
|
+
setResolvedProfile(resolvedProfileInfo.name, resolvedProfileInfo.source);
|
|
109
110
|
// Banner ownership: MCP `serve` prints its own banner in bindStartupProfile
|
|
110
111
|
// so host logs (Claude Desktop) always see it. Suppress here to avoid a
|
|
111
112
|
// duplicate on TTY invocations like `noxctl --profile X serve`.
|
|
@@ -198,15 +199,23 @@ program
|
|
|
198
199
|
.option('--profile <name>', 'Profile to create/re-auth (defaults to resolved profile or "default")')
|
|
199
200
|
.option('--with-salary', 'Also request the Lön (salary/payroll) scope — requires the Lön permission enabled on your Fortnox app')
|
|
200
201
|
.option('--with-orders', 'Also request the offer/order scopes — requires the Order licence on the Fortnox company')
|
|
202
|
+
.option('--with-archive', 'Also request the archive (Arkivplats) scope — lets noxctl read files from the Fortnox file archive, e.g. attachments made via the Fortnox web UI')
|
|
201
203
|
.action(async (initOpts) => {
|
|
202
|
-
const {
|
|
204
|
+
const { inspectCredentials, runOAuthSetup, SCOPES, SALARY_SCOPE, ORDER_SCOPES, ARCHIVE_SCOPE, } = await import('./auth.js');
|
|
203
205
|
const { validateProfileName } = await import('./profile-name.js');
|
|
204
|
-
// Opt-in scopes: flag for TTY, env var for non-interactive/CI runs.
|
|
205
|
-
// licence-gated in Fortnox (Lön / Order), and asking for a scope
|
|
206
|
-
// is not licensed for fails the whole authorization — hence not
|
|
206
|
+
// Opt-in scopes: flag for TTY, env var for non-interactive/CI runs. All are
|
|
207
|
+
// licence-gated in Fortnox (Lön / Order / Arkivplats), and asking for a scope
|
|
208
|
+
// the company is not licensed for fails the whole authorization — hence not
|
|
209
|
+
// defaults.
|
|
207
210
|
const withSalary = Boolean(initOpts.withSalary) || process.env.FORTNOX_WITH_SALARY === '1';
|
|
208
211
|
const withOrders = Boolean(initOpts.withOrders) || process.env.FORTNOX_WITH_ORDERS === '1';
|
|
209
|
-
const
|
|
212
|
+
const withArchive = Boolean(initOpts.withArchive) || process.env.FORTNOX_WITH_ARCHIVE === '1';
|
|
213
|
+
const scopes = [
|
|
214
|
+
SCOPES,
|
|
215
|
+
withOrders ? ORDER_SCOPES : '',
|
|
216
|
+
withSalary ? SALARY_SCOPE : '',
|
|
217
|
+
withArchive ? ARCHIVE_SCOPE : '',
|
|
218
|
+
]
|
|
210
219
|
.filter(Boolean)
|
|
211
220
|
.join(' ');
|
|
212
221
|
let targetProfile;
|
|
@@ -222,11 +231,15 @@ program
|
|
|
222
231
|
// (verification via getCompanyInfo, runOAuthSetup's internal saveCredentials
|
|
223
232
|
// call chain) targets the profile being initialized — not a stale pointer.
|
|
224
233
|
if (targetProfile.toLowerCase() !== resolvedProfileInfo.name.toLowerCase()) {
|
|
225
|
-
setResolvedProfile(targetProfile);
|
|
234
|
+
setResolvedProfile(targetProfile, 'flag');
|
|
226
235
|
resolvedProfileInfo = { name: targetProfile, source: 'flag' };
|
|
227
236
|
}
|
|
228
237
|
// Step 1: Check if already configured
|
|
229
|
-
const
|
|
238
|
+
const inspection = await inspectCredentials(targetProfile);
|
|
239
|
+
if (inspection.state === 'locked' || inspection.state === 'inaccessible') {
|
|
240
|
+
fail(inspection.detail);
|
|
241
|
+
}
|
|
242
|
+
const existing = inspection.credentials;
|
|
230
243
|
if (existing) {
|
|
231
244
|
console.log('Existing credentials found.');
|
|
232
245
|
if (process.stdin.isTTY && process.stdout.isTTY) {
|
|
@@ -556,9 +569,12 @@ profile
|
|
|
556
569
|
console.error(err instanceof Error ? err.message : String(err));
|
|
557
570
|
process.exit(2);
|
|
558
571
|
}
|
|
559
|
-
const {
|
|
560
|
-
const
|
|
561
|
-
if (
|
|
572
|
+
const { inspectCredentials } = await import('./auth.js');
|
|
573
|
+
const inspection = await inspectCredentials(validated);
|
|
574
|
+
if (inspection.state === 'locked' || inspection.state === 'inaccessible') {
|
|
575
|
+
fail(inspection.detail);
|
|
576
|
+
}
|
|
577
|
+
if (inspection.state === 'missing') {
|
|
562
578
|
console.error(`No credentials found for profile "${validated}". Run \`noxctl init --profile ${validated}\` first.`);
|
|
563
579
|
process.exit(1);
|
|
564
580
|
}
|
|
@@ -606,7 +622,7 @@ program
|
|
|
606
622
|
.command('doctor')
|
|
607
623
|
.description('Check setup: credentials, token, API connectivity, and scopes')
|
|
608
624
|
.action(async () => {
|
|
609
|
-
const {
|
|
625
|
+
const { inspectCredentials } = await import('./auth.js');
|
|
610
626
|
let ok = true;
|
|
611
627
|
function pass(label, detail) {
|
|
612
628
|
console.log(` ✓ ${label}${detail ? ` — ${detail}` : ''}`);
|
|
@@ -641,6 +657,9 @@ program
|
|
|
641
657
|
if (state === 'locked') {
|
|
642
658
|
fail('Dedicated keychain', 'locked — run `noxctl keychain unlock` (tap your YubiKey)');
|
|
643
659
|
}
|
|
660
|
+
else if (state === 'missing') {
|
|
661
|
+
fail('Dedicated keychain', `configured but inaccessible at ${activePath} — inspect it from an unsandboxed terminal`);
|
|
662
|
+
}
|
|
644
663
|
else {
|
|
645
664
|
pass('Dedicated keychain', `active, ${state} (${activePath})`);
|
|
646
665
|
}
|
|
@@ -682,27 +701,21 @@ program
|
|
|
682
701
|
catch {
|
|
683
702
|
// best-effort diagnostic — don't block doctor on filesystem issues
|
|
684
703
|
}
|
|
685
|
-
// 3.
|
|
686
|
-
|
|
687
|
-
|
|
688
|
-
|
|
689
|
-
|
|
690
|
-
|
|
691
|
-
const { KeychainLockedError } = await import('./keychain-target.js');
|
|
692
|
-
if (err instanceof KeychainLockedError) {
|
|
693
|
-
fail('Credentials', err.message);
|
|
694
|
-
console.log(`\n${ok ? 'All checks passed.' : 'Some checks failed.'}`);
|
|
695
|
-
return;
|
|
696
|
-
}
|
|
697
|
-
throw err;
|
|
704
|
+
// 3. Credential state
|
|
705
|
+
const inspection = await inspectCredentials();
|
|
706
|
+
if (inspection.state === 'locked' || inspection.state === 'inaccessible') {
|
|
707
|
+
fail('Credentials', `${inspection.state} — ${inspection.detail}`);
|
|
708
|
+
console.log(`\n${ok ? 'All checks passed.' : 'Some checks failed.'}`);
|
|
709
|
+
return;
|
|
698
710
|
}
|
|
699
|
-
if (
|
|
700
|
-
fail('Credentials', `not found for profile "${resolvedProfileInfo.name}" — run \`noxctl init${resolvedProfileInfo.name === DEFAULT_PROFILE
|
|
711
|
+
if (inspection.state === 'missing') {
|
|
712
|
+
fail('Credentials', `missing — not found for profile "${resolvedProfileInfo.name}" — run \`noxctl init${resolvedProfileInfo.name === DEFAULT_PROFILE
|
|
701
713
|
? ''
|
|
702
714
|
: ` --profile ${resolvedProfileInfo.name}`}\` to set up`);
|
|
703
715
|
console.log(`\n${ok ? 'All checks passed.' : 'Some checks failed.'}`);
|
|
704
716
|
return;
|
|
705
717
|
}
|
|
718
|
+
const creds = inspection.credentials;
|
|
706
719
|
pass('Credentials', 'found');
|
|
707
720
|
if (creds.company_name) {
|
|
708
721
|
pass('Company (cached)', creds.company_name);
|
|
@@ -795,6 +808,12 @@ keychain
|
|
|
795
808
|
const onDarwin = process.platform === 'darwin';
|
|
796
809
|
console.log('Keychain status\n');
|
|
797
810
|
console.log(` Platform ${process.platform}${onDarwin ? '' : ' (dedicated keychain is macOS-only)'}`);
|
|
811
|
+
const { inspectCredentials } = await import('./auth.js');
|
|
812
|
+
const inspection = await inspectCredentials();
|
|
813
|
+
console.log(` Credential state ${inspection.state} — profile "${inspection.profile}" (source: ${inspection.source})`);
|
|
814
|
+
if (inspection.state === 'locked' || inspection.state === 'inaccessible') {
|
|
815
|
+
console.log(`\n ${inspection.detail}`);
|
|
816
|
+
}
|
|
798
817
|
if (!onDarwin)
|
|
799
818
|
return;
|
|
800
819
|
const activePath = kt.activeKeychainPath();
|
|
@@ -822,10 +841,13 @@ keychain
|
|
|
822
841
|
.action(async () => {
|
|
823
842
|
requireDarwin();
|
|
824
843
|
const kt = await import('./keychain-target.js');
|
|
825
|
-
const
|
|
844
|
+
const activePath = kt.activeKeychainPath();
|
|
845
|
+
const kcPath = activePath ?? kt.dedicatedKeychainPath();
|
|
826
846
|
const state = kt.keychainLockState(kcPath);
|
|
827
847
|
if (state === 'missing') {
|
|
828
|
-
console.error(
|
|
848
|
+
console.error(activePath
|
|
849
|
+
? `The configured keychain at ${kcPath} is inaccessible. Retry from an unsandboxed terminal; do not re-run noxctl keychain init.`
|
|
850
|
+
: 'No dedicated keychain found. Run `noxctl keychain init` first.');
|
|
829
851
|
process.exit(1);
|
|
830
852
|
}
|
|
831
853
|
if (state === 'unlocked') {
|
|
@@ -868,9 +890,12 @@ keychain
|
|
|
868
890
|
.action(async () => {
|
|
869
891
|
requireDarwin();
|
|
870
892
|
const kt = await import('./keychain-target.js');
|
|
871
|
-
const
|
|
893
|
+
const activePath = kt.activeKeychainPath();
|
|
894
|
+
const kcPath = activePath ?? kt.dedicatedKeychainPath();
|
|
872
895
|
if (kt.keychainLockState(kcPath) === 'missing') {
|
|
873
|
-
console.error(
|
|
896
|
+
console.error(activePath
|
|
897
|
+
? `The configured keychain at ${kcPath} is inaccessible. Retry from an unsandboxed terminal.`
|
|
898
|
+
: 'No dedicated keychain found.');
|
|
874
899
|
process.exit(1);
|
|
875
900
|
}
|
|
876
901
|
kt.lockKeychain(kcPath);
|
|
@@ -1263,6 +1288,47 @@ invoices
|
|
|
1263
1288
|
const data = await creditInvoice(documentNumber);
|
|
1264
1289
|
outputConfirmation(`Invoice ${documentNumber} credited.`, json(), data, invoiceConfirmColumns, 'Invoice');
|
|
1265
1290
|
});
|
|
1291
|
+
invoices
|
|
1292
|
+
.command('attach <documentNumber> <files...>')
|
|
1293
|
+
.description('Upload receipt/underlag files and attach them to a customer invoice')
|
|
1294
|
+
.option('--no-include-on-send', 'Do not bundle the file when the invoice is sent (default: bundled)')
|
|
1295
|
+
.option('-y, --yes', 'Skip confirmation prompt')
|
|
1296
|
+
.option('--dry-run', 'Preview without uploading')
|
|
1297
|
+
.addHelpText('after', `
|
|
1298
|
+
Requires the "archive" scope — run \`noxctl init --with-archive\` first if
|
|
1299
|
+
\`noxctl doctor\` reports it missing.
|
|
1300
|
+
|
|
1301
|
+
Examples:
|
|
1302
|
+
noxctl invoices attach 91110646 underlag.pdf
|
|
1303
|
+
noxctl invoices attach 91110646 underlag.pdf --no-include-on-send`)
|
|
1304
|
+
.action(async (documentNumber, files, opts) => {
|
|
1305
|
+
const { attachInvoiceFiles } = await import('./operations/invoices.js');
|
|
1306
|
+
if (!(await confirmMutation(`Attach ${files.length} file(s) to invoice ${documentNumber}`, opts, {
|
|
1307
|
+
files,
|
|
1308
|
+
includeOnSend: opts.includeOnSend ?? true,
|
|
1309
|
+
}))) {
|
|
1310
|
+
return;
|
|
1311
|
+
}
|
|
1312
|
+
const results = await attachInvoiceFiles({
|
|
1313
|
+
documentNumber,
|
|
1314
|
+
filePaths: files,
|
|
1315
|
+
includeOnSend: opts.includeOnSend,
|
|
1316
|
+
});
|
|
1317
|
+
if (json()) {
|
|
1318
|
+
console.log(JSON.stringify({ Attachments: results }, null, 2));
|
|
1319
|
+
}
|
|
1320
|
+
else {
|
|
1321
|
+
outputList(results, invoiceAttachmentColumns, false, results);
|
|
1322
|
+
}
|
|
1323
|
+
});
|
|
1324
|
+
invoices
|
|
1325
|
+
.command('attachments <documentNumber>')
|
|
1326
|
+
.description('List files attached to a customer invoice')
|
|
1327
|
+
.action(async (documentNumber) => {
|
|
1328
|
+
const { listInvoiceAttachments } = await import('./operations/invoices.js');
|
|
1329
|
+
const results = await listInvoiceAttachments(documentNumber);
|
|
1330
|
+
outputList(results, invoiceAttachmentListColumns, json(), { Attachments: results });
|
|
1331
|
+
});
|
|
1266
1332
|
// --- tax ---
|
|
1267
1333
|
const tax = program.command('tax').description('Tax operations');
|
|
1268
1334
|
tax
|
|
@@ -1748,6 +1814,51 @@ Examples:
|
|
|
1748
1814
|
outputList(results, voucherAttachmentColumns, false, results);
|
|
1749
1815
|
}
|
|
1750
1816
|
});
|
|
1817
|
+
vouchers
|
|
1818
|
+
.command('attachments <series> <voucherNumber>')
|
|
1819
|
+
.description('List files (receipts/underlag) attached to a voucher')
|
|
1820
|
+
.option('--year <number>', 'Financial year (recommended — voucher numbers reset between financial years)', parseInt)
|
|
1821
|
+
.action(async (series, voucherNumber, opts) => {
|
|
1822
|
+
const { listVoucherAttachments } = await import('./operations/vouchers.js');
|
|
1823
|
+
const attachments = await listVoucherAttachments(series, voucherNumber, opts.year);
|
|
1824
|
+
if (json()) {
|
|
1825
|
+
console.log(JSON.stringify({ Attachments: attachments }, null, 2));
|
|
1826
|
+
}
|
|
1827
|
+
else {
|
|
1828
|
+
outputList(attachments, voucherAttachmentColumns, false, attachments);
|
|
1829
|
+
}
|
|
1830
|
+
});
|
|
1831
|
+
vouchers
|
|
1832
|
+
.command('file <fileId>')
|
|
1833
|
+
.description('Download a file attached to a voucher (get the fileId from "vouchers attachments")')
|
|
1834
|
+
.option('-f, --file <path>', 'Write the file here (- for stdout; default: voucher-file-<fileId><ext>)')
|
|
1835
|
+
.addHelpText('after', `
|
|
1836
|
+
Examples:
|
|
1837
|
+
noxctl vouchers attachments A 60 --year 4
|
|
1838
|
+
noxctl vouchers file c7e578d8-59a7-4e00-b29b-b99e4d9ede63
|
|
1839
|
+
noxctl vouchers file c7e578d8-59a7-4e00-b29b-b99e4d9ede63 --file receipt.pdf`)
|
|
1840
|
+
.action(async (fileId, opts) => {
|
|
1841
|
+
const { getVoucherFile, extensionForMime } = await import('./operations/vouchers.js');
|
|
1842
|
+
const toStdout = opts.file === '-';
|
|
1843
|
+
if (toStdout && program.opts().output === 'json') {
|
|
1844
|
+
throw new Error('--file - writes raw file bytes to stdout and cannot be combined with --output json.');
|
|
1845
|
+
}
|
|
1846
|
+
const file = await getVoucherFile(fileId);
|
|
1847
|
+
if (toStdout) {
|
|
1848
|
+
await new Promise((resolve, reject) => {
|
|
1849
|
+
process.stdout.write(file.buffer, (err) => (err ? reject(err) : resolve()));
|
|
1850
|
+
});
|
|
1851
|
+
return;
|
|
1852
|
+
}
|
|
1853
|
+
const path = opts.file ?? `voucher-file-${fileId}${extensionForMime(file.contentType)}`;
|
|
1854
|
+
writeFileSync(path, file.buffer);
|
|
1855
|
+
outputConfirmation(`File saved to ${path} (${file.contentType}, ${file.buffer.length} bytes).`, json(), {
|
|
1856
|
+
FileId: fileId,
|
|
1857
|
+
Path: path,
|
|
1858
|
+
ContentType: file.contentType,
|
|
1859
|
+
Bytes: file.buffer.length,
|
|
1860
|
+
});
|
|
1861
|
+
});
|
|
1751
1862
|
// --- invoice-payments ---
|
|
1752
1863
|
const invoicePayments = program
|
|
1753
1864
|
.command('invoice-payments')
|