@agentprojectcontext/apx 1.8.2 → 1.10.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
@@ -26,7 +26,7 @@ npm install -g apx
26
26
  # In any directory with an AGENTS.md
27
27
  apx init
28
28
 
29
- # Spawn an agent with a full Claude Code runtime
29
+ # Spawn an agent with a full external runtime
30
30
  apx run sofia --runtime claude-code "Review the open PRs and summarize them"
31
31
 
32
32
  # Or a quick one-shot LLM exec
@@ -84,6 +84,7 @@ apx memory <slug> # read agent memory
84
84
  apx memory <slug> --append "<note>" # append to memory
85
85
 
86
86
  apx run <slug> --runtime claude-code "<prompt>" # full runtime session
87
+ apx run <slug> --runtime cursor-agent "<prompt>" # Cursor Agent runtime
87
88
  apx exec <slug> "<prompt>" # quick LLM call
88
89
 
89
90
  apx session list <slug> # list past sessions
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@agentprojectcontext/apx",
3
- "version": "1.8.2",
3
+ "version": "1.10.0",
4
4
  "description": "APX — unified CLI + daemon for the Agent Project Context (APC) standard.",
5
5
  "publishConfig": {
6
6
  "access": "public"
@@ -29,6 +29,9 @@
29
29
  },
30
30
  "dependencies": {
31
31
  "@modelcontextprotocol/sdk": "^1.29.0",
32
+ "chalk": "^5.6.2",
33
+ "cli-cursor": "^5.0.0",
34
+ "cron-parser": "^5.5.0",
32
35
  "express": "^4.21.0",
33
36
  "node-fetch": "^3.3.2"
34
37
  },
@@ -26,6 +26,11 @@ apx mcp list # MCP servers available to this project
26
26
  # Full external session (best for complex, multi-step tasks)
27
27
  apx run <slug> --runtime claude-code "<prompt>"
28
28
  apx run <slug> --runtime codex "<prompt>"
29
+ apx run <slug> --runtime opencode "<prompt>"
30
+ apx run <slug> --runtime aider "<prompt>"
31
+ apx run <slug> --runtime cursor-agent "<prompt>"
32
+ apx run <slug> --runtime gemini-cli "<prompt>"
33
+ apx run <slug> --runtime qwen-code "<prompt>"
29
34
 
30
35
  # Quick one-shot LLM call (requires engine API key in ~/.apx/config.json)
31
36
  apx exec <slug> "<prompt>"
