@agentprojectcontext/apx 1.15.1 → 1.15.3

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 CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.15.1",
3
+ "version": "1.15.3",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -192,9 +192,20 @@ export async function cmdSetup() {
192
192
  // ── Language ────────────────────────────────────────────────────────────────
193
193
  console.log();
194
194
  console.log(b(" Language:"));
195
- console.log(di(" The super-agent will always respond in your language."));
195
+ console.log(di(" Used for audio transcription, super-agent replies, and Telegram messages."));
196
+ console.log(di(" Enter a 2-letter ISO 639-1 code. Common codes:"));
197
+ console.log(di(" es=Spanish en=English pt=Portuguese fr=French de=German"));
198
+ console.log(di(" it=Italian zh=Chinese ja=Japanese ko=Korean ar=Arabic"));
196
199
  console.log();
197
- const language = await ask(" Your language (e.g. English, Spanish, Portuguese): ") || "English";
200
+ let language = "";
201
+ while (!language) {
202
+ const raw = (await ask(" Language code [en]: ")).trim().toLowerCase() || "en";
203
+ if (/^[a-z]{2}$/.test(raw)) {
204
+ language = raw;
205
+ } else {
206
+ console.log(` ${c.yellow}Please enter exactly 2 letters (e.g. es, en, pt).${c.reset}`);
207
+ }
208
+ }
198
209
 
199
210
  // ── Summary ─────────────────────────────────────────────────────────────────
200
211
  console.log();
@@ -236,6 +247,8 @@ export async function cmdSetup() {
236
247
  cfg.telegram.chat_id = chatId;
237
248
  }
238
249
 
250
+ cfg.user = { ...(cfg.user || {}), language };
251
+
239
252
  writeConfig(cfg);
240
253
  console.log(`\n ${gr("✓")} Config saved to ${di("~/.apx/config.json")}`);
241
254
 
@@ -282,7 +295,7 @@ export async function cmdSetup() {
282
295
  async function sendTelegramWakeup({ botToken, chatId, language, model }) {
283
296
  const prompt =
284
297
  `You are APX, an AI agent assistant that just came online for the first time. ` +
285
- `Write a short, enthusiastic wake-up message in ${language}. ` +
298
+ `Write a short, enthusiastic wake-up message in the language with ISO 639-1 code "${language}". ` +
286
299
  `Structure it in exactly 3 short lines: ` +
287
300
  `1) An energetic line announcing you are online (use ⚡ emoji). ` +
288
301
  `2) Say you don't have a name yet and ask the user what they'd like to call you. ` +
@@ -321,18 +334,20 @@ async function sendTelegramWakeup({ botToken, chatId, language, model }) {
321
334
  });
322
335
  }
323
336
 
324
- // Minimal fallback messages per common language (used only if daemon can't respond)
337
+ // Minimal fallback messages per ISO 639-1 code (used only if daemon can't respond)
338
+ const WAKEUP_FALLBACK = {
339
+ es: "⚡ ¡APX está en línea y listo!\nAún no tengo nombre — ¿cómo te gustaría llamarme?\n¿Y a vos, cómo te llamo?",
340
+ pt: "⚡ APX está online e pronto!\nAinda não tenho nome — como você gostaria de me chamar?\nE você, como devo te chamar?",
341
+ fr: "⚡ APX est en ligne et prêt !\nJe n'ai pas encore de nom — comment souhaitez-vous m'appeler ?\nEt vous, comment dois-je vous appeler ?",
342
+ de: "⚡ APX ist online und bereit!\nIch habe noch keinen Namen — wie möchten Sie mich nennen?\nUnd Sie, wie soll ich Sie nennen?",
343
+ it: "⚡ APX è online e pronto!\nNon ho ancora un nome — come vorresti chiamarmi?\nE tu, come ti chiamo?",
344
+ zh: "⚡ APX 已上线,随时待命!\n我还没有名字——你想叫我什么?\n你希望我怎么称呼你?",
345
+ ja: "⚡ APXがオンラインになりました!\nまだ名前がありません — 何と呼びたいですか?\nあなたのことは何とお呼びすればよいですか?",
346
+ ko: "⚡ APX가 온라인 상태입니다!\n아직 이름이 없어요 — 어떻게 불러주실 건가요?\n그리고 당신은 어떻게 불러드릴까요?",
347
+ };
325
348
  function languageFallback(lang) {
326
- const l = lang.toLowerCase();
327
- if (/spanish|arg|lat/i.test(l))
328
- return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
329
- if (/portugu|brasil/i.test(l))
330
- return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
331
- if (/franc|french/i.test(l))
332
- return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
333
- if (/deutsch|german/i.test(l))
334
- return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
335
- if (/ital/i.test(l))
336
- return "⚡ APX is online and ready to work.\nI do not have a name yet. What would you like to call me?\nAnd what should I call you?";
337
- return "⚡ I'm awake and ready to go! APX is online.\nI don't have a name yet — what would you like to call me?\nAnd you, what's your name or what should I call you?";
349
+ return (
350
+ WAKEUP_FALLBACK[lang.toLowerCase().slice(0, 2)] ||
351
+ "⚡ I'm awake and ready to go! APX is online.\nI don't have a name yet what would you like to call me?\nAnd you, what's your name or what should I call you?"
352
+ );
338
353
  }
@@ -4,6 +4,29 @@ import { getLatestVersion } from "../../core/update-check.js";
4
4
 
5
5
  const PACKAGE_NAME = "@agentprojectcontext/apx";
6
6
 
7
+ function isNewer(cur, lat) {
8
+ const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
9
+ const [ma, mi, pa] = parse(cur);
10
+ const [mb, mib, pb] = parse(lat);
11
+ if (mb > ma) return true;
12
+ if (mb === ma && mib > mi) return true;
13
+ if (mb === ma && mib === mi && pb > pa) return true;
14
+ return false;
15
+ }
16
+
17
+ function hasPnpmGlobal() {
18
+ const r = spawnSync("pnpm", ["--version"], { encoding: "utf8", stdio: "pipe" });
19
+ if (r.status !== 0) return false;
20
+ // pnpm needs PNPM_HOME configured to manage global packages
21
+ const check = spawnSync("pnpm", ["root", "-g"], { encoding: "utf8", stdio: "pipe" });
22
+ return check.status === 0 && !!check.stdout?.trim();
23
+ }
24
+
25
+ function daemonRunning() {
26
+ const r = spawnSync("apx", ["daemon", "status", "--json"], { encoding: "utf8", stdio: "pipe" });
27
+ try { return JSON.parse(r.stdout)?.running === true; } catch { return false; }
28
+ }
29
+
7
30
  export async function cmdUpdate(args, currentVersion) {
8
31
  const force = args.flags.force || args.flags.yes || args.flags.y;
9
32
 
@@ -15,24 +38,12 @@ export async function cmdUpdate(args, currentVersion) {
15
38
  process.exit(1);
16
39
  }
17
40
 
18
- const current = currentVersion;
19
-
20
- function isNewer(cur, lat) {
21
- const parse = (v) => v.replace(/^v/, "").split(".").map(Number);
22
- const [ma, mi, pa] = parse(cur);
23
- const [mb, mib, pb] = parse(lat);
24
- if (mb > ma) return true;
25
- if (mb === ma && mib > mi) return true;
26
- if (mb === ma && mib === mi && pb > pa) return true;
27
- return false;
28
- }
29
-
30
- if (!isNewer(current, latest)) {
31
- console.log(`✅ Already up to date (${current})`);
41
+ if (!isNewer(currentVersion, latest)) {
42
+ console.log(`✅ Already up to date (${currentVersion})`);
32
43
  return;
33
44
  }
34
45
 
35
- console.log(`\n Current: ${current}`);
46
+ console.log(`\n Current: ${currentVersion}`);
36
47
  console.log(` Latest: ${latest}`);
37
48
 
38
49
  if (!force) {
@@ -43,20 +54,41 @@ export async function cmdUpdate(args, currentVersion) {
43
54
  }
44
55
  }
45
56
 
46
- console.log(`\nRunning: npm install -g ${PACKAGE_NAME}@${latest}\n`);
47
- const result = spawnSync(
48
- "npm",
49
- ["install", "-g", `${PACKAGE_NAME}@${latest}`],
50
- { stdio: "inherit" }
51
- );
57
+ // Stop daemon before replacing the binary so Node doesn't lock files on Windows.
58
+ const wasDaemonRunning = daemonRunning();
59
+ if (wasDaemonRunning) {
60
+ process.stdout.write("\nStopping daemon... ");
61
+ spawnSync("apx", ["daemon", "stop"], { stdio: "inherit" });
62
+ console.log("stopped.");
63
+ }
64
+
65
+ // Prefer pnpm global if configured, fall back to npm.
66
+ const usePnpm = hasPnpmGlobal();
67
+ const pm = usePnpm ? "pnpm" : "npm";
68
+ const installArgs = usePnpm
69
+ ? ["add", "-g", `${PACKAGE_NAME}@${latest}`]
70
+ : ["install", "-g", `${PACKAGE_NAME}@${latest}`];
71
+
72
+ console.log(`\nInstalling ${PACKAGE_NAME}@${latest} via ${pm}...\n`);
73
+ const result = spawnSync(pm, installArgs, { stdio: "inherit" });
52
74
 
53
75
  if (result.status !== 0) {
54
76
  console.error(`\n❌ Update failed (exit ${result.status})`);
77
+ if (wasDaemonRunning) {
78
+ console.log("Restarting daemon with old version...");
79
+ spawnSync("apx", ["daemon", "start"], { stdio: "inherit" });
80
+ }
55
81
  process.exit(result.status || 1);
56
82
  }
57
83
 
58
- console.log(`\n✅ Updated to ${latest}. Restart any running apx daemon:`);
59
- console.log(` apx daemon stop && apx daemon start`);
84
+ // Restart daemon with new version.
85
+ if (wasDaemonRunning) {
86
+ process.stdout.write("\nStarting daemon... ");
87
+ spawnSync("apx", ["daemon", "start"], { stdio: "inherit" });
88
+ console.log("done.");
89
+ }
90
+
91
+ console.log(`\n✅ Updated to ${latest}.`);
60
92
  }
61
93
 
62
94
  function confirm(prompt) {
@@ -32,6 +32,9 @@ const DEFAULT_CONFIG = {
32
32
  host: "127.0.0.1",
33
33
  log_level: "info",
34
34
  projects: [],
35
+ user: {
36
+ language: "en", // ISO 639-1 two-letter code; used by transcription, super-agent, and wake-up message
37
+ },
35
38
  telegram: {
36
39
  enabled: false,
37
40
  bot_token: "",
@@ -87,6 +90,7 @@ export function mergeDefaults(cfg) {
87
90
  return {
88
91
  ...DEFAULT_CONFIG,
89
92
  ...cfg,
93
+ user: { ...DEFAULT_CONFIG.user, ...(cfg.user || {}) },
90
94
  telegram: {
91
95
  ...DEFAULT_CONFIG.telegram,
92
96
  ...(cfg.telegram || {}),
@@ -176,7 +176,12 @@ export function installIdeSkills(root, targetIds = null) {
176
176
  return results;
177
177
  }
178
178
 
179
- // Install bundled APX/APC skills + runtime docs to global ~/.../skills/ dirs.
179
+ // Install bundled APX/APC skills to global ~/.../skills/ dirs.
180
+ // Only apx and apc-context are installed everywhere — they teach IDE tools
181
+ // (Claude Code, Cursor, Codex) about the APX CLI and APC project standard.
182
+ // Runtime CLI skills (claude-code, codex-cli, etc.) are APX-internal; APX
183
+ // loads them from src/daemon/runtime-skills/ at startup and does NOT push
184
+ // them to other tools' global skill dirs.
180
185
  // Returns an array of result objects with { dir, skill, status }.
181
186
  export function installGlobalSkills() {
182
187
  const results = [];
@@ -186,7 +191,8 @@ export function installGlobalSkills() {
186
191
  const apcRaw = readBundledSkill("apc-context");
187
192
  if (apxRaw) skills.push({ slug: "apx", md: apxRaw });
188
193
  if (apcRaw) skills.push({ slug: "apc-context", md: apcRaw });
189
- skills.push(...readRuntimeSkillFiles());
194
+ // Runtime skills (claude-code, codex-cli, opencode-cli, openrouter, …) are
195
+ // NOT included here — they are APX-only internals, not for IDE consumption.
190
196
 
191
197
  for (const base of GLOBAL_SKILL_DIRS) {
192
198
  for (const { slug, md } of skills) {
@@ -55,9 +55,16 @@ async function getConfig() {
55
55
  const cfg = readConfig() || {};
56
56
  const t = cfg.transcription || {};
57
57
  const openaiKey = cfg.engines?.openai?.api_key || process.env.OPENAI_API_KEY || "";
58
+ // Use user.language as default for transcription language if not explicitly set.
59
+ // Explicit transcription.local.language always wins; "auto" means fall back to user.language.
60
+ const userLang = cfg.user?.language || "";
61
+ const localBase = { ...DEFAULT_LOCAL, ...(t.local || {}) };
62
+ if ((!localBase.language || localBase.language === "auto") && userLang) {
63
+ localBase.language = userLang;
64
+ }
58
65
  return {
59
66
  provider: t.provider || "auto",
60
- local: { ...DEFAULT_LOCAL, ...(t.local || {}) },
67
+ local: localBase,
61
68
  openaiKey,
62
69
  };
63
70
  } catch {