@agentprojectcontext/apx 1.1.0 → 1.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/package.json +1 -1
- package/src/cli/commands/setup.js +336 -0
- package/src/cli/index.js +7 -0
package/package.json
CHANGED
|
@@ -0,0 +1,336 @@
|
|
|
1
|
+
// apx setup — interactive first-run wizard.
|
|
2
|
+
// Guides the user through provider, model, channels, and language.
|
|
3
|
+
// Starts the daemon and sends a fun wake-up message when done.
|
|
4
|
+
|
|
5
|
+
import fs from "node:fs";
|
|
6
|
+
import https from "node:https";
|
|
7
|
+
import http from "node:http";
|
|
8
|
+
import readline from "node:readline";
|
|
9
|
+
import { spawnSync, spawn } from "node:child_process";
|
|
10
|
+
import { readConfig, writeConfig, APX_HOME } from "../../core/config.js";
|
|
11
|
+
|
|
12
|
+
// ── ANSI helpers ──────────────────────────────────────────────────────────────
|
|
13
|
+
const c = {
|
|
14
|
+
reset: "\x1b[0m",
|
|
15
|
+
bold: "\x1b[1m",
|
|
16
|
+
dim: "\x1b[2m",
|
|
17
|
+
cyan: "\x1b[36m",
|
|
18
|
+
green: "\x1b[32m",
|
|
19
|
+
yellow:"\x1b[33m",
|
|
20
|
+
red: "\x1b[31m",
|
|
21
|
+
gray: "\x1b[90m",
|
|
22
|
+
};
|
|
23
|
+
const b = (s) => `${c.bold}${s}${c.reset}`;
|
|
24
|
+
const cy = (s) => `${c.cyan}${s}${c.reset}`;
|
|
25
|
+
const gr = (s) => `${c.green}${s}${c.reset}`;
|
|
26
|
+
const di = (s) => `${c.dim}${s}${c.reset}`;
|
|
27
|
+
|
|
28
|
+
// ── readline helpers ─────────────────────────────────────────────────────────
|
|
29
|
+
let rl;
|
|
30
|
+
function initRl() {
|
|
31
|
+
rl = readline.createInterface({ input: process.stdin, output: process.stdout });
|
|
32
|
+
}
|
|
33
|
+
function ask(prompt) {
|
|
34
|
+
return new Promise((resolve) => rl.question(prompt, (a) => resolve(a.trim())));
|
|
35
|
+
}
|
|
36
|
+
function close() { rl.close(); }
|
|
37
|
+
|
|
38
|
+
// ── Fetch helpers ─────────────────────────────────────────────────────────────
|
|
39
|
+
function fetchJson(url, timeout = 4000) {
|
|
40
|
+
return new Promise((resolve) => {
|
|
41
|
+
const mod = url.startsWith("https") ? https : http;
|
|
42
|
+
const req = mod.get(url, { timeout }, (res) => {
|
|
43
|
+
let body = "";
|
|
44
|
+
res.on("data", (c) => (body += c));
|
|
45
|
+
res.on("end", () => { try { resolve(JSON.parse(body)); } catch { resolve(null); } });
|
|
46
|
+
});
|
|
47
|
+
req.on("error", () => resolve(null));
|
|
48
|
+
req.on("timeout", () => { req.destroy(); resolve(null); });
|
|
49
|
+
});
|
|
50
|
+
}
|
|
51
|
+
|
|
52
|
+
async function fetchOllamaModels(baseUrl) {
|
|
53
|
+
const data = await fetchJson(`${baseUrl.replace(/\/$/, "")}/api/tags`);
|
|
54
|
+
if (!data?.models) return [];
|
|
55
|
+
return data.models.map((m) => m.name).filter(Boolean);
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
// ── Provider definitions ──────────────────────────────────────────────────────
|
|
59
|
+
const PROVIDERS = [
|
|
60
|
+
{
|
|
61
|
+
id: "anthropic",
|
|
62
|
+
label: "Anthropic (Claude)",
|
|
63
|
+
needsKey: true,
|
|
64
|
+
keyLabel: "Anthropic API key",
|
|
65
|
+
keyHint: "sk-ant-...",
|
|
66
|
+
models: ["claude-sonnet-4-5", "claude-haiku-4-5", "claude-opus-4-5"],
|
|
67
|
+
},
|
|
68
|
+
{
|
|
69
|
+
id: "openai",
|
|
70
|
+
label: "OpenAI (GPT)",
|
|
71
|
+
needsKey: true,
|
|
72
|
+
keyLabel: "OpenAI API key",
|
|
73
|
+
keyHint: "sk-...",
|
|
74
|
+
models: ["gpt-4o", "gpt-4o-mini", "gpt-4-turbo"],
|
|
75
|
+
},
|
|
76
|
+
{
|
|
77
|
+
id: "ollama",
|
|
78
|
+
label: "Ollama (local / self-hosted)",
|
|
79
|
+
needsKey: false,
|
|
80
|
+
models: [], // fetched dynamically
|
|
81
|
+
},
|
|
82
|
+
{
|
|
83
|
+
id: "gemini",
|
|
84
|
+
label: "Gemini (Google)",
|
|
85
|
+
needsKey: true,
|
|
86
|
+
keyLabel: "Gemini API key",
|
|
87
|
+
keyHint: "AIza...",
|
|
88
|
+
models: ["gemini-2.0-flash", "gemini-1.5-pro"],
|
|
89
|
+
},
|
|
90
|
+
];
|
|
91
|
+
|
|
92
|
+
// ── Main wizard ───────────────────────────────────────────────────────────────
|
|
93
|
+
export async function cmdSetup() {
|
|
94
|
+
initRl();
|
|
95
|
+
|
|
96
|
+
console.log();
|
|
97
|
+
console.log(b(cy(" ╔═══════════════════════════════════╗")));
|
|
98
|
+
console.log(b(cy(" ║ APX Setup Wizard ║")));
|
|
99
|
+
console.log(b(cy(" ╚═══════════════════════════════════╝")));
|
|
100
|
+
console.log();
|
|
101
|
+
console.log(di(" This will configure the APX daemon and super-agent."));
|
|
102
|
+
console.log(di(" You can re-run `apx setup` at any time to change settings."));
|
|
103
|
+
console.log();
|
|
104
|
+
|
|
105
|
+
// ── Super-agent? ────────────────────────────────────────────────────────────
|
|
106
|
+
const wantAgent = await ask(` Enable super-agent? ${di("[Y/n]")} `);
|
|
107
|
+
if (/^n/i.test(wantAgent)) {
|
|
108
|
+
console.log(`\n ${gr("✓")} Skipping super-agent. Run ${cy("apx daemon start")} to start the daemon.\n`);
|
|
109
|
+
close();
|
|
110
|
+
return;
|
|
111
|
+
}
|
|
112
|
+
|
|
113
|
+
// ── Provider ────────────────────────────────────────────────────────────────
|
|
114
|
+
console.log();
|
|
115
|
+
console.log(b(" AI Provider:"));
|
|
116
|
+
PROVIDERS.forEach((p, i) => console.log(` ${cy(String(i + 1))}. ${p.label}`));
|
|
117
|
+
console.log();
|
|
118
|
+
let providerIdx = -1;
|
|
119
|
+
while (providerIdx < 0) {
|
|
120
|
+
const ans = await ask(` Choose [1-${PROVIDERS.length}]: `);
|
|
121
|
+
const n = parseInt(ans, 10);
|
|
122
|
+
if (n >= 1 && n <= PROVIDERS.length) providerIdx = n - 1;
|
|
123
|
+
else console.log(` ${c.yellow}Please enter a number between 1 and ${PROVIDERS.length}.${c.reset}`);
|
|
124
|
+
}
|
|
125
|
+
const provider = PROVIDERS[providerIdx];
|
|
126
|
+
let apiKey = "";
|
|
127
|
+
let ollamaUrl = "http://localhost:11434";
|
|
128
|
+
|
|
129
|
+
if (provider.id === "ollama") {
|
|
130
|
+
const urlAns = await ask(` Ollama URL ${di("[http://localhost:11434]")}: `);
|
|
131
|
+
ollamaUrl = urlAns || "http://localhost:11434";
|
|
132
|
+
} else if (provider.needsKey) {
|
|
133
|
+
apiKey = await ask(` ${provider.keyLabel} ${di(`(${provider.keyHint})`)}: `);
|
|
134
|
+
}
|
|
135
|
+
|
|
136
|
+
// ── Model ───────────────────────────────────────────────────────────────────
|
|
137
|
+
console.log();
|
|
138
|
+
let models = [...provider.models];
|
|
139
|
+
|
|
140
|
+
if (provider.id === "ollama") {
|
|
141
|
+
process.stdout.write(` Fetching models from Ollama... `);
|
|
142
|
+
const fetched = await fetchOllamaModels(ollamaUrl);
|
|
143
|
+
if (fetched.length) {
|
|
144
|
+
models = fetched;
|
|
145
|
+
console.log(gr(`${fetched.length} found`));
|
|
146
|
+
} else {
|
|
147
|
+
console.log(di("(couldn't reach Ollama, enter manually)"));
|
|
148
|
+
}
|
|
149
|
+
}
|
|
150
|
+
|
|
151
|
+
let chosenModel = "";
|
|
152
|
+
if (models.length) {
|
|
153
|
+
console.log(b(" Model:"));
|
|
154
|
+
models.forEach((m, i) => console.log(` ${cy(String(i + 1))}. ${m}`));
|
|
155
|
+
console.log(` ${cy(String(models.length + 1))}. ${di("Enter manually")}`);
|
|
156
|
+
console.log();
|
|
157
|
+
let modelIdx = -1;
|
|
158
|
+
while (modelIdx < 0) {
|
|
159
|
+
const ans = await ask(` Choose [1-${models.length + 1}]: `);
|
|
160
|
+
const n = parseInt(ans, 10);
|
|
161
|
+
if (n >= 1 && n <= models.length) { modelIdx = n - 1; chosenModel = models[modelIdx]; }
|
|
162
|
+
else if (n === models.length + 1) {
|
|
163
|
+
chosenModel = await ask(" Model name: ");
|
|
164
|
+
modelIdx = 0;
|
|
165
|
+
} else {
|
|
166
|
+
console.log(` ${c.yellow}Invalid choice.${c.reset}`);
|
|
167
|
+
}
|
|
168
|
+
}
|
|
169
|
+
} else {
|
|
170
|
+
chosenModel = await ask(" Model name (e.g. qwen2.5:14b): ");
|
|
171
|
+
}
|
|
172
|
+
chosenModel = `${provider.id}:${chosenModel}`;
|
|
173
|
+
|
|
174
|
+
// ── Channels ────────────────────────────────────────────────────────────────
|
|
175
|
+
console.log();
|
|
176
|
+
console.log(b(" Channels:"));
|
|
177
|
+
console.log(` ${cy("1")}. Web (local API — always on)`);
|
|
178
|
+
console.log(` ${cy("2")}. Telegram`);
|
|
179
|
+
console.log();
|
|
180
|
+
const chAns = await ask(` Enable Telegram? ${di("[Y/n]")} `);
|
|
181
|
+
const wantTelegram = !/^n/i.test(chAns);
|
|
182
|
+
|
|
183
|
+
let botToken = "";
|
|
184
|
+
let chatId = "";
|
|
185
|
+
|
|
186
|
+
if (wantTelegram) {
|
|
187
|
+
console.log();
|
|
188
|
+
console.log(di(" Create a bot at https://t.me/BotFather → get the token."));
|
|
189
|
+
console.log(di(" Then message your bot and visit:"));
|
|
190
|
+
console.log(di(" https://api.telegram.org/bot<TOKEN>/getUpdates to find your chat_id."));
|
|
191
|
+
console.log();
|
|
192
|
+
botToken = await ask(" Bot token: ");
|
|
193
|
+
chatId = await ask(" Your chat ID: ");
|
|
194
|
+
}
|
|
195
|
+
|
|
196
|
+
// ── Language ────────────────────────────────────────────────────────────────
|
|
197
|
+
console.log();
|
|
198
|
+
console.log(b(" Language:"));
|
|
199
|
+
console.log(di(" The super-agent will always respond in your language."));
|
|
200
|
+
console.log();
|
|
201
|
+
const language = await ask(" Your language (e.g. English, Español, Português): ") || "English";
|
|
202
|
+
|
|
203
|
+
// ── Summary ─────────────────────────────────────────────────────────────────
|
|
204
|
+
console.log();
|
|
205
|
+
console.log(b(" ─── Summary ───────────────────────────────────────────"));
|
|
206
|
+
console.log(` Provider: ${cy(provider.label)}`);
|
|
207
|
+
console.log(` Model: ${cy(chosenModel)}`);
|
|
208
|
+
if (provider.id === "ollama") console.log(` Ollama URL: ${cy(ollamaUrl)}`);
|
|
209
|
+
console.log(` Telegram: ${wantTelegram ? gr("enabled") : di("disabled")}`);
|
|
210
|
+
console.log(` Language: ${cy(language)}`);
|
|
211
|
+
console.log(b(" ────────────────────────────────────────────────────────"));
|
|
212
|
+
console.log();
|
|
213
|
+
|
|
214
|
+
const confirm = await ask(` Start the daemon with these settings? ${di("[Y/n]")} `);
|
|
215
|
+
if (/^n/i.test(confirm)) {
|
|
216
|
+
console.log("\n Cancelled. Run `apx setup` again to configure.\n");
|
|
217
|
+
close();
|
|
218
|
+
return;
|
|
219
|
+
}
|
|
220
|
+
|
|
221
|
+
close(); // done with prompts
|
|
222
|
+
|
|
223
|
+
// ── Write config ─────────────────────────────────────────────────────────────
|
|
224
|
+
const cfg = readConfig();
|
|
225
|
+
|
|
226
|
+
cfg.super_agent.enabled = true;
|
|
227
|
+
cfg.super_agent.model = chosenModel;
|
|
228
|
+
// System prompt: language instruction only, no wizard references
|
|
229
|
+
cfg.super_agent.system = `Always respond in the user's language: ${language}.`;
|
|
230
|
+
|
|
231
|
+
if (provider.id === "ollama") {
|
|
232
|
+
cfg.engines.ollama.base_url = ollamaUrl;
|
|
233
|
+
} else if (provider.needsKey && apiKey) {
|
|
234
|
+
cfg.engines[provider.id].api_key = apiKey;
|
|
235
|
+
}
|
|
236
|
+
|
|
237
|
+
if (wantTelegram && botToken && chatId) {
|
|
238
|
+
cfg.telegram.enabled = true;
|
|
239
|
+
cfg.telegram.bot_token = botToken;
|
|
240
|
+
cfg.telegram.chat_id = chatId;
|
|
241
|
+
}
|
|
242
|
+
|
|
243
|
+
writeConfig(cfg);
|
|
244
|
+
console.log(`\n ${gr("✓")} Config saved to ${di("~/.apx/config.json")}`);
|
|
245
|
+
|
|
246
|
+
// ── Start daemon ─────────────────────────────────────────────────────────────
|
|
247
|
+
console.log();
|
|
248
|
+
process.stdout.write(` Starting daemon... `);
|
|
249
|
+
|
|
250
|
+
const start = spawnSync("apx", ["daemon", "start"], { encoding: "utf8" });
|
|
251
|
+
if (start.status !== 0) {
|
|
252
|
+
console.log(c.red + "failed" + c.reset);
|
|
253
|
+
console.log(start.stderr || start.stdout);
|
|
254
|
+
process.exit(1);
|
|
255
|
+
}
|
|
256
|
+
console.log(gr("running ✓"));
|
|
257
|
+
|
|
258
|
+
// Give daemon a moment to come up
|
|
259
|
+
await new Promise((r) => setTimeout(r, 2000));
|
|
260
|
+
|
|
261
|
+
// ── Wake-up Telegram message ─────────────────────────────────────────────────
|
|
262
|
+
if (wantTelegram && botToken && chatId) {
|
|
263
|
+
console.log();
|
|
264
|
+
process.stdout.write(` Sending wake-up message... `);
|
|
265
|
+
try {
|
|
266
|
+
const resp = await sendTelegramWakeup({ botToken, chatId, language, model: chosenModel });
|
|
267
|
+
if (resp) console.log(gr("sent ✓"));
|
|
268
|
+
else console.log(di("(couldn't reach Telegram)"));
|
|
269
|
+
} catch {
|
|
270
|
+
console.log(di("(couldn't reach Telegram)"));
|
|
271
|
+
}
|
|
272
|
+
}
|
|
273
|
+
|
|
274
|
+
console.log();
|
|
275
|
+
console.log(gr(b(" ✅ APX is ready!")));
|
|
276
|
+
console.log();
|
|
277
|
+
console.log(` Daemon: ${cy("http://127.0.0.1:7430")}`);
|
|
278
|
+
if (wantTelegram) console.log(` Telegram: ${cy("active — message your bot")}`);
|
|
279
|
+
console.log();
|
|
280
|
+
console.log(di(" Tip: run `apx daemon status` anytime to check health."));
|
|
281
|
+
console.log();
|
|
282
|
+
}
|
|
283
|
+
|
|
284
|
+
// Send a fun wake-up message via Telegram using super-agent.
|
|
285
|
+
// The prompt is in English so the model knows to reply in the user's language.
|
|
286
|
+
async function sendTelegramWakeup({ botToken, chatId, language, model }) {
|
|
287
|
+
const prompt =
|
|
288
|
+
`You are APX, an AI agent assistant that just came online. ` +
|
|
289
|
+
`Send a short, fun, enthusiastic wake-up message to the user. ` +
|
|
290
|
+
`Be playful and creative — like a friendly AI that just woke up. ` +
|
|
291
|
+
`Keep it under 3 sentences. ` +
|
|
292
|
+
`IMPORTANT: respond in ${language}. ` +
|
|
293
|
+
`Do not mention that you were configured or set up.`;
|
|
294
|
+
|
|
295
|
+
// Ask the daemon's super-agent (give it a second attempt window)
|
|
296
|
+
let text;
|
|
297
|
+
try {
|
|
298
|
+
const res = await fetchJson("http://127.0.0.1:7430/super-agent/ask", 8000);
|
|
299
|
+
text = res?.text;
|
|
300
|
+
} catch {}
|
|
301
|
+
|
|
302
|
+
// Fallback: generate a simple message without daemon
|
|
303
|
+
if (!text) {
|
|
304
|
+
text = languageFallback(language);
|
|
305
|
+
}
|
|
306
|
+
|
|
307
|
+
// Send via Telegram bot API
|
|
308
|
+
return new Promise((resolve) => {
|
|
309
|
+
const body = JSON.stringify({ chat_id: chatId, text, parse_mode: "Markdown" });
|
|
310
|
+
const req = https.request({
|
|
311
|
+
hostname: "api.telegram.org",
|
|
312
|
+
path: `/bot${botToken}/sendMessage`,
|
|
313
|
+
method: "POST",
|
|
314
|
+
headers: { "Content-Type": "application/json", "Content-Length": Buffer.byteLength(body) },
|
|
315
|
+
timeout: 6000,
|
|
316
|
+
}, (res) => {
|
|
317
|
+
let d = "";
|
|
318
|
+
res.on("data", (c) => (d += c));
|
|
319
|
+
res.on("end", () => { try { resolve(JSON.parse(d)); } catch { resolve(null); } });
|
|
320
|
+
});
|
|
321
|
+
req.on("error", () => resolve(null));
|
|
322
|
+
req.write(body);
|
|
323
|
+
req.end();
|
|
324
|
+
});
|
|
325
|
+
}
|
|
326
|
+
|
|
327
|
+
// Minimal fallback messages per common language (used only if daemon can't respond)
|
|
328
|
+
function languageFallback(lang) {
|
|
329
|
+
const l = lang.toLowerCase();
|
|
330
|
+
if (/espa[ñn]|spanish|arg|lat/i.test(l)) return "⚡ ¡Despierto y listo para trabajar! APX online.";
|
|
331
|
+
if (/portugu|brasil/i.test(l)) return "⚡ Acordei e pronto para trabalhar! APX online.";
|
|
332
|
+
if (/franc|french/i.test(l)) return "⚡ Réveillé et prêt à travailler ! APX en ligne.";
|
|
333
|
+
if (/deutsch|german/i.test(l)) return "⚡ Aufgewacht und bereit! APX ist online.";
|
|
334
|
+
if (/ital/i.test(l)) return "⚡ Sveglio e pronto a lavorare! APX online.";
|
|
335
|
+
return "⚡ I'm awake and ready to go! APX is online.";
|
|
336
|
+
}
|
package/src/cli/index.js
CHANGED
|
@@ -72,6 +72,7 @@ import { cmdSkillsAdd, cmdSkillsList, cmdSkillsStatus } from "./commands/skills.
|
|
|
72
72
|
import { cmdIdentity } from "./commands/identity.js";
|
|
73
73
|
import { cmdCommandList, cmdCommandShow } from "./commands/command.js";
|
|
74
74
|
import { cmdUpdate } from "./commands/update.js";
|
|
75
|
+
import { cmdSetup } from "./commands/setup.js";
|
|
75
76
|
import { checkForUpdate } from "../core/update-check.js";
|
|
76
77
|
import {
|
|
77
78
|
cmdRoutineList,
|
|
@@ -203,6 +204,7 @@ Skills (IDE integration):
|
|
|
203
204
|
apx skills status show which IDE targets are installed (project + global)
|
|
204
205
|
|
|
205
206
|
Other:
|
|
207
|
+
apx setup interactive setup wizard (alias: apx install)
|
|
206
208
|
apx update check for updates and upgrade (alias: apx upgrade)
|
|
207
209
|
apx --help
|
|
208
210
|
apx --version
|
|
@@ -478,6 +480,11 @@ async function dispatch(cmd, rest) {
|
|
|
478
480
|
break;
|
|
479
481
|
}
|
|
480
482
|
|
|
483
|
+
case "setup":
|
|
484
|
+
case "install":
|
|
485
|
+
await cmdSetup();
|
|
486
|
+
return;
|
|
487
|
+
|
|
481
488
|
case "update":
|
|
482
489
|
case "upgrade":
|
|
483
490
|
await cmdUpdate(parseArgs(rest));
|