@agentprojectcontext/apx 1.7.0 → 1.8.1

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 CHANGED
@@ -88,6 +88,7 @@ apx exec <slug> "<prompt>" # quick LLM call
88
88
 
89
89
  apx session list <slug> # list past sessions
90
90
  apx messages tail # last 50 messages, all channels
91
+ apx messages chat --channel telegram # chat view with user/agent/system type
91
92
  apx messages tail --channel runtime # only agent invocations
92
93
  ```
93
94
 
@@ -96,6 +97,9 @@ apx messages tail --channel runtime # only agent invocations
96
97
  Activity belongs to APX runtime state, not `.apc/`. Message storage is local to APX, under
97
98
  `~/.apx/`:
98
99
 
100
+ JSONL messages include `type` (`user`, `agent`, `tool`, or `system`) plus `actor_id`, so chat views
101
+ can distinguish Telegram users from APX agents and future subagents.
102
+
99
103
  | Channel | What it captures |
100
104
  |---------|-----------------|
101
105
  | `runtime` | `apx run` invocations (prompt in, response out) |
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.7.0",
3
+ "version": "1.8.1",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -1,5 +1,6 @@
1
1
  import { http } from "../http.js";
2
2
  import { resolveProjectId } from "./project.js";
3
+ import { readConfig, writeConfig } from "../../core/config.js";
3
4
 
4
5
  function parseValue(raw) {
5
6
  // best-effort: try JSON first (covers numbers, bools, objects, arrays, null,
@@ -54,3 +55,25 @@ export async function cmdConfigUnset(args) {
54
55
  await http.patch(`/projects/${pid}/config`, { unset: [key] });
55
56
  console.log(`unset ${key}`);
56
57
  }
58
+
59
+ export function cmdPermission(args = {}) {
60
+ const sub = args._[0] || "show";
61
+ const cfg = readConfig();
62
+ cfg.super_agent = cfg.super_agent || {};
63
+ if (sub === "show" || sub === "get" || sub === "ls") {
64
+ console.log(`permission_mode=${cfg.super_agent.permission_mode || "automatico"}`);
65
+ console.log(`allowed_tools=${(cfg.super_agent.allowed_tools || []).join(",") || "(none)"}`);
66
+ return;
67
+ }
68
+ if (sub === "set") {
69
+ const mode = args._[1];
70
+ if (!["total", "automatico", "permiso"].includes(mode)) {
71
+ throw new Error("apx permissions set: mode must be total, automatico, or permiso");
72
+ }
73
+ cfg.super_agent.permission_mode = mode;
74
+ writeConfig(cfg);
75
+ console.log(`permission_mode=${mode}`);
76
+ return;
77
+ }
78
+ throw new Error(`unknown permissions subcommand: ${sub}`);
79
+ }
@@ -19,7 +19,7 @@ function askYN(rl, question, defaultYes = false) {
19
19
  rl.question(`${question} [${hint}]: `, (ans) => {
20
20
  const a = ans.trim().toLowerCase();
21
21
  if (!a) return resolve(defaultYes);
22
- resolve(a === "y" || a === "yes" || a === "s" || a === "si" || a === "sí");
22
+ resolve(a === "y" || a === "yes");
23
23
  });
24
24
  });
25
25
  }
@@ -120,7 +120,7 @@ export async function runWizard() {
120
120
  const personality = await ask(rl, " Personality (comma-separated traits)", existing.personality || "direct, curious, helpful");
121
121
  const owner_name = await ask(rl, " Your name", existing.owner_name || "");
122
122
  const owner_context = await ask(rl, " What are you building / working on", existing.owner_context || "");
123
- const language = await ask(rl, " Language for agent messages (e.g. Spanish, English)", existing.language || "Spanish (Español)");
123
+ const language = await ask(rl, " Language for agent messages (e.g. Spanish, English)", existing.language || "English");
124
124
 
125
125
  console.log("\n Claude Code permissions");
126
126
  console.log(" APX can configure Claude Code to allow terminal commands without prompts.");
@@ -46,6 +46,51 @@ export async function cmdMessagesTail(args) {
46
46
  }
47
47
  }
48
48
 
49
+ function chatTimestamp(ts) {
50
+ if (!ts) return "????-??-?? ??:??:??";
51
+ return ts.replace("T", " ").replace(/Z$/, "");
52
+ }
53
+
54
+ function chatActor(row) {
55
+ const type = row.type || (row.direction === "in" ? "user" : "agent");
56
+ const who = row.author || row.actor_id || "";
57
+ return who ? `${type} ${who}` : type;
58
+ }
59
+
60
+ function printChatRows(rows) {
61
+ if (rows.length === 0) {
62
+ console.log("(no messages)");
63
+ return;
64
+ }
65
+ for (const r of rows) {
66
+ const body = String(r.body || "");
67
+ const lines = body.split("\n");
68
+ console.log(`[${chatTimestamp(r.ts)}] ${chatActor(r)}: ${lines[0] || ""}`);
69
+ for (const line of lines.slice(1)) console.log(` ${line}`);
70
+ }
71
+ }
72
+
73
+ export async function cmdMessagesChat(args) {
74
+ const channel = args.flags.channel && args.flags.channel !== true ? args.flags.channel : "telegram";
75
+ const n = args.flags.n || args.flags.last || "50";
76
+ const isGlobal = args.flags.global || isGlobalChannel(channel);
77
+
78
+ if (isGlobal) {
79
+ const params = new URLSearchParams({ limit: String(n) });
80
+ if (channel) params.set("channel", channel);
81
+ const rows = await http.get(`/messages/global?${params}`);
82
+ printChatRows(rows);
83
+ return;
84
+ }
85
+
86
+ const pid = await resolveProjectId(args?.flags?.project);
87
+ const params = new URLSearchParams({ limit: String(n) });
88
+ if (args.flags.agent && args.flags.agent !== true) params.set("agent", args.flags.agent);
89
+ if (channel) params.set("channel", channel);
90
+ const rows = await http.get(`/projects/${pid}/messages?${params}`);
91
+ printChatRows(rows.reverse());
92
+ }
93
+
49
94
  export async function cmdMessagesSearch(args) {
50
95
  const q = args._[0];
51
96
  if (!q) throw new Error("apx messages search: missing <query>");
@@ -12,7 +12,7 @@ function parseSpec(args) {
12
12
  }
13
13
  const spec = {};
14
14
  for (const [k, v] of Object.entries(args.flags)) {
15
- if (["name", "kind", "schedule", "spec", "verbose"].includes(k)) continue;
15
+ if (["name", "kind", "schedule", "spec", "verbose", "permission-mode", "allowed-tools"].includes(k)) continue;
16
16
  if (v === true) continue;
17
17
  // try to JSON-parse the value (numbers, bools), else keep as string
18
18
  let val = v;
@@ -59,8 +59,14 @@ export async function cmdRoutineAdd(args) {
59
59
  if (!kind || !schedule)
60
60
  throw new Error("apx routine add: --kind and --schedule are required");
61
61
  const spec = parseSpec(args);
62
+ const permission_mode = args.flags["permission-mode"] && args.flags["permission-mode"] !== true
63
+ ? args.flags["permission-mode"]
64
+ : undefined;
65
+ const allowed_tools = args.flags["allowed-tools"] && args.flags["allowed-tools"] !== true
66
+ ? String(args.flags["allowed-tools"]).split(",").map((s) => s.trim()).filter(Boolean)
67
+ : undefined;
62
68
  const pid = await resolveProjectId(args?.flags?.project);
63
- const r = await http.post(`/projects/${pid}/routines`, { name, kind, schedule, spec });
69
+ const r = await http.post(`/projects/${pid}/routines`, { name, kind, schedule, spec, permission_mode, allowed_tools });
64
70
  console.log(`added routine "${r.name}" (${r.kind}, ${r.schedule}) → next ${r.next_run_at}`);
65
71
  }
66
72
 
@@ -97,3 +103,22 @@ export async function cmdRoutineRun(args) {
97
103
  const r = await http.post(`/projects/${pid}/routines/${name}/run`);
98
104
  console.log(JSON.stringify(r, null, 2));
99
105
  }
106
+
107
+ export async function cmdRoutineHistory(args) {
108
+ const name = args._[0];
109
+ if (!name) throw new Error("apx routine history: missing <name>");
110
+ const pid = await resolveProjectId(args?.flags?.project);
111
+ const limit = args.flags.n || args.flags.last || "50";
112
+ const rows = await http.get(`/projects/${pid}/messages?channel=routine&limit=${encodeURIComponent(limit)}`);
113
+ const filtered = rows
114
+ .filter((r) => r.meta?.routine === name)
115
+ .reverse();
116
+ if (filtered.length === 0) {
117
+ console.log("(no routine history)");
118
+ return;
119
+ }
120
+ for (const r of filtered) {
121
+ const status = r.meta?.status ? ` ${r.meta.status}` : "";
122
+ console.log(`${r.ts}${status} ${r.author || ""}: ${r.body}`);
123
+ }
124
+ }
@@ -104,9 +104,9 @@ function findSessionById(root, id) {
104
104
  }
105
105
 
106
106
  function statusEmoji(status) {
107
- if (/Completada|complete/i.test(status)) return "✅";
108
- if (/En progreso|in.progress/i.test(status)) return "🔄";
109
- if (/stale|Cerrada/i.test(status)) return "⚠️";
107
+ if (/complete/i.test(status)) return "✅";
108
+ if (/in.progress/i.test(status)) return "🔄";
109
+ if (/stale|closed/i.test(status)) return "⚠️";
110
110
  return "❓";
111
111
  }
112
112
 
@@ -242,7 +242,7 @@ export function cmdSessionClose(args) {
242
242
  if (!s) throw new Error(`session "${id}" not found`);
243
243
 
244
244
  let text = fs.readFileSync(s.path, "utf8");
245
- text = setFrontmatterField(text, "status", "✅ Completada");
245
+ text = setFrontmatterField(text, "status", "✅ Completed");
246
246
  text = setFrontmatterField(text, "completed", nowIso());
247
247
  if (args.flags.result && args.flags.result !== true) {
248
248
  text = setFrontmatterField(text, "result", String(args.flags.result));
@@ -254,7 +254,7 @@ export function cmdSessionClose(args) {
254
254
  export function cmdSessionCheck() {
255
255
  const root = requireRoot();
256
256
  const sessions = listAllSessions(root).filter((s) =>
257
- /En progreso/i.test(s.status)
257
+ /in.progress/i.test(s.status)
258
258
  );
259
259
 
260
260
  if (sessions.length === 0) {
@@ -338,7 +338,7 @@ export async function cmdSessionResume(args) {
338
338
  export function cmdSessionCloseStale() {
339
339
  const root = requireRoot();
340
340
  const sessions = listAllSessions(root).filter((s) =>
341
- /En progreso/i.test(s.status)
341
+ /in.progress/i.test(s.status)
342
342
  );
343
343
  let closed = 0;
344
344
  for (const s of sessions) {
@@ -348,7 +348,7 @@ export function cmdSessionCloseStale() {
348
348
  text = setFrontmatterField(
349
349
  text,
350
350
  "status",
351
- `⚠️ Cerrada automáticamente (stale >${STALE_HOURS}h)`
351
+ `⚠️ Automatically closed (stale >${STALE_HOURS}h)`
352
352
  );
353
353
  text = setFrontmatterField(text, "completed", nowIso());
354
354
  text = setFrontmatterField(
@@ -194,7 +194,7 @@ export async function cmdSetup() {
194
194
  console.log(b(" Language:"));
195
195
  console.log(di(" The super-agent will always respond in your language."));
196
196
  console.log();
197
- const language = await ask(" Your language (e.g. English, Español, Português): ") || "English";
197
+ const language = await ask(" Your language (e.g. English, Spanish, Portuguese): ") || "English";
198
198
 
199
199
  // ── Summary ─────────────────────────────────────────────────────────────────
200
200
  console.log();
@@ -221,8 +221,8 @@ export async function cmdSetup() {
221
221
 
222
222
  cfg.super_agent.enabled = true;
223
223
  cfg.super_agent.model = chosenModel;
224
- // System prompt: language instruction only, no wizard references
225
- cfg.super_agent.system = `Always respond in the user's language: ${language}.`;
224
+ cfg.super_agent.system = "";
225
+ cfg.super_agent.permission_mode = cfg.super_agent.permission_mode || "automatico";
226
226
 
