latchkey 2.4.3 → 2.5.1

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 (52) hide show
  1. package/README.md +5 -0
  2. package/dist/src/cli.js +0 -0
  3. package/dist/src/skillMd.js +2 -2
  4. package/dist/src/skillMd.js.map +1 -1
  5. package/package.json +7 -3
  6. package/dist/package.json +0 -67
  7. package/dist/scripts/encryptFile.d.ts +0 -21
  8. package/dist/scripts/encryptFile.d.ts.map +0 -1
  9. package/dist/scripts/encryptFile.js +0 -101
  10. package/dist/scripts/encryptFile.js.map +0 -1
  11. package/dist/src/browserState.d.ts +0 -8
  12. package/dist/src/browserState.d.ts.map +0 -1
  13. package/dist/src/browserState.js +0 -21
  14. package/dist/src/browserState.js.map +0 -1
  15. package/dist/src/latestVersionCheck.d.ts +0 -10
  16. package/dist/src/latestVersionCheck.d.ts.map +0 -1
  17. package/dist/src/latestVersionCheck.js +0 -42
  18. package/dist/src/latestVersionCheck.js.map +0 -1
  19. package/dist/src/registeredService.d.ts +0 -20
  20. package/dist/src/registeredService.d.ts.map +0 -1
  21. package/dist/src/registeredService.js +0 -34
  22. package/dist/src/registeredService.js.map +0 -1
  23. package/dist/src/registeredServiceStore.d.ts +0 -24
  24. package/dist/src/registeredServiceStore.d.ts.map +0 -1
  25. package/dist/src/registeredServiceStore.js +0 -70
  26. package/dist/src/registeredServiceStore.js.map +0 -1
  27. package/dist/src/services/base.d.ts +0 -141
  28. package/dist/src/services/base.d.ts.map +0 -1
  29. package/dist/src/services/base.js +0 -189
  30. package/dist/src/services/base.js.map +0 -1
  31. package/dist/src/services/google/maps.d.ts +0 -39
  32. package/dist/src/services/google/maps.d.ts.map +0 -1
  33. package/dist/src/services/google/maps.js +0 -94
  34. package/dist/src/services/google/maps.js.map +0 -1
  35. package/dist/src/services/google.d.ts +0 -34
  36. package/dist/src/services/google.d.ts.map +0 -1
  37. package/dist/src/services/google.js +0 -336
  38. package/dist/src/services/google.js.map +0 -1
  39. package/dist/src/services/googleAnalytics.d.ts +0 -11
  40. package/dist/src/services/googleAnalytics.d.ts.map +0 -1
  41. package/dist/src/services/googleAnalytics.js +0 -18
  42. package/dist/src/services/googleAnalytics.js.map +0 -1
  43. package/dist/src/services/googleMaps.d.ts +0 -12
  44. package/dist/src/services/googleMaps.d.ts.map +0 -1
  45. package/dist/src/services/googleMaps.js +0 -17
  46. package/dist/src/services/googleMaps.js.map +0 -1
  47. package/dist/tests/latestVersionCheck.test.d.ts +0 -2
  48. package/dist/tests/latestVersionCheck.test.d.ts.map +0 -1
  49. package/dist/tests/latestVersionCheck.test.js +0 -80
  50. package/dist/tests/latestVersionCheck.test.js.map +0 -1
  51. /package/dist/{integrations → skills/generic/latchkey}/SKILL.md +0 -0
  52. /package/dist/{integrations/openclaw → skills/openclaw/latchkey}/SKILL.md +0 -0
package/README.md CHANGED
@@ -1,5 +1,10 @@
1
1
  # Latchkey
2
2
 
