@agentprojectcontext/apx 1.10.3 → 1.11.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/README.md CHANGED
@@ -113,10 +113,14 @@ can distinguish Telegram users from APX agents and future subagents.
113
113
  | Runtime | Description |
114
114
  |---------|-------------|
115
115
  | `claude-code` | Spawns Claude Code CLI with the agent's system prompt injected |
116
- | `codex` | OpenAI Codex CLI |
116
+ | `codex` | OpenAI Codex CLI via non-interactive `codex exec --sandbox workspace-write --skip-git-repo-check` |
117
117
  | `opencode` | OpenCode CLI |
118
118
  | `aider` | Aider CLI |
119
119
 
120
+ Global APX skill installation also writes named helper skills for `codex-cli`, `claude-code`,
121
+ `opencode-cli`, and `openrouter`. They are intentionally narrow and should activate only when those
122
+ tools/providers are explicitly mentioned.
123
+
120
124
  ## Engines (for `apx exec`)
121
125
 
122
126
  Configured in `~/.apx/config.json`:
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.10.3",
3
+ "version": "1.11.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -35,6 +35,10 @@
35
35
  "express": "^4.21.0",
36
36
  "node-fetch": "^3.3.2"
37
37
  },
38
+ "optionalDependencies": {
39
+ "fast-glob": "^3.3.2",
40
+ "puppeteer": "^22.0.0"
41
+ },
38
42
  "devDependencies": {
39
43
  "@semantic-release/changelog": "^6.0.3",
40
44
  "@semantic-release/git": "^10.0.1",
@@ -0,0 +1,62 @@
1
+ // cli/commands/search.js
2
+ // `apx search "query"` — web search from the terminal.
3
+ // Uses the daemon's tools/search.js module directly (no HTTP roundtrip,
4
+ // no need to have `apx daemon start` running).
5
+
6
+ import { webSearch } from "../../daemon/tools/search.js";
7
+
8
+ const DIM = "\x1b[2m";
9
+ const BOLD = "\x1b[1m";
10
+ const BLUE = "\x1b[34m";
11
+ const CYAN = "\x1b[36m";
12
+ const RESET = "\x1b[0m";
13
+
14
+ export async function cmdSearch(args) {
15
+ const query = (args._ || []).join(" ").trim();
16
+ if (!query) {
17
+ console.error("apx search: missing <query>");
18
+ console.error("usage: apx search \"<query>\" [--mode auto|ddg|brave|browser] [-n N]");
19
+ process.exit(1);
20
+ }
21
+
22
+ const mode = args.flags?.mode || "auto";
23
+ const limit = parseInt(args.flags?.n || args.flags?.limit || "5", 10) || 5;
24
+ const json = !!args.flags?.json;
25
+
26
+ process.stderr.write(`${DIM}Searching... (${mode})${RESET}\n`);
27
+
28
+ let r;
29
+ try {
30
+ r = await webSearch({ query, mode, limit });
31
+ } catch (e) {
32
+ console.error(`${DIM}error:${RESET} ${e.message}`);
33
+ process.exit(1);
34
+ }
35
+
36
+ if (json) {
37
+ console.log(JSON.stringify(r, null, 2));
38
+ return;
39
+ }
40
+
41
+ const results = r.results || [];
42
+ if (results.length === 0) {
43
+ console.log(`${DIM}(no results from ${r.mode})${RESET}`);
44
+ if (r.raw_excerpt) console.log(`${DIM}excerpt:${RESET} ${r.raw_excerpt.slice(0, 400)}…`);
45
+ return;
46
+ }
47
+
48
+ console.log(`${DIM}${results.length} result${results.length === 1 ? "" : "s"} via ${r.mode}${RESET}\n`);
49
+ results.forEach((item, i) => {
50
+ const num = `[${i + 1}]`;
51
+ const title = item.title || "(no title)";
52
+ console.log(`${BOLD}${num} ${title}${RESET} ${DIM}— ${hostname(item.url)}${RESET}`);
53
+ if (item.snippet) console.log(` ${item.snippet}`);
54
+ console.log(` ${BLUE}${item.url}${RESET}`);
55
+ if (i < results.length - 1) console.log("");
56
+ });
57
+ }
58
+
59
+ function hostname(url) {
60
+ try { return new URL(url).hostname.replace(/^www\./, ""); }
61
+ catch { return url; }
62
+ }
@@ -32,10 +32,12 @@ export async function cmdSys(args) {
32
32
  hasStarted: false,
33
33
  sessionTitle: "",
34
34
  usage: { input: 0, output: 0, percent: 0 },
35
+ chatScrollOffset: 0,
35
36
  transcript: [],
36
37
  };
37
38
 
38
39
  const previousMessages = [];
40
+ const pendingPrompts = [];
39
41
  let restored = false;
40
42
  let isRequesting = false;
41
43
 
@@ -74,8 +76,6 @@ export async function cmdSys(args) {
74
76
  renderScreen();
75
77
 
76
78
  process.stdin.on("keypress", async (str, key) => {
77
- if (isRequesting) return;
78
-
79
79
  if (key.ctrl && key.name === "c") {
80
80
  close();
81
81
  }
@@ -102,9 +102,16 @@ export async function cmdSys(args) {
102
102
  return;
103
103
  }
104
104
 
105
+ if (handleScrollKey(key, state, renderScreen)) return;
106
+
105
107
  if (isReturnKey(key)) {
108
+ if (isRequesting) {
109
+ queuePrompt(state, pendingPrompts, renderScreen);
110
+ return;
111
+ }
112
+
106
113
  isRequesting = true;
107
- await submitPrompt(pid, state, previousMessages, renderScreen, close);
114
+ await submitPromptQueue(pid, state, previousMessages, pendingPrompts, renderScreen, close);
108
115
  isRequesting = false;
109
116
  return;
110
117
  }
@@ -117,6 +124,37 @@ export function isReturnKey(key) {
117
124
  return key?.name === "return" || key?.name === "enter";
118
125
  }
119
126
 
127
+ export function handleScrollKey(key, state, renderScreen) {
128
+ if (!state.hasStarted || !key) return false;
129
+ const pageSize = key.name === "pageup" || key.name === "pagedown" ? 8 : 3;
130
+
131
+ if (key.name === "pageup" || key.name === "up" || (key.ctrl && key.name === "up")) {
132
+ state.chatScrollOffset = Math.min(100000, (state.chatScrollOffset || 0) + pageSize);
133
+ renderScreen();
134
+ return true;
135
+ }
136
+
137
+ if (key.name === "pagedown" || key.name === "down" || (key.ctrl && key.name === "down")) {
138
+ state.chatScrollOffset = Math.max(0, (state.chatScrollOffset || 0) - pageSize);
139
+ renderScreen();
140
+ return true;
141
+ }
142
+
143
+ if (key.meta && key.name === "up") {
144
+ state.chatScrollOffset = Math.min(100000, (state.chatScrollOffset || 0) + 20);
145
+ renderScreen();
146
+ return true;
147
+ }
148
+
149
+ if (key.meta && key.name === "down") {
150
+ state.chatScrollOffset = 0;
151
+ renderScreen();
152
+ return true;
153
+ }
154
+
155
+ return false;
156
+ }
157
+
120
158
  async function handlePaletteKey(key, cfg, state, renderScreen, close) {
121
159
  if (key.name === "up") {
122
160
  state.paletteSelection = Math.max(0, state.paletteSelection - 1);
@@ -276,18 +314,46 @@ export function handleEditingKey(str, key, state, renderScreen) {
276
314
  return false;
277
315
  }
278
316
 
279
- async function submitPrompt(pid, state, previousMessages, renderScreen, close) {
317
+ function queuePrompt(state, pendingPrompts, renderScreen) {
280
318
  const text = state.inputText.trim();
281
319
  if (!text) return;
282
- if (text.toLowerCase() === "exit" || text.toLowerCase() === "quit") {
320
+
321
+ state.hasStarted = true;
322
+ state.inputText = "";
323
+ state.cursorIndex = 0;
324
+ state.chatScrollOffset = 0;
325
+
326
+ const item = { type: "user", text, meta: "queued" };
327
+ pendingPrompts.push({ text, item });
328
+ state.transcript.push(item);
329
+ renderScreen();
330
+ }
331
+
332
+ async function submitPromptQueue(pid, state, previousMessages, pendingPrompts, renderScreen, close) {
333
+ const firstText = state.inputText.trim();
334
+ if (!firstText) return;
335
+ if (firstText.toLowerCase() === "exit" || firstText.toLowerCase() === "quit") {
283
336
  close();
284
337
  }
285
338
 
286
339
  state.hasStarted = true;
287
340
  state.inputText = "";
288
341
  state.cursorIndex = 0;
289
- state.transcript.push({ type: "user", text });
290
- state.transcript.push({ type: "status", text: "Thinking..." });
342
+ state.chatScrollOffset = 0;
343
+
344
+ const firstItem = { type: "user", text: firstText };
345
+ state.transcript.push(firstItem);
346
+ await runPrompt(pid, state, previousMessages, renderScreen, firstText, firstItem);
347
+
348
+ while (pendingPrompts.length > 0) {
349
+ const queued = pendingPrompts.shift();
350
+ delete queued.item.meta;
351
+ await runPrompt(pid, state, previousMessages, renderScreen, queued.text, queued.item);
352
+ }
353
+ }
354
+
355
+ async function runPrompt(pid, state, previousMessages, renderScreen, text, userItem) {
356
+ appendLiveItem(state, { type: "status", text: "Thinking...", active: true });
291
357
  renderScreen();
292
358
 
293
359
  const startTime = Date.now();
@@ -312,29 +378,42 @@ async function submitPrompt(pid, state, previousMessages, renderScreen, close) {
312
378
  result = await http.post(`/projects/${pid}/super-agent/chat`, body);
313
379
  removeStatus(state);
314
380
  for (const trace of result.trace || []) {
315
- state.transcript.push({ type: "tool", trace });
381
+ appendLiveItem(state, { type: "tool", trace });
316
382
  }
317
383
  }
318
384
 
319
385
  completeSuperAgentResult(result, text, startTime, state, previousMessages);
320
386
  } catch (e) {
321
387
  removeStatus(state);
322
- state.transcript.push({ type: "error", text: e.message });
388
+ appendLiveItem(state, { type: "error", text: e.message });
323
389
  }
324
390
 
391
+ if (userItem) delete userItem.meta;
325
392
  renderScreen();
326
393
  }
327
394
 
328
395
  function removeStatus(state) {
329
- const last = state.transcript[state.transcript.length - 1];
330
- if (last?.type === "status") state.transcript.pop();
396
+ for (let i = state.transcript.length - 1; i >= 0; i--) {
397
+ if (state.transcript[i]?.type === "status" && state.transcript[i]?.active) {
398
+ state.transcript.splice(i, 1);
399
+ return;
400
+ }
401
+ }
402
+ }
403
+
404
+ function appendLiveItem(state, item) {
405
+ const queuedIndex = state.transcript.findIndex(
406
+ (entry) => entry?.type === "user" && entry?.meta === "queued"
407
+ );
408
+ if (queuedIndex >= 0) state.transcript.splice(queuedIndex, 0, item);
409
+ else state.transcript.push(item);
331
410
  }
332
411
 
333
412
  function handleProgressEvent(event, state, renderScreen) {
334
413
  if (event.type === "model_start") {
335
- const last = state.transcript[state.transcript.length - 1];
336
- if (last?.type === "status") {
337
- last.text = event.iteration > 1 ? `Thinking... step ${event.iteration}` : "Thinking...";
414
+ const status = [...state.transcript].reverse().find((item) => item?.type === "status" && item?.active);
415
+ if (status) {
416
+ status.text = event.iteration > 1 ? `Thinking... step ${event.iteration}` : "Thinking...";
338
417
  renderScreen();
339
418
  }
340
419
  return;
@@ -342,7 +421,7 @@ function handleProgressEvent(event, state, renderScreen) {
342
421
 
343
422
  if (event.type === "assistant_text" && event.text) {
344
423
  removeStatus(state);
345
- state.transcript.push({
424
+ appendLiveItem(state, {
346
425
  type: "assistant",
347
426
  name: state.activeAgent,
348
427
  text: event.text,
@@ -354,7 +433,7 @@ function handleProgressEvent(event, state, renderScreen) {
354
433
 
355
434
  if (event.type === "tool_start" && event.trace) {
356
435
  removeStatus(state);
357
- state.transcript.push({ type: "tool", trace: event.trace });
436
+ appendLiveItem(state, { type: "tool", trace: event.trace });
358
437
  renderScreen();
359
438
  return;
360
439
  }
@@ -365,7 +444,7 @@ function handleProgressEvent(event, state, renderScreen) {
365
444
  (item) => item.type === "tool" && item.trace?.id && item.trace.id === event.trace.id
366
445
  );
367
446
  if (idx >= 0) state.transcript[idx] = { type: "tool", trace: event.trace };
368
- else state.transcript.push({ type: "tool", trace: event.trace });
447
+ else appendLiveItem(state, { type: "tool", trace: event.trace });
369
448
  renderScreen();
370
449
  }
371
450
  }
@@ -383,7 +462,7 @@ function completeSuperAgentResult(result, userText, startTime, state, previousMe
383
462
  state.usage.output += result.usage?.output_tokens || 0;
384
463
  state.usage.percent = Math.min(99, Math.round((state.usage.input / 200000) * 100));
385
464
 
386
- state.transcript.push({
465
+ appendLiveItem(state, {
387
466
  type: "assistant",
388
467
  name: state.activeAgent,
389
468
  text: result.text,
package/src/cli/index.js CHANGED
@@ -54,6 +54,7 @@ import {
54
54
  cmdTelegramSetup,
55
55
  } from "./commands/telegram.js";
56
56
  import { cmdMessagesTail, cmdMessagesSearch, cmdMessagesChat } from "./commands/messages.js";
57
+ import { cmdSearch } from "./commands/search.js";
57
58
  import { cmdExec } from "./commands/exec.js";
58
59
  import {
59
60
  cmdChat,
@@ -653,6 +654,21 @@ const HELP_TOPICS = new Map(Object.entries({
653
654
  options: [["--project <name|id|path>", "Pin command to a specific project."]],
654
655
  examples: ["apx messages search \"deploy\""],
655
656
  }),
657
+ search: topic({
658
+ title: "apx search",
659
+ summary: "Web search from the terminal (DuckDuckGo / Brave / Puppeteer fallback).",
660
+ usage: ["apx search \"<query>\" [--mode auto|ddg|brave|browser] [-n N] [--json]"],
661
+ options: [
662
+ ["--mode <m>", "auto (default) | ddg | brave | browser. Brave needs BRAVE_API_KEY."],
663
+ ["-n N", "Number of results (default 5, max 20)."],
664
+ ["--json", "Output raw JSON instead of the formatted list."],
665
+ ],
666
+ examples: [
667
+ "apx search \"agent project context\"",
668
+ "apx search \"node 22 release notes\" --mode ddg -n 10",
669
+ "apx search \"weather buenos aires\" --json",
670
+ ],
671
+ }),
656
672
  "messages chat": topic({
657
673
  title: "apx messages chat",
658
674
  summary: "Print APX messages as a chat transcript with user, agent, tool, or system type.",
@@ -1129,6 +1145,7 @@ function buildHelp(version) {
1129
1145
  hCmd("apx code", 36, "APX terminal coding assistant"),
1130
1146
  hCmd("apx exec <agent> \"prompt\"",36, "one-shot agent call --model <id> --max-tokens N"),
1131
1147
  hCmd("apx chat <agent>", 36, "interactive agent REPL --conversation <id>"),
1148
+ hCmd("apx search \"query\"", 36, "web search (ddg | brave | browser) --mode <m> -n N"),
1132
1149
  hCmd("apx conversations list", 36, "stored exec/chat conversations for <agent>"),
1133
1150
  hCmd("apx conversations get", 36, "<agent> <id>"),
1134
1151
 
@@ -1433,6 +1450,10 @@ async function dispatch(cmd, rest) {
1433
1450
  await cmdExec(parseArgs(rest));
1434
1451
  break;
1435
1452
 
1453
+ case "search":
1454
+ await cmdSearch(parseArgs(rest));
1455
+ break;
1456
+
1436
1457
  case "chat":
1437
1458
  await cmdChat(parseArgs(rest));
1438
1459
  break;
@@ -275,6 +275,9 @@ function transcriptLines(transcript, width) {
275
275
  for (const chunk of chunks) {
276
276
  addLine(lines, margin + C.primary + "┃" + C.panel + " " + C.text + padAnsi(chunk, inner), C.bg);
277
277
  }
278
+ if (item.meta) {
279
+ addLine(lines, margin + C.primary + "┃" + C.panel + " " + C.muted + padAnsi(item.meta, inner), C.bg);
280
+ }
278
281
  addLine(lines, margin + C.primary + "┃" + C.panel + " " + " ".repeat(inner), C.bg);
279
282
  continue;
280
283
  }
@@ -315,14 +318,22 @@ function transcriptLines(transcript, width) {
315
318
  return lines;
316
319
  }
317
320
 
318
- function renderChat(transcript, chatWidth, height, promptTop) {
321
+ function renderChat(state, chatWidth, height, promptTop) {
319
322
  const maxRows = Math.max(1, promptTop - 1);
320
- const lines = transcriptLines(transcript, chatWidth - 2);
321
- const slice = lines.slice(Math.max(0, lines.length - maxRows));
323
+ const lines = transcriptLines(state.transcript, chatWidth - 2);
324
+ const maxOffset = Math.max(0, lines.length - maxRows);
325
+ const offset = Math.min(Math.max(0, state.chatScrollOffset || 0), maxOffset);
326
+ state.chatScrollOffset = offset;
327
+ const start = Math.max(0, lines.length - maxRows - offset);
328
+ const slice = lines.slice(start, start + maxRows);
322
329
 
323
330
  for (let i = 0; i < slice.length && i < maxRows; i++) {
324
331
  writeAt(i, 0, slice[i].text, chatWidth - 1, slice[i].bg);
325
332
  }
333
+
334
+ if (offset > 0 && maxRows > 1) {
335
+ writeAt(0, 0, C.muted + `↑ ${offset} lines above bottom`, chatWidth - 1, C.bg);
336
+ }
326
337
  }
327
338
 
328
339
  function renderSidebar(state) {
@@ -407,7 +418,7 @@ export function renderTerminalChat(state) {
407
418
  renderLogo(chatWidth, Math.max(1, Math.floor(height / 2) - 8));
408
419
  } else {
409
420
  const prompt = promptGeometry(false, true, chatWidth);
410
- renderChat(state.transcript, chatWidth, height, prompt.top);
421
+ renderChat(state, chatWidth, height, prompt.top);
411
422
  }
412
423
 
413
424
  const cursor = renderPromptBlock(state, chatWidth);
@@ -1,6 +1,17 @@
1
1
  import fs from "node:fs";
2
2
  import path from "node:path";
3
3
 
4
+ // ---------------------------------------------------------------------------
5
+ // Anti-ghost-response rules injected into every agent system prompt.
6
+ // Prevents agents from saying "Ok, I'll do that" and then doing nothing.
7
+ // ---------------------------------------------------------------------------
8
+ const ACTION_DISCIPLINE_RULES = `## Action Discipline (mandatory)
9
+ - NEVER acknowledge an action without executing it in the same turn. If you are going to do something, call the tool FIRST, then report the result.
10
+ - NEVER use empty acknowledgments like "Ok", "Got it", "Sure", "Understood", "On it", "Give me a moment", "I'll do that now" as standalone responses when a tool call is expected. These are invalid responses.
11
+ - Action first, report after. Produce the tool call in the same response as your acknowledgment.
12
+ - If you cannot execute the action (missing permission, unclear params, tool not available), explain WHY — do not promise and disappear.
13
+ - If the user asks you to do multiple things, do them all in the same turn using sequential tool calls if needed.`;
14
+
4
15
  function listField(value) {
5
16
  if (Array.isArray(value)) return value.map(String).map((s) => s.trim()).filter(Boolean);
6
17
  return String(value || "").split(",").map((s) => s.trim()).filter(Boolean);
@@ -66,6 +77,10 @@ export function buildAgentSystem(project, agent, {
66
77
  if (ep) parts.push(ep);
67
78
  }
68
79
 
80
+ // Always append action discipline rules last so they are close to the end
81
+ // of the system prompt and harder for the model to "forget".
82
+ parts.push(ACTION_DISCIPLINE_RULES);
83
+
69
84
  return parts.join("\n\n");
70
85
  }
71
86
 
@@ -42,6 +42,22 @@ The output is the agent's full stdout. If it printed `APC_RESULT: <value>`, that
42
42
  apx exec <slug> "<prompt>"
43
43
  ```
44
44
 
45
+ ## Command accuracy
46
+
47
+ Do not invent APX subcommands. Before telling another runtime to call APX, verify the exact CLI
48
+ form with `apx --help` or `apx <command> --help`.
49
+
50
+ Known Telegram form:
51
+
52
+ ```bash
53
+ apx telegram status
54
+ apx telegram send "message"
55
+ apx telegram send "message" --chat 123456
56
+ ```
57
+
58
+ Do not use guessed aliases such as `apx send-telegram` or `apx telegram "message"` unless current
59
+ `apx --help` shows that exact form.
60
+
45
61
  ## MCP tools
46
62
 
47
63
  MCPs declared in `.apc/mcps.json` are proxied through the APX daemon. Use `apx mcp` only for MCPs registered there — not for MCPs that are already running locally in your IDE session.
@@ -0,0 +1,68 @@
1
+ ---
2
+ name: claude-code
3
+ description: "Activate ONLY when the user explicitly mentions Claude Code, Claude CLI, claude command, Anthropic Claude Code, installing Claude Code, using Claude Code, or APX runtime claude-code. Do not activate for generic Claude model discussion."
4
+ homepage: https://docs.anthropic.com/en/docs/claude-code
5
+ ---
6
+ # Claude Code CLI
7
+
8
+ Use this skill only for Claude Code CLI install, auth, usage, or APX runtime dispatch.
9
+
10
+ ## Verify before acting
11
+
12
+ Check the local CLI first:
13
+
14
+ ```bash
15
+ claude --version
16
+ claude --help
17
+ ```
18
+
19
+ Do not invent flags. If a command is uncertain, inspect help for the exact subcommand before
20
+ running it.
21
+
22
+ ## Install
23
+
24
+ Common install/update path:
25
+
26
+ ```bash
27
+ npm install -g @anthropic-ai/claude-code
28
+ claude --version
29
+ ```
30
+
31
+ Claude Code also exposes:
32
+
33
+ ```bash
34
+ claude install
35
+ claude update
36
+ claude auth
37
+ ```
38
+
39
+ Use `claude --help` to confirm current syntax.
40
+
41
+ ## Non-interactive use
42
+
43
+ Prefer headless print mode:
44
+
45
+ ```bash
46
+ claude -p "task" --output-format json
47
+ claude -p "task" --append-system-prompt "system instructions" --output-format json
48
+ ```
49
+
50
+ For high-trust automation in an already sandboxed environment:
51
+
52
+ ```bash
53
+ claude -p "task" --permission-mode bypassPermissions --output-format json
54
+ ```
55
+
56
+ ## APX runtime
57
+
58
+ Run a project agent through Claude Code:
59
+
60
+ ```bash
61
+ apx run <agent> --runtime claude-code "task"
62
+ ```
63
+
64
+ If the task needs Telegram, tell Claude Code the exact APX command:
65
+
66
+ ```bash
67
+ apx telegram send "message"
68
+ ```
@@ -0,0 +1,75 @@
1
+ ---
2
+ name: codex-cli
3
+ description: "Activate ONLY when the user explicitly mentions Codex CLI, OpenAI Codex, @openai/codex, codex command, codex exec, installing Codex, using Codex, ~/.codex, or APX runtime codex."
4
+ homepage: https://developers.openai.com/codex
5
+ ---
6
+ # Codex CLI
7
+
8
+ Use this skill only for Codex CLI install, auth, usage, or APX runtime dispatch.
9
+
10
+ ## Verify before acting
11
+
12
+ Check local CLI and exact flags first:
13
+
14
+ ```bash
15
+ codex --version
16
+ codex --help
17
+ codex exec --help
18
+ ```
19
+
20
+ Do not invent flags. Current Codex CLI may reject older flags such as `--approval-mode` or
21
+ `--full-auto`.
22
+
23
+ ## Install
24
+
25
+ Common install/update path:
26
+
27
+ ```bash
28
+ npm install -g @openai/codex
29
+ codex --version
30
+ ```
31
+
32
+ Codex also exposes:
33
+
34
+ ```bash
35
+ codex login
36
+ codex update
37
+ ```
38
+
39
+ Auth check:
40
+
41
+ ```bash
42
+ test -f ~/.codex/auth.json && echo "codex auth present"
43
+ ```
44
+
45
+ ## Non-interactive use
46
+
47
+ Use `exec`, not interactive TUI, for automation:
48
+
49
+ ```bash
50
+ codex exec --sandbox workspace-write --skip-git-repo-check "task"
51
+ ```
52
+
53
+ Useful options:
54
+
55
+ ```bash
56
+ codex exec --sandbox workspace-write --skip-git-repo-check --output-last-message /tmp/codex-last.txt "task"
57
+ codex exec --json --sandbox workspace-write --skip-git-repo-check "task"
58
+ ```
59
+
60
+ `--skip-git-repo-check` matters for APX default runtime dirs such as `~/.apx/projects/default`,
61
+ which may not be Git repositories.
62
+
63
+ ## APX runtime
64
+
65
+ Run a project agent through Codex:
66
+
67
+ ```bash
68
+ apx run <agent> --runtime codex "task"
69
+ ```
70
+
71
+ If the task needs Telegram, tell Codex the exact APX command:
72
+
73
+ ```bash
74
+ apx telegram send "message"
75
+ ```
@@ -0,0 +1,59 @@
1
+ ---
2
+ name: opencode-cli
3
+ description: "Activate ONLY when the user explicitly mentions OpenCode, opencode command, installing OpenCode, using OpenCode, OpenCode provider setup, or APX runtime opencode."
4
+ homepage: https://opencode.ai/docs
5
+ ---
6
+ # OpenCode CLI
7
+
8
+ Use this skill only for OpenCode CLI install, auth/provider setup, usage, or APX runtime dispatch.
9
+
10
+ ## Verify before acting
11
+
12
+ Check local CLI and exact flags first:
13
+
14
+ ```bash
15
+ opencode --version
16
+ opencode --help
17
+ opencode run --help
18
+ ```
19
+
20
+ Do not invent flags. Inspect help for the exact subcommand before running uncertain commands.
21
+
22
+ ## Install
23
+
24
+ Use the official install method for the target machine, then verify:
25
+
26
+ ```bash
27
+ opencode --version
28
+ ```
29
+
30
+ Provider/auth management:
31
+
32
+ ```bash
33
+ opencode providers
34
+ opencode models
35
+ ```
36
+
37
+ ## Non-interactive use
38
+
39
+ Use headless run:
40
+
41
+ ```bash
42
+ opencode run "task"
43
+ opencode run --model provider/model "task"
44
+ opencode run --dangerously-skip-permissions "task"
45
+ ```
46
+
47
+ ## APX runtime
48
+
49
+ Run a project agent through OpenCode:
50
+
51
+ ```bash
52
+ apx run <agent> --runtime opencode "task"
53
+ ```
54
+
55
+ If the task needs Telegram, tell OpenCode the exact APX command:
56
+
57
+ ```bash
58
+ apx telegram send "message"
59
+ ```