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 CHANGED
@@ -1 +1 @@
1
- 2.1.1
1
+ 2.2.0
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
@@ -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,
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
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "arkaos",
3
- "version": "2.1.1",
3
+ "version": "2.2.0",
4
4
  "description": "The Operating System for AI Agent Teams",
5
5
  "type": "module",
6
6
  "bin": {
package/pyproject.toml CHANGED
@@ -1,6 +1,6 @@
1
1
  [project]
2
2
  name = "arkaos-core"
3
- version = "2.1.1"
3
+ version = "2.2.0"
4
4
  description = "Core engine for ArkaOS — The Operating System for AI Agent Teams"
5
5
  readme = "README.md"
6
6
  license = {text = "MIT"}