@satori-sh/cli 0.0.2
This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
- package/README.md +88 -0
- package/bun.lock +37 -0
- package/dist/add.d.ts +2 -0
- package/dist/add.d.ts.map +1 -0
- package/dist/add.js +27 -0
- package/dist/add.js.map +1 -0
- package/dist/config.d.ts +3 -0
- package/dist/config.d.ts.map +1 -0
- package/dist/config.js +15 -0
- package/dist/config.js.map +1 -0
- package/dist/index.d.ts +4 -0
- package/dist/index.d.ts.map +1 -0
- package/dist/index.js +49 -0
- package/dist/index.js.map +1 -0
- package/dist/memory.d.ts +12 -0
- package/dist/memory.d.ts.map +1 -0
- package/dist/memory.js +42 -0
- package/dist/memory.js.map +1 -0
- package/dist/search.d.ts +2 -0
- package/dist/search.d.ts.map +1 -0
- package/dist/search.js +27 -0
- package/dist/search.js.map +1 -0
- package/dist/src/add.d.ts +23 -0
- package/dist/src/add.d.ts.map +1 -0
- package/dist/src/add.js +46 -0
- package/dist/src/add.js.map +1 -0
- package/dist/src/config.d.ts +39 -0
- package/dist/src/config.d.ts.map +1 -0
- package/dist/src/config.js +165 -0
- package/dist/src/config.js.map +1 -0
- package/dist/src/index.d.ts +14 -0
- package/dist/src/index.d.ts.map +1 -0
- package/dist/src/index.js +141 -0
- package/dist/src/index.js.map +1 -0
- package/dist/src/memory.d.ts +52 -0
- package/dist/src/memory.d.ts.map +1 -0
- package/dist/src/memory.js +98 -0
- package/dist/src/memory.js.map +1 -0
- package/dist/src/providers.d.ts +34 -0
- package/dist/src/providers.d.ts.map +1 -0
- package/dist/src/providers.js +107 -0
- package/dist/src/providers.js.map +1 -0
- package/dist/src/search.d.ts +14 -0
- package/dist/src/search.d.ts.map +1 -0
- package/dist/src/search.js +39 -0
- package/dist/src/search.js.map +1 -0
- package/dist/src/types.d.ts +51 -0
- package/dist/src/types.d.ts.map +1 -0
- package/dist/src/types.js +2 -0
- package/dist/src/types.js.map +1 -0
- package/dist/tests/index.test.d.ts +2 -0
- package/dist/tests/index.test.d.ts.map +1 -0
- package/dist/tests/index.test.js +257 -0
- package/dist/tests/index.test.js.map +1 -0
- package/dist/types.d.ts +19 -0
- package/dist/types.d.ts.map +1 -0
- package/dist/types.js +2 -0
- package/dist/types.js.map +1 -0
- package/logo.txt +2 -0
- package/package.json +25 -0
- package/src/add.ts +49 -0
- package/src/config.ts +170 -0
- package/src/index.ts +163 -0
- package/src/memory.ts +118 -0
- package/src/providers.ts +133 -0
- package/src/search.ts +42 -0
- package/src/types.ts +45 -0
- package/tests/index.test.ts +322 -0
- package/tsconfig.json +33 -0
package/README.md
ADDED
|
@@ -0,0 +1,88 @@
|
|
|
1
|
+
# satori
|
|
2
|
+
|
|
3
|
+
CLI tool for Satori memory server.
|
|
4
|
+
|
|
5
|
+
## Installation
|
|
6
|
+
|
|
7
|
+
Using Bun (recommended):
|
|
8
|
+
```bash
|
|
9
|
+
bun add -g @satori-sh/cli
|
|
10
|
+
```
|
|
11
|
+
|
|
12
|
+
Using npm:
|
|
13
|
+
```bash
|
|
14
|
+
npm install -g @satori-sh/cli
|
|
15
|
+
```
|
|
16
|
+
|
|
17
|
+
## Usage
|
|
18
|
+
|
|
19
|
+
### Chat (Default)
|
|
20
|
+
|
|
21
|
+
Start memory-augmented chat sessions:
|
|
22
|
+
```bash
|
|
23
|
+
satori "What's the best pizza topping?"
|
|
24
|
+
satori "hello" --provider openai --model gpt-4o
|
|
25
|
+
```
|
|
26
|
+
|
|
27
|
+
### Search Memories
|
|
28
|
+
|
|
29
|
+
Query the memory database:
|
|
30
|
+
```bash
|
|
31
|
+
satori search "what is my favorite food?"
|
|
32
|
+
```
|
|
33
|
+
|
|
34
|
+
### Add Memory
|
|
35
|
+
|
|
36
|
+
Add new memories:
|
|
37
|
+
```bash
|
|
38
|
+
satori add "I like pizza"
|
|
39
|
+
```
|
|
40
|
+
|
|
41
|
+
**Options & Memory:**
|
|
42
|
+
- `--provider <openai|anthropic>` (default: openai)
|
|
43
|
+
- `--model <model>` (default: gpt-4o)
|
|
44
|
+
- `--memory-id <id>` (scopes conversations)
|
|
45
|
+
- `--no-stream` (disables streaming)
|
|
46
|
+
|
|
47
|
+
**Memory Sessions:** If no `--memory-id` is provided, a random ID is generated. Set `SATORI_MEMORY_ID=generated-id` to continue sessions:
|
|
48
|
+
```bash
|
|
49
|
+
export SATORI_MEMORY_ID=example-words-joined
|
|
50
|
+
satori chat "Follow up question"
|
|
51
|
+
```
|
|
52
|
+
|
|
53
|
+
## Configuration
|
|
54
|
+
|
|
55
|
+
**Required:**
|
|
56
|
+
- `SATORI_API_KEY` - Satori authentication key
|
|
57
|
+
|
|
58
|
+
**For Chat:**
|
|
59
|
+
- `OPENAI_API_KEY` / `ANTHROPIC_API_KEY` - LLM provider keys
|
|
60
|
+
|
|
61
|
+
**Optional:**
|
|
62
|
+
- `SATORI_BASE_URL` (default: http://localhost:8000)
|
|
63
|
+
- `SATORI_PROVIDER` (default: openai)
|
|
64
|
+
- `SATORI_MODEL` (default: gpt-4o)
|
|
65
|
+
- `SATORI_MEMORY_ID` - Session scoping
|
|
66
|
+
- `SATORI_MOCK` - Enable mock mode
|
|
67
|
+
|
|
68
|
+
## Troubleshooting
|
|
69
|
+
|
|
70
|
+
- **API Key Errors**: Ensure provider keys are set (`OPENAI_API_KEY` or `ANTHROPIC_API_KEY`)
|
|
71
|
+
- **Memory Server Down**: Chat falls back to raw LLM responses with warning logs
|
|
72
|
+
- **Memory ID Issues**: Invalid IDs scope searches but don't break functionality
|
|
73
|
+
- **Streaming Problems**: Use `--no-stream` for terminal compatibility
|
|
74
|
+
|
|
75
|
+
## Contributing
|
|
76
|
+
|
|
77
|
+
### Publishing
|
|
78
|
+
|
|
79
|
+
1. Update version: `npm version patch|minor|major`
|
|
80
|
+
2. Publish beta: `npm publish --tag beta`
|
|
81
|
+
3. Test: `npm install @satori-sh/cli@beta`
|
|
82
|
+
4. Promote: `npm dist-tag add @satori-sh/cli@<version> latest`
|
|
83
|
+
|
|
84
|
+
### Notes
|
|
85
|
+
- Uses ES modules for modern compatibility
|
|
86
|
+
- `prepublishOnly` builds automatically
|
|
87
|
+
- Test beta versions before promoting
|
|
88
|
+
- OTP required for publishing
|
package/bun.lock
ADDED
|
@@ -0,0 +1,37 @@
|
|
|
1
|
+
{
|
|
2
|
+
"lockfileVersion": 1,
|
|
3
|
+
"workspaces": {
|
|
4
|
+
"": {
|
|
5
|
+
"name": "@satori-sh/cli",
|
|
6
|
+
"dependencies": {
|
|
7
|
+
"chalk": "^5.6.2",
|
|
8
|
+
"commander": "^12.1.0",
|
|
9
|
+
"random-words": "^2.0.1",
|
|
10
|
+
},
|
|
11
|
+
"devDependencies": {
|
|
12
|
+
"@types/bun": "^1.3.3",
|
|
13
|
+
"@types/node": "^24.10.1",
|
|
14
|
+
"typescript": "^5.9.3",
|
|
15
|
+
},
|
|
16
|
+
},
|
|
17
|
+
},
|
|
18
|
+
"packages": {
|
|
19
|
+
"@types/bun": ["@types/bun@1.3.5", "", { "dependencies": { "bun-types": "1.3.5" } }, "sha512-RnygCqNrd3srIPEWBd5LFeUYG7plCoH2Yw9WaZGyNmdTEei+gWaHqydbaIRkIkcbXwhBT94q78QljxN0Sk838w=="],
|
|
20
|
+
|
|
21
|
+
"@types/node": ["@types/node@24.10.4", "", { "dependencies": { "undici-types": "~7.16.0" } }, "sha512-vnDVpYPMzs4wunl27jHrfmwojOGKya0xyM3sH+UE5iv5uPS6vX7UIoh6m+vQc5LGBq52HBKPIn/zcSZVzeDEZg=="],
|
|
22
|
+
|
|
23
|
+
"bun-types": ["bun-types@1.3.5", "", { "dependencies": { "@types/node": "*" } }, "sha512-inmAYe2PFLs0SUbFOWSVD24sg1jFlMPxOjOSSCYqUgn4Hsc3rDc7dFvfVYjFPNHtov6kgUeulV4SxbuIV/stPw=="],
|
|
24
|
+
|
|
25
|
+
"chalk": ["chalk@5.6.2", "", {}, "sha512-7NzBL0rN6fMUW+f7A6Io4h40qQlG+xGmtMxfbnH/K7TAtt8JQWVQK+6g0UXKMeVJoyV5EkkNsErQ8pVD3bLHbA=="],
|
|
26
|
+
|
|
27
|
+
"commander": ["commander@12.1.0", "", {}, "sha512-Vw8qHK3bZM9y/P10u3Vib8o/DdkvA2OtPtZvD871QKjy74Wj1WSKFILMPRPSdUSx5RFK1arlJzEtA4PkFgnbuA=="],
|
|
28
|
+
|
|
29
|
+
"random-words": ["random-words@2.0.1", "", { "dependencies": { "seedrandom": "^3.0.5" } }, "sha512-nZNJAmgcFmtJMTDDIUCm/iK4R6RydC6NvALvWhYItXQrgYGk1F7Gww416LpVROFQtfVd5TaLEf4WuSsko03N7w=="],
|
|
30
|
+
|
|
31
|
+
"seedrandom": ["seedrandom@3.0.5", "", {}, "sha512-8OwmbklUNzwezjGInmZ+2clQmExQPvomqjL7LFqOYqtmuxRgQYqOD3mHaU+MvZn5FLUeVxVfQjwLZW/n/JFuqg=="],
|
|
32
|
+
|
|
33
|
+
"typescript": ["typescript@5.9.3", "", { "bin": { "tsc": "bin/tsc", "tsserver": "bin/tsserver" } }, "sha512-jl1vZzPDinLr9eUt3J/t7V6FgNEw9QjvBPdysz9KfQDD41fQrC2Y4vKQdiaUpFT4bXlb1RHhLpp8wtm6M5TgSw=="],
|
|
34
|
+
|
|
35
|
+
"undici-types": ["undici-types@7.16.0", "", {}, "sha512-Zz+aZWSj8LE6zoxD+xrjh4VfkIG8Ya6LvYkZqtUQGJPZjYl53ypCaUwWqo7eI0x66KBGeRo+mlBEkMSeSZ38Nw=="],
|
|
36
|
+
}
|
|
37
|
+
}
|
package/dist/add.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../src/add.ts"],"names":[],"mappings":"AAGA,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0B7D"}
|
package/dist/add.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getConfig } from './config.js';
|
|
2
|
+
export async function addMemories(text) {
|
|
3
|
+
if (!text || !text.trim()) {
|
|
4
|
+
console.error('Text cannot be empty');
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const response = await fetch(`${config.baseUrl}/memories`, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
'Authorization': `Bearer ${config.apiKey}`
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify({ messages: [{ role: "user", content: text }] })
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
console.error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
await response.json();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error(`Error adding memory: ${error instanceof Error ? error.message : error}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=add.js.map
|
package/dist/add.js.map
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.js","sourceRoot":"","sources":["../src/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY;IAC5C,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACtC,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,WAAW,EAAE;YACzD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,eAAe,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;aAC3C;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,CAAC;SACtE,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,eAAe,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,CAAC,IAAI,EAAiB,CAAC;IACvC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1F,CAAC;AACH,CAAC"}
|
package/dist/config.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAEpC,wBAAgB,SAAS,IAAI,MAAM,CAelC"}
|
package/dist/config.js
ADDED
|
@@ -0,0 +1,15 @@
|
|
|
1
|
+
export function getConfig() {
|
|
2
|
+
const apiKey = process.env.SATORI_API_KEY;
|
|
3
|
+
const baseUrl = process.env.SATORI_BASE_URL || 'http://localhost:8000';
|
|
4
|
+
if (!apiKey) {
|
|
5
|
+
throw new Error('Missing SATORI_API_KEY environment variable');
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
new URL(baseUrl);
|
|
9
|
+
}
|
|
10
|
+
catch {
|
|
11
|
+
throw new Error('Invalid SATORI_BASE_URL format');
|
|
12
|
+
}
|
|
13
|
+
return { apiKey, baseUrl };
|
|
14
|
+
}
|
|
15
|
+
//# sourceMappingURL=config.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.js","sourceRoot":"","sources":["../src/config.ts"],"names":[],"mappings":"AAEA,MAAM,UAAU,SAAS;IACvB,MAAM,MAAM,GAAG,OAAO,CAAC,GAAG,CAAC,cAAc,CAAC;IAC1C,MAAM,OAAO,GAAG,OAAO,CAAC,GAAG,CAAC,eAAe,IAAI,uBAAuB,CAAC;IAEvE,IAAI,CAAC,MAAM,EAAE,CAAC;QACZ,MAAM,IAAI,KAAK,CAAC,6CAA6C,CAAC,CAAC;IACjE,CAAC;IAED,IAAI,CAAC;QACH,IAAI,GAAG,CAAC,OAAO,CAAC,CAAC;IACnB,CAAC;IAAC,MAAM,CAAC;QACP,MAAM,IAAI,KAAK,CAAC,gCAAgC,CAAC,CAAC;IACpD,CAAC;IAED,OAAO,EAAE,MAAM,EAAE,OAAO,EAAE,CAAC;AAC7B,CAAC"}
|
package/dist/index.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAMA,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,wBAAsB,IAAI,kBA0CzB"}
|
package/dist/index.js
ADDED
|
@@ -0,0 +1,49 @@
|
|
|
1
|
+
import { Command } from 'commander';
|
|
2
|
+
import { getConfig } from './config.js';
|
|
3
|
+
import { searchMemories } from './search.js';
|
|
4
|
+
import { addMemories } from './add.js';
|
|
5
|
+
// Re-export for external use
|
|
6
|
+
export { searchMemories } from './search.js';
|
|
7
|
+
export { addMemories } from './add.js';
|
|
8
|
+
export async function main() {
|
|
9
|
+
try {
|
|
10
|
+
getConfig(); // Validate early
|
|
11
|
+
}
|
|
12
|
+
catch (error) {
|
|
13
|
+
console.error(error instanceof Error ? error.message : 'Configuration error');
|
|
14
|
+
process.exit(1);
|
|
15
|
+
}
|
|
16
|
+
const program = new Command();
|
|
17
|
+
program
|
|
18
|
+
.name('satori')
|
|
19
|
+
.description('CLI tool for Satori memory server')
|
|
20
|
+
.version('0.0.1');
|
|
21
|
+
program
|
|
22
|
+
.argument('<query>', 'search query for memories')
|
|
23
|
+
.action(async (query) => {
|
|
24
|
+
await searchMemories(query);
|
|
25
|
+
});
|
|
26
|
+
program
|
|
27
|
+
.command('add')
|
|
28
|
+
.description('add a new memory')
|
|
29
|
+
.argument('<text>', 'text to add as memory')
|
|
30
|
+
.action(async (text) => {
|
|
31
|
+
await addMemories(text);
|
|
32
|
+
});
|
|
33
|
+
program
|
|
34
|
+
.command('chat')
|
|
35
|
+
.description('Start a chat session')
|
|
36
|
+
.option('--provider <provider>', 'Provider to use (openai or anthropic)', 'openai')
|
|
37
|
+
.option('--model <model>', 'Model to use', 'gpt-4o')
|
|
38
|
+
.option('--memory-id <id>', 'Memory ID for scoping')
|
|
39
|
+
.option('--no-stream', 'Disable streaming')
|
|
40
|
+
.action(async (options) => {
|
|
41
|
+
// TODO: Implement chat logic
|
|
42
|
+
console.log('Chat command invoked with options:', options);
|
|
43
|
+
});
|
|
44
|
+
program.parse();
|
|
45
|
+
}
|
|
46
|
+
if (import.meta.main) {
|
|
47
|
+
main();
|
|
48
|
+
}
|
|
49
|
+
//# sourceMappingURL=index.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"index.js","sourceRoot":"","sources":["../src/index.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,OAAO,EAAE,MAAM,WAAW,CAAC;AACpC,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AACxC,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,6BAA6B;AAC7B,OAAO,EAAE,cAAc,EAAE,MAAM,aAAa,CAAC;AAC7C,OAAO,EAAE,WAAW,EAAE,MAAM,UAAU,CAAC;AAEvC,MAAM,CAAC,KAAK,UAAU,IAAI;IACxB,IAAI,CAAC;QACH,SAAS,EAAE,CAAC,CAAC,iBAAiB;IAChC,CAAC;IAAC,OAAO,KAAK,EAAE,CAAC;QACf,OAAO,CAAC,KAAK,CAAC,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,qBAAqB,CAAC,CAAC;QAC9E,OAAO,CAAC,IAAI,CAAC,CAAC,CAAC,CAAC;IAClB,CAAC;IAED,MAAM,OAAO,GAAG,IAAI,OAAO,EAAE,CAAC;IAE9B,OAAO;SACJ,IAAI,CAAC,QAAQ,CAAC;SACd,WAAW,CAAC,mCAAmC,CAAC;SAChD,OAAO,CAAC,OAAO,CAAC,CAAC;IAEpB,OAAO;SACJ,QAAQ,CAAC,SAAS,EAAE,2BAA2B,CAAC;SAChD,MAAM,CAAC,KAAK,EAAE,KAAa,EAAE,EAAE;QAC9B,MAAM,cAAc,CAAC,KAAK,CAAC,CAAC;IAC9B,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,KAAK,CAAC;SACd,WAAW,CAAC,kBAAkB,CAAC;SAC/B,QAAQ,CAAC,QAAQ,EAAE,uBAAuB,CAAC;SAC3C,MAAM,CAAC,KAAK,EAAE,IAAY,EAAE,EAAE;QAC7B,MAAM,WAAW,CAAC,IAAI,CAAC,CAAC;IAC1B,CAAC,CAAC,CAAC;IAEL,OAAO;SACJ,OAAO,CAAC,MAAM,CAAC;SACf,WAAW,CAAC,sBAAsB,CAAC;SACnC,MAAM,CAAC,uBAAuB,EAAE,uCAAuC,EAAE,QAAQ,CAAC;SAClF,MAAM,CAAC,iBAAiB,EAAE,cAAc,EAAE,QAAQ,CAAC;SACnD,MAAM,CAAC,kBAAkB,EAAE,uBAAuB,CAAC;SACnD,MAAM,CAAC,aAAa,EAAE,mBAAmB,CAAC;SAC1C,MAAM,CAAC,KAAK,EAAE,OAAO,EAAE,EAAE;QACxB,6BAA6B;QAC7B,OAAO,CAAC,GAAG,CAAC,oCAAoC,EAAE,OAAO,CAAC,CAAC;IAC7D,CAAC,CAAC,CAAC;IAEL,OAAO,CAAC,KAAK,EAAE,CAAC;AAClB,CAAC;AAED,IAAI,MAAM,CAAC,IAAI,CAAC,IAAI,EAAE,CAAC;IACrB,IAAI,EAAE,CAAC;AACT,CAAC"}
|
package/dist/memory.d.ts
ADDED
|
@@ -0,0 +1,12 @@
|
|
|
1
|
+
import { SearchResponse } from './types.js';
|
|
2
|
+
interface MemoryOptions {
|
|
3
|
+
memoryId?: string;
|
|
4
|
+
topK?: number;
|
|
5
|
+
}
|
|
6
|
+
export declare function buildMemoryContext(prompt: string, options?: MemoryOptions): Promise<{
|
|
7
|
+
results: SearchResponse['results'];
|
|
8
|
+
memoryId: string;
|
|
9
|
+
instruction?: string;
|
|
10
|
+
}>;
|
|
11
|
+
export {};
|
|
12
|
+
//# sourceMappingURL=memory.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.d.ts","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AACA,OAAO,EAAE,cAAc,EAAE,MAAM,YAAY,CAAC;AAG5C,UAAU,aAAa;IACrB,QAAQ,CAAC,EAAE,MAAM,CAAC;IAClB,IAAI,CAAC,EAAE,MAAM,CAAC;CACf;AAED,wBAAsB,kBAAkB,CAAC,MAAM,EAAE,MAAM,EAAE,OAAO,GAAE,aAAkB,GAAG,OAAO,CAAC;IAAE,OAAO,EAAE,cAAc,CAAC,SAAS,CAAC,CAAC;IAAC,QAAQ,EAAE,MAAM,CAAC;IAAC,WAAW,CAAC,EAAE,MAAM,CAAA;CAAE,CAAC,CA2C7K"}
|
package/dist/memory.js
ADDED
|
@@ -0,0 +1,42 @@
|
|
|
1
|
+
import { getConfig } from './config.js';
|
|
2
|
+
import { generate } from 'random-words';
|
|
3
|
+
export async function buildMemoryContext(prompt, options = {}) {
|
|
4
|
+
let memoryId;
|
|
5
|
+
let generated = false;
|
|
6
|
+
if (options.memoryId) {
|
|
7
|
+
memoryId = options.memoryId;
|
|
8
|
+
}
|
|
9
|
+
else if (process.env.SATORI_MEMORY_ID) {
|
|
10
|
+
memoryId = process.env.SATORI_MEMORY_ID;
|
|
11
|
+
}
|
|
12
|
+
else {
|
|
13
|
+
const words = generate({ exactly: 3 });
|
|
14
|
+
memoryId = words.join('-');
|
|
15
|
+
generated = true;
|
|
16
|
+
}
|
|
17
|
+
const topK = options.topK || 5;
|
|
18
|
+
const config = getConfig();
|
|
19
|
+
const response = await fetch(`${config.baseUrl}/search`, {
|
|
20
|
+
method: 'POST',
|
|
21
|
+
headers: {
|
|
22
|
+
'Content-Type': 'application/json',
|
|
23
|
+
'Authorization': `Bearer ${config.apiKey}`
|
|
24
|
+
},
|
|
25
|
+
body: JSON.stringify({
|
|
26
|
+
query: prompt,
|
|
27
|
+
memory_id: memoryId,
|
|
28
|
+
top_k: topK
|
|
29
|
+
})
|
|
30
|
+
});
|
|
31
|
+
if (!response.ok) {
|
|
32
|
+
throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
33
|
+
}
|
|
34
|
+
const data = await response.json();
|
|
35
|
+
const instruction = generated ? `Set SATORI_MEMORY_ID=${memoryId} to reuse this session.` : undefined;
|
|
36
|
+
return {
|
|
37
|
+
results: data.results,
|
|
38
|
+
memoryId,
|
|
39
|
+
instruction
|
|
40
|
+
};
|
|
41
|
+
}
|
|
42
|
+
//# sourceMappingURL=memory.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"memory.js","sourceRoot":"","sources":["../src/memory.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAExC,OAAO,EAAE,QAAQ,EAAE,MAAM,cAAc,CAAC;AAOxC,MAAM,CAAC,KAAK,UAAU,kBAAkB,CAAC,MAAc,EAAE,UAAyB,EAAE;IAClF,IAAI,QAAgB,CAAC;IACrB,IAAI,SAAS,GAAG,KAAK,CAAC;IAEtB,IAAI,OAAO,CAAC,QAAQ,EAAE,CAAC;QACrB,QAAQ,GAAG,OAAO,CAAC,QAAQ,CAAC;IAC9B,CAAC;SAAM,IAAI,OAAO,CAAC,GAAG,CAAC,gBAAgB,EAAE,CAAC;QACxC,QAAQ,GAAG,OAAO,CAAC,GAAG,CAAC,gBAAgB,CAAC;IAC1C,CAAC;SAAM,CAAC;QACN,MAAM,KAAK,GAAG,QAAQ,CAAC,EAAE,OAAO,EAAE,CAAC,EAAE,CAAa,CAAC;QACnD,QAAQ,GAAG,KAAK,CAAC,IAAI,CAAC,GAAG,CAAC,CAAC;QAC3B,SAAS,GAAG,IAAI,CAAC;IACnB,CAAC;IAED,MAAM,IAAI,GAAG,OAAO,CAAC,IAAI,IAAI,CAAC,CAAC;IAE/B,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;IAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,SAAS,EAAE;QACvD,MAAM,EAAE,MAAM;QACd,OAAO,EAAE;YACP,cAAc,EAAE,kBAAkB;YAClC,eAAe,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;SAC3C;QACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC;YACnB,KAAK,EAAE,MAAM;YACb,SAAS,EAAE,QAAQ;YACnB,KAAK,EAAE,IAAI;SACZ,CAAC;KACH,CAAC,CAAC;IAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;QACjB,MAAM,IAAI,KAAK,CAAC,eAAe,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;IAC3E,CAAC;IAED,MAAM,IAAI,GAAG,MAAM,QAAQ,CAAC,IAAI,EAAoB,CAAC;IAErD,MAAM,WAAW,GAAG,SAAS,CAAC,CAAC,CAAC,wBAAwB,QAAQ,yBAAyB,CAAC,CAAC,CAAC,SAAS,CAAC;IAEtG,OAAO;QACL,OAAO,EAAE,IAAI,CAAC,OAAO;QACrB,QAAQ;QACR,WAAW;KACZ,CAAC;AACJ,CAAC"}
|
package/dist/search.d.ts
ADDED
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.d.ts","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAGA,wBAAsB,cAAc,CAAC,KAAK,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BjE"}
|
package/dist/search.js
ADDED
|
@@ -0,0 +1,27 @@
|
|
|
1
|
+
import { getConfig } from './config.js';
|
|
2
|
+
export async function searchMemories(query) {
|
|
3
|
+
if (!query || !query.trim()) {
|
|
4
|
+
console.error('Query cannot be empty');
|
|
5
|
+
return;
|
|
6
|
+
}
|
|
7
|
+
try {
|
|
8
|
+
const config = getConfig();
|
|
9
|
+
const response = await fetch(`${config.baseUrl}/search`, {
|
|
10
|
+
method: 'POST',
|
|
11
|
+
headers: {
|
|
12
|
+
'Content-Type': 'application/json',
|
|
13
|
+
'Authorization': `Bearer ${config.apiKey}`
|
|
14
|
+
},
|
|
15
|
+
body: JSON.stringify({ query })
|
|
16
|
+
});
|
|
17
|
+
if (!response.ok) {
|
|
18
|
+
console.error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
19
|
+
return;
|
|
20
|
+
}
|
|
21
|
+
await response.json();
|
|
22
|
+
}
|
|
23
|
+
catch (error) {
|
|
24
|
+
console.error(`Error searching memories: ${error instanceof Error ? error.message : error}`);
|
|
25
|
+
}
|
|
26
|
+
}
|
|
27
|
+
//# sourceMappingURL=search.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"search.js","sourceRoot":"","sources":["../src/search.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC,MAAM,CAAC,KAAK,UAAU,cAAc,CAAC,KAAa;IAChD,IAAI,CAAC,KAAK,IAAI,CAAC,KAAK,CAAC,IAAI,EAAE,EAAE,CAAC;QAC5B,OAAO,CAAC,KAAK,CAAC,uBAAuB,CAAC,CAAC;QACvC,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,SAAS,EAAE,CAAC;QAC3B,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,SAAS,EAAE;YACvD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,eAAe,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;aAC3C;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,KAAK,EAAE,CAAC;SAChC,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,eAAe,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,CAAC,IAAI,EAAoB,CAAC;IAC1C,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,6BAA6B,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC/F,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,23 @@
|
|
|
1
|
+
/**
|
|
2
|
+
* Adds a new memory to the Satori server.
|
|
3
|
+
*
|
|
4
|
+
* @param {string} text - The text content to add as a memory
|
|
5
|
+
* @param {object} [options] - Additional options for the memory
|
|
6
|
+
* @param {string} [options.memoryId] - Optional memory ID for scoping
|
|
7
|
+
* @returns {Promise<void>} Logs success or error messages to console
|
|
8
|
+
* @throws {Error} If the text is empty
|
|
9
|
+
*
|
|
10
|
+
* @example
|
|
11
|
+
* ```bash
|
|
12
|
+
* satori add "I like pizza"
|
|
13
|
+
* ```
|
|
14
|
+
*
|
|
15
|
+
* @example
|
|
16
|
+
* ```typescript
|
|
17
|
+
* await addMemories("User prefers dark mode", { memoryId: "session-123" });
|
|
18
|
+
* ```
|
|
19
|
+
*/
|
|
20
|
+
export declare function addMemories(text: string, options?: {
|
|
21
|
+
memoryId?: string;
|
|
22
|
+
}): Promise<void>;
|
|
23
|
+
//# sourceMappingURL=add.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.d.ts","sourceRoot":"","sources":["../../src/add.ts"],"names":[],"mappings":"AAGA;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAsB,WAAW,CAAC,IAAI,EAAE,MAAM,EAAE,OAAO,GAAE;IAAE,QAAQ,CAAC,EAAE,MAAM,CAAA;CAAO,GAAG,OAAO,CAAC,IAAI,CAAC,CA0BlG"}
|
package/dist/src/add.js
ADDED
|
@@ -0,0 +1,46 @@
|
|
|
1
|
+
import { getConfig } from './config.js';
|
|
2
|
+
/**
|
|
3
|
+
* Adds a new memory to the Satori server.
|
|
4
|
+
*
|
|
5
|
+
* @param {string} text - The text content to add as a memory
|
|
6
|
+
* @param {object} [options] - Additional options for the memory
|
|
7
|
+
* @param {string} [options.memoryId] - Optional memory ID for scoping
|
|
8
|
+
* @returns {Promise<void>} Logs success or error messages to console
|
|
9
|
+
* @throws {Error} If the text is empty
|
|
10
|
+
*
|
|
11
|
+
* @example
|
|
12
|
+
* ```bash
|
|
13
|
+
* satori add "I like pizza"
|
|
14
|
+
* ```
|
|
15
|
+
*
|
|
16
|
+
* @example
|
|
17
|
+
* ```typescript
|
|
18
|
+
* await addMemories("User prefers dark mode", { memoryId: "session-123" });
|
|
19
|
+
* ```
|
|
20
|
+
*/
|
|
21
|
+
export async function addMemories(text, options = {}) {
|
|
22
|
+
if (!text || !text.trim()) {
|
|
23
|
+
console.error('Text cannot be empty');
|
|
24
|
+
return;
|
|
25
|
+
}
|
|
26
|
+
try {
|
|
27
|
+
const config = await getConfig();
|
|
28
|
+
const response = await fetch(`${config.baseUrl}/memories`, {
|
|
29
|
+
method: 'POST',
|
|
30
|
+
headers: {
|
|
31
|
+
'Content-Type': 'application/json',
|
|
32
|
+
'Authorization': `Bearer ${config.apiKey}`
|
|
33
|
+
},
|
|
34
|
+
body: JSON.stringify({ messages: [{ role: "user", content: text }], ...(options.memoryId && { memory_id: options.memoryId }) })
|
|
35
|
+
});
|
|
36
|
+
if (!response.ok) {
|
|
37
|
+
console.error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
38
|
+
return;
|
|
39
|
+
}
|
|
40
|
+
await response.json();
|
|
41
|
+
}
|
|
42
|
+
catch (error) {
|
|
43
|
+
console.error(`Error adding memory: ${error instanceof Error ? error.message : error}`);
|
|
44
|
+
}
|
|
45
|
+
}
|
|
46
|
+
//# sourceMappingURL=add.js.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"add.js","sourceRoot":"","sources":["../../src/add.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,SAAS,EAAE,MAAM,aAAa,CAAC;AAGxC;;;;;;;;;;;;;;;;;;GAkBG;AACH,MAAM,CAAC,KAAK,UAAU,WAAW,CAAC,IAAY,EAAE,UAAiC,EAAE;IACjF,IAAI,CAAC,IAAI,IAAI,CAAC,IAAI,CAAC,IAAI,EAAE,EAAE,CAAC;QAC1B,OAAO,CAAC,KAAK,CAAC,sBAAsB,CAAC,CAAC;QACtC,OAAO;IACT,CAAC;IAED,IAAI,CAAC;QACH,MAAM,MAAM,GAAG,MAAM,SAAS,EAAE,CAAC;QACjC,MAAM,QAAQ,GAAG,MAAM,KAAK,CAAC,GAAG,MAAM,CAAC,OAAO,WAAW,EAAE;YACzD,MAAM,EAAE,MAAM;YACd,OAAO,EAAE;gBACP,cAAc,EAAE,kBAAkB;gBAClC,eAAe,EAAE,UAAU,MAAM,CAAC,MAAM,EAAE;aAC3C;YACD,IAAI,EAAE,IAAI,CAAC,SAAS,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,IAAI,EAAE,MAAM,EAAE,OAAO,EAAE,IAAI,EAAE,CAAC,EAAE,GAAG,CAAC,OAAO,CAAC,QAAQ,IAAI,EAAE,SAAS,EAAE,OAAO,CAAC,QAAQ,EAAE,CAAC,EAAE,CAAC;SAChI,CAAC,CAAC;QAEH,IAAI,CAAC,QAAQ,CAAC,EAAE,EAAE,CAAC;YACjB,OAAO,CAAC,KAAK,CAAC,eAAe,QAAQ,CAAC,MAAM,IAAI,QAAQ,CAAC,UAAU,EAAE,CAAC,CAAC;YACvE,OAAO;QACT,CAAC;QAED,MAAM,QAAQ,CAAC,IAAI,EAAiB,CAAC;IACvC,CAAC;IAAC,OAAO,KAAc,EAAE,CAAC;QACxB,OAAO,CAAC,KAAK,CAAC,wBAAwB,KAAK,YAAY,KAAK,CAAC,CAAC,CAAC,KAAK,CAAC,OAAO,CAAC,CAAC,CAAC,KAAK,EAAE,CAAC,CAAC;IAC1F,CAAC;AACH,CAAC"}
|
|
@@ -0,0 +1,39 @@
|
|
|
1
|
+
import { Config } from './types.js';
|
|
2
|
+
/**
|
|
3
|
+
* Checks write access to the config directory by attempting to create, write, and delete a temporary file.
|
|
4
|
+
*
|
|
5
|
+
* @throws {Error} If write access is denied or any operation fails
|
|
6
|
+
*/
|
|
7
|
+
export declare function checkWriteAccess(): Promise<void>;
|
|
8
|
+
/**
|
|
9
|
+
* Saves the API key to the config file.
|
|
10
|
+
*
|
|
11
|
+
* @param {string} apiKey - The API key to save
|
|
12
|
+
* @throws {Error} If write access fails or saving fails
|
|
13
|
+
*/
|
|
14
|
+
export declare function saveApiKey(apiKey: string): Promise<void>;
|
|
15
|
+
/**
|
|
16
|
+
* Saves the memory ID to the config file.
|
|
17
|
+
*
|
|
18
|
+
* @param {string} memoryId - The memory ID to save
|
|
19
|
+
* @throws {Error} If write access fails or saving fails
|
|
20
|
+
*/
|
|
21
|
+
export declare function saveMemoryId(memoryId: string): Promise<void>;
|
|
22
|
+
/**
|
|
23
|
+
* Retrieves and validates configuration from file and environment variables.
|
|
24
|
+
* Fetches the memory_id and api_key from the config file in ~/.config/satori/satori.json.
|
|
25
|
+
*
|
|
26
|
+
* If the API key isn't in the config, it looks in SATORI_API_KEY.
|
|
27
|
+
* If there is no API key in either place it will call the API to get a new key.
|
|
28
|
+
*
|
|
29
|
+
* @returns {Promise<Config>} The validated configuration object
|
|
30
|
+
* @throws {Error} If required environment variables are missing or invalid
|
|
31
|
+
*
|
|
32
|
+
* @example
|
|
33
|
+
* ```typescript
|
|
34
|
+
* const config = await getConfig();
|
|
35
|
+
* console.log(config.apiKey); // API key from ~/.config/satori/satori.json or SATORI_API_KEY env var, or null
|
|
36
|
+
* ```
|
|
37
|
+
*/
|
|
38
|
+
export declare function getConfig(): Promise<Config>;
|
|
39
|
+
//# sourceMappingURL=config.d.ts.map
|
|
@@ -0,0 +1 @@
|
|
|
1
|
+
{"version":3,"file":"config.d.ts","sourceRoot":"","sources":["../../src/config.ts"],"names":[],"mappings":"AAAA,OAAO,EAAE,MAAM,EAAE,MAAM,YAAY,CAAC;AAKpC;;;;GAIG;AACH,wBAAsB,gBAAgB,IAAI,OAAO,CAAC,IAAI,CAAC,CAatD;AAED;;;;;GAKG;AACH,wBAAsB,UAAU,CAAC,MAAM,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAa9D;AAED;;;;;GAKG;AACH,wBAAsB,YAAY,CAAC,QAAQ,EAAE,MAAM,GAAG,OAAO,CAAC,IAAI,CAAC,CAalE;AAoBD;;;;;;;;;;;;;;;GAeG;AACH,wBAAsB,SAAS,IAAI,OAAO,CAAC,MAAM,CAAC,CAoEjD"}
|
|
@@ -0,0 +1,165 @@
|
|
|
1
|
+
import { homedir } from 'node:os';
|
|
2
|
+
import { join } from 'node:path';
|
|
3
|
+
import { randomUUID } from 'node:crypto';
|
|
4
|
+
/**
|
|
5
|
+
* Checks write access to the config directory by attempting to create, write, and delete a temporary file.
|
|
6
|
+
*
|
|
7
|
+
* @throws {Error} If write access is denied or any operation fails
|
|
8
|
+
*/
|
|
9
|
+
export async function checkWriteAccess() {
|
|
10
|
+
// Dynamic import used instead of static import at top-level to enable mocking with Bun's mock.module() in tests.
|
|
11
|
+
// Static imports cannot be overridden by module mocks in Bun, so this allows fs operations to be properly mocked.
|
|
12
|
+
const { promises: fs } = await import('node:fs');
|
|
13
|
+
const dir = join(homedir(), '.config', 'satori');
|
|
14
|
+
try {
|
|
15
|
+
await fs.mkdir(dir, { recursive: true });
|
|
16
|
+
const tempFile = join(dir, `temp-${randomUUID()}.txt`);
|
|
17
|
+
await fs.writeFile(tempFile, 'test');
|
|
18
|
+
await fs.unlink(tempFile);
|
|
19
|
+
}
|
|
20
|
+
catch (error) {
|
|
21
|
+
throw new Error(`Cannot write to config directory: ${error instanceof Error ? error.message : error}`);
|
|
22
|
+
}
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Saves the API key to the config file.
|
|
26
|
+
*
|
|
27
|
+
* @param {string} apiKey - The API key to save
|
|
28
|
+
* @throws {Error} If write access fails or saving fails
|
|
29
|
+
*/
|
|
30
|
+
export async function saveApiKey(apiKey) {
|
|
31
|
+
// Dynamic import used instead of static import at top-level to enable mocking with Bun's mock.module() in tests.
|
|
32
|
+
// Static imports cannot be overridden by module mocks in Bun, so this allows fs operations to be properly mocked.
|
|
33
|
+
const { promises: fs } = await import('node:fs');
|
|
34
|
+
await checkWriteAccess();
|
|
35
|
+
const configPath = join(homedir(), '.config', 'satori', 'satori.json');
|
|
36
|
+
try {
|
|
37
|
+
const existing = await loadConfigFile();
|
|
38
|
+
const config = { ...existing, api_key: apiKey };
|
|
39
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
40
|
+
}
|
|
41
|
+
catch (error) {
|
|
42
|
+
throw new Error(`Failed to save API key: ${error instanceof Error ? error.message : error}`);
|
|
43
|
+
}
|
|
44
|
+
}
|
|
45
|
+
/**
|
|
46
|
+
* Saves the memory ID to the config file.
|
|
47
|
+
*
|
|
48
|
+
* @param {string} memoryId - The memory ID to save
|
|
49
|
+
* @throws {Error} If write access fails or saving fails
|
|
50
|
+
*/
|
|
51
|
+
export async function saveMemoryId(memoryId) {
|
|
52
|
+
// Dynamic import used instead of static import at top-level to enable mocking with Bun's mock.module() in tests.
|
|
53
|
+
// Static imports cannot be overridden by module mocks in Bun, so this allows fs operations to be properly mocked.
|
|
54
|
+
const { promises: fs } = await import('node:fs');
|
|
55
|
+
await checkWriteAccess();
|
|
56
|
+
const configPath = join(homedir(), '.config', 'satori', 'satori.json');
|
|
57
|
+
try {
|
|
58
|
+
const existing = await loadConfigFile();
|
|
59
|
+
const config = { ...existing, memory_id: memoryId };
|
|
60
|
+
await fs.writeFile(configPath, JSON.stringify(config, null, 2));
|
|
61
|
+
}
|
|
62
|
+
catch (error) {
|
|
63
|
+
throw new Error(`Failed to save memory ID: ${error instanceof Error ? error.message : error}`);
|
|
64
|
+
}
|
|
65
|
+
}
|
|
66
|
+
/**
|
|
67
|
+
* Loads the config file data.
|
|
68
|
+
*
|
|
69
|
+
* @returns {Promise<Record<string, any>>} The config data
|
|
70
|
+
*/
|
|
71
|
+
async function loadConfigFile() {
|
|
72
|
+
try {
|
|
73
|
+
const configPath = join(homedir(), '.config', 'satori', 'satori.json');
|
|
74
|
+
const configFile = Bun.file(configPath);
|
|
75
|
+
if (await configFile.exists()) {
|
|
76
|
+
return await configFile.json();
|
|
77
|
+
}
|
|
78
|
+
}
|
|
79
|
+
catch {
|
|
80
|
+
// Ignore errors
|
|
81
|
+
}
|
|
82
|
+
return {};
|
|
83
|
+
}
|
|
84
|
+
/**
|
|
85
|
+
* Retrieves and validates configuration from file and environment variables.
|
|
86
|
+
* Fetches the memory_id and api_key from the config file in ~/.config/satori/satori.json.
|
|
87
|
+
*
|
|
88
|
+
* If the API key isn't in the config, it looks in SATORI_API_KEY.
|
|
89
|
+
* If there is no API key in either place it will call the API to get a new key.
|
|
90
|
+
*
|
|
91
|
+
* @returns {Promise<Config>} The validated configuration object
|
|
92
|
+
* @throws {Error} If required environment variables are missing or invalid
|
|
93
|
+
*
|
|
94
|
+
* @example
|
|
95
|
+
* ```typescript
|
|
96
|
+
* const config = await getConfig();
|
|
97
|
+
* console.log(config.apiKey); // API key from ~/.config/satori/satori.json or SATORI_API_KEY env var, or null
|
|
98
|
+
* ```
|
|
99
|
+
*/
|
|
100
|
+
export async function getConfig() {
|
|
101
|
+
if (process.platform !== 'darwin') {
|
|
102
|
+
throw new Error('We do not currently support Windows yet, email support@satori.sh to request Windows support');
|
|
103
|
+
}
|
|
104
|
+
let apiKey = null;
|
|
105
|
+
let memoryId = undefined;
|
|
106
|
+
try {
|
|
107
|
+
const configPath = join(homedir(), '.config', 'satori', 'satori.json');
|
|
108
|
+
const configFile = Bun.file(configPath);
|
|
109
|
+
if (await configFile.exists()) {
|
|
110
|
+
const data = await configFile.json();
|
|
111
|
+
if (data && typeof data.api_key === 'string') {
|
|
112
|
+
apiKey = data.api_key;
|
|
113
|
+
}
|
|
114
|
+
if (data && typeof data.memory_id === 'string') {
|
|
115
|
+
memoryId = data.memory_id;
|
|
116
|
+
}
|
|
117
|
+
}
|
|
118
|
+
}
|
|
119
|
+
catch {
|
|
120
|
+
// If file reading or parsing fails, apiKey and memoryId remain default
|
|
121
|
+
}
|
|
122
|
+
// Fallback to environment variable if not set from file
|
|
123
|
+
if (!apiKey) {
|
|
124
|
+
apiKey = process.env.SATORI_API_KEY || null;
|
|
125
|
+
}
|
|
126
|
+
const baseUrl = process.env.SATORI_BASE_URL || 'https://api.satori.sh';
|
|
127
|
+
try {
|
|
128
|
+
new URL(baseUrl);
|
|
129
|
+
}
|
|
130
|
+
catch {
|
|
131
|
+
throw new Error('Invalid SATORI_BASE_URL format');
|
|
132
|
+
}
|
|
133
|
+
// Generate new API key if still null
|
|
134
|
+
if (!apiKey) {
|
|
135
|
+
try {
|
|
136
|
+
const response = await fetch(`${baseUrl}/orgs`, {
|
|
137
|
+
method: 'POST',
|
|
138
|
+
});
|
|
139
|
+
if (!response.ok) {
|
|
140
|
+
throw new Error(`HTTP error: ${response.status} ${response.statusText}`);
|
|
141
|
+
}
|
|
142
|
+
const data = await response.json();
|
|
143
|
+
if (data && typeof data.api_key === 'string') {
|
|
144
|
+
apiKey = data.api_key;
|
|
145
|
+
await saveApiKey(apiKey);
|
|
146
|
+
}
|
|
147
|
+
else {
|
|
148
|
+
throw new Error('Invalid response: missing api_key');
|
|
149
|
+
}
|
|
150
|
+
}
|
|
151
|
+
catch (error) {
|
|
152
|
+
throw new Error(`Failed to generate API key: ${error instanceof Error ? error.message : error}`);
|
|
153
|
+
}
|
|
154
|
+
}
|
|
155
|
+
const provider = process.env.SATORI_PROVIDER || 'openai';
|
|
156
|
+
const model = process.env.SATORI_MODEL || 'gpt-4o';
|
|
157
|
+
const openaiKey = process.env.OPENAI_API_KEY;
|
|
158
|
+
const anthropicKey = process.env.ANTHROPIC_API_KEY;
|
|
159
|
+
// Fallback to config file memoryId if not set via env
|
|
160
|
+
if (!memoryId) {
|
|
161
|
+
memoryId = process.env.SATORI_MEMORY_ID;
|
|
162
|
+
}
|
|
163
|
+
return { apiKey, baseUrl, provider, model, openaiKey, anthropicKey, memoryId };
|
|
164
|
+
}
|
|
165
|
+
//# sourceMappingURL=config.js.map
|