@@ -0,0 +1,45 @@
1
+ import { http } from "../http.js";
2
+ import { resolveProjectId } from "./project.js";
3
+
4
+ export async function cmdArtifactCreate(args) {
5
+ const name = args._[0];
6
+ if (!name) throw new Error("apx artifact create: missing <name>");
7
+ const pid = await resolveProjectId(args?.flags?.project);
8
+ const content = args.flags.content && args.flags.content !== true ? String(args.flags.content) : "";
9
+ const r = await http.post(`/projects/${pid}/artifacts`, { name, content });
10
+ console.log(r.path);
11
+ }
12
+
13
+ export async function cmdArtifactList(args = {}) {
14
+ const pid = await resolveProjectId(args?.flags?.project);
15
+ const rows = await http.get(`/projects/${pid}/artifacts`);
16
+ if (rows.length === 0) {
17
+ console.log(`(no artifacts in project #${pid})`);
18
+ return;
19
+ }
20
+ console.log(`project #${pid} artifacts:`);
21
+ console.log("NAME".padEnd(30) + " SIZE MODIFIED");
22
+ for (const a of rows) {
23
+ console.log(
24
+ a.name.padEnd(30) + " " +
25
+ String(a.size).padEnd(6) + " " +
26
+ (a.modified || "—")
27
+ );
28
+ }
29
+ }
30
+
31
+ export async function cmdArtifactShow(args) {
32
+ const name = args._[0];
33
+ if (!name) throw new Error("apx artifact show: missing <name>");
34
+ const pid = await resolveProjectId(args?.flags?.project);
35
+ const r = await http.get(`/projects/${pid}/artifacts/${encodeURIComponent(name)}`);
36
+ process.stdout.write(r.content);
37
+ }
38
+
39
+ export async function cmdArtifactRemove(args) {
40
+ const name = args._[0];
41
+ if (!name) throw new Error("apx artifact remove: missing <name>");
42
+ const pid = await resolveProjectId(args?.flags?.project);
43
+ await http.delete(`/projects/${pid}/artifacts/${encodeURIComponent(name)}`);
44
+ console.log(`removed artifact "${name}"`);
45
+ }
@@ -67,9 +67,23 @@ export async function cmdRoutineAdd(args) {
67
67
  const allowed_tools = args.flags["allowed-tools"] && args.flags["allowed-tools"] !== true
68
68
  ? String(args.flags["allowed-tools"]).split(",").map((s) => s.trim()).filter(Boolean)
69
69
  : undefined;
70
+ // Pipeline: pre/post commands as comma-separated list or repeated --pre-commands flags.
71
+ const parseCmdList = (val) => {
72
+ if (!val || val === true) return undefined;
73
+ if (Array.isArray(val)) return val.flatMap((v) => String(v).split(",")).map((s) => s.trim()).filter(Boolean);
74
+ return String(val).split(",").map((s) => s.trim()).filter(Boolean);
75
+ };
76
+ const pre_commands = parseCmdList(args.flags["pre-commands"]);
77
+ const post_commands = parseCmdList(args.flags["post-commands"]);
78
+ const skip_prompt_on = args.flags["skip-prompt-on"] && args.flags["skip-prompt-on"] !== true
79
+ ? args.flags["skip-prompt-on"]
80
+ : undefined;
70
81
  const pid = await resolveProjectId(args?.flags?.project);
71
- const r = await http.post(`/projects/${pid}/routines`, { name, kind, schedule, spec, permission_mode, allowed_tools });
82
+ const r = await http.post(`/projects/${pid}/routines`, { name, kind, schedule, spec, permission_mode, allowed_tools, pre_commands, post_commands, skip_prompt_on });
72
83
  console.log(`added routine "${r.name}" (${r.kind}, ${r.schedule}) → next ${r.next_run_at}`);
84
+ if (r.pre_commands?.length) console.log(` pre: ${r.pre_commands.join(", ")}`);
85
+ if (r.post_commands?.length) console.log(` post: ${r.post_commands.join(", ")}`);
86
+ if (r.skip_prompt_on && r.skip_prompt_on !== "signal") console.log(` skip_prompt_on: ${r.skip_prompt_on}`);
73
87
  }
74
88
 
