arkaos 2.1.1 → 2.2.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/VERSION +1 -1
- package/core/__pycache__/keys.cpython-313.pyc +0 -0
- package/core/keys.py +91 -0
- package/core/knowledge/__pycache__/ingest.cpython-313.pyc +0 -0
- package/core/knowledge/ingest.py +16 -0
- package/core/personas/__pycache__/__init__.cpython-313.pyc +0 -0
- package/core/personas/__pycache__/manager.cpython-313.pyc +0 -0
- package/core/personas/__pycache__/schema.cpython-313.pyc +0 -0
- package/installer/cli.js +7 -0
- package/installer/keys.js +84 -0
- package/package.json +1 -1
- package/pyproject.toml +1 -1
package/VERSION
CHANGED
|
@@ -1 +1 @@
|
|
|
1
|
-
2.
|
|
1
|
+
2.2.0
|
|
Binary file
|
package/core/keys.py
ADDED
|
@@ -0,0 +1,91 @@
|
|
|
1
|
+
"""API key management — store, retrieve, and list API keys securely.
|
|
2
|
+
|
|
3
|
+
Keys stored in ~/.arkaos/keys.json with 600 permissions (owner only).
|
|
4
|
+
"""
|
|
5
|
+
|
|
6
|
+
import json
|
|
7
|
+
import os
|
|
8
|
+
import stat
|
|
9
|
+
from pathlib import Path
|
|
10
|
+
from typing import Optional
|
|
11
|
+
|
|
12
|
+
KEYS_PATH = Path.home() / ".arkaos" / "keys.json"
|
|
13
|
+
|
|
14
|
+
# Known providers with descriptions
|
|
15
|
+
PROVIDERS = {
|
|
16
|
+
"OPENAI_API_KEY": {"name": "OpenAI", "used_for": "Whisper transcription, embeddings, GPT"},
|
|
17
|
+
"GOOGLE_API_KEY": {"name": "Google", "used_for": "Gemini API, Nano Banana, Google Cloud AI"},
|
|
18
|
+
"FAL_API_KEY": {"name": "fal.ai", "used_for": "Image generation, video generation"},
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
|
|
22
|
+
def _load() -> dict[str, str]:
|
|
23
|
+
if not KEYS_PATH.exists():
|
|
24
|
+
return {}
|
|
25
|
+
return json.loads(KEYS_PATH.read_text())
|
|
26
|
+
|
|
27
|
+
|
|
28
|
+
def _save(keys: dict[str, str]) -> None:
|
|
29
|
+
KEYS_PATH.parent.mkdir(parents=True, exist_ok=True)
|
|
30
|
+
KEYS_PATH.write_text(json.dumps(keys, indent=2))
|
|
31
|
+
os.chmod(KEYS_PATH, stat.S_IRUSR | stat.S_IWUSR) # 600
|
|
32
|
+
|
|
33
|
+
|
|
34
|
+
def get_key(name: str) -> Optional[str]:
|
|
35
|
+
"""Get an API key by name. Also checks environment variables."""
|
|
36
|
+
env_val = os.environ.get(name)
|
|
37
|
+
if env_val:
|
|
38
|
+
return env_val
|
|
39
|
+
keys = _load()
|
|
40
|
+
return keys.get(name)
|
|
41
|
+
|
|
42
|
+
|
|
43
|
+
def set_key(name: str, value: str) -> None:
|
|
44
|
+
"""Set an API key."""
|
|
45
|
+
keys = _load()
|
|
46
|
+
keys[name] = value
|
|
47
|
+
_save(keys)
|
|
48
|
+
|
|
49
|
+
|
|
50
|
+
def remove_key(name: str) -> bool:
|
|
51
|
+
"""Remove an API key. Returns True if it existed."""
|
|
52
|
+
keys = _load()
|
|
53
|
+
if name in keys:
|
|
54
|
+
del keys[name]
|
|
55
|
+
_save(keys)
|
|
56
|
+
return True
|
|
57
|
+
return False
|
|
58
|
+
|
|
59
|
+
|
|
60
|
+
def list_keys() -> list[dict]:
|
|
61
|
+
"""List configured keys (masked values)."""
|
|
62
|
+
keys = _load()
|
|
63
|
+
result = []
|
|
64
|
+
for name, info in PROVIDERS.items():
|
|
65
|
+
value = keys.get(name, "")
|
|
66
|
+
env_val = os.environ.get(name, "")
|
|
67
|
+
configured = bool(value or env_val)
|
|
68
|
+
masked = ""
|
|
69
|
+
if value:
|
|
70
|
+
masked = value[:4] + "..." + value[-4:] if len(value) > 10 else "****"
|
|
71
|
+
elif env_val:
|
|
72
|
+
masked = "(from environment)"
|
|
73
|
+
result.append({
|
|
74
|
+
"key": name,
|
|
75
|
+
"provider": info["name"],
|
|
76
|
+
"used_for": info["used_for"],
|
|
77
|
+
"configured": configured,
|
|
78
|
+
"masked_value": masked,
|
|
79
|
+
})
|
|
80
|
+
# Include any custom keys not in PROVIDERS
|
|
81
|
+
for name, value in keys.items():
|
|
82
|
+
if name not in PROVIDERS:
|
|
83
|
+
masked = value[:4] + "..." + value[-4:] if len(value) > 10 else "****"
|
|
84
|
+
result.append({
|
|
85
|
+
"key": name,
|
|
86
|
+
"provider": "Custom",
|
|
87
|
+
"used_for": "",
|
|
88
|
+
"configured": True,
|
|
89
|
+
"masked_value": masked,
|
|
90
|
+
})
|
|
91
|
+
return result
|
|
Binary file
|
package/core/knowledge/ingest.py
CHANGED
|
@@ -119,6 +119,22 @@ class IngestEngine:
|
|
|
119
119
|
|
|
120
120
|
progress(100, f"Done — {count} chunks indexed")
|
|
121
121
|
|
|
122
|
+
# Record token usage in budget
|
|
123
|
+
try:
|
|
124
|
+
from core.budget.manager import BudgetManager
|
|
125
|
+
from pathlib import Path as BudgetPath
|
|
126
|
+
budget_mgr = BudgetManager(storage_path=BudgetPath.home() / ".arkaos" / "budget-usage.json")
|
|
127
|
+
tokens_est = len(text) // 4 # ~1 token per 4 chars
|
|
128
|
+
budget_mgr.record_usage(
|
|
129
|
+
agent_id="kb-indexer",
|
|
130
|
+
tokens=tokens_est,
|
|
131
|
+
tier=2,
|
|
132
|
+
department="kb",
|
|
133
|
+
description=f"ingest-{source_type}: {source[:60]}",
|
|
134
|
+
)
|
|
135
|
+
except Exception:
|
|
136
|
+
pass
|
|
137
|
+
|
|
122
138
|
return IngestResult(
|
|
123
139
|
source=source,
|
|
124
140
|
source_type=source_type,
|
|
Binary file
|
|
Binary file
|
|
Binary file
|
package/installer/cli.js
CHANGED
|
@@ -40,6 +40,7 @@ Usage:
|
|
|
40
40
|
npx arkaos update Update to latest version
|
|
41
41
|
npx arkaos migrate Migrate from v1 to v2
|
|
42
42
|
npx arkaos dashboard Start monitoring dashboard
|
|
43
|
+
npx arkaos keys Manage API keys (OpenAI, fal.ai, etc.)
|
|
43
44
|
npx arkaos doctor Run health checks
|
|
44
45
|
npx arkaos uninstall Remove ArkaOS
|
|
45
46
|
|
|
@@ -99,6 +100,12 @@ async function main() {
|
|
|
99
100
|
await migrate();
|
|
100
101
|
break;
|
|
101
102
|
|
|
103
|
+
case "keys": {
|
|
104
|
+
const { keys: keysCmd } = await import("./keys.js");
|
|
105
|
+
await keysCmd(positionals.slice(1));
|
|
106
|
+
break;
|
|
107
|
+
}
|
|
108
|
+
|
|
102
109
|
case "dashboard": {
|
|
103
110
|
const { execSync: execDash } = await import("node:child_process");
|
|
104
111
|
const repoRootDash = dirname(fileURLToPath(import.meta.url)).replace(/\/installer$/, "");
|
|
@@ -0,0 +1,84 @@
|
|
|
1
|
+
import { existsSync, readFileSync, writeFileSync, chmodSync } from "node:fs";
|
|
2
|
+
import { join } from "node:path";
|
|
3
|
+
import { homedir } from "node:os";
|
|
4
|
+
|
|
5
|
+
const KEYS_PATH = join(homedir(), ".arkaos", "keys.json");
|
|
6
|
+
|
|
7
|
+
const PROVIDERS = {
|
|
8
|
+
OPENAI_API_KEY: { name: "OpenAI", used_for: "Whisper transcription, embeddings, GPT" },
|
|
9
|
+
GOOGLE_API_KEY: { name: "Google", used_for: "Gemini API, Nano Banana, Google Cloud AI" },
|
|
10
|
+
FAL_API_KEY: { name: "fal.ai", used_for: "Image generation, video generation" },
|
|
11
|
+
};
|
|
12
|
+
|
|
13
|
+
function loadKeys() {
|
|
14
|
+
if (!existsSync(KEYS_PATH)) return {};
|
|
15
|
+
return JSON.parse(readFileSync(KEYS_PATH, "utf-8"));
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
function saveKeys(keys) {
|
|
19
|
+
writeFileSync(KEYS_PATH, JSON.stringify(keys, null, 2));
|
|
20
|
+
try { chmodSync(KEYS_PATH, 0o600); } catch {}
|
|
21
|
+
}
|
|
22
|
+
|
|
23
|
+
function mask(value) {
|
|
24
|
+
if (!value) return "";
|
|
25
|
+
return value.length > 10 ? value.slice(0, 4) + "..." + value.slice(-4) : "****";
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
export async function keys(args) {
|
|
29
|
+
const [action, keyName, keyValue] = args;
|
|
30
|
+
|
|
31
|
+
if (action === "set") {
|
|
32
|
+
if (!keyName || !keyValue) {
|
|
33
|
+
console.error(" Usage: npx arkaos keys set KEY_NAME value");
|
|
34
|
+
console.error("\n Available keys:");
|
|
35
|
+
for (const [k, v] of Object.entries(PROVIDERS)) {
|
|
36
|
+
console.error(` ${k.padEnd(20)} ${v.name} — ${v.used_for}`);
|
|
37
|
+
}
|
|
38
|
+
process.exit(1);
|
|
39
|
+
}
|
|
40
|
+
const keys = loadKeys();
|
|
41
|
+
keys[keyName] = keyValue;
|
|
42
|
+
saveKeys(keys);
|
|
43
|
+
console.log(`\n Set ${keyName} = ${mask(keyValue)}`);
|
|
44
|
+
console.log(` Stored in: ${KEYS_PATH}\n`);
|
|
45
|
+
return;
|
|
46
|
+
}
|
|
47
|
+
|
|
48
|
+
if (action === "remove" || action === "delete") {
|
|
49
|
+
if (!keyName) {
|
|
50
|
+
console.error(" Usage: npx arkaos keys remove KEY_NAME");
|
|
51
|
+
process.exit(1);
|
|
52
|
+
}
|
|
53
|
+
const keys = loadKeys();
|
|
54
|
+
if (keyName in keys) {
|
|
55
|
+
delete keys[keyName];
|
|
56
|
+
saveKeys(keys);
|
|
57
|
+
console.log(`\n Removed ${keyName}\n`);
|
|
58
|
+
} else {
|
|
59
|
+
console.log(`\n Key ${keyName} not found.\n`);
|
|
60
|
+
}
|
|
61
|
+
return;
|
|
62
|
+
}
|
|
63
|
+
|
|
64
|
+
// Default: list keys
|
|
65
|
+
const keys = loadKeys();
|
|
66
|
+
console.log("\n ArkaOS API Keys\n");
|
|
67
|
+
|
|
68
|
+
for (const [k, v] of Object.entries(PROVIDERS)) {
|
|
69
|
+
const value = keys[k] || process.env[k] || "";
|
|
70
|
+
const status = value ? "\x1b[32m configured\x1b[0m" : "\x1b[90m not set\x1b[0m";
|
|
71
|
+
const masked = keys[k] ? mask(keys[k]) : (process.env[k] ? "(env)" : "");
|
|
72
|
+
console.log(` ${k.padEnd(22)} ${v.name.padEnd(12)} ${status} ${masked}`);
|
|
73
|
+
}
|
|
74
|
+
|
|
75
|
+
// Custom keys
|
|
76
|
+
for (const [k, v] of Object.entries(keys)) {
|
|
77
|
+
if (!(k in PROVIDERS)) {
|
|
78
|
+
console.log(` ${k.padEnd(22)} Custom \x1b[32m configured\x1b[0m ${mask(v)}`);
|
|
79
|
+
}
|
|
80
|
+
}
|
|
81
|
+
|
|
82
|
+
console.log(`\n Set a key: npx arkaos keys set OPENAI_API_KEY sk-...`);
|
|
83
|
+
console.log(` Remove a key: npx arkaos keys remove OPENAI_API_KEY\n`);
|
|
84
|
+
}
|
package/package.json
CHANGED