noxctl 0.2.0 → 0.4.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.
Files changed (116) hide show
  1. package/README.md +149 -6
  2. package/dist/auth.d.ts +7 -3
  3. package/dist/auth.d.ts.map +1 -1
  4. package/dist/auth.js +24 -7
  5. package/dist/auth.js.map +1 -1
  6. package/dist/cli.d.ts.map +1 -1
  7. package/dist/cli.js +1118 -95
  8. package/dist/cli.js.map +1 -1
  9. package/dist/completions.d.ts +13 -0
  10. package/dist/completions.d.ts.map +1 -0
  11. package/dist/completions.js +150 -0
  12. package/dist/completions.js.map +1 -0
  13. package/dist/credentials-store.d.ts.map +1 -1
  14. package/dist/credentials-store.js +45 -16
  15. package/dist/credentials-store.js.map +1 -1
  16. package/dist/date-periods.d.ts +14 -0
  17. package/dist/date-periods.d.ts.map +1 -0
  18. package/dist/date-periods.js +103 -0
  19. package/dist/date-periods.js.map +1 -0
  20. package/dist/formatter.d.ts +11 -2
  21. package/dist/formatter.d.ts.map +1 -1
  22. package/dist/formatter.js +27 -4
  23. package/dist/formatter.js.map +1 -1
  24. package/dist/fortnox-client.d.ts +1 -0
  25. package/dist/fortnox-client.d.ts.map +1 -1
  26. package/dist/fortnox-client.js +26 -3
  27. package/dist/fortnox-client.js.map +1 -1
  28. package/dist/index.d.ts.map +1 -1
  29. package/dist/index.js +17 -1
  30. package/dist/index.js.map +1 -1
  31. package/dist/keychain-target.d.ts +32 -0
  32. package/dist/keychain-target.d.ts.map +1 -0
  33. package/dist/keychain-target.js +349 -0
  34. package/dist/keychain-target.js.map +1 -0
  35. package/dist/operations/absencetransactions.d.ts +21 -0
  36. package/dist/operations/absencetransactions.d.ts.map +1 -0
  37. package/dist/operations/absencetransactions.js +36 -0
  38. package/dist/operations/absencetransactions.js.map +1 -0
  39. package/dist/operations/analytics.d.ts +84 -0
  40. package/dist/operations/analytics.d.ts.map +1 -0
  41. package/dist/operations/analytics.js +154 -0
  42. package/dist/operations/analytics.js.map +1 -0
  43. package/dist/operations/attendancetransactions.d.ts +21 -0
  44. package/dist/operations/attendancetransactions.d.ts.map +1 -0
  45. package/dist/operations/attendancetransactions.js +40 -0
  46. package/dist/operations/attendancetransactions.js.map +1 -0
  47. package/dist/operations/contracts.d.ts +23 -0
  48. package/dist/operations/contracts.d.ts.map +1 -0
  49. package/dist/operations/contracts.js +52 -0
  50. package/dist/operations/contracts.js.map +1 -0
  51. package/dist/operations/customers.d.ts.map +1 -1
  52. package/dist/operations/customers.js +12 -2
  53. package/dist/operations/customers.js.map +1 -1
  54. package/dist/operations/employees.d.ts +19 -0
  55. package/dist/operations/employees.d.ts.map +1 -0
  56. package/dist/operations/employees.js +34 -0
  57. package/dist/operations/employees.js.map +1 -0
  58. package/dist/operations/financial-years.d.ts +16 -0
  59. package/dist/operations/financial-years.d.ts.map +1 -0
  60. package/dist/operations/financial-years.js +32 -0
  61. package/dist/operations/financial-years.js.map +1 -0
  62. package/dist/operations/salarytransactions.d.ts +21 -0
  63. package/dist/operations/salarytransactions.d.ts.map +1 -0
  64. package/dist/operations/salarytransactions.js +35 -0
  65. package/dist/operations/salarytransactions.js.map +1 -0
  66. package/dist/operations/scheduletimes.d.ts +4 -0
  67. package/dist/operations/scheduletimes.d.ts.map +1 -0
  68. package/dist/operations/scheduletimes.js +24 -0
  69. package/dist/operations/scheduletimes.js.map +1 -0
  70. package/dist/operations/vouchers.d.ts +15 -0
  71. package/dist/operations/vouchers.d.ts.map +1 -1
  72. package/dist/operations/vouchers.js +97 -0
  73. package/dist/operations/vouchers.js.map +1 -1
  74. package/dist/tools/absencetransactions.d.ts +3 -0
  75. package/dist/tools/absencetransactions.d.ts.map +1 -0
  76. package/dist/tools/absencetransactions.js +66 -0
  77. package/dist/tools/absencetransactions.js.map +1 -0
  78. package/dist/tools/analytics.d.ts +3 -0
  79. package/dist/tools/analytics.d.ts.map +1 -0
  80. package/dist/tools/analytics.js +52 -0
  81. package/dist/tools/analytics.js.map +1 -0
  82. package/dist/tools/attendancetransactions.d.ts +3 -0
  83. package/dist/tools/attendancetransactions.d.ts.map +1 -0
  84. package/dist/tools/attendancetransactions.js +63 -0
  85. package/dist/tools/attendancetransactions.js.map +1 -0
  86. package/dist/tools/bookkeeping.d.ts.map +1 -1
  87. package/dist/tools/bookkeeping.js +38 -2
  88. package/dist/tools/bookkeeping.js.map +1 -1
  89. package/dist/tools/contracts.d.ts +3 -0
  90. package/dist/tools/contracts.d.ts.map +1 -0
  91. package/dist/tools/contracts.js +136 -0
  92. package/dist/tools/contracts.js.map +1 -0
  93. package/dist/tools/employees.d.ts +3 -0
  94. package/dist/tools/employees.d.ts.map +1 -0
  95. package/dist/tools/employees.js +107 -0
  96. package/dist/tools/employees.js.map +1 -0
  97. package/dist/tools/financial-years.d.ts +3 -0
  98. package/dist/tools/financial-years.d.ts.map +1 -0
  99. package/dist/tools/financial-years.js +33 -0
  100. package/dist/tools/financial-years.js.map +1 -0
  101. package/dist/tools/salarytransactions.d.ts +3 -0
  102. package/dist/tools/salarytransactions.d.ts.map +1 -0
  103. package/dist/tools/salarytransactions.js +70 -0
  104. package/dist/tools/salarytransactions.js.map +1 -0
  105. package/dist/tools/scheduletimes.d.ts +3 -0
  106. package/dist/tools/scheduletimes.d.ts.map +1 -0
  107. package/dist/tools/scheduletimes.js +62 -0
  108. package/dist/tools/scheduletimes.js.map +1 -0
  109. package/dist/tools/status.d.ts.map +1 -1
  110. package/dist/tools/status.js +10 -3
  111. package/dist/tools/status.js.map +1 -1
  112. package/dist/views.d.ts +17 -0
  113. package/dist/views.d.ts.map +1 -1
  114. package/dist/views.js +154 -0
  115. package/dist/views.js.map +1 -1
  116. package/package.json +2 -1