227
227
  if (provider.id === "ollama") {
228
228
  cfg.engines.ollama.base_url = ollamaUrl;
@@ -324,15 +324,15 @@ async function sendTelegramWakeup({ botToken, chatId, language, model }) {
324
324
  // Minimal fallback messages per common language (used only if daemon can't respond)
325
325
  function languageFallback(lang) {
326
326
  const l = lang.toLowerCase();
327
- if (/espa[ñn]|spanish|arg|lat/i.test(l))
328
- return "⚡ ¡Despierto y listo para trabajar! APX online.\nAún no tengo nombre, ¿cómo te gustaría llamarme?\nY vos, ¿cómo te llamas o cómo puedo llamarte?";
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
329
  if (/portugu|brasil/i.test(l))
330
- return "⚡ Acordei e pronto para trabalhar! APX online.\nAinda não tenho nome, como você gostaria de me chamar?\nE você, como posso te chamar?";
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
331
  if (/franc|french/i.test(l))
332
- return "⚡ Réveillé et prêt à travailler ! APX en ligne.\nJe n'ai pas encore de nom, comment souhaitez-vous m'appeler ?\nEt vous, comment puis-je vous appeler ?";
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
333
  if (/deutsch|german/i.test(l))
334
- return "⚡ Aufgewacht und bereit! APX ist online.\nIch habe noch keinen Namen wie möchtest du mich nennen?\nUnd du, wie kann ich dich nennen?";
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
335
  if (/ital/i.test(l))
336
- return "⚡ Sveglio e pronto a lavorare! APX online.\nNon ho ancora un nome, come vorresti chiamarmi?\nE tu, come posso chiamarti?";
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
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?";
338
338
  }