3
+ [![npm](https://img.shields.io/npm/v/latchkey?style=flat-square)](https://npmjs.com/package/your-pkg)
4
+ [![CI](https://img.shields.io/github/actions/workflow/status/imbue-ai/latchkey/test.yml?style=flat-square)](https://github.com/imbue-ai/latchkey/actions)
5
+ [![license](https://img.shields.io/npm/l/latchkey?style=flat-square)](LICENSE)
6
+ [![downloads](https://img.shields.io/npm/dm/latchkey?style=flat-square)](https://npmjs.com/package/latchkey)
7
+
3
8
  Inject API credentials into local agent requests.
4
9
 
5
10
  ## Quick example
package/dist/src/cli.js CHANGED
File without changes
@@ -7,13 +7,13 @@ async function getSkillMdPath() {
7
7
  try {
8
8
  // @ts-expect-error - Bun-specific import attribute
9
9
  // eslint-disable-next-line @typescript-eslint/no-unsafe-assignment
10
- const mod = await import('../integrations/SKILL.md', {
10
+ const mod = await import('../skills/generic/latchkey/SKILL.md', {
11
11
  with: { type: 'text' },
12
12
  });
13
13
  return mod.default;
14
14
  }
15
15
  catch {
16
- return resolve(import.meta.dirname, '../integrations/SKILL.md');
16
+ return resolve(import.meta.dirname, '../skills/generic/latchkey/SKILL.md');
17
17
  }
18
18
  }
19
19
  //# sourceMappingURL=skillMd.js.map
@@ -1 +1 @@
1
- {"version":3,"file":"skillMd.js","sourceRoot":"","sources":["../../src/skillMd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,OAAO,YAAY,CAAC,MAAM,cAAc,EAAE,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC;QACH,mDAAmD;QACnD,mEAAmE;QACnE,MAAM,GAAG,GAAwB,MAAM,MAAM,CAAC,0BAA0B,EAAE;YACxE,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;SACvB,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,0BAA0B,CAAC,CAAC;IAClE,CAAC;AACH,CAAC"}
1
+ {"version":3,"file":"skillMd.js","sourceRoot":"","sources":["../../src/skillMd.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,YAAY,EAAE,MAAM,SAAS,CAAC;AACvC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,CAAC,KAAK,UAAU,iBAAiB;IACrC,OAAO,YAAY,CAAC,MAAM,cAAc,EAAE,EAAE,OAAO,CAAC,CAAC;AACvD,CAAC;AAED,KAAK,UAAU,cAAc;IAC3B,IAAI,CAAC;QACH,mDAAmD;QACnD,mEAAmE;QACnE,MAAM,GAAG,GAAwB,MAAM,MAAM,CAAC,qCAAqC,EAAE;YACnF,IAAI,EAAE,EAAE,IAAI,EAAE,MAAM,EAAE;SACvB,CAAC,CAAC;QACH,OAAO,GAAG,CAAC,OAAO,CAAC;IACrB,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,OAAO,CAAC,MAAM,CAAC,IAAI,CAAC,OAAO,EAAE,qCAAqC,CAAC,CAAC;IAC7E,CAAC;AACH,CAAC"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "latchkey",
3
- "version": "2.4.3",
3
+ "version": "2.5.1",
4
4
  "description": "A CLI tool that injects API credentials into curl requests to third-party services",
5
5
  "author": "Imbue <hynek@imbue.com>",
6
6
  "repository": {
@@ -24,7 +24,7 @@
24
24
  ],
25
25
  "scripts": {
26
26
  "prepublishOnly": "npm run build",
27
- "build": "tsc && node -e \"require('fs').cpSync('integrations', 'dist/integrations', {recursive:true})\" ",
27
+ "build": "tsc && node -e \"require('fs').cpSync('skills', 'dist/skills', {recursive:true})\" ",
28
28
  "dev": "tsc --watch",
29
29
  "lint": "eslint src tests scripts",
30
30
  "lint:fix": "eslint src tests scripts --fix",
@@ -43,7 +43,8 @@
43
43
  "credentials",
44
44
  "authentication",
45
45
  "agents",
46
- "imbue"
46
+ "imbue",
47
+ "pi-package"
47
48
  ],
48
49
  "license": "MIT",
49
50
  "engines": {
@@ -63,5 +64,8 @@
63
64
  "typescript": "^5.3.0",
64
65
  "typescript-eslint": "^8.53.1",
65
66
  "vitest": "^4.0.18"
67
+ },
68
+ "pi": {
69
+ "skills": ["./dist/skills/generic"]
66
70
  }
67
71
  }
package/dist/package.json DELETED
@@ -1,67 +0,0 @@
1
- {
2
- "name": "latchkey",
3
- "version": "2.3.0",
4
- "description": "A CLI tool that injects API credentials into curl requests to third-party services",
5
- "author": "Imbue <hynek@imbue.com>",
6
- "repository": {
7
- "type": "git",
8
- "url": "git+https://github.com/imbue-ai/latchkey.git"
9
- },
10
- "homepage": "https://github.com/imbue-ai/latchkey#readme",
11
- "bugs": {
12
- "url": "https://github.com/imbue-ai/latchkey/issues"
13
- },
14
- "type": "module",
15
- "main": "dist/src/index.js",
16
- "types": "dist/src/index.d.ts",
17
- "bin": {
18
- "latchkey": "./dist/src/cli.js"
19
- },
20
- "files": [
21
- "dist",
22
- "README.md",
23
- "LICENSE"
24
- ],
25
- "scripts": {
26
- "prepublishOnly": "npm run build",
27
- "build": "tsc && node -e \"require('fs').cpSync('integrations', 'dist/integrations', {recursive:true})\" ",
28
- "dev": "tsc --watch",
29
- "lint": "eslint src tests scripts",
30
- "lint:fix": "eslint src tests scripts --fix",
31
- "format": "prettier --write \"src/**/*.ts\" \"tests/**/*.ts\" \"scripts/**/*.ts\"",
32
- "format:check": "prettier --check \"src/**/*.ts\" \"tests/**/*.ts\" \"scripts/**/*.ts\"",
33
- "typecheck": "tsc --noEmit",
34
- "test": "vitest run",
35
- "test:watch": "vitest",
36
- "start": "node dist/cli.js",
37
- "bun-compile": "bun build ./src/cli.ts --compile --external chromium-bidi --external electron --outfile latchkey"
38
- },
39
- "keywords": [
40
- "cli",
41
- "curl",
42
- "api",
43
- "credentials",
44
- "authentication",
45
- "agents",
46
- "imbue"
47
- ],
48
- "license": "MIT",
49
- "engines": {
50
- "node": ">=20"
51
- },
52
- "dependencies": {
53
- "@napi-rs/keyring": "^1.2.0",
54
- "commander": "^12.0.0",
55
- "playwright": "^1.58.2",
56
- "zod": "^3.22.0"
57
- },
58
- "devDependencies": {
59
- "@eslint/js": "^9.39.2",
60
- "@types/node": "^20.0.0",
61
- "eslint": "^9.39.2",
62
- "prettier": "^3.8.1",
63
- "typescript": "^5.3.0",
64
- "typescript-eslint": "^8.53.1",
65
- "vitest": "^4.0.18"
66
- }
67
- }
@@ -1,21 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
- /**
3
- * CLI tool for encrypting and decrypting latchkey files.
4
- *
5
- * This is a developer utility for inspecting and modifying encrypted
6
- * credential and browser state files.
7
- *
8
- * Usage:
9
- * npx tsx scripts/encryptFile.ts decrypt <file> # Decrypt file to stdout
10
- * npx tsx scripts/encryptFile.ts encrypt <file> # Encrypt file in place
11
- *
12
- * The encryption key is sourced from:
13
- * 1. LATCHKEY_ENCRYPTION_KEY environment variable
14
- * 2. System keychain
15
- *
16
- * Examples:
17
- * npx tsx scripts/encryptFile.ts decrypt ~/.latchkey/credentials.json
18
- * npx tsx scripts/encryptFile.ts encrypt ~/.latchkey/credentials.json
19
- */
20
- export {};
21
- //# sourceMappingURL=encryptFile.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"encryptFile.d.ts","sourceRoot":"","sources":["../../scripts/encryptFile.ts"],"names":[],"mappings":";AACA;;;;;;;;;;;;;;;;;GAiBG"}
@@ -1,101 +0,0 @@
1
- #!/usr/bin/env npx tsx
2
- /**
3
- * CLI tool for encrypting and decrypting latchkey files.
4
- *
5
- * This is a developer utility for inspecting and modifying encrypted
6
- * credential and browser state files.
7
- *
8
- * Usage:
9
- * npx tsx scripts/encryptFile.ts decrypt <file> # Decrypt file to stdout
10
- * npx tsx scripts/encryptFile.ts encrypt <file> # Encrypt file in place
11
- *
12
- * The encryption key is sourced from:
13
- * 1. LATCHKEY_ENCRYPTION_KEY environment variable
14
- * 2. System keychain
15
- *
16
- * Examples:
17
- * npx tsx scripts/encryptFile.ts decrypt ~/.latchkey/credentials.json
18
- * npx tsx scripts/encryptFile.ts encrypt ~/.latchkey/credentials.json
19
- */
20
- import { program } from 'commander';
21
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
22
- import { CONFIG } from '../src/config.js';
23
- import { EncryptedStorage } from '../src/encryptedStorage.js';
24
- import { encrypt, generateKey } from '../src/encryption.js';
25
- import { isKeychainAvailable, retrieveFromKeychain } from '../src/keychain.js';
26
- const ENCRYPTED_FILE_PREFIX = 'LATCHKEY_ENCRYPTED:';
27
- function getEncryptionKey() {
28
- // 1. Check environment variable via Config
29
- if (CONFIG.encryptionKeyOverride) {
30
- return CONFIG.encryptionKeyOverride;
31
- }
32
- // 2. Check keychain
33
- if (isKeychainAvailable(CONFIG.serviceName, CONFIG.accountName)) {
34
- const keychainKey = retrieveFromKeychain(CONFIG.serviceName, CONFIG.accountName);
35
- if (keychainKey) {
36
- return keychainKey;
37
- }
38
- }
39
- console.error(`\
40
- Error: No encryption key available.
41
- Set LATCHKEY_ENCRYPTION_KEY or ensure the system keychain has a stored key.
42
-
43
- To generate a new key:
44
- export LATCHKEY_ENCRYPTION_KEY="${generateKey()}"`);
45
- process.exit(1);
46
- }
47
- function decryptCommand(filePath) {
48
- if (!existsSync(filePath)) {
49
- console.error(`Error: File not found: ${filePath}`);
50
- process.exit(1);
51
- }
52
- const storage = new EncryptedStorage({
53
- serviceName: CONFIG.serviceName,
54
- accountName: CONFIG.accountName,
55
- });
56
- const content = storage.readFile(filePath);
57
- if (content === null) {
58
- console.error(`Error: Could not read file: ${filePath}`);
59
- process.exit(1);
60
- }
61
- // Output to stdout
62
- process.stdout.write(content);
63
- }
64
- function encryptCommand(filePath) {
65
- if (!existsSync(filePath)) {
66
- console.error(`Error: File not found: ${filePath}`);
67
- process.exit(1);
68
- }
69
- const content = readFileSync(filePath, 'utf-8');
70
- if (content.startsWith(ENCRYPTED_FILE_PREFIX)) {
71
- console.error(`Error: File is already encrypted: ${filePath}`);
72
- process.exit(1);
73
- }
74
- const key = getEncryptionKey();
75
- const encryptedData = encrypt(content, key);
76
- const dataToWrite = ENCRYPTED_FILE_PREFIX + encryptedData;
77
- writeFileSync(filePath, dataToWrite, { encoding: 'utf-8', mode: 0o600 });
78
- console.error(`Encrypted: ${filePath}`);
79
- }
80
- program.name('encryptFile').description(`\
81
- CLI tool for encrypting and decrypting latchkey files.
82
-
83
- The encryption key is sourced from:
84
- 1. LATCHKEY_ENCRYPTION_KEY environment variable
85
- 2. System keychain`);
86
- program
87
- .command('decrypt')
88
- .description('Decrypt file and print to stdout')
89
- .argument('<file>', 'Path to the encrypted file')
90
- .action((filePath) => {
91
- decryptCommand(filePath);
92
- });
93
- program
94
- .command('encrypt')
95
- .description('Encrypt an unencrypted file in place')
96
- .argument('<file>', 'Path to the file to encrypt')
97
- .action((filePath) => {
98
- encryptCommand(filePath);
99
- });
100
- program.parse();
101
- //# sourceMappingURL=encryptFile.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"encryptFile.js","sourceRoot":"","sources":["../../scripts/encryptFile.ts"],"names":[],"mappings":"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"}
@@ -1,8 +0,0 @@
1
- /**
2
- * Browser state management utilities.
3
- */
4
- /**
5
- * Get the browser state path from the LATCHKEY_BROWSER_STATE environment variable.
6
- */
7
- export declare function getBrowserStatePath(): string | null;
8
- //# sourceMappingURL=browserState.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browserState.d.ts","sourceRoot":"","sources":["../../src/browserState.ts"],"names":[],"mappings":"AAAA;;GAEG;AAOH;;GAEG;AACH,wBAAgB,mBAAmB,IAAI,MAAM,GAAG,IAAI,CAUnD"}
@@ -1,21 +0,0 @@
1
- /**
2
- * Browser state management utilities.
3
- */
4
- import { homedir } from 'node:os';
5
- import { resolve } from 'node:path';
6
- const LATCHKEY_BROWSER_STATE_ENV_VAR = 'LATCHKEY_BROWSER_STATE';
7
- /**
8
- * Get the browser state path from the LATCHKEY_BROWSER_STATE environment variable.
9
- */
10
- export function getBrowserStatePath() {
11
- const envValue = process.env[LATCHKEY_BROWSER_STATE_ENV_VAR];
12
- if (envValue) {
13
- // Expand ~ to home directory
14
- if (envValue.startsWith('~')) {
15
- return resolve(homedir(), envValue.slice(2));
16
- }
17
- return resolve(envValue);
18
- }
19
- return null;
20
- }
21
- //# sourceMappingURL=browserState.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"browserState.js","sourceRoot":"","sources":["../../src/browserState.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,OAAO,EAAE,MAAM,SAAS,CAAC;AAClC,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AAEpC,MAAM,8BAA8B,GAAG,wBAAwB,CAAC;AAEhE;;GAEG;AACH,MAAM,UAAU,mBAAmB;IACjC,MAAM,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,8BAA8B,CAAC,CAAC;IAC7D,IAAI,QAAQ,EAAE,CAAC;QACb,6BAA6B;QAC7B,IAAI,QAAQ,CAAC,UAAU,CAAC,GAAG,CAAC,EAAE,CAAC;YAC7B,OAAO,OAAO,CAAC,OAAO,EAAE,EAAE,QAAQ,CAAC,KAAK,CAAC,CAAC,CAAC,CAAC,CAAC;QAC/C,CAAC;QACD,OAAO,OAAO,CAAC,QAAQ,CAAC,CAAC;IAC3B,CAAC;IACD,OAAO,IAAI,CAAC;AACd,CAAC"}
@@ -1,10 +0,0 @@
1
- /**
2
- * Fire-and-forget version check on startup.
3
- *
4
- * Reads `LATCHKEY_DIR/latest-version-check` for an ISO timestamp.
5
- * If the file is missing or the timestamp is older than 24 hours,
6
- * spawns a detached curl process to ping the version endpoint.
7
- */
8
- import type { Config } from './config.js';
9
- export declare function checkLatestVersionIfNeeded(config: Config): void;
10
- //# sourceMappingURL=latestVersionCheck.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"latestVersionCheck.d.ts","sourceRoot":"","sources":["../../src/latestVersionCheck.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAIH,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,aAAa,CAAC;AAyB1C,wBAAgB,0BAA0B,CAAC,MAAM,EAAE,MAAM,GAAG,IAAI,CAgB/D"}
@@ -1,42 +0,0 @@
1
- /**
2
- * Fire-and-forget version check on startup.
3
- *
4
- * Reads `LATCHKEY_DIR/latest-version-check` for an ISO timestamp.
5
- * If the file is missing or the timestamp is older than 24 hours,
6
- * spawns a detached curl process to ping the version endpoint.
7
- */
8
- import { existsSync, readFileSync, writeFileSync } from 'node:fs';
9
- import { join } from 'node:path';
10
- import { runDetached } from './curl.js';
11
- const VERSION_CHECK_FILENAME = 'latest-version-check';
12
- const VERSION_CHECK_URL = 'https://dau-tracker.latchkey.host.imbue.com/api/version/latchkey';
13
- const ONE_DAY_IN_MILLISECONDS = 24 * 60 * 60 * 1000;
14
- const TIMEOUT_SECONDS = 5;
15
- function shouldCheck(config) {
16
- const filePath = join(config.directory, VERSION_CHECK_FILENAME);
17
- if (!existsSync(filePath)) {
18
- return true;
19
- }
20
- const content = readFileSync(filePath, 'utf-8').trim();
21
- const timestamp = new Date(content);
22
- if (isNaN(timestamp.getTime())) {
23
- return true;
24
- }
25
- return Date.now() - timestamp.getTime() > ONE_DAY_IN_MILLISECONDS;
26
- }
27
- export function checkLatestVersionIfNeeded(config) {
28
- if (config.telemetryDisabled || !shouldCheck(config)) {
29
- return;
30
- }
31
- const filePath = join(config.directory, VERSION_CHECK_FILENAME);
32
- writeFileSync(filePath, new Date().toISOString(), 'utf-8');
33
- runDetached([
34
- '--silent',
35
- '--max-time',
36
- String(TIMEOUT_SECONDS),
37
- '--output',
38
- '/dev/null',
39
- VERSION_CHECK_URL,
40
- ]);
41
- }
42
- //# sourceMappingURL=latestVersionCheck.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"latestVersionCheck.js","sourceRoot":"","sources":["../../src/latestVersionCheck.ts"],"names":[],"mappings":"AAAA;;;;;;GAMG;AAEH,OAAO,EAAE,UAAU,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAClE,OAAO,EAAE,IAAI,EAAE,MAAM,WAAW,CAAC;AAEjC,OAAO,EAAE,WAAW,EAAE,MAAM,WAAW,CAAC;AAExC,MAAM,sBAAsB,GAAG,sBAAsB,CAAC;AACtD,MAAM,iBAAiB,GAAG,kEAAkE,CAAC;AAC7F,MAAM,uBAAuB,GAAG,EAAE,GAAG,EAAE,GAAG,EAAE,GAAG,IAAI,CAAC;AACpD,MAAM,eAAe,GAAG,CAAC,CAAC;AAE1B,SAAS,WAAW,CAAC,MAAc;IACjC,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IAEhE,IAAI,CAAC,UAAU,CAAC,QAAQ,CAAC,EAAE,CAAC;QAC1B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,MAAM,OAAO,GAAG,YAAY,CAAC,QAAQ,EAAE,OAAO,CAAC,CAAC,IAAI,EAAE,CAAC;IACvD,MAAM,SAAS,GAAG,IAAI,IAAI,CAAC,OAAO,CAAC,CAAC;IAEpC,IAAI,KAAK,CAAC,SAAS,CAAC,OAAO,EAAE,CAAC,EAAE,CAAC;QAC/B,OAAO,IAAI,CAAC;IACd,CAAC;IAED,OAAO,IAAI,CAAC,GAAG,EAAE,GAAG,SAAS,CAAC,OAAO,EAAE,GAAG,uBAAuB,CAAC;AACpE,CAAC;AAED,MAAM,UAAU,0BAA0B,CAAC,MAAc;IACvD,IAAI,MAAM,CAAC,iBAAiB,IAAI,CAAC,WAAW,CAAC,MAAM,CAAC,EAAE,CAAC;QACrD,OAAO;IACT,CAAC;IAED,MAAM,QAAQ,GAAG,IAAI,CAAC,MAAM,CAAC,SAAS,EAAE,sBAAsB,CAAC,CAAC;IAChE,aAAa,CAAC,QAAQ,EAAE,IAAI,IAAI,EAAE,CAAC,WAAW,EAAE,EAAE,OAAO,CAAC,CAAC;IAE3D,WAAW,CAAC;QACV,UAAU;QACV,YAAY;QACZ,MAAM,CAAC,eAAe,CAAC;QACvB,UAAU;QACV,WAAW;QACX,iBAAiB;KAClB,CAAC,CAAC;AACL,CAAC"}
@@ -1,20 +0,0 @@
1
- /**
2
- * A user-registered service that wraps a built-in "family" service
3
- * with a custom name and base API URL. Used for self-hosted instances.
4
- */
5
- import type { ApiCredentials } from './apiCredentials.js';
6
- import { Service, type ServiceSession } from './services/base.js';
7
- export declare class RegisteredService extends Service {
8
- readonly name: string;
9
- readonly displayName: string;
10
- readonly baseApiUrls: readonly string[];
11
- readonly loginUrl: string;
12
- readonly info: string;
13
- readonly credentialCheckCurlArguments: readonly string[];
14
- private readonly familyService;
15
- constructor(name: string, baseApiUrl: string, familyService: Service, loginUrl?: string);
16
- getSession?(): ServiceSession;
17
- setCredentialsExample(serviceName: string): string;
18
- getCredentialsNoCurl(arguments_: readonly string[]): ApiCredentials;
19
- }
20
- //# sourceMappingURL=registeredService.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registeredService.d.ts","sourceRoot":"","sources":["../../src/registeredService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAEH,OAAO,KAAK,EAAE,cAAc,EAAE,MAAM,qBAAqB,CAAC;AAC1D,OAAO,EAAE,OAAO,EAAE,KAAK,cAAc,EAAE,MAAM,oBAAoB,CAAC;AAElE,qBAAa,iBAAkB,SAAQ,OAAO;IAC5C,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,WAAW,EAAE,MAAM,CAAC;IAC7B,QAAQ,CAAC,WAAW,EAAE,SAAS,MAAM,EAAE,CAAC;IACxC,QAAQ,CAAC,QAAQ,EAAE,MAAM,CAAC;IAC1B,QAAQ,CAAC,IAAI,EAAE,MAAM,CAAC;IACtB,QAAQ,CAAC,4BAA4B,EAAE,SAAS,MAAM,EAAE,CAAC;IAEzD,OAAO,CAAC,QAAQ,CAAC,aAAa,CAAU;gBAE5B,IAAI,EAAE,MAAM,EAAE,UAAU,EAAE,MAAM,EAAE,aAAa,EAAE,OAAO,EAAE,QAAQ,CAAC,EAAE,MAAM;IAe9E,UAAU,CAAC,IAAI,cAAc;IAEtC,qBAAqB,CAAC,WAAW,EAAE,MAAM,GAAG,MAAM;IAIzC,oBAAoB,CAAC,UAAU,EAAE,SAAS,MAAM,EAAE,GAAG,cAAc;CAG7E"}
@@ -1,34 +0,0 @@
1
- /**
2
- * A user-registered service that wraps a built-in "family" service
3
- * with a custom name and base API URL. Used for self-hosted instances.
4
- */
5
- import { Service } from './services/base.js';
6
- export class RegisteredService extends Service {
7
- name;
8
- displayName;
9
- baseApiUrls;
10
- loginUrl;
11
- info;
12
- credentialCheckCurlArguments;
13
- familyService;
14
- constructor(name, baseApiUrl, familyService, loginUrl) {
15
- super();
16
- this.name = name;
17
- this.displayName = name;
18
- this.baseApiUrls = [baseApiUrl];
19
- this.loginUrl = loginUrl ?? '';
20
- this.info = `Self-hosted ${familyService.displayName} instance. ${familyService.info}`;
21
- this.credentialCheckCurlArguments = [];
22
- this.familyService = familyService;
23
- if (loginUrl !== undefined && familyService.getSession !== undefined) {
24
- this.getSession = () => familyService.getSession();
25
- }
26
- }
27
- setCredentialsExample(serviceName) {
28
- return this.familyService.setCredentialsExample(serviceName);
29
- }
30
- getCredentialsNoCurl(arguments_) {
31
- return this.familyService.getCredentialsNoCurl(arguments_);
32
- }
33
- }
34
- //# sourceMappingURL=registeredService.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registeredService.js","sourceRoot":"","sources":["../../src/registeredService.ts"],"names":[],"mappings":"AAAA;;;GAGG;AAGH,OAAO,EAAE,OAAO,EAAuB,MAAM,oBAAoB,CAAC;AAElE,MAAM,OAAO,iBAAkB,SAAQ,OAAO;IACnC,IAAI,CAAS;IACb,WAAW,CAAS;IACpB,WAAW,CAAoB;IAC/B,QAAQ,CAAS;IACjB,IAAI,CAAS;IACb,4BAA4B,CAAoB;IAExC,aAAa,CAAU;IAExC,YAAY,IAAY,EAAE,UAAkB,EAAE,aAAsB,EAAE,QAAiB;QACrF,KAAK,EAAE,CAAC;QACR,IAAI,CAAC,IAAI,GAAG,IAAI,CAAC;QACjB,IAAI,CAAC,WAAW,GAAG,IAAI,CAAC;QACxB,IAAI,CAAC,WAAW,GAAG,CAAC,UAAU,CAAC,CAAC;QAChC,IAAI,CAAC,QAAQ,GAAG,QAAQ,IAAI,EAAE,CAAC;QAC/B,IAAI,CAAC,IAAI,GAAG,eAAe,aAAa,CAAC,WAAW,cAAc,aAAa,CAAC,IAAI,EAAE,CAAC;QACvF,IAAI,CAAC,4BAA4B,GAAG,EAAE,CAAC;QACvC,IAAI,CAAC,aAAa,GAAG,aAAa,CAAC;QAEnC,IAAI,QAAQ,KAAK,SAAS,IAAI,aAAa,CAAC,UAAU,KAAK,SAAS,EAAE,CAAC;YACrE,IAAI,CAAC,UAAU,GAAG,GAAG,EAAE,CAAC,aAAa,CAAC,UAAW,EAAE,CAAC;QACtD,CAAC;IACH,CAAC;IAID,qBAAqB,CAAC,WAAmB;QACvC,OAAO,IAAI,CAAC,aAAa,CAAC,qBAAqB,CAAC,WAAW,CAAC,CAAC;IAC/D,CAAC;IAEQ,oBAAoB,CAAC,UAA6B;QACzD,OAAO,IAAI,CAAC,aAAa,CAAC,oBAAoB,CAAC,UAAU,CAAC,CAAC;IAC7D,CAAC;CACF"}
@@ -1,24 +0,0 @@
1
- /**
2
- * Persistence for user-registered services in config.json.
3
- */
4
- import { z } from 'zod';
5
- import type { Registry } from './registry.js';
6
- declare const RegisteredServiceEntrySchema: z.ZodObject<{
7
- baseApiUrl: z.ZodString;
8
- serviceFamily: z.ZodString;
9
- loginUrl: z.ZodOptional<z.ZodString>;
10
- }, "strip", z.ZodTypeAny, {
11
- baseApiUrl: string;
12
- serviceFamily: string;
13
- loginUrl?: string | undefined;
14
- }, {
15
- baseApiUrl: string;
16
- serviceFamily: string;
17
- loginUrl?: string | undefined;
18
- }>;
19
- export type RegisteredServiceEntry = z.infer<typeof RegisteredServiceEntrySchema>;
20
- export declare function loadRegisteredServices(configPath: string): ReadonlyMap<string, RegisteredServiceEntry>;
21
- export declare function saveRegisteredService(configPath: string, name: string, entry: RegisteredServiceEntry): void;
22
- export declare function loadRegisteredServicesIntoRegistry(configPath: string, registry: Registry): void;
23
- export {};
24
- //# sourceMappingURL=registeredServiceStore.d.ts.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registeredServiceStore.d.ts","sourceRoot":"","sources":["../../src/registeredServiceStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAIH,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AAExB,OAAO,KAAK,EAAE,QAAQ,EAAE,MAAM,eAAe,CAAC;AAE9C,QAAA,MAAM,4BAA4B;;;;;;;;;;;;EAIhC,CAAC;AAEH,MAAM,MAAM,sBAAsB,GAAG,CAAC,CAAC,KAAK,CAAC,OAAO,4BAA4B,CAAC,CAAC;AAIlF,wBAAgB,sBAAsB,CACpC,UAAU,EAAE,MAAM,GACjB,WAAW,CAAC,MAAM,EAAE,sBAAsB,CAAC,CAiB7C;AAED,wBAAgB,qBAAqB,CACnC,UAAU,EAAE,MAAM,EAClB,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,sBAAsB,GAC5B,IAAI,CA2BN;AAED,wBAAgB,kCAAkC,CAAC,UAAU,EAAE,MAAM,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAkB/F"}
@@ -1,70 +0,0 @@
1
- /**
2
- * Persistence for user-registered services in config.json.
3
- */
4
- import { existsSync, mkdirSync, readFileSync, writeFileSync } from 'node:fs';
5
- import { dirname } from 'node:path';
6
- import { z } from 'zod';
7
- import { RegisteredService } from './registeredService.js';
8
- const RegisteredServiceEntrySchema = z.object({
9
- baseApiUrl: z.string(),
10
- serviceFamily: z.string(),
11
- loginUrl: z.string().optional(),
12
- });
13
- const RegisteredServicesSchema = z.record(z.string(), RegisteredServiceEntrySchema);
14
- export function loadRegisteredServices(configPath) {
15
- if (!existsSync(configPath)) {
16
- return new Map();
17
- }
18
- try {
19
- const content = readFileSync(configPath, 'utf-8');
20
- const data = JSON.parse(content);
21
- if (typeof data !== 'object' || data === null) {
22
- return new Map();
23
- }
24
- const record = data;
25
- const registeredServices = RegisteredServicesSchema.parse(record.registeredServices ?? {});
26
- return new Map(Object.entries(registeredServices));
27
- }
28
- catch {
29
- return new Map();
30
- }
31
- }
32
- export function saveRegisteredService(configPath, name, entry) {
33
- const directory = dirname(configPath);
34
- if (!existsSync(directory)) {
35
- mkdirSync(directory, { recursive: true, mode: 0o700 });
36
- }
37
- let existingConfig = {};
38
- if (existsSync(configPath)) {
39
- try {
40
- const existingContent = readFileSync(configPath, 'utf-8');
41
- existingConfig = JSON.parse(existingContent);
42
- }
43
- catch {
44
- // Ignore parse errors, start fresh
45
- }
46
- }
47
- const registeredServices = typeof existingConfig.registeredServices === 'object' &&
48
- existingConfig.registeredServices !== null
49
- ? existingConfig.registeredServices
50
- : {};
51
- registeredServices[name] = entry;
52
- existingConfig.registeredServices = registeredServices;
53
- const content = JSON.stringify(existingConfig, null, 2);
54
- writeFileSync(configPath, content, { encoding: 'utf-8' });
55
- }
56
- export function loadRegisteredServicesIntoRegistry(configPath, registry) {
57
- const entries = loadRegisteredServices(configPath);
58
- for (const [name, entry] of entries) {
59
- const familyService = registry.getByName(entry.serviceFamily);
60
- if (familyService === null) {
61
- continue;
62
- }
63
- if (registry.getByName(name) !== null) {
64
- continue;
65
- }
66
- const registeredService = new RegisteredService(name, entry.baseApiUrl, familyService, entry.loginUrl);
67
- registry.addService(registeredService);
68
- }
69
- }
70
- //# sourceMappingURL=registeredServiceStore.js.map
@@ -1 +0,0 @@
1
- {"version":3,"file":"registeredServiceStore.js","sourceRoot":"","sources":["../../src/registeredServiceStore.ts"],"names":[],"mappings":"AAAA;;GAEG;AAEH,OAAO,EAAE,UAAU,EAAE,SAAS,EAAE,YAAY,EAAE,aAAa,EAAE,MAAM,SAAS,CAAC;AAC7E,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,CAAC,EAAE,MAAM,KAAK,CAAC;AACxB,OAAO,EAAE,iBAAiB,EAAE,MAAM,wBAAwB,CAAC;AAG3D,MAAM,4BAA4B,GAAG,CAAC,CAAC,MAAM,CAAC;IAC5C,UAAU,EAAE,CAAC,CAAC,MAAM,EAAE;IACtB,aAAa,EAAE,CAAC,CAAC,MAAM,EAAE;IACzB,QAAQ,EAAE,CAAC,CAAC,MAAM,EAAE,CAAC,QAAQ,EAAE;CAChC,CAAC,CAAC;AAIH,MAAM,wBAAwB,GAAG,CAAC,CAAC,MAAM,CAAC,CAAC,CAAC,MAAM,EAAE,EAAE,4BAA4B,CAAC,CAAC;AAEpF,MAAM,UAAU,sBAAsB,CACpC,UAAkB;IAElB,IAAI,CAAC,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC5B,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;IAED,IAAI,CAAC;QACH,MAAM,OAAO,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;QAClD,MAAM,IAAI,GAAG,IAAI,CAAC,KAAK,CAAC,OAAO,CAAY,CAAC;QAC5C,IAAI,OAAO,IAAI,KAAK,QAAQ,IAAI,IAAI,KAAK,IAAI,EAAE,CAAC;YAC9C,OAAO,IAAI,GAAG,EAAE,CAAC;QACnB,CAAC;QACD,MAAM,MAAM,GAAG,IAA+B,CAAC;QAC/C,MAAM,kBAAkB,GAAG,wBAAwB,CAAC,KAAK,CAAC,MAAM,CAAC,kBAAkB,IAAI,EAAE,CAAC,CAAC;QAC3F,OAAO,IAAI,GAAG,CAAC,MAAM,CAAC,OAAO,CAAC,kBAAkB,CAAC,CAAC,CAAC;IACrD,CAAC;IAAC,MAAM,CAAC;QACP,OAAO,IAAI,GAAG,EAAE,CAAC;IACnB,CAAC;AACH,CAAC;AAED,MAAM,UAAU,qBAAqB,CACnC,UAAkB,EAClB,IAAY,EACZ,KAA6B;IAE7B,MAAM,SAAS,GAAG,OAAO,CAAC,UAAU,CAAC,CAAC;IACtC,IAAI,CAAC,UAAU,CAAC,SAAS,CAAC,EAAE,CAAC;QAC3B,SAAS,CAAC,SAAS,EAAE,EAAE,SAAS,EAAE,IAAI,EAAE,IAAI,EAAE,KAAK,EAAE,CAAC,CAAC;IACzD,CAAC;IAED,IAAI,cAAc,GAA4B,EAAE,CAAC;IACjD,IAAI,UAAU,CAAC,UAAU,CAAC,EAAE,CAAC;QAC3B,IAAI,CAAC;YACH,MAAM,eAAe,GAAG,YAAY,CAAC,UAAU,EAAE,OAAO,CAAC,CAAC;YAC1D,cAAc,GAAG,IAAI,CAAC,KAAK,CAAC,eAAe,CAA4B,CAAC;QAC1E,CAAC;QAAC,MAAM,CAAC;YACP,mCAAmC;QACrC,CAAC;IACH,CAAC;IAED,MAAM,kBAAkB,GACtB,OAAO,cAAc,CAAC,kBAAkB,KAAK,QAAQ;QACrD,cAAc,CAAC,kBAAkB,KAAK,IAAI;QACxC,CAAC,CAAE,cAAc,CAAC,kBAA8C;QAChE,CAAC,CAAC,EAAE,CAAC;IAET,kBAAkB,CAAC,IAAI,CAAC,GAAG,KAAK,CAAC;IACjC,cAAc,CAAC,kBAAkB,GAAG,kBAAkB,CAAC;IAEvD,MAAM,OAAO,GAAG,IAAI,CAAC,SAAS,CAAC,cAAc,EAAE,IAAI,EAAE,CAAC,CAAC,CAAC;IACxD,aAAa,CAAC,UAAU,EAAE,OAAO,EAAE,EAAE,QAAQ,EAAE,OAAO,EAAE,CAAC,CAAC;AAC5D,CAAC;AAED,MAAM,UAAU,kCAAkC,CAAC,UAAkB,EAAE,QAAkB;IACvF,MAAM,OAAO,GAAG,sBAAsB,CAAC,UAAU,CAAC,CAAC;IACnD,KAAK,MAAM,CAAC,IAAI,EAAE,KAAK,CAAC,IAAI,OAAO,EAAE,CAAC;QACpC,MAAM,aAAa,GAAG,QAAQ,CAAC,SAAS,CAAC,KAAK,CAAC,aAAa,CAAC,CAAC;QAC9D,IAAI,aAAa,KAAK,IAAI,EAAE,CAAC;YAC3B,SAAS;QACX,CAAC;QACD,IAAI,QAAQ,CAAC,SAAS,CAAC,IAAI,CAAC,KAAK,IAAI,EAAE,CAAC;YACtC,SAAS;QACX,CAAC;QACD,MAAM,iBAAiB,GAAG,IAAI,iBAAiB,CAC7C,IAAI,EACJ,KAAK,CAAC,UAAU,EAChB,aAAa,EACb,KAAK,CAAC,QAAQ,CACf,CAAC;QACF,QAAQ,CAAC,UAAU,CAAC,iBAAiB,CAAC,CAAC;IACzC,CAAC;AACH,CAAC"}