@@ -0,0 +1,32 @@
1
+ export declare const CR_SLOT = "2";
2
+ export declare class KeychainLockedError extends Error {
3
+ constructor(message?: string);
4
+ }
5
+ export declare class ChallengeResponseError extends Error {
6
+ constructor(message: string);
7
+ }
8
+ export declare function dedicatedKeychainPath(): string;
9
+ export declare function loginKeychainPath(): string;
10
+ export declare function challengeFilePath(): string;
11
+ export declare function activeKeychainPath(): string | null;
12
+ export declare function isDedicatedModeActive(): boolean;
13
+ export declare function readDedicatedSecret(account: string, keychainPath: string): string | null;
14
+ export declare function generateChallenge(): string;
15
+ export declare function readChallenge(): string | null;
16
+ export declare function writeChallenge(challengeHex: string): void;
17
+ export declare function enrolledSerialFilePath(): string;
18
+ export declare function readEnrolledSerial(): string | null;
19
+ export declare function writeEnrolledSerial(serial: string): void;
20
+ export declare function listYubikeySerials(): string[];
21
+ export declare function diagnoseSerialMismatch(enrolledSerial: string | null, presentSerials: string[]): string | null;
22
+ export declare function ykmanAvailable(): boolean;
23
+ export declare function yubikeyPresent(): boolean;
24
+ export declare function computeChallengeResponse(challengeHex: string): string;
25
+ export declare function createDedicatedKeychain(keychainPath: string, password: string): 'created' | 'exists';
26
+ export declare function unlockDedicatedKeychain(keychainPath: string, password: string): void;
27
+ export type LockState = 'locked' | 'unlocked' | 'missing';
28
+ export declare function keychainLockState(keychainPath: string): LockState;
29
+ export declare function setLockOnSleep(keychainPath: string): void;
30
+ export declare function lockKeychain(keychainPath: string): void;
31
+ export declare function deleteLoginSecret(account: string): boolean;
32
+ //# sourceMappingURL=keychain-target.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain-target.d.ts","sourceRoot":"","sources":["../src/keychain-target.ts"],"names":[],"mappings":"AAYA,eAAO,MAAM,OAAO,MAAM,CAAC;AAK3B,qBAAa,mBAAoB,SAAQ,KAAK;gBAE1C,OAAO,SAAiF;CAK3F;AAID,qBAAa,sBAAuB,SAAQ,KAAK;gBACnC,OAAO,EAAE,MAAM;CAI5B;AAED,wBAAgB,qBAAqB,IAAI,MAAM,CAG9C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAG1C;AAED,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAQD,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAQlD;AAED,wBAAgB,qBAAqB,IAAI,OAAO,CAE/C;AAeD,wBAAgB,mBAAmB,CAAC,OAAO,EAAE,MAAM,EAAE,YAAY,EAAE,MAAM,GAAG,MAAM,GAAG,IAAI,CA8CxF;AAKD,wBAAgB,iBAAiB,IAAI,MAAM,CAE1C;AAED,wBAAgB,aAAa,IAAI,MAAM,GAAG,IAAI,CAO7C;AAED,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAGzD;AAED,wBAAgB,sBAAsB,IAAI,MAAM,CAE/C;AAKD,wBAAgB,kBAAkB,IAAI,MAAM,GAAG,IAAI,CAOlD;AAED,wBAAgB,mBAAmB,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAGxD;AAED,wBAAgB,kBAAkB,IAAI,MAAM,EAAE,CAO7C;AAED,wBAAgB,sBAAsB,CACpC,cAAc,EAAE,MAAM,GAAG,IAAI,EAC7B,cAAc,EAAE,MAAM,EAAE,GACvB,MAAM,GAAG,IAAI,CAYf;AAED,wBAAgB,cAAc,IAAI,OAAO,CAGxC;AAED,wBAAgB,cAAc,IAAI,OAAO,CAGxC;AAMD,wBAAgB,wBAAwB,CAAC,YAAY,EAAE,MAAM,GAAG,MAAM,CAgCrE;AAwBD,wBAAgB,uBAAuB,CACrC,YAAY,EAAE,MAAM,EACpB,QAAQ,EAAE,MAAM,GACf,SAAS,GAAG,QAAQ,CAkBtB;AAID,wBAAgB,uBAAuB,CAAC,YAAY,EAAE,MAAM,EAAE,QAAQ,EAAE,MAAM,GAAG,IAAI,CAsBpF;AAED,MAAM,MAAM,SAAS,GAAG,QAAQ,GAAG,UAAU,GAAG,SAAS,CAAC;AAE1D,wBAAgB,iBAAiB,CAAC,YAAY,EAAE,MAAM,GAAG,SAAS,CA0BjE;AAID,wBAAgB,cAAc,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAUzD;AAED,wBAAgB,YAAY,CAAC,YAAY,EAAE,MAAM,GAAG,IAAI,CAQvD;AAOD,wBAAgB,iBAAiB,CAAC,OAAO,EAAE,MAAM,GAAG,OAAO,CAO1D"}
@@ -0,0 +1,349 @@
1
+ import os from 'node:os';
2
+ import path from 'node:path';
3
+ import fsSync from 'node:fs';
4
+ import { spawnSync } from 'node:child_process';
5
+ import { randomBytes } from 'node:crypto';
6
+ import { configDir } from './config-paths.js';
7
+ const SERVICE_NAME = 'fortnox-mcp';
8
+ // OTP slot programmed for HMAC-SHA1 challenge-response. Slot 2 is the
9
+ // conventional "long-press / second" slot and is what `noxctl keychain init`
10
+ // expects the user to have programmed with `ykman otp chalresp --generate --touch 2`.
11
+ export const CR_SLOT = '2';
12
+ // Thrown when the dedicated keychain exists but is locked. Distinct from
13
+ // "no credentials" so callers can tell the user to run `noxctl keychain unlock`
14
+ // (tap the YubiKey) rather than `noxctl init`.
15
+ export class KeychainLockedError extends Error {
16
+ constructor(message = 'Fortnox keychain is locked — run `noxctl keychain unlock` (tap your YubiKey)') {
17
+ super(message);
18
+ this.name = 'KeychainLockedError';
19
+ }
20
+ }
21
+ // Thrown when a challenge-response operation fails — most often a missed touch
22
+ // (ykman reports "Failed to write to the YubiKey" when the tap window lapses).
23
+ export class ChallengeResponseError extends Error {
24
+ constructor(message) {
25
+ super(message);
26
+ this.name = 'ChallengeResponseError';
27
+ }
28
+ }
29
+ export function dedicatedKeychainPath() {
30
+ const home = process.env.HOME || os.homedir() || '~';
31
+ return path.join(home, 'Library', 'Keychains', 'fortnox-mcp.keychain-db');
32
+ }
33
+ export function loginKeychainPath() {
34
+ const home = process.env.HOME || os.homedir() || '~';
35
+ return path.join(home, 'Library', 'Keychains', 'login.keychain-db');
36
+ }
37
+ export function challengeFilePath() {
38
+ return path.join(configDir(), 'keychain-challenge');
39
+ }
40
+ // Resolve which keychain credential operations should target.
41
+ // 1. NOXCTL_KEYCHAIN_PATH env override (used by tests and power users).
42
+ // 2. darwin only: the dedicated keychain + its challenge file both exist.
43
+ // 3. otherwise null — caller falls back to the login keychain (legacy behavior).
44
+ // The challenge file gates files-exist detection so a half-created keychain
45
+ // (file present, never `init`-ed) doesn't silently divert reads.
46
+ export function activeKeychainPath() {
47
+ const override = process.env.NOXCTL_KEYCHAIN_PATH;
48
+ if (override && override.trim())
49
+ return override;
50
+ if (process.platform !== 'darwin')
51
+ return null;
52
+ if (fsSync.existsSync(dedicatedKeychainPath()) && fsSync.existsSync(challengeFilePath())) {
53
+ return dedicatedKeychainPath();
54
+ }
55
+ return null;
56
+ }
57
+ export function isDedicatedModeActive() {
58
+ return activeKeychainPath() !== null;
59
+ }
60
+ function decodeBase64(value) {
61
+ return Buffer.from(value.trim(), 'base64').toString('utf-8');
62
+ }
63
+ // Read a generic-password secret from a specific keychain WITHOUT triggering
64
+ // the macOS GUI unlock dialog. With challenge-response we cannot type the
65
+ // keychain password into that dialog, so on a locked keychain we must fail
66
+ // fast (KeychainLockedError) instead of popping an un-fillable prompt.
67
+ //
68
+ // SecKeychainSetUserInteractionAllowed(false) makes a locked keychain return
69
+ // errSecInteractionNotAllowed rather than prompting. Account and keychain path
70
+ // are passed as argv (not string-interpolated) so an odd path can't break or
71
+ // inject into the Swift source. Returns null when the item is absent.
72
+ export function readDedicatedSecret(account, keychainPath) {
73
+ const swiftScript = `
74
+ import Foundation
75
+ import Security
76
+
77
+ let service = "${SERVICE_NAME}"
78
+ let account = CommandLine.arguments[1]
79
+ let kcPath = CommandLine.arguments[2]
80
+
81
+ var kc: SecKeychain?
82
+ let openStatus = SecKeychainOpen(kcPath, &kc)
83
+ if openStatus != errSecSuccess || kc == nil { exit(3) }
84
+
85
+ SecKeychainSetUserInteractionAllowed(false)
86
+
87
+ let query: [String: Any] = [
88
+ kSecClass as String: kSecClassGenericPassword,
89
+ kSecAttrService as String: service,
90
+ kSecAttrAccount as String: account,
91
+ kSecMatchSearchList as String: [kc!],
92
+ kSecReturnData as String: true,
93
+ kSecMatchLimit as String: kSecMatchLimitOne
94
+ ]
95
+ var item: CFTypeRef?
96
+ let status = SecItemCopyMatching(query as CFDictionary, &item)
97
+ if status == errSecSuccess, let data = item as? Data {
98
+ FileHandle.standardOutput.write(data.base64EncodedData())
99
+ exit(0)
100
+ }
101
+ if status == errSecInteractionNotAllowed || status == errSecAuthFailed { exit(2) }
102
+ exit(3)
103
+ `;
104
+ const scriptPath = path.join(os.tmpdir(), `noxctl-kcread-${process.pid}.swift`);
105
+ try {
106
+ fsSync.writeFileSync(scriptPath, swiftScript, { mode: 0o600 });
107
+ const result = spawnSync('swift', [scriptPath, account, keychainPath], { encoding: 'utf-8' });
108
+ if (result.status === 0)
109
+ return decodeBase64(result.stdout);
110
+ if (result.status === 2)
111
+ throw new KeychainLockedError();
112
+ return null;
113
+ }
114
+ finally {
115
+ try {
116
+ fsSync.unlinkSync(scriptPath);
117
+ }
118
+ catch {
119
+ // ignore cleanup failure
120
+ }
121
+ }
122
+ }
123
+ // The fixed challenge is NOT a secret — the secret lives inside the YubiKey.
124
+ // It's a stable random value so the same key always derives the same keychain
125
+ // password. 32 bytes -> 64 hex chars; `ykman otp calculate` wants hex.
126
+ export function generateChallenge() {
127
+ return randomBytes(32).toString('hex');
128
+ }
129
+ export function readChallenge() {
130
+ try {
131
+ const raw = fsSync.readFileSync(challengeFilePath(), 'utf-8').trim();
132
+ return raw.length > 0 ? raw : null;
133
+ }
134
+ catch {
135
+ return null;
136
+ }
137
+ }
138
+ export function writeChallenge(challengeHex) {
139
+ fsSync.mkdirSync(configDir(), { recursive: true, mode: 0o700 });
140
+ fsSync.writeFileSync(challengeFilePath(), `${challengeHex}\n`, { mode: 0o600 });
141
+ }
142
+ export function enrolledSerialFilePath() {
143
+ return path.join(configDir(), 'keychain-serial');
144
+ }
145
+ // Serial of the YubiKey the keychain was enrolled against. Persisted at init
146
+ // so unlock can tell "wrong/unenrolled key present" apart from "missed tap" —
147
+ // the two failure modes produce indistinguishable ykman errors otherwise.
148
+ export function readEnrolledSerial() {
149
+ try {
150
+ const raw = fsSync.readFileSync(enrolledSerialFilePath(), 'utf-8').trim();
151
+ return raw.length > 0 ? raw : null;
152
+ }
153
+ catch {
154
+ return null;
155
+ }
156
+ }
157
+ export function writeEnrolledSerial(serial) {
158
+ fsSync.mkdirSync(configDir(), { recursive: true, mode: 0o700 });
159
+ fsSync.writeFileSync(enrolledSerialFilePath(), `${serial}\n`, { mode: 0o600 });
160
+ }
161
+ export function listYubikeySerials() {
162
+ const r = spawnSync('ykman', ['list', '--serials'], { encoding: 'utf-8' });
163
+ if (r.status !== 0)
164
+ return [];
165
+ return (r.stdout || '')
166
+ .split('\n')
167
+ .map((line) => line.trim())
168
+ .filter((line) => line.length > 0);
169
+ }
170
+ export function diagnoseSerialMismatch(enrolledSerial, presentSerials) {
171
+ if (!enrolledSerial)
172
+ return null;
173
+ if (presentSerials.includes(enrolledSerial))
174
+ return null;
175
+ if (presentSerials.length === 0) {
176
+ return `No YubiKey detected. The keychain was enrolled against YubiKey serial ${enrolledSerial} — insert that key and re-run.`;
177
+ }
178
+ return (`Keychain was enrolled against YubiKey serial ${enrolledSerial}, but serial ` +
179
+ `${presentSerials.join(', ')} is currently present — this is a different key. ` +
180
+ `Insert the enrolled key, or re-enroll this one (program slot 2 with ` +
181
+ '`ykman otp chalresp --generate --touch 2`, then re-run `noxctl keychain init`).');
182
+ }
183
+ export function ykmanAvailable() {
184
+ const r = spawnSync('ykman', ['--version'], { encoding: 'utf-8' });
185
+ return r.status === 0;
186
+ }
187
+ export function yubikeyPresent() {
188
+ const r = spawnSync('ykman', ['list'], { encoding: 'utf-8' });
189
+ return r.status === 0 && r.stdout.trim().length > 0;
190
+ }
191
+ // Ask the YubiKey (OTP slot CR_SLOT) to HMAC the fixed challenge. Touch is
192
+ // required, so ykman's "Touch your YubiKey..." prompt is inherited to the
193
+ // terminal. A missed tap exits non-zero ("Failed to write to the YubiKey").
194
+ // The 40-hex response is the keychain password — returned, never logged.
195
+ export function computeChallengeResponse(challengeHex) {
196
+ const r = spawnSync('ykman', ['otp', 'calculate', CR_SLOT, challengeHex], {
197
+ encoding: 'utf-8',
198
+ stdio: ['ignore', 'pipe', 'pipe'],
199
+ });
200
+ if (r.error && r.error.code === 'ENOENT') {
201
+ throw new ChallengeResponseError('ykman not found — install it with `brew install ykman`');
202
+ }
203
+ if (r.status !== 0) {
204
+ // ykman's own wording is misleading here: "Failed to write to the
205
+ // YubiKey... restricted access" is what a touch timeout produces, and
206
+ // "empty slot" means this key was never enrolled. Translate both.
207
+ const stderr = (r.stderr || '').toString();
208
+ if (/empty slot/i.test(stderr)) {
209
+ throw new ChallengeResponseError(`Slot ${CR_SLOT} is not programmed on this YubiKey — it is not the enrolled key ` +
210
+ '(or its slot was wiped). Program it with `ykman otp chalresp --generate --touch 2` ' +
211
+ 'or insert the enrolled key.');
212
+ }
213
+ throw new ChallengeResponseError('Challenge-response failed. This is usually a missed touch — re-run and tap the key firmly when it blinks.' +
214
+ (stderr.trim() ? `\n(ykman said: ${stderr.trim()})` : ''));
215
+ }
216
+ const response = (r.stdout || '').trim();
217
+ if (!/^[0-9a-fA-F]{40}$/.test(response)) {
218
+ throw new ChallengeResponseError(`Unexpected challenge-response output (expected 40 hex chars)`);
219
+ }
220
+ return response;
221
+ }
222
+ function runSwift(script, args, input) {
223
+ const scriptPath = path.join(os.tmpdir(), `noxctl-kc-${process.pid}-${Date.now()}.swift`);
224
+ try {
225
+ fsSync.writeFileSync(scriptPath, script, { mode: 0o600 });
226
+ const r = spawnSync('swift', [scriptPath, ...args], {
227
+ input,
228
+ encoding: 'utf-8',
229
+ stdio: input === undefined ? 'pipe' : ['pipe', 'pipe', 'pipe'],
230
+ });
231
+ if (r.error)
232
+ throw r.error;
233
+ return r.status ?? 1;
234
+ }
235
+ finally {
236
+ try {
237
+ fsSync.unlinkSync(scriptPath);
238
+ }
239
+ catch {
240
+ // ignore cleanup failure
241
+ }
242
+ }
243
+ }
244
+ // Create a file-based keychain at keychainPath with the given password (fed via
245
+ // stdin, never argv). Returns 'created', or 'exists' if one is already there.
246
+ export function createDedicatedKeychain(keychainPath, password) {
247
+ const script = `
248
+ import Foundation
249
+ import Security
250
+
251
+ let data = FileHandle.standardInput.readDataToEndOfFile()
252
+ guard let password = String(data: data, encoding: .utf8) else { exit(1) }
253
+ let kcPath = CommandLine.arguments[1]
254
+ let pwBytes = Array(password.utf8)
255
+ var kc: SecKeychain?
256
+ let status = SecKeychainCreate(kcPath, UInt32(pwBytes.count), pwBytes, false, nil, &kc)
257
+ if status == errSecDuplicateKeychain { exit(4) }
258
+ if status != errSecSuccess { exit(1) }
259
+ `;
260
+ const code = runSwift(script, [keychainPath], password);
261
+ if (code === 4)
262
+ return 'exists';
263
+ if (code !== 0)
264
+ throw new Error('Failed to create keychain');
265
+ return 'created';
266
+ }
267
+ // Unlock the keychain with the given password (fed via stdin). Throws on a
268
+ // wrong password (e.g. a different YubiKey or re-programmed slot).
269
+ export function unlockDedicatedKeychain(keychainPath, password) {
270
+ const script = `
271
+ import Foundation
272
+ import Security
273
+
274
+ let data = FileHandle.standardInput.readDataToEndOfFile()
275
+ guard let password = String(data: data, encoding: .utf8) else { exit(1) }
276
+ let kcPath = CommandLine.arguments[1]
277
+ var kc: SecKeychain?
278
+ if SecKeychainOpen(kcPath, &kc) != errSecSuccess || kc == nil { exit(3) }
279
+ let pwBytes = Array(password.utf8)
280
+ let status = SecKeychainUnlock(kc!, UInt32(pwBytes.count), pwBytes, true)
281
+ if status != errSecSuccess { exit(2) }
282
+ `;
283
+ const code = runSwift(script, [keychainPath], password);
284
+ if (code === 3)
285
+ throw new Error(`Keychain not found at ${keychainPath} — run \`noxctl keychain init\``);
286
+ if (code !== 0) {
287
+ throw new Error('Unlock failed — wrong response. Is this the YubiKey you set up, with slot 2 intact?');
288
+ }
289
+ }
290
+ export function keychainLockState(keychainPath) {
291
+ const script = `
292
+ import Foundation
293
+ import Security
294
+
295
+ let kcPath = CommandLine.arguments[1]
296
+ var kc: SecKeychain?
297
+ if SecKeychainOpen(kcPath, &kc) != errSecSuccess || kc == nil { print("missing"); exit(0) }
298
+ var st: SecKeychainStatus = 0
299
+ if SecKeychainGetStatus(kc!, &st) != errSecSuccess { print("missing"); exit(0) }
300
+ if (st & SecKeychainStatus(kSecUnlockStateStatus)) != 0 { print("unlocked") } else { print("locked") }
301
+ `;
302
+ const scriptPath = path.join(os.tmpdir(), `noxctl-kcstat-${process.pid}.swift`);
303
+ try {
304
+ fsSync.writeFileSync(scriptPath, script, { mode: 0o600 });
305
+ const r = spawnSync('swift', [scriptPath, keychainPath], { encoding: 'utf-8' });
306
+ const out = (r.stdout || '').trim();
307
+ if (out === 'unlocked' || out === 'locked' || out === 'missing')
308
+ return out;
309
+ return 'missing';
310
+ }
311
+ finally {
312
+ try {
313
+ fsSync.unlinkSync(scriptPath);
314
+ }
315
+ catch {
316
+ // ignore cleanup failure
317
+ }
318
+ }
319
+ }
320
+ // Lock when the system sleeps, no idle timeout. This is the per-session secret:
321
+ // one tap on wake, open until next sleep.
322
+ export function setLockOnSleep(keychainPath) {
323
+ const r = spawnSync('security', ['set-keychain-settings', '-l', keychainPath], {
324
+ encoding: 'utf-8',
325
+ });
326
+ if (r.error)
327
+ throw r.error;
328
+ if (r.status !== 0) {
329
+ throw new Error(`security set-keychain-settings failed: ${(r.stderr || '').trim() || `exit ${r.status}`}`);
330
+ }
331
+ }
332
+ export function lockKeychain(keychainPath) {
333
+ const r = spawnSync('security', ['lock-keychain', keychainPath], { encoding: 'utf-8' });
334
+ if (r.error)
335
+ throw r.error;
336
+ if (r.status !== 0) {
337
+ throw new Error(`security lock-keychain failed: ${(r.stderr || '').trim() || `exit ${r.status}`}`);
338
+ }
339
+ }
340
+ // Delete a generic-password from the LOGIN keychain specifically. The login
341
+ // keychain path is passed explicitly so this never touches the dedicated
342
+ // keychain even if SecKeychainCreate added it to the session search list.
343
+ // Used by `noxctl keychain seal` to remove the pre-migration login copies.
344
+ // Returns true if an item was deleted (exit 0), false if none matched.
345
+ export function deleteLoginSecret(account) {
346
+ const r = spawnSync('security', ['delete-generic-password', '-a', account, '-s', SERVICE_NAME, loginKeychainPath()], { encoding: 'utf-8' });
347
+ return r.status === 0;
348
+ }
349
+ //# sourceMappingURL=keychain-target.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"keychain-target.js","sourceRoot":"","sources":["../src/keychain-target.ts"],"names":[],"mappings":"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"}
@@ -0,0 +1,21 @@
1
+ interface AbsenceTransactionsResponse {
2
+ AbsenceTransactions: Record<string, unknown>[];
3
+ MetaInformation?: {
4
+ '@TotalResources': number;
5
+ '@TotalPages': number;
6
+ '@CurrentPage': number;
7
+ };
8
+ }
9
+ export interface ListAbsenceTransactionsParams {
10
+ employeeId?: string;
11
+ date?: string;
12
+ page?: number;
13
+ limit?: number;
14
+ all?: boolean;
15
+ }
16
+ export declare function listAbsenceTransactions(params?: ListAbsenceTransactionsParams): Promise<AbsenceTransactionsResponse>;
17
+ export declare function getAbsenceTransaction(id: string): Promise<Record<string, unknown>>;
18
+ export declare function createAbsenceTransaction(params: Record<string, unknown>): Promise<Record<string, unknown>>;
19
+ export declare function deleteAbsenceTransaction(id: string): Promise<void>;
20
+ export {};
21
+ //# sourceMappingURL=absencetransactions.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"absencetransactions.d.ts","sourceRoot":"","sources":["../../src/operations/absencetransactions.ts"],"names":[],"mappings":"AAMA,UAAU,2BAA2B;IACnC,mBAAmB,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,EAAE,CAAC;IAC/C,eAAe,CAAC,EAAE;QAAE,iBAAiB,EAAE,MAAM,CAAC;QAAC,aAAa,EAAE,MAAM,CAAC;QAAC,cAAc,EAAE,MAAM,CAAA;KAAE,CAAC;CAChG;AAED,MAAM,WAAW,6BAA6B;IAC5C,UAAU,CAAC,EAAE,MAAM,CAAC;IACpB,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,IAAI,CAAC,EAAE,MAAM,CAAC;IACd,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,OAAO,CAAC;CACf;AAED,wBAAsB,uBAAuB,CAC3C,MAAM,GAAE,6BAAkC,GACzC,OAAO,CAAC,2BAA2B,CAAC,CAsBtC;AAED,wBAAsB,qBAAqB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAKxF;AAED,wBAAsB,wBAAwB,CAC5C,MAAM,EAAE,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,GAC9B,OAAO,CAAC,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC,CAMlC;AAED,wBAAsB,wBAAwB,CAAC,EAAE,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAIxE"}
@@ -0,0 +1,36 @@
1
+ import { fortnoxRequest, fetchAllPages } from '../fortnox-client.js';
2
+ export async function listAbsenceTransactions(params = {}) {
3
+ // Note the lowercase query keys: Fortnox expects "employeeid" and "date".
4
+ if (params.all) {
5
+ const { items, totalResources } = await fetchAllPages('absencetransactions', 'AbsenceTransactions', { employeeid: params.employeeId, date: params.date });
6
+ return {
7
+ AbsenceTransactions: items,
8
+ MetaInformation: { '@TotalResources': totalResources, '@TotalPages': 1, '@CurrentPage': 1 },
9
+ };
10
+ }
11
+ return fortnoxRequest('absencetransactions', {
12
+ params: {
13
+ employeeid: params.employeeId,
14
+ date: params.date,
15
+ page: params.page || 1,
16
+ limit: params.limit || 100,
17
+ },
18
+ });
19
+ }
20
+ export async function getAbsenceTransaction(id) {
21
+ const data = await fortnoxRequest(`absencetransactions/${encodeURIComponent(id)}`);
22
+ return data.AbsenceTransaction;
23
+ }
24
+ export async function createAbsenceTransaction(params) {
25
+ const data = await fortnoxRequest('absencetransactions', {
26
+ method: 'POST',
27
+ body: { AbsenceTransaction: params },
28
+ });
29
+ return data.AbsenceTransaction;
30
+ }
31
+ export async function deleteAbsenceTransaction(id) {
32
+ await fortnoxRequest(`absencetransactions/${encodeURIComponent(id)}`, {
33
+ method: 'DELETE',
34
+ });
35
+ }
36
+ //# sourceMappingURL=absencetransactions.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"absencetransactions.js","sourceRoot":"","sources":["../../src/operations/absencetransactions.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,cAAc,EAAE,aAAa,EAAE,MAAM,sBAAsB,CAAC;AAmBrE,MAAM,CAAC,KAAK,UAAU,uBAAuB,CAC3C,SAAwC,EAAE;IAE1C,0EAA0E;IAC1E,IAAI,MAAM,CAAC,GAAG,EAAE,CAAC;QACf,MAAM,EAAE,KAAK,EAAE,cAAc,EAAE,GAAG,MAAM,aAAa,CACnD,qBAAqB,EACrB,qBAAqB,EACrB,EAAE,UAAU,EAAE,MAAM,CAAC,UAAU,EAAE,IAAI,EAAE,MAAM,CAAC,IAAI,EAAE,CACrD,CAAC;QACF,OAAO;YACL,mBAAmB,EAAE,KAAK;YAC1B,eAAe,EAAE,EAAE,iBAAiB,EAAE,cAAc,EAAE,aAAa,EAAE,CAAC,EAAE,cAAc,EAAE,CAAC,EAAE;SAC5F,CAAC;IACJ,CAAC;IAED,OAAO,cAAc,CAA8B,qBAAqB,EAAE;QACxE,MAAM,EAAE;YACN,UAAU,EAAE,MAAM,CAAC,UAAU;YAC7B,IAAI,EAAE,MAAM,CAAC,IAAI;YACjB,IAAI,EAAE,MAAM,CAAC,IAAI,IAAI,CAAC;YACtB,KAAK,EAAE,MAAM,CAAC,KAAK,IAAI,GAAG;SAC3B;KACF,CAAC,CAAC;AACL,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,qBAAqB,CAAC,EAAU;IACpD,MAAM,IAAI,GAAG,MAAM,cAAc,CAC/B,uBAAuB,kBAAkB,CAAC,EAAE,CAAC,EAAE,CAChD,CAAC;IACF,OAAO,IAAI,CAAC,kBAAkB,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAC5C,MAA+B;IAE/B,MAAM,IAAI,GAAG,MAAM,cAAc,CAA6B,qBAAqB,EAAE;QACnF,MAAM,EAAE,MAAM;QACd,IAAI,EAAE,EAAE,kBAAkB,EAAE,MAAM,EAAE;KACrC,CAAC,CAAC;IACH,OAAO,IAAI,CAAC,kBAAkB,CAAC;AACjC,CAAC;AAED,MAAM,CAAC,KAAK,UAAU,wBAAwB,CAAC,EAAU;IACvD,MAAM,cAAc,CAAC,uBAAuB,kBAAkB,CAAC,EAAE,CAAC,EAAE,EAAE;QACpE,MAAM,EAAE,QAAQ;KACjB,CAAC,CAAC;AACL,CAAC"}
@@ -0,0 +1,84 @@
1
+ type InvoiceRow = Record<string, unknown>;
2
+ export interface OverdueSummary {
3
+ count: number;
4
+ totalBalance: number;
5
+ oldestDueDate: string | null;
6
+ invoices: InvoiceRow[];
7
+ }
8
+ export declare function summarizeOverdue(invoices: InvoiceRow[], today: string): OverdueSummary;
9
+ export interface UnpaidSummary {
10
+ count: number;
11
+ totalBalance: number;
12
+ overdueCount: number;
13
+ overdueBalance: number;
14
+ }
15
+ export declare function summarizeUnpaid(invoices: InvoiceRow[], today: string): UnpaidSummary;
16
+ export interface CustomerRevenue {
17
+ CustomerNumber: string;
18
+ CustomerName: string;
19
+ total: number;
20
+ invoiceCount: number;
21
+ }
22
+ export declare function topCustomersFrom(invoices: InvoiceRow[], limit: number): CustomerRevenue[];
23
+ export interface MonthlyRevenue {
24
+ month: string;
25
+ total: number;
26
+ invoiceCount: number;
27
+ }
28
+ export declare function monthlyRevenueFrom(invoices: InvoiceRow[]): MonthlyRevenue[];
29
+ export declare function localIsoDate(d: Date): string;
30
+ export declare function dashboardWindowStart(today: Date, months: number): string;
31
+ export declare function getOverdueSummary(): Promise<OverdueSummary>;
32
+ export declare function getUnpaidTotals(): Promise<UnpaidSummary>;
33
+ export interface TopCustomersParams {
34
+ fromDate?: string;
35
+ toDate?: string;
36
+ limit?: number;
37
+ }
38
+ export declare function getTopCustomers(params?: TopCustomersParams): Promise<{
39
+ period: {
40
+ from?: string;
41
+ to?: string;
42
+ };
43
+ customers: CustomerRevenue[];
44
+ }>;
45
+ export interface VatSummaryParams {
46
+ fromDate: string;
47
+ toDate: string;
48
+ financialYear?: number;
49
+ }
50
+ export declare function netVatFromVatAccounts(vatAccounts: Record<number, {
51
+ debit?: number;
52
+ credit?: number;
53
+ }> | undefined): number;
54
+ export declare function getVatSummary(params: VatSummaryParams): Promise<{
55
+ netVat: number;
56
+ period: {
57
+ from: string;
58
+ to: string;
59
+ };
60
+ vatAccounts: Record<number, {
61
+ debit: number;
62
+ credit: number;
63
+ description: string;
64
+ }>;
65
+ accountBalances: {
66
+ account: number;
67
+ description: string;
68
+ balance: number;
69
+ }[];
70
+ summary: {
71
+ note: string;
72
+ };
73
+ }>;
74
+ export interface Dashboard {
75
+ unpaid: UnpaidSummary;
76
+ overdue: OverdueSummary;
77
+ recentInvoices: InvoiceRow[];
78
+ monthlyRevenue: MonthlyRevenue[];
79
+ }
80
+ export declare function getDashboard(options?: {
81
+ months?: number;
82
+ }): Promise<Dashboard>;
83
+ export {};
84
+ //# sourceMappingURL=analytics.d.ts.map
@@ -0,0 +1 @@
1
+ {"version":3,"file":"analytics.d.ts","sourceRoot":"","sources":["../../src/operations/analytics.ts"],"names":[],"mappings":"AAUA,KAAK,UAAU,GAAG,MAAM,CAAC,MAAM,EAAE,OAAO,CAAC,CAAC;AAmB1C,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,aAAa,EAAE,MAAM,GAAG,IAAI,CAAC;IAC7B,QAAQ,EAAE,UAAU,EAAE,CAAC;CACxB;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,cAAc,CAUtF;AAED,MAAM,WAAW,aAAa;IAC5B,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;IACrB,YAAY,EAAE,MAAM,CAAC;IACrB,cAAc,EAAE,MAAM,CAAC;CACxB;AAED,wBAAgB,eAAe,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,aAAa,CASpF;AAED,MAAM,WAAW,eAAe;IAC9B,cAAc,EAAE,MAAM,CAAC;IACvB,YAAY,EAAE,MAAM,CAAC;IACrB,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,gBAAgB,CAAC,QAAQ,EAAE,UAAU,EAAE,EAAE,KAAK,EAAE,MAAM,GAAG,eAAe,EAAE,CAgBzF;AAED,MAAM,WAAW,cAAc;IAC7B,KAAK,EAAE,MAAM,CAAC;IACd,KAAK,EAAE,MAAM,CAAC;IACd,YAAY,EAAE,MAAM,CAAC;CACtB;AAED,wBAAgB,kBAAkB,CAAC,QAAQ,EAAE,UAAU,EAAE,GAAG,cAAc,EAAE,CAY3E;AAMD,wBAAgB,YAAY,CAAC,CAAC,EAAE,IAAI,GAAG,MAAM,CAK5C;AAYD,wBAAgB,oBAAoB,CAAC,KAAK,EAAE,IAAI,EAAE,MAAM,EAAE,MAAM,GAAG,MAAM,CAExE;AAED,wBAAsB,iBAAiB,IAAI,OAAO,CAAC,cAAc,CAAC,CAGjE;AAED,wBAAsB,eAAe,IAAI,OAAO,CAAC,aAAa,CAAC,CAG9D;AAED,MAAM,WAAW,kBAAkB;IACjC,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,MAAM,CAAC,EAAE,MAAM,CAAC;IAChB,KAAK,CAAC,EAAE,MAAM,CAAC;CAChB;AAED,wBAAsB,eAAe,CAAC,MAAM,GAAE,kBAAuB,GAAG,OAAO,CAAC;IAC9E,MAAM,EAAE;QAAE,IAAI,CAAC,EAAE,MAAM,CAAC;QAAC,EAAE,CAAC,EAAE,MAAM,CAAA;KAAE,CAAC;IACvC,SAAS,EAAE,eAAe,EAAE,CAAC;CAC9B,CAAC,CAMD;AAED,MAAM,WAAW,gBAAgB;IAC/B,QAAQ,EAAE,MAAM,CAAC;IACjB,MAAM,EAAE,MAAM,CAAC;IACf,aAAa,CAAC,EAAE,MAAM,CAAC;CACxB;AAID,wBAAgB,qBAAqB,CACnC,WAAW,EAAE,MAAM,CAAC,MAAM,EAAE;IAAE,KAAK,CAAC,EAAE,MAAM,CAAC;IAAC,MAAM,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,GAAG,SAAS,GAC3E,MAAM,CAMR;AAID,wBAAsB,aAAa,CAAC,MAAM,EAAE,gBAAgB;;;;;;;;;;;;;;;;;;;GAG3D;AAED,MAAM,WAAW,SAAS;IACxB,MAAM,EAAE,aAAa,CAAC;IACtB,OAAO,EAAE,cAAc,CAAC;IACxB,cAAc,EAAE,UAAU,EAAE,CAAC;IAC7B,cAAc,EAAE,cAAc,EAAE,CAAC;CAClC;AAID,wBAAsB,YAAY,CAAC,OAAO,GAAE;IAAE,MAAM,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,SAAS,CAAC,CA2BxF"}