@milldr/crono 0.1.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/LICENSE +21 -0
- package/README.md +219 -0
- package/dist/commands/diary.d.ts +7 -0
- package/dist/commands/diary.d.ts.map +1 -0
- package/dist/commands/diary.js +79 -0
- package/dist/commands/diary.js.map +1 -0
- package/dist/commands/login.d.ts +19 -0
- package/dist/commands/login.d.ts.map +1 -0
- package/dist/commands/login.js +160 -0
- package/dist/commands/login.js.map +1 -0
- package/dist/commands/quick-add.d.ts +8 -0
- package/dist/commands/quick-add.d.ts.map +1 -0
- package/dist/commands/quick-add.js +49 -0
- package/dist/commands/quick-add.js.map +1 -0
- package/dist/commands/weight.d.ts +7 -0
- package/dist/commands/weight.d.ts.map +1 -0
- package/dist/commands/weight.js +78 -0
- package/dist/commands/weight.js.map +1 -0
- package/dist/config.d.ts +17 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +43 -0
- package/dist/config.js.map +1 -0
- package/dist/credentials.d.ts +27 -0
- package/dist/credentials.d.ts.map +1 -0
- package/dist/credentials.js +178 -0
- package/dist/credentials.js.map +1 -0
- package/dist/debug-nav.d.ts +2 -0
- package/dist/debug-nav.d.ts.map +1 -0
- package/dist/debug-nav.js +99 -0
- package/dist/debug-nav.js.map +1 -0
- package/dist/index.d.ts +3 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +47 -0
- package/dist/index.js.map +1 -0
- package/dist/kernel/client.d.ts +40 -0
- package/dist/kernel/client.d.ts.map +1 -0
- package/dist/kernel/client.js +205 -0
- package/dist/kernel/client.js.map +1 -0
- package/dist/kernel/diary.d.ts +22 -0
- package/dist/kernel/diary.d.ts.map +1 -0
- package/dist/kernel/diary.js +156 -0
- package/dist/kernel/diary.js.map +1 -0
- package/dist/kernel/login.d.ts +24 -0
- package/dist/kernel/login.d.ts.map +1 -0
- package/dist/kernel/login.js +125 -0
- package/dist/kernel/login.js.map +1 -0
- package/dist/kernel/quick-add.d.ts +22 -0
- package/dist/kernel/quick-add.d.ts.map +1 -0
- package/dist/kernel/quick-add.js +260 -0
- package/dist/kernel/quick-add.js.map +1 -0
- package/dist/kernel/weight.d.ts +20 -0
- package/dist/kernel/weight.d.ts.map +1 -0
- package/dist/kernel/weight.js +160 -0
- package/dist/kernel/weight.js.map +1 -0
- package/dist/utils/date.d.ts +31 -0
- package/dist/utils/date.d.ts.map +1 -0
- package/dist/utils/date.js +82 -0
- package/dist/utils/date.js.map +1 -0
- package/package.json +54 -0
|
@@ -0,0 +1,78 @@
|
|
|
1
|
+
import * as p from "@clack/prompts";
|
|
2
|
+
import { getKernelClient } from "../kernel/client.js";
|
|
3
|
+
import { parseDate, parseRange, dateRange, todayStr } from "../utils/date.js";
|
|
4
|
+
export async function weight(options) {
|
|
5
|
+
// Validate mutual exclusivity
|
|
6
|
+
if (options.date && options.range) {
|
|
7
|
+
p.log.error("-d and -r are mutually exclusive");
|
|
8
|
+
process.exit(1);
|
|
9
|
+
}
|
|
10
|
+
// Resolve dates to fetch
|
|
11
|
+
let dates;
|
|
12
|
+
let isRange = false;
|
|
13
|
+
try {
|
|
14
|
+
if (options.range) {
|
|
15
|
+
isRange = true;
|
|
16
|
+
const { start, end } = parseRange(options.range);
|
|
17
|
+
dates = dateRange(start, end);
|
|
18
|
+
}
|
|
19
|
+
else if (options.date) {
|
|
20
|
+
dates = [parseDate(options.date)];
|
|
21
|
+
}
|
|
22
|
+
else {
|
|
23
|
+
dates = [todayStr()];
|
|
24
|
+
}
|
|
25
|
+
}
|
|
26
|
+
catch (error) {
|
|
27
|
+
p.log.error(String(error instanceof Error ? error.message : error));
|
|
28
|
+
process.exit(1);
|
|
29
|
+
}
|
|
30
|
+
if (!options.json) {
|
|
31
|
+
p.intro("🍎 crono weight");
|
|
32
|
+
}
|
|
33
|
+
const s = options.json ? null : p.spinner();
|
|
34
|
+
s?.start("Connecting...");
|
|
35
|
+
try {
|
|
36
|
+
const kernel = await getKernelClient();
|
|
37
|
+
const entries = await kernel.getWeight(dates, (msg) => s?.message(msg));
|
|
38
|
+
s?.stop("Done.");
|
|
39
|
+
if (options.json) {
|
|
40
|
+
if (isRange) {
|
|
41
|
+
console.log(JSON.stringify(entries, null, 2));
|
|
42
|
+
}
|
|
43
|
+
else {
|
|
44
|
+
console.log(JSON.stringify(entries[0] ?? { date: dates[0], weight: null, unit: "lbs" }, null, 2));
|
|
45
|
+
}
|
|
46
|
+
}
|
|
47
|
+
else {
|
|
48
|
+
formatPlainText(entries, isRange);
|
|
49
|
+
}
|
|
50
|
+
}
|
|
51
|
+
catch (error) {
|
|
52
|
+
s?.stop("Failed.");
|
|
53
|
+
p.log.error(`Failed to read weight: ${error}`);
|
|
54
|
+
process.exit(1);
|
|
55
|
+
}
|
|
56
|
+
}
|
|
57
|
+
function formatPlainText(entries, isRange) {
|
|
58
|
+
if (!isRange) {
|
|
59
|
+
const entry = entries[0];
|
|
60
|
+
if (!entry || entry.weight === null) {
|
|
61
|
+
p.outro(`No weight recorded for ${entry?.date ?? "today"}`);
|
|
62
|
+
}
|
|
63
|
+
else {
|
|
64
|
+
p.outro(`${entry.weight} ${entry.unit}`);
|
|
65
|
+
}
|
|
66
|
+
return;
|
|
67
|
+
}
|
|
68
|
+
// Range output
|
|
69
|
+
for (const entry of entries) {
|
|
70
|
+
if (entry.weight === null) {
|
|
71
|
+
p.log.info(`${entry.date}: \u2014`);
|
|
72
|
+
}
|
|
73
|
+
else {
|
|
74
|
+
p.log.info(`${entry.date}: ${entry.weight}`);
|
|
75
|
+
}
|
|
76
|
+
}
|
|
77
|
+
}
|
|
78
|
+
//# sourceMappingURL=weight.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"weight.js","sourceRoot":"","sources":["../../src/commands/weight.ts"],"names":[],"mappings":"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"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1,17 @@
|
|
|
1
|
+
export interface CronoConfig {
|
|
2
|
+
kernelProfile?: string;
|
|
3
|
+
defaultMeal?: string;
|
|
4
|
+
}
|
|
5
|
+
/**
|
|
6
|
+
* Load configuration from disk.
|
|
7
|
+
*/
|
|
8
|
+
export declare function loadConfig(): CronoConfig;
|
|
9
|
+
/**
|
|
10
|
+
* Save configuration to disk.
|
|
11
|
+
*/
|
|
12
|
+
export declare function saveConfig(config: CronoConfig): void;
|
|
13
|
+
/**
|
|
14
|
+
* Get the Kernel session directory.
|
|
15
|
+
*/
|
|
16
|
+
export declare function getSessionDir(): string;
|
|
17
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAIA,MAAM,WAAW,WAAW;IAC1B,aAAa,CAAC,EAAE,MAAM,CAAC;IACvB,WAAW,CAAC,EAAE,MAAM,CAAC;CACtB;AAcD;;GAEG;AACH,wBAAgB,UAAU,IAAI,WAAW,CAaxC;AAED;;GAEG;AACH,wBAAgB,UAAU,CAAC,MAAM,EAAE,WAAW,GAAG,IAAI,CAGpD;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,MAAM,CAEtC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,43 @@
|
|
|
1
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
2
|
+
import { homedir } from "os";
|
|
3
|
+
import { join } from "path";
|
|
4
|
+
const CONFIG_DIR = join(homedir(), ".config", "crono");
|
|
5
|
+
const CONFIG_FILE = join(CONFIG_DIR, "config.json");
|
|
6
|
+
/**
|
|
7
|
+
* Ensure config directory exists.
|
|
8
|
+
*/
|
|
9
|
+
function ensureConfigDir() {
|
|
10
|
+
if (!existsSync(CONFIG_DIR)) {
|
|
11
|
+
mkdirSync(CONFIG_DIR, { recursive: true });
|
|
12
|
+
}
|
|
13
|
+
}
|
|
14
|
+
/**
|
|
15
|
+
* Load configuration from disk.
|
|
16
|
+
*/
|
|
17
|
+
export function loadConfig() {
|
|
18
|
+
ensureConfigDir();
|
|
19
|
+
if (!existsSync(CONFIG_FILE)) {
|
|
20
|
+
return {};
|
|
21
|
+
}
|
|
22
|
+
try {
|
|
23
|
+
const content = readFileSync(CONFIG_FILE, "utf-8");
|
|
24
|
+
return JSON.parse(content);
|
|
25
|
+
}
|
|
26
|
+
catch {
|
|
27
|
+
return {};
|
|
28
|
+
}
|
|
29
|
+
}
|
|
30
|
+
/**
|
|
31
|
+
* Save configuration to disk.
|
|
32
|
+
*/
|
|
33
|
+
export function saveConfig(config) {
|
|
34
|
+
ensureConfigDir();
|
|
35
|
+
writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Get the Kernel session directory.
|
|
39
|
+
*/
|
|
40
|
+
export function getSessionDir() {
|
|
41
|
+
return join(CONFIG_DIR, "sessions");
|
|
42
|
+
}
|
|
43
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,IAAI,CAAC;AACxE,OAAO,EAAE,OAAO,EAAE,MAAM,IAAI,CAAC;AAC7B,OAAO,EAAE,IAAI,EAAE,MAAM,MAAM,CAAC;AAO5B,MAAM,UAAU,GAAG,IAAI,CAAC,OAAO,EAAE,EAAE,SAAS,EAAE,OAAO,CAAC,CAAC;AACvD,MAAM,WAAW,GAAG,IAAI,CAAC,UAAU,EAAE,aAAa,CAAC,CAAC;AAEpD;;GAEG;AACH,SAAS,eAAe;IACtB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,SAAS,CAAC,UAAU,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,CAAC,CAAC;IAC7C,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU;IACxB,eAAe,EAAE,CAAC;IAElB,IAAI,CAAC,UAAU,CAAC,WAAW,CAAC,EAAE,CAAC;QAC7B,OAAO,EAAE,CAAC;IACZ,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,WAAW,EAAE,OAAO,CAAC,CAAC;QACnD,OAAO,IAAI,CAAC,KAAK,CAAC,OAAO,CAAgB,CAAC;IAC5C,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,EAAE,CAAC;IACZ,CAAC;AACH,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,UAAU,CAAC,MAAmB;IAC5C,eAAe,EAAE,CAAC;IAClB,aAAa,CAAC,WAAW,EAAE,IAAI,CAAC,SAAS,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;AAC9D,CAAC;AAED;;GAEG;AACH,MAAM,UAAU,aAAa;IAC3B,OAAO,IAAI,CAAC,UAAU,EAAE,UAAU,CAAC,CAAC;AACtC,CAAC"}
|
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage abstraction with keychain + encrypted file fallback.
|
|
3
|
+
*
|
|
4
|
+
* Primary backend: @napi-rs/keyring (OS keychain).
|
|
5
|
+
* Fallback: AES-256-GCM encrypted file at ~/.config/crono/credentials.enc
|
|
6
|
+
* for headless/CI environments without a system keychain.
|
|
7
|
+
*/
|
|
8
|
+
export type CredentialKey = "kernel-api-key" | "cronometer-username" | "cronometer-password";
|
|
9
|
+
type Backend = "keyring" | "file";
|
|
10
|
+
export declare function getCredential(key: CredentialKey): string | null;
|
|
11
|
+
export declare function setCredential(key: CredentialKey, value: string): void;
|
|
12
|
+
export declare function deleteCredential(key: CredentialKey): void;
|
|
13
|
+
export declare function hasCredentials(): boolean;
|
|
14
|
+
/**
|
|
15
|
+
* Reset the cached backend (for testing).
|
|
16
|
+
*/
|
|
17
|
+
export declare function _resetBackend(): void;
|
|
18
|
+
/**
|
|
19
|
+
* Force a specific backend (for testing).
|
|
20
|
+
*/
|
|
21
|
+
export declare function _setBackend(backend: Backend): void;
|
|
22
|
+
/**
|
|
23
|
+
* Override the config directory (for testing).
|
|
24
|
+
*/
|
|
25
|
+
export declare function _setConfigDir(dir: string | null): void;
|
|
26
|
+
export {};
|
|
27
|
+
//# sourceMappingURL=credentials.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.d.ts","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAYH,MAAM,MAAM,aAAa,GACrB,gBAAgB,GAChB,qBAAqB,GACrB,qBAAqB,CAAC;AAmB1B,KAAK,OAAO,GAAG,SAAS,GAAG,MAAM,CAAC;AAuIlC,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,GAAG,MAAM,GAAG,IAAI,CAE/D;AAED,wBAAgB,aAAa,CAAC,GAAG,EAAE,aAAa,EAAE,KAAK,EAAE,MAAM,GAAG,IAAI,CAMrE;AAED,wBAAgB,gBAAgB,CAAC,GAAG,EAAE,aAAa,GAAG,IAAI,CAMzD;AAED,wBAAgB,cAAc,IAAI,OAAO,CAExC;AAED;;GAEG;AACH,wBAAgB,aAAa,IAAI,IAAI,CAEpC;AAED;;GAEG;AACH,wBAAgB,WAAW,CAAC,OAAO,EAAE,OAAO,GAAG,IAAI,CAElD;AAED;;GAEG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,MAAM,GAAG,IAAI,GAAG,IAAI,CAEtD"}
|
|
@@ -0,0 +1,178 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Credential storage abstraction with keychain + encrypted file fallback.
|
|
3
|
+
*
|
|
4
|
+
* Primary backend: @napi-rs/keyring (OS keychain).
|
|
5
|
+
* Fallback: AES-256-GCM encrypted file at ~/.config/crono/credentials.enc
|
|
6
|
+
* for headless/CI environments without a system keychain.
|
|
7
|
+
*/
|
|
8
|
+
import { createCipheriv, createDecipheriv, randomBytes, scryptSync, } from "crypto";
|
|
9
|
+
import { existsSync, mkdirSync, readFileSync, writeFileSync } from "fs";
|
|
10
|
+
import { homedir, hostname, userInfo } from "os";
|
|
11
|
+
import { join } from "path";
|
|
12
|
+
const SERVICE_NAME = "crono";
|
|
13
|
+
let configDirOverride = null;
|
|
14
|
+
function getConfigDir() {
|
|
15
|
+
return configDirOverride ?? join(homedir(), ".config", "crono");
|
|
16
|
+
}
|
|
17
|
+
function getCredFile() {
|
|
18
|
+
return join(getConfigDir(), "credentials.enc");
|
|
19
|
+
}
|
|
20
|
+
// Salt length, IV length, auth tag length for AES-256-GCM
|
|
21
|
+
const SALT_LEN = 16;
|
|
22
|
+
const IV_LEN = 12;
|
|
23
|
+
const TAG_LEN = 16;
|
|
24
|
+
let resolvedBackend = null;
|
|
25
|
+
/**
|
|
26
|
+
* Resolve which backend to use. Try keyring first; fall back to encrypted file.
|
|
27
|
+
* Cached per process.
|
|
28
|
+
*/
|
|
29
|
+
function getBackend() {
|
|
30
|
+
if (resolvedBackend)
|
|
31
|
+
return resolvedBackend;
|
|
32
|
+
try {
|
|
33
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
34
|
+
const { Entry } = require("@napi-rs/keyring");
|
|
35
|
+
// Probe the keychain with a test write/read/delete cycle
|
|
36
|
+
const probe = new Entry(SERVICE_NAME, "__probe__");
|
|
37
|
+
probe.setPassword("test");
|
|
38
|
+
probe.deletePassword();
|
|
39
|
+
resolvedBackend = "keyring";
|
|
40
|
+
}
|
|
41
|
+
catch {
|
|
42
|
+
resolvedBackend = "file";
|
|
43
|
+
}
|
|
44
|
+
return resolvedBackend;
|
|
45
|
+
}
|
|
46
|
+
// -- Keyring backend --
|
|
47
|
+
function keyringGet(key) {
|
|
48
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
49
|
+
const { Entry } = require("@napi-rs/keyring");
|
|
50
|
+
try {
|
|
51
|
+
const entry = new Entry(SERVICE_NAME, key);
|
|
52
|
+
return entry.getPassword();
|
|
53
|
+
}
|
|
54
|
+
catch {
|
|
55
|
+
return null;
|
|
56
|
+
}
|
|
57
|
+
}
|
|
58
|
+
function keyringSet(key, value) {
|
|
59
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
60
|
+
const { Entry } = require("@napi-rs/keyring");
|
|
61
|
+
const entry = new Entry(SERVICE_NAME, key);
|
|
62
|
+
entry.setPassword(value);
|
|
63
|
+
}
|
|
64
|
+
function keyringDelete(key) {
|
|
65
|
+
// eslint-disable-next-line @typescript-eslint/no-require-imports
|
|
66
|
+
const { Entry } = require("@napi-rs/keyring");
|
|
67
|
+
try {
|
|
68
|
+
const entry = new Entry(SERVICE_NAME, key);
|
|
69
|
+
entry.deletePassword();
|
|
70
|
+
}
|
|
71
|
+
catch {
|
|
72
|
+
// Ignore if not found
|
|
73
|
+
}
|
|
74
|
+
}
|
|
75
|
+
// -- Encrypted file backend --
|
|
76
|
+
function deriveKey(salt) {
|
|
77
|
+
const material = `${hostname()}${homedir()}${userInfo().username}`;
|
|
78
|
+
return scryptSync(material, salt, 32);
|
|
79
|
+
}
|
|
80
|
+
function ensureConfigDir() {
|
|
81
|
+
const dir = getConfigDir();
|
|
82
|
+
if (!existsSync(dir)) {
|
|
83
|
+
mkdirSync(dir, { recursive: true });
|
|
84
|
+
}
|
|
85
|
+
}
|
|
86
|
+
function readEncryptedStore() {
|
|
87
|
+
const CRED_FILE = getCredFile();
|
|
88
|
+
if (!existsSync(CRED_FILE))
|
|
89
|
+
return {};
|
|
90
|
+
try {
|
|
91
|
+
const buf = readFileSync(CRED_FILE);
|
|
92
|
+
if (buf.length < SALT_LEN + IV_LEN + TAG_LEN + 1)
|
|
93
|
+
return {};
|
|
94
|
+
const salt = buf.subarray(0, SALT_LEN);
|
|
95
|
+
const iv = buf.subarray(SALT_LEN, SALT_LEN + IV_LEN);
|
|
96
|
+
const tag = buf.subarray(buf.length - TAG_LEN);
|
|
97
|
+
const ciphertext = buf.subarray(SALT_LEN + IV_LEN, buf.length - TAG_LEN);
|
|
98
|
+
const key = deriveKey(salt);
|
|
99
|
+
const decipher = createDecipheriv("aes-256-gcm", key, iv);
|
|
100
|
+
decipher.setAuthTag(tag);
|
|
101
|
+
const plaintext = Buffer.concat([
|
|
102
|
+
decipher.update(ciphertext),
|
|
103
|
+
decipher.final(),
|
|
104
|
+
]);
|
|
105
|
+
return JSON.parse(plaintext.toString("utf-8"));
|
|
106
|
+
}
|
|
107
|
+
catch {
|
|
108
|
+
return {};
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
function writeEncryptedStore(data) {
|
|
112
|
+
ensureConfigDir();
|
|
113
|
+
const salt = randomBytes(SALT_LEN);
|
|
114
|
+
const iv = randomBytes(IV_LEN);
|
|
115
|
+
const key = deriveKey(salt);
|
|
116
|
+
const cipher = createCipheriv("aes-256-gcm", key, iv);
|
|
117
|
+
const plaintext = Buffer.from(JSON.stringify(data), "utf-8");
|
|
118
|
+
const ciphertext = Buffer.concat([cipher.update(plaintext), cipher.final()]);
|
|
119
|
+
const tag = cipher.getAuthTag();
|
|
120
|
+
const output = Buffer.concat([salt, iv, ciphertext, tag]);
|
|
121
|
+
writeFileSync(getCredFile(), output, { mode: 0o600 });
|
|
122
|
+
}
|
|
123
|
+
function fileGet(key) {
|
|
124
|
+
const store = readEncryptedStore();
|
|
125
|
+
return store[key] ?? null;
|
|
126
|
+
}
|
|
127
|
+
function fileSet(key, value) {
|
|
128
|
+
const store = readEncryptedStore();
|
|
129
|
+
store[key] = value;
|
|
130
|
+
writeEncryptedStore(store);
|
|
131
|
+
}
|
|
132
|
+
function fileDelete(key) {
|
|
133
|
+
const store = readEncryptedStore();
|
|
134
|
+
delete store[key];
|
|
135
|
+
writeEncryptedStore(store);
|
|
136
|
+
}
|
|
137
|
+
// -- Public API --
|
|
138
|
+
export function getCredential(key) {
|
|
139
|
+
return getBackend() === "keyring" ? keyringGet(key) : fileGet(key);
|
|
140
|
+
}
|
|
141
|
+
export function setCredential(key, value) {
|
|
142
|
+
if (getBackend() === "keyring") {
|
|
143
|
+
keyringSet(key, value);
|
|
144
|
+
}
|
|
145
|
+
else {
|
|
146
|
+
fileSet(key, value);
|
|
147
|
+
}
|
|
148
|
+
}
|
|
149
|
+
export function deleteCredential(key) {
|
|
150
|
+
if (getBackend() === "keyring") {
|
|
151
|
+
keyringDelete(key);
|
|
152
|
+
}
|
|
153
|
+
else {
|
|
154
|
+
fileDelete(key);
|
|
155
|
+
}
|
|
156
|
+
}
|
|
157
|
+
export function hasCredentials() {
|
|
158
|
+
return getCredential("kernel-api-key") !== null;
|
|
159
|
+
}
|
|
160
|
+
/**
|
|
161
|
+
* Reset the cached backend (for testing).
|
|
162
|
+
*/
|
|
163
|
+
export function _resetBackend() {
|
|
164
|
+
resolvedBackend = null;
|
|
165
|
+
}
|
|
166
|
+
/**
|
|
167
|
+
* Force a specific backend (for testing).
|
|
168
|
+
*/
|
|
169
|
+
export function _setBackend(backend) {
|
|
170
|
+
resolvedBackend = backend;
|
|
171
|
+
}
|
|
172
|
+
/**
|
|
173
|
+
* Override the config directory (for testing).
|
|
174
|
+
*/
|
|
175
|
+
export function _setConfigDir(dir) {
|
|
176
|
+
configDirOverride = dir;
|
|
177
|
+
}
|
|
178
|
+
//# sourceMappingURL=credentials.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"credentials.js","sourceRoot":"","sources":["../src/credentials.ts"],"names":[],"mappings":"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"}
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug-nav.d.ts","sourceRoot":"","sources":["../src/debug-nav.ts"],"names":[],"mappings":""}
|
|
@@ -0,0 +1,99 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Debug script: dump date navigation elements from Cronometer diary.
|
|
3
|
+
* Run with: npx tsx src/debug-nav.ts
|
|
4
|
+
*/
|
|
5
|
+
import Kernel from "@onkernel/sdk";
|
|
6
|
+
import { getCredential } from "./credentials.js";
|
|
7
|
+
import { buildAutoLoginCode } from "./kernel/login.js";
|
|
8
|
+
async function main() {
|
|
9
|
+
const apiKey = process.env["KERNEL_API_KEY"] ?? getCredential("kernel-api-key");
|
|
10
|
+
process.env["KERNEL_API_KEY"] = apiKey;
|
|
11
|
+
const kernel = new Kernel();
|
|
12
|
+
const browser = await kernel.browsers.create({
|
|
13
|
+
headless: true,
|
|
14
|
+
stealth: true,
|
|
15
|
+
timeout_seconds: 120,
|
|
16
|
+
});
|
|
17
|
+
try {
|
|
18
|
+
const username = getCredential("cronometer-username");
|
|
19
|
+
const password = getCredential("cronometer-password");
|
|
20
|
+
// Login
|
|
21
|
+
await kernel.browsers.playwright.execute(browser.session_id, {
|
|
22
|
+
code: buildAutoLoginCode(username, password),
|
|
23
|
+
timeout_sec: 60,
|
|
24
|
+
});
|
|
25
|
+
// Navigate to diary and dump nav info
|
|
26
|
+
const result = await kernel.browsers.playwright.execute(browser.session_id, {
|
|
27
|
+
code: `
|
|
28
|
+
await page.goto('https://cronometer.com/#diary', { waitUntil: 'domcontentloaded', timeout: 15000 });
|
|
29
|
+
await page.waitForLoadState('networkidle', { timeout: 10000 }).catch(() => {});
|
|
30
|
+
await page.waitForTimeout(3000);
|
|
31
|
+
|
|
32
|
+
const debug = await page.evaluate(() => {
|
|
33
|
+
// 1. All images and their src attributes
|
|
34
|
+
const imgs = Array.from(document.querySelectorAll('img')).map(img => ({
|
|
35
|
+
src: img.getAttribute('src'),
|
|
36
|
+
alt: img.getAttribute('alt'),
|
|
37
|
+
visible: img.offsetParent !== null,
|
|
38
|
+
width: img.offsetWidth,
|
|
39
|
+
height: img.offsetHeight,
|
|
40
|
+
classes: img.className,
|
|
41
|
+
}));
|
|
42
|
+
|
|
43
|
+
// 2. All buttons/clickable elements near the top of the page
|
|
44
|
+
const buttons = Array.from(document.querySelectorAll('button, [role="button"], .gwt-PushButton')).map(el => ({
|
|
45
|
+
text: (el.textContent || '').trim().substring(0, 80),
|
|
46
|
+
classes: el.className,
|
|
47
|
+
visible: el.offsetParent !== null,
|
|
48
|
+
tag: el.tagName,
|
|
49
|
+
}));
|
|
50
|
+
|
|
51
|
+
// 3. Elements that might be date-related
|
|
52
|
+
const dateEls = Array.from(document.querySelectorAll('[class*="date" i], [class*="calendar" i], [class*="nav" i], [class*="arrow" i], [class*="picker" i]')).map(el => ({
|
|
53
|
+
tag: el.tagName,
|
|
54
|
+
classes: el.className,
|
|
55
|
+
text: (el.textContent || '').trim().substring(0, 80),
|
|
56
|
+
visible: el.offsetParent !== null,
|
|
57
|
+
html: el.outerHTML.substring(0, 200),
|
|
58
|
+
}));
|
|
59
|
+
|
|
60
|
+
// 4. Look for any element containing a date-like text (month name)
|
|
61
|
+
const dateTexts = [];
|
|
62
|
+
const walker = document.createTreeWalker(document.body, NodeFilter.SHOW_TEXT);
|
|
63
|
+
let node;
|
|
64
|
+
while (node = walker.nextNode()) {
|
|
65
|
+
const t = (node.textContent || '').trim();
|
|
66
|
+
if (/(?:January|February|March|April|May|June|July|August|September|October|November|December)/i.test(t)) {
|
|
67
|
+
dateTexts.push({
|
|
68
|
+
text: t.substring(0, 100),
|
|
69
|
+
parentTag: node.parentElement?.tagName,
|
|
70
|
+
parentClasses: node.parentElement?.className,
|
|
71
|
+
});
|
|
72
|
+
}
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// 5. GWT PushButton elements (Cronometer uses GWT)
|
|
76
|
+
const pushButtons = Array.from(document.querySelectorAll('.gwt-PushButton, .gwt-PushButton-up, .gwt-PushButton-down')).map(el => ({
|
|
77
|
+
classes: el.className,
|
|
78
|
+
html: el.outerHTML.substring(0, 300),
|
|
79
|
+
visible: el.offsetParent !== null,
|
|
80
|
+
}));
|
|
81
|
+
|
|
82
|
+
return { imgs, buttons, dateEls, dateTexts, pushButtons };
|
|
83
|
+
});
|
|
84
|
+
|
|
85
|
+
return debug;
|
|
86
|
+
`,
|
|
87
|
+
timeout_sec: 60,
|
|
88
|
+
});
|
|
89
|
+
console.log(JSON.stringify(result.result, null, 2));
|
|
90
|
+
}
|
|
91
|
+
finally {
|
|
92
|
+
try {
|
|
93
|
+
await kernel.browsers.deleteByID(browser.session_id);
|
|
94
|
+
}
|
|
95
|
+
catch { }
|
|
96
|
+
}
|
|
97
|
+
}
|
|
98
|
+
main().catch(console.error);
|
|
99
|
+
//# sourceMappingURL=debug-nav.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"debug-nav.js","sourceRoot":"","sources":["../src/debug-nav.ts"],"names":[],"mappings":"AAAA;;;GAGG;AACH,OAAO,MAAM,MAAM,eAAe,CAAC;AACnC,OAAO,EAAE,aAAa,EAAE,MAAM,kBAAkB,CAAC;AACjD,OAAO,EAAE,kBAAkB,EAAE,MAAM,mBAAmB,CAAC;AAEvD,KAAK,UAAU,IAAI;IACjB,MAAM,MAAM,GACV,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,IAAI,aAAa,CAAC,gBAAgB,CAAC,CAAC;IACnE,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC,GAAG,MAAO,CAAC;IAExC,MAAM,MAAM,GAAG,IAAI,MAAM,EAAE,CAAC;IAC5B,MAAM,OAAO,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,MAAM,CAAC;QAC3C,QAAQ,EAAE,IAAI;QACd,OAAO,EAAE,IAAI;QACb,eAAe,EAAE,GAAG;KACrB,CAAC,CAAC;IAEH,IAAI,CAAC;QACH,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAE,CAAC;QACvD,MAAM,QAAQ,GAAG,aAAa,CAAC,qBAAqB,CAAE,CAAC;QAEvD,QAAQ;QACR,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,OAAO,CAAC,UAAU,EAAE;YAC3D,IAAI,EAAE,kBAAkB,CAAC,QAAQ,EAAE,QAAQ,CAAC;YAC5C,WAAW,EAAE,EAAE;SAChB,CAAC,CAAC;QAEH,sCAAsC;QACtC,MAAM,MAAM,GAAG,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CACrD,OAAO,CAAC,UAAU,EAClB;YACE,IAAI,EAAE;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;;SA2DL;YACD,WAAW,EAAE,EAAE;SAChB,CACF,CAAC;QAEF,OAAO,CAAC,GAAG,CAAC,IAAI,CAAC,SAAS,CAAC,MAAM,CAAC,MAAM,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC,CAAC;IACtD,CAAC;YAAS,CAAC;QACT,IAAI,CAAC;YACH,MAAM,MAAM,CAAC,QAAQ,CAAC,UAAU,CAAC,OAAO,CAAC,UAAU,CAAC,CAAC;QACvD,CAAC;QAAC,MAAM,CAAC,CAAA,CAAC;IACZ,CAAC;AACH,CAAC;AAED,IAAI,EAAE,CAAC,KAAK,CAAC,OAAO,CAAC,KAAK,CAAC,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":""}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
#!/usr/bin/env node
|
|
2
|
+
import { Command } from "commander";
|
|
3
|
+
import { login } from "./commands/login.js";
|
|
4
|
+
import { quickAdd } from "./commands/quick-add.js";
|
|
5
|
+
import { diary } from "./commands/diary.js";
|
|
6
|
+
import { weight } from "./commands/weight.js";
|
|
7
|
+
const program = new Command();
|
|
8
|
+
program
|
|
9
|
+
.name("crono")
|
|
10
|
+
.description("CLI for Cronometer automation via Kernel.sh")
|
|
11
|
+
.version("0.1.0");
|
|
12
|
+
program
|
|
13
|
+
.command("login")
|
|
14
|
+
.description("Set up Kernel API key and Cronometer credentials")
|
|
15
|
+
.action(async () => {
|
|
16
|
+
await login();
|
|
17
|
+
});
|
|
18
|
+
program
|
|
19
|
+
.command("quick-add")
|
|
20
|
+
.description("Add a quick macro entry to your diary")
|
|
21
|
+
.option("-p, --protein <grams>", "Grams of protein", parseFloat)
|
|
22
|
+
.option("-c, --carbs <grams>", "Grams of carbohydrates", parseFloat)
|
|
23
|
+
.option("-f, --fat <grams>", "Grams of fat", parseFloat)
|
|
24
|
+
.option("-m, --meal <name>", "Meal category (Breakfast, Lunch, Dinner, Snacks)")
|
|
25
|
+
.action(async (options) => {
|
|
26
|
+
await quickAdd(options);
|
|
27
|
+
});
|
|
28
|
+
program
|
|
29
|
+
.command("weight")
|
|
30
|
+
.description("Check your weight from Cronometer")
|
|
31
|
+
.option("-d, --date <date>", "Date (YYYY-MM-DD)")
|
|
32
|
+
.option("-r, --range <range>", "Range (7d, 30d, or YYYY-MM-DD:YYYY-MM-DD)")
|
|
33
|
+
.option("--json", "Output as JSON")
|
|
34
|
+
.action(async (options) => {
|
|
35
|
+
await weight(options);
|
|
36
|
+
});
|
|
37
|
+
program
|
|
38
|
+
.command("diary")
|
|
39
|
+
.description("View daily nutrition totals from Cronometer")
|
|
40
|
+
.option("-d, --date <date>", "Date (YYYY-MM-DD)")
|
|
41
|
+
.option("-r, --range <range>", "Range (7d, 30d, or YYYY-MM-DD:YYYY-MM-DD)")
|
|
42
|
+
.option("--json", "Output as JSON")
|
|
43
|
+
.action(async (options) => {
|
|
44
|
+
await diary(options);
|
|
45
|
+
});
|
|
46
|
+
program.parse();
|
|
47
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":";AAEA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,QAAQ,EAAE,MAAM,yBAAyB,CAAC;AACnD,OAAO,EAAE,KAAK,EAAE,MAAM,qBAAqB,CAAC;AAC5C,OAAO,EAAE,MAAM,EAAE,MAAM,sBAAsB,CAAC;AAE9C,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;AAE9B,OAAO;KACJ,IAAI,CAAC,OAAO,CAAC;KACb,WAAW,CAAC,6CAA6C,CAAC;KAC1D,OAAO,CAAC,OAAO,CAAC,CAAC;AAEpB,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,kDAAkD,CAAC;KAC/D,MAAM,CAAC,KAAK,IAAI,EAAE;IACjB,MAAM,KAAK,EAAE,CAAC;AAChB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,WAAW,CAAC;KACpB,WAAW,CAAC,uCAAuC,CAAC;KACpD,MAAM,CAAC,uBAAuB,EAAE,kBAAkB,EAAE,UAAU,CAAC;KAC/D,MAAM,CAAC,qBAAqB,EAAE,wBAAwB,EAAE,UAAU,CAAC;KACnE,MAAM,CAAC,mBAAmB,EAAE,cAAc,EAAE,UAAU,CAAC;KACvD,MAAM,CACL,mBAAmB,EACnB,kDAAkD,CACnD;KACA,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,QAAQ,CAAC,OAAO,CAAC,CAAC;AAC1B,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,QAAQ,CAAC;KACjB,WAAW,CAAC,mCAAmC,CAAC;KAChD,MAAM,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;KAChD,MAAM,CAAC,qBAAqB,EAAE,2CAA2C,CAAC;KAC1E,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,MAAM,CAAC,OAAO,CAAC,CAAC;AACxB,CAAC,CAAC,CAAC;AAEL,OAAO;KACJ,OAAO,CAAC,OAAO,CAAC;KAChB,WAAW,CAAC,6CAA6C,CAAC;KAC1D,MAAM,CAAC,mBAAmB,EAAE,mBAAmB,CAAC;KAChD,MAAM,CAAC,qBAAqB,EAAE,2CAA2C,CAAC;KAC1E,MAAM,CAAC,QAAQ,EAAE,gBAAgB,CAAC;KAClC,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;IACxB,MAAM,KAAK,CAAC,OAAO,CAAC,CAAC;AACvB,CAAC,CAAC,CAAC;AAEL,OAAO,CAAC,KAAK,EAAE,CAAC"}
|
|
@@ -0,0 +1,40 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Kernel.sh browser automation client for Cronometer.
|
|
3
|
+
*
|
|
4
|
+
* Orchestrates the SDK to manage browser sessions, handle login,
|
|
5
|
+
* and execute Playwright automation for diary entries.
|
|
6
|
+
*
|
|
7
|
+
* Each operation creates a fresh browser, logs in, performs the
|
|
8
|
+
* action, and tears down.
|
|
9
|
+
*/
|
|
10
|
+
export interface MacroEntry {
|
|
11
|
+
protein?: number;
|
|
12
|
+
carbs?: number;
|
|
13
|
+
fat?: number;
|
|
14
|
+
meal?: string;
|
|
15
|
+
}
|
|
16
|
+
export interface WeightData {
|
|
17
|
+
date: string;
|
|
18
|
+
weight: number | null;
|
|
19
|
+
unit: string;
|
|
20
|
+
}
|
|
21
|
+
export interface DiaryData {
|
|
22
|
+
date: string;
|
|
23
|
+
calories: number;
|
|
24
|
+
protein: number;
|
|
25
|
+
carbs: number;
|
|
26
|
+
fat: number;
|
|
27
|
+
}
|
|
28
|
+
export interface KernelClient {
|
|
29
|
+
addQuickEntry(entry: MacroEntry, onStatus?: (msg: string) => void): Promise<void>;
|
|
30
|
+
getWeight(dates: string[], onStatus?: (msg: string) => void): Promise<WeightData[]>;
|
|
31
|
+
getDiary(dates: string[], onStatus?: (msg: string) => void): Promise<DiaryData[]>;
|
|
32
|
+
}
|
|
33
|
+
/**
|
|
34
|
+
* Create a Kernel client for Cronometer automation.
|
|
35
|
+
*
|
|
36
|
+
* Resolves the API key from env var or credential store.
|
|
37
|
+
* Each operation creates a fresh browser and logs in.
|
|
38
|
+
*/
|
|
39
|
+
export declare function getKernelClient(): Promise<KernelClient>;
|
|
40
|
+
//# sourceMappingURL=client.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"client.d.ts","sourceRoot":"","sources":["../../src/kernel/client.ts"],"names":[],"mappings":"AAAA;;;;;;;;GAQG;AAcH,MAAM,WAAW,UAAU;IACzB,OAAO,CAAC,EAAE,MAAM,CAAC;IACjB,KAAK,CAAC,EAAE,MAAM,CAAC;IACf,GAAG,CAAC,EAAE,MAAM,CAAC;IACb,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,MAAM,WAAW,UAAU;IACzB,IAAI,EAAE,MAAM,CAAC;IACb,MAAM,EAAE,MAAM,GAAG,IAAI,CAAC;IACtB,IAAI,EAAE,MAAM,CAAC;CACd;AAED,MAAM,WAAW,SAAS;IACxB,IAAI,EAAE,MAAM,CAAC;IACb,QAAQ,EAAE,MAAM,CAAC;IACjB,OAAO,EAAE,MAAM,CAAC;IAChB,KAAK,EAAE,MAAM,CAAC;IACd,GAAG,EAAE,MAAM,CAAC;CACb;AAED,MAAM,WAAW,YAAY;IAC3B,aAAa,CACX,KAAK,EAAE,UAAU,EACjB,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,IAAI,CAAC,CAAC;IACjB,SAAS,CACP,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,UAAU,EAAE,CAAC,CAAC;IACzB,QAAQ,CACN,KAAK,EAAE,MAAM,EAAE,EACf,QAAQ,CAAC,EAAE,CAAC,GAAG,EAAE,MAAM,KAAK,IAAI,GAC/B,OAAO,CAAC,SAAS,EAAE,CAAC,CAAC;CACzB;AAED;;;;;GAKG;AACH,wBAAsB,eAAe,IAAI,OAAO,CAAC,YAAY,CAAC,CAsB7D"}
|