75
89
  export async function cmdRoutineRemove(args) {
@@ -5,7 +5,7 @@ export async function cmdRun(args) {
5
5
  const slug = args._[0];
6
6
  if (!slug) throw new Error("apx run: usage: apx run <agent> --runtime <id> \"<prompt>\"");
7
7
  const runtime = args.flags.runtime === true ? null : args.flags.runtime;
8
- if (!runtime) throw new Error("apx run: --runtime required (claude-code | codex | opencode | aider)");
8
+ if (!runtime) throw new Error("apx run: --runtime required (claude-code | codex | opencode | aider | cursor-agent | gemini-cli | qwen-code)");
9
9
 
10
10
  let prompt = args._.slice(1).join(" ").trim();
11
11
  if (!prompt || prompt === "-") {
@@ -0,0 +1,330 @@
1
+ import readline from "node:readline";
2
+ import { http } from "../http.js";
3
+ import { resolveProjectId } from "./project.js";
4
+ import { readConfig } from "../../core/config.js";
5
+ import { readIdentity } from "../../core/identity.js";
6
+ import {
7
+ C,
8
+ MODES,
9
+ readPackageVersion,
10
+ renderTerminalChat,
11
+ titlecase,
12
+ } from "../terminal-chat/renderer.js";
13
+
14
+ const MAIN_PALETTE_OPTIONS = ["Switch model", "Connect provider", "Open editor", "Exit"];
15
+
16
+ export async function cmdSys(args) {
17
+ const pid = await resolveProjectId(args?.flags?.project);
18
+ const cfg = readConfig();
19
+ const id = readIdentity();
20
+
21
+ const state = {
22
+ currentModeIdx: 0,
23
+ inputText: "",
24
+ cursorIndex: 0,
25
+ inCommandPalette: false,
26
+ paletteSelection: 0,
27
+ paletteState: "main",
28
+ paletteOptions: [...MAIN_PALETTE_OPTIONS],
29
+ activeAgent: titlecase(id?.agent_name || cfg.super_agent?.name || "SuperAgent"),
30
+ activeModel: cfg.super_agent?.model || "Claude 3.5 Sonnet",
31
+ version: readPackageVersion(),
32
+ hasStarted: false,
33
+ sessionTitle: "",
34
+ usage: { input: 0, output: 0, percent: 0 },
35
+ transcript: [],
36
+ };
37
+
38
+ const previousMessages = [];
39
+ let restored = false;
40
+ let isRequesting = false;
41
+
42
+ function restoreTerminal() {
43
+ if (restored) return;
44
+ restored = true;
45
+ if (process.stdin.isTTY) process.stdin.setRawMode(false);
46
+ process.stdout.write(C.reset + C.showCursor + C.resetBg + C.altOff);
47
+ }
48
+
49
+ function renderScreen() {
50
+ state.sessionTitle = state.transcript.find((item) => item.type === "user")?.text || "";
51
+ renderTerminalChat(state);
52
+ }
53
+
54
+ function resetPalette() {
55
+ state.paletteState = "main";
56
+ state.paletteOptions = [...MAIN_PALETTE_OPTIONS];
57
+ state.paletteSelection = 0;
58
+ }
59
+
60
+ function close() {
61
+ restoreTerminal();
62
+ process.exit(0);
63
+ }
64
+
65
+ readline.emitKeypressEvents(process.stdin);
66
+ if (process.stdin.isTTY) process.stdin.setRawMode(true);
67
+ process.stdout.write(C.altOn + C.setBgBlack + C.showCursor + C.bg);
68
+ process.once("exit", restoreTerminal);
69
+ process.once("SIGINT", close);
70
+ process.once("SIGTERM", close);
71
+ process.stdout.on?.("resize", renderScreen);
72
+ process.on("SIGWINCH", renderScreen);
73
+
74
+ renderScreen();
75
+
76
+ process.stdin.on("keypress", async (str, key) => {
77
+ if (isRequesting) return;
78
+
79
+ if (key.ctrl && key.name === "c") {
80
+ close();
81
+ }
82
+
83
+ if (key.ctrl && key.name === "p") {
84
+ state.inCommandPalette = !state.inCommandPalette;
85
+ resetPalette();
86
+ renderScreen();
87
+ return;
88
+ }
89
+
90
+ if (key.name === "escape" && state.inCommandPalette) {
91
+ if (state.paletteState !== "main") {
92
+ resetPalette();
93
+ } else {
94
+ state.inCommandPalette = false;
95
+ }
96
+ renderScreen();
97
+ return;
98
+ }
99
+
100
+ if (state.inCommandPalette) {
101
+ await handlePaletteKey(key, cfg, state, renderScreen, close);
102
+ return;
103
+ }
104
+
105
+ if (isReturnKey(key)) {
106
+ isRequesting = true;
107
+ await submitPrompt(pid, state, previousMessages, renderScreen, close);
108
+ isRequesting = false;
109
+ return;
110
+ }
111
+
112
+ if (handleEditingKey(str, key, state, renderScreen)) return;
113
+ });
114
+ }
115
+
116
+ export function isReturnKey(key) {
117
+ return key?.name === "return" || key?.name === "enter";
118
+ }
119
+
120
+ async function handlePaletteKey(key, cfg, state, renderScreen, close) {
121
+ if (key.name === "up") {
122
+ state.paletteSelection = Math.max(0, state.paletteSelection - 1);
123
+ renderScreen();
124
+ return;
125
+ }
126
+
127
+ if (key.name === "down") {
128
+ state.paletteSelection = Math.min(state.paletteOptions.length - 1, state.paletteSelection + 1);
129
+ renderScreen();
130
+ return;
131
+ }
132
+
133
+ if (key.name !== "return") {
134
+ renderScreen();
135
+ return;
136
+ }
137
+
138
+ const selected = state.paletteOptions[state.paletteSelection];
139
+
140
+ if (state.paletteState === "main") {
141
+ if (selected === "Exit") close();
142
+
143
+ if (selected === "Switch model") {
144
+ state.paletteState = "switch_model";
145
+ state.paletteOptions = ["Loading models..."];
146
+ state.paletteSelection = 0;
147
+ renderScreen();
148
+ loadModelOptions(cfg, state, renderScreen);
149
+ return;
150
+ }
151
+
152
+ state.inCommandPalette = false;
153
+ state.transcript.push({ type: "status", text: `Executing command: ${selected} (not implemented yet)` });
154
+ renderScreen();
155
+ return;
156
+ }
157
+
158
+ if (
159
+ state.paletteState === "switch_model" &&
160
+ !selected.startsWith("Loading") &&
161
+ !selected.startsWith("Failed") &&
162
+ !selected.startsWith("No ")
163
+ ) {
164
+ state.activeModel = selected;
165
+ const configModule = await import("../../core/config.js");
166
+ const currentCfg = configModule.readConfig();
167
+ if (!currentCfg.super_agent) currentCfg.super_agent = {};
168
+ currentCfg.super_agent.model = selected;
169
+ configModule.writeConfig(currentCfg);
170
+
171
+ state.inCommandPalette = false;
172
+ state.transcript.push({ type: "status", text: `Model updated globally to ${selected}` });
173
+ renderScreen();
174
+ return;
175
+ }
176
+
177
+ renderScreen();
178
+ }
179
+
180
+ function loadModelOptions(cfg, state, renderScreen) {
181
+ const baseUrl = cfg.engines?.ollama?.base_url || "http://127.0.0.1:11434";
182
+ fetch(`${baseUrl}/api/tags`)
183
+ .then((r) => r.json())
184
+ .then((data) => {
185
+ state.paletteOptions = data.models?.length
186
+ ? data.models.map((m) => "ollama:" + m.name)
187
+ : ["No Ollama models found"];
188
+ state.paletteOptions.push("openai:gpt-4o", "anthropic:claude-3-5-sonnet-20240620");
189
+ if (state.paletteState === "switch_model") renderScreen();
190
+ })
191
+ .catch(() => {
192
+ state.paletteOptions = ["Failed to load from Ollama", "openai:gpt-4o", "anthropic:claude-3-5-sonnet-20240620"];
193
+ if (state.paletteState === "switch_model") renderScreen();
194
+ });
195
+ }
196
+
197
+ export function handleEditingKey(str, key, state, renderScreen) {
198
+ if (key.name === "tab") {
199
+ state.currentModeIdx = (state.currentModeIdx + 1) % MODES.length;
200
+ renderScreen();
201
+ return true;
202
+ }
203
+
204
+ if (key.ctrl && key.name === "a") {
205
+ state.cursorIndex = 0;
206
+ renderScreen();
207
+ return true;
208
+ }
209
+
210
+ if (key.ctrl && key.name === "e") {
211
+ state.cursorIndex = state.inputText.length;
212
+ renderScreen();
213
+ return true;
214
+ }
215
+
216
+ if (key.ctrl && key.name === "u") {
217
+ state.inputText = state.inputText.slice(state.cursorIndex);
218
+ state.cursorIndex = 0;
219
+ renderScreen();
220
+ return true;
221
+ }
222
+
223
+ if (key.ctrl && key.name === "w") {
224
+ const before = state.inputText.slice(0, state.cursorIndex).replace(/\s*\S+\s*$/, "");
225
+ state.inputText = before + state.inputText.slice(state.cursorIndex);
226
+ state.cursorIndex = before.length;
227
+ renderScreen();
228
+ return true;
229
+ }
230
+
231
+ if (key.name === "left") {
232
+ state.cursorIndex = Math.max(0, state.cursorIndex - 1);
233
+ renderScreen();
234
+ return true;
235
+ }
236
+
237
+ if (key.name === "right") {
238
+ state.cursorIndex = Math.min(state.inputText.length, state.cursorIndex + 1);
239
+ renderScreen();
240
+ return true;
241
+ }
242
+
243
+ if (key.name === "home") {
244
+ state.cursorIndex = 0;
245
+ renderScreen();
246
+ return true;
247
+ }
248
+
249
+ if (key.name === "end") {
250
+ state.cursorIndex = state.inputText.length;
251
+ renderScreen();
252
+ return true;
253
+ }
254
+
255
+ if (key.name === "delete") {
256
+ state.inputText = state.inputText.slice(0, state.cursorIndex) + state.inputText.slice(state.cursorIndex + 1);
257
+ renderScreen();
258
+ return true;
259
+ }
260
+
261
+ if (key.name === "backspace") {
262
+ if (state.cursorIndex === 0) return true;
263
+ state.inputText = state.inputText.slice(0, state.cursorIndex - 1) + state.inputText.slice(state.cursorIndex);
264
+ state.cursorIndex -= 1;
265
+ renderScreen();
266
+ return true;
267
+ }
268
+
269
+ if (str && str.length === 1 && !key.ctrl && !key.meta && str >= " ") {
270
+ state.inputText = state.inputText.slice(0, state.cursorIndex) + str + state.inputText.slice(state.cursorIndex);
271
+ state.cursorIndex += str.length;
272
+ renderScreen();
273
+ return true;
274
+ }
275
+
276
+ return false;
277
+ }
278
+
279
+ async function submitPrompt(pid, state, previousMessages, renderScreen, close) {
280
+ const text = state.inputText.trim();
281
+ if (!text) return;
282
+ if (text.toLowerCase() === "exit" || text.toLowerCase() === "quit") {
283
+ close();
284
+ }
285
+
286
+ state.hasStarted = true;
287
+ state.inputText = "";
288
+ state.cursorIndex = 0;
289
+ state.transcript.push({ type: "user", text });
290
+ state.transcript.push({ type: "status", text: "Thinking..." });
291
+ renderScreen();
292
+
293
+ const startTime = Date.now();
294
+
295
+ try {
296
+ const body = {
297
+ prompt: `[Mode: ${MODES[state.currentModeIdx]}]\n${text}`,
298
+ contextNote: "Channel: terminal. Format freely using markdown, but keep it readable. Use code diffs when editing.",
299
+ previousMessages,
300
+ model: state.activeModel,
301
+ };
302
+
303
+ const result = await http.post(`/projects/${pid}/super-agent/chat`, body);
304
+ state.transcript.pop();
305
+ for (const trace of result.trace || []) {
306
+ state.transcript.push({ type: "tool", trace });
307
+ }
308
+
309
+ previousMessages.push({ role: "user", content: text });
310
+ previousMessages.push({ role: "assistant", content: result.text });
311
+ if (previousMessages.length > 20) previousMessages.splice(0, previousMessages.length - 20);
312
+
313
+ const elapsed = ((Date.now() - startTime) / 1000).toFixed(1);
314
+ state.usage.input += result.usage?.input_tokens || 0;
315
+ state.usage.output += result.usage?.output_tokens || 0;
316
+ state.usage.percent = Math.min(99, Math.round((state.usage.input / 200000) * 100));
317
+
318
+ state.transcript.push({
319
+ type: "assistant",
320
+ name: state.activeAgent,
321
+ text: result.text,
322
+ meta: `${elapsed}s · In: ${result.usage?.input_tokens || 0} Out: ${result.usage?.output_tokens || 0}`,
323
+ });
324
+ } catch (e) {
325
+ state.transcript.pop();
326
+ state.transcript.push({ type: "error", text: e.message });
327
+ }
328
+
329
+ renderScreen();
330
+ }
package/src/cli/index.js CHANGED
@@ -60,6 +60,7 @@ import {
60
60
  cmdConversationsList,
61
61
  cmdConversationsGet,
62
62
  } from "./commands/chat.js";
63
+ import { cmdSys } from "./commands/sys.js";
63
64
  import { cmdRun, cmdEnvDetect } from "./commands/runtime.js";
64
65
  import { cmdSend, cmdConnections } from "./commands/a2a.js";
65
66
  import {
@@ -87,6 +88,12 @@ import {
87
88
  cmdRoutineRun,
88
89
  cmdRoutineHistory,
89
90
  } from "./commands/routine.js";
91
+ import {
92
+ cmdArtifactCreate,
93
+ cmdArtifactList,
94
+ cmdArtifactShow,
95
+ cmdArtifactRemove,
96
+ } from "./commands/artifact.js";
90
97
 
91
98
  const __filename = fileURLToPath(import.meta.url);
92
99
  const __dirname = path.dirname(__filename);
@@ -695,6 +702,15 @@ const HELP_TOPICS = new Map(Object.entries({
695
702
  ],
696
703
  examples: ["apx chat reviewer", "apx chat reviewer --conversation abc123"],
697
704
  }),
705
+ sys: topic({
706
+ title: "apx sys",
707
+ summary: "Start an interactive super-agent chat REPL with system and workspace context.",
708
+ usage: ["apx sys [--project <name|id|path>]"],
709
+ options: [
710
+ ["--project <name|id|path>", "Pin command to a specific project."],
711
+ ],
712
+ examples: ["apx sys", "apx sys --project default"],
713
+ }),
698
714
  conversations: topic({
699
715
  title: "apx conversations",
700
716
  summary: "List or read stored agent conversations.",
@@ -729,9 +745,9 @@ const HELP_TOPICS = new Map(Object.entries({
729
745
  run: topic({
730
746
  title: "apx run",
731
747
  summary: "Launch a full external runtime session for an agent.",
732
- usage: ["apx run <agent> --runtime <claude-code|codex|opencode|aider> \"<prompt>\" [--timeout <seconds>] [--project <name|id|path>]"],
748
+ usage: ["apx run <agent> --runtime <claude-code|codex|opencode|aider|cursor-agent|gemini-cli|qwen-code> \"<prompt>\" [--timeout <seconds>] [--project <name|id|path>]"],
733
749
  options: [
734
- ["--runtime <id>", "Runtime CLI to launch: claude-code, codex, opencode, aider."],
750
+ ["--runtime <id>", "Runtime CLI to launch: claude-code, codex, opencode, aider, cursor-agent, gemini-cli, qwen-code."],
735
751
  ["--timeout <seconds>", "Runtime timeout."],
736
752
  ["--project <name|id|path>", "Pin command to a specific project."],
737
753
  ],
@@ -806,16 +822,22 @@ const HELP_TOPICS = new Map(Object.entries({
806
822
  "routine add": topic({
807
823
  title: "apx routine add",
808
824
  summary: "Create a scheduled routine.",
809
- usage: ["apx routine add <name> --kind <kind> --schedule <schedule> [--spec '<json>'] [--permission-mode <mode>] [--allowed-tools a,b] [--project <name|id|path>]"],
825
+ usage: ["apx routine add <name> --kind <kind> --schedule <schedule> [--spec '<json>'] [--pre-commands 'cmd1,cmd2'] [--post-commands 'cmd'] [--skip-prompt-on signal|pre_failure|pre_success|always|never] [--project <name|id|path>]"],
810
826
  options: [
811
827
  ["--kind <kind>", "heartbeat, exec_agent, super_agent, telegram, or shell."],
812
828
  ["--schedule <schedule>", "every:60s, every:5m, every:1h, or once:<iso>."],
813
829
  ["--spec '<json>'", "Routine-specific JSON config."],
830
+ ["--pre-commands 'cmd'", "Comma-separated shell commands to run BEFORE the LLM. Use 'artifact:<name>' shorthand."],
831
+ ["--post-commands 'cmd'", "Comma-separated shell commands to run AFTER the LLM. Env: APX_LLM_OUTPUT, APX_PRE_OUTPUT, APX_STATUS."],
832
+ ["--skip-prompt-on <mode>", "signal (default), pre_failure, pre_success, always, never."],
814
833
  ["--permission-mode <mode>", "total, automatico, or permiso."],
815
834
  ["--allowed-tools a,b", "Comma-separated allowed tool names."],
816
835
  ["--project <name|id|path>", "Pin command to a specific project."],
817
836
  ],
818
- examples: ["apx routine add check --kind shell --schedule every:1h --spec '{\"cmd\":\"npm test\"}'"],
837
+ examples: [
838
+ "apx routine add check --kind shell --schedule every:1h --spec '{\"cmd\":\"npm test\"}'",
839
+ "apx routine add asana-check --kind super_agent --schedule '*/5 * * * *' --pre-commands 'artifact:check_asana.sh' --skip-prompt-on pre_success --project 0",
840
+ ],
819
841
  }),
820
842
  "routine get": topic({
821
843
  title: "apx routine get",
@@ -859,6 +881,53 @@ const HELP_TOPICS = new Map(Object.entries({
859
881
  options: [["--project <name|id|path>", "Pin command to a specific project."]],
860
882
  examples: ["apx routine remove check"],
861
883
  }),
884
+ artifact: topic({
885
+ title: "apx artifact",
886
+ summary: "Managed files stored in project storage. Used as pre/post-commands scripts or data files for routines.",
887
+ usage: ["apx artifact <subcommand> [args] [--flags]"],
888
+ commands: [
889
+ ["create <name>", "Create a new empty artifact. Prints its absolute path."],
890
+ ["list | ls", "List artifacts in the project."],
891
+ ["show <name>", "Print artifact content."],
892
+ ["remove | rm <name>", "Delete an artifact."],
893
+ ],
894
+ examples: [
895
+ "apx artifact create check_asana.sh --project 0",
896
+ "apx artifact list",
897
+ "apx artifact show check_asana.sh",
898
+ ],
899
+ }),
900
+ "artifact create": topic({
901
+ title: "apx artifact create",
902
+ summary: "Create a new managed artifact file.",
903
+ usage: ["apx artifact create <name> [--content 'text'] [--project <name|id|path>]"],
904
+ options: [
905
+ ["--content 'text'", "Initial file content (default: empty)."],
906
+ ["--project <name|id|path>", "Pin command to a specific project."],
907
+ ],
908
+ examples: ["apx artifact create check_asana.sh --project 0"],
909
+ }),
910
+ "artifact list": topic({
911
+ title: "apx artifact list",
912
+ summary: "List artifacts in the project storage.",
913
+ usage: ["apx artifact list [--project <name|id|path>]"],
914
+ options: [["--project <name|id|path>", "Pin command to a specific project."]],
915
+ examples: ["apx artifact list"],
916
+ }),
917
+ "artifact show": topic({
918
+ title: "apx artifact show",
919
+ summary: "Print artifact content.",
920
+ usage: ["apx artifact show <name> [--project <name|id|path>]"],
921
+ options: [["--project <name|id|path>", "Pin command to a specific project."]],
922
+ examples: ["apx artifact show check_asana.sh"],
923
+ }),
924
+ "artifact remove": topic({
925
+ title: "apx artifact remove",
926
+ summary: "Delete an artifact.",
927
+ usage: ["apx artifact remove <name> [--project <name|id|path>]", "apx artifact rm <name>"],
928
+ options: [["--project <name|id|path>", "Pin command to a specific project."]],
929
+ examples: ["apx artifact remove check_asana.sh"],
930
+ }),
862
931
  command: topic({
863
932
  title: "apx command",
864
933
  summary: "List or show workflow commands from .apc/commands/.",
@@ -1073,12 +1142,13 @@ function buildHelp(version) {
1073
1142
  hSec("LLM / Chat"),
1074
1143
  hCmd("apx exec <agent> \"prompt\"",36, "--model <id> --max-tokens N --temperature T"),
1075
1144
  hCmd("apx chat <agent>", 36, "--conversation <id> --model <id> (interactive REPL)"),
1145
+ hCmd("apx sys", 36, "super-agent chat w/ system+workspace context"),
1076
1146
  hCmd("apx conversations list", 36, "<agent>"),
1077
1147
  hCmd("apx conversations get", 36, "<agent> <id>"),
1078
1148
 
1079
1149
  hSec("Runtimes"),
1080
1150
  hCmd("apx run <agent>", 36, "--runtime <id> \"prompt\" --timeout <s>"),
1081
- ` ${H.DI}runtimes: claude-code | codex | opencode | aider${H.R}`,
1151
+ ` ${H.DI}runtimes: claude-code | codex | opencode | aider | cursor-agent | gemini-cli | qwen-code${H.R}`,
1082
1152
  hCmd("apx env detect", 36, "which agent CLIs are installed"),
1083
1153
 
1084
1154
  hSec("Agent-to-Agent"),
@@ -1086,11 +1156,13 @@ function buildHelp(version) {
1086
1156
  hCmd("apx connections <agent>", 36, "mental map: who/how/when this agent talked"),
1087
1157
  hCmd("apx graph <agent>", 36, "alias for connections"),
1088
1158
 
1089
- hSec("Routines"),
1159
+ hSec("Routines & Pipeline"),
1090
1160
  hCmd("apx routine list", 36, "list routines + next/last run"),
1091
1161
  hCmd("apx routine add <name>", 36, "--kind K --schedule S [--spec '{...}']"),
1092
1162
  ` ${H.DI}kinds: heartbeat | exec_agent | super_agent | telegram | shell${H.R}`,
1093
1163
  ` ${H.DI}flags: --permission-mode total|automatico|permiso --allowed-tools a,b${H.R}`,
1164
+ ` ${H.DI}pipeline: --pre-commands 'cmd1,cmd2' --post-commands 'cmd'${H.R}`,
1165
+ ` ${H.DI} --skip-prompt-on signal|pre_failure|pre_success|always|never${H.R}`,
1094
1166
  ` ${H.DI}schedule: every:60s | every:5m | every:1h | once:<iso>${H.R}`,
1095
1167
  hCmd("apx routine get <name>", 36, ""),
1096
1168
  hCmd("apx routine history <name>", 36, "show routine execution history"),
@@ -1099,6 +1171,12 @@ function buildHelp(version) {
1099
1171
  hCmd("apx routine disable <name>", 36, ""),
1100
1172
  hCmd("apx routine remove <name>", 36, ""),
1101
1173
 
1174
+ hSec("Artifacts"),
1175
+ hCmd("apx artifact create <name>", 36, "create managed file in project storage [--content '...'] [--project 0]"),
1176
+ hCmd("apx artifact list", 36, "list artifacts"),
1177
+ hCmd("apx artifact show <name>", 36, "print artifact content"),
1178
+ hCmd("apx artifact remove <name>", 36, ""),
1179
+
1102
1180
  hSec("Commands & Skills"),
1103
1181
  hCmd("apx command list", 36, "list workflow commands (.apc/commands/)"),
1104
1182
  hCmd("apx command show <name>", 36, "print command content"),
@@ -1374,6 +1452,10 @@ async function dispatch(cmd, rest) {
1374
1452
  await cmdChat(parseArgs(rest));
1375
1453
  break;
1376
1454
 
1455
+ case "sys":
1456
+ await cmdSys(parseArgs(rest));
1457
+ break;
1458
+
1377
1459
  case "conversations":
1378
1460
  case "conv": {
1379
1461
  const sub = rest[0];
@@ -1445,6 +1527,18 @@ async function dispatch(cmd, rest) {
1445
1527
  break;
1446
1528
  }
1447
1529
 
1530
+ case "artifact":
1531
+ case "artifacts": {
1532
+ const sub = rest[0];
1533
+ const a = parseArgs(rest.slice(1));
1534
+ if (!sub || sub === "list" || sub === "ls") await cmdArtifactList(a);
1535
+ else if (sub === "create" || sub === "new") await cmdArtifactCreate(a);
1536
+ else if (sub === "show" || sub === "get") await cmdArtifactShow(a);
1537
+ else if (sub === "remove" || sub === "rm") await cmdArtifactRemove(a);
1538
+ else die(`unknown artifact subcommand: ${sub}`);
1539
+ break;
1540
+ }
1541
+
1448
1542
  case "command":
1449
1543
  case "commands": {
1450
1544
  const sub = rest[0];