@openape/apes 1.1.1 → 1.2.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/dist/cli.js CHANGED
@@ -3050,6 +3050,37 @@ function readAuth() {
3050
3050
  if (!parsed.access_token) throw new CliError("auth.json missing access_token");
3051
3051
  return parsed;
3052
3052
  }
3053
+ async function postRunResultToChat(opts) {
3054
+ const endpoint = (opts.endpoint ?? process.env.APE_CHAT_ENDPOINT ?? "https://chat.openape.ai").replace(/\/$/, "");
3055
+ try {
3056
+ const contactsRes = await fetch(`${endpoint}/api/contacts`, {
3057
+ headers: { Authorization: `Bearer ${opts.authToken}` }
3058
+ });
3059
+ if (!contactsRes.ok) return;
3060
+ const contacts = await contactsRes.json();
3061
+ const ownerLower = opts.ownerEmail.toLowerCase();
3062
+ const ownerRow = contacts.find((c) => c.peerEmail.toLowerCase() === ownerLower && c.connected && c.roomId);
3063
+ if (!ownerRow?.roomId) {
3064
+ consola22.info("chat DM skipped \u2014 no active room with owner (accept the contact request in chat to enable)");
3065
+ return;
3066
+ }
3067
+ const prefix = opts.status === "ok" ? "\u2705" : "\u274C";
3068
+ const msg = opts.finalMessage?.trim() || (opts.status === "ok" ? "(no output)" : "(crashed)");
3069
+ const body = `${prefix} *${opts.taskName}* (${opts.stepCount} steps)
3070
+
3071
+ ${msg}`.slice(0, 9e3);
3072
+ const postRes = await fetch(`${endpoint}/api/rooms/${encodeURIComponent(ownerRow.roomId)}/messages`, {
3073
+ method: "POST",
3074
+ headers: { "Authorization": `Bearer ${opts.authToken}`, "Content-Type": "application/json" },
3075
+ body: JSON.stringify({ body })
3076
+ });
3077
+ if (!postRes.ok) {
3078
+ consola22.warn(`chat DM post failed: ${postRes.status}`);
3079
+ }
3080
+ } catch (err) {
3081
+ consola22.warn(`chat DM error: ${err.message}`);
3082
+ }
3083
+ }
3053
3084
  function readTaskSpec(taskId) {
3054
3085
  const path2 = join3(TASK_CACHE_DIR, `${taskId}.json`);
3055
3086
  if (!existsSync5(path2)) {
@@ -3057,6 +3088,15 @@ function readTaskSpec(taskId) {
3057
3088
  }
3058
3089
  return JSON.parse(readFileSync5(path2, "utf8"));
3059
3090
  }
3091
+ var AGENT_CONFIG_PATH = join3(homedir5(), ".openape", "agent", "agent.json");
3092
+ function readAgentConfig() {
3093
+ if (!existsSync5(AGENT_CONFIG_PATH)) return { systemPrompt: "" };
3094
+ try {
3095
+ return JSON.parse(readFileSync5(AGENT_CONFIG_PATH, "utf8"));
3096
+ } catch {
3097
+ return { systemPrompt: "" };
3098
+ }
3099
+ }
3060
3100
  function readLitellmConfig(model) {
3061
3101
  const envPath = join3(homedir5(), "litellm", ".env");
3062
3102
  const env = {};
@@ -3100,6 +3140,7 @@ var runAgentCommand = defineCommand24({
3100
3140
  const taskId = args["task-id"];
3101
3141
  const auth = readAuth();
3102
3142
  const spec = readTaskSpec(taskId);
3143
+ const agentCfg = readAgentConfig();
3103
3144
  const config = readLitellmConfig(args.model);
3104
3145
  let tools;
3105
3146
  try {
@@ -3113,11 +3154,12 @@ var runAgentCommand = defineCommand24({
3113
3154
  try {
3114
3155
  const result = await runLoop({
3115
3156
  config,
3116
- systemPrompt: spec.systemPrompt,
3117
- // Cron tasks have no prior user message the task fires by
3118
- // schedule. We use a synthetic kick-off message; tasks can
3119
- // ignore it via their system prompt.
3120
- userMessage: "It is time to run this task. Use your tools as needed and report when done.",
3157
+ // Agent persona/behaviour ("you are Igor, …") set at agent level;
3158
+ // the task's userPrompt is the imperative job ("read my mail and
3159
+ // summarise"). The cron firing is the trigger — the message body
3160
+ // is the task itself.
3161
+ systemPrompt: agentCfg.systemPrompt,
3162
+ userMessage: spec.userPrompt,
3121
3163
  tools,
3122
3164
  maxSteps: spec.maxSteps
3123
3165
  });
@@ -3128,6 +3170,16 @@ var runAgentCommand = defineCommand24({
3128
3170
  trace: result.trace
3129
3171
  });
3130
3172
  consola22.success(`Run ${runId} ${result.status} (${result.stepCount} steps)`);
3173
+ if (auth.owner_email) {
3174
+ await postRunResultToChat({
3175
+ authToken: auth.access_token,
3176
+ ownerEmail: auth.owner_email,
3177
+ taskName: spec.name,
3178
+ status: result.status,
3179
+ stepCount: result.stepCount,
3180
+ finalMessage: result.finalMessage
3181
+ });
3182
+ }
3131
3183
  if (result.status === "error") process.exit(1);
3132
3184
  } catch (err) {
3133
3185
  const message = err?.message ?? String(err);
@@ -3138,6 +3190,16 @@ var runAgentCommand = defineCommand24({
3138
3190
  trace: []
3139
3191
  }).catch(() => {
3140
3192
  });
3193
+ if (auth.owner_email) {
3194
+ await postRunResultToChat({
3195
+ authToken: auth.access_token,
3196
+ ownerEmail: auth.owner_email,
3197
+ taskName: spec.name,
3198
+ status: "error",
3199
+ stepCount: 0,
3200
+ finalMessage: message
3201
+ });
3202
+ }
3141
3203
  throw new CliError(`Run ${runId} crashed: ${message}`);
3142
3204
  }
3143
3205
  }
@@ -3281,16 +3343,18 @@ function escape(s) {
3281
3343
  }
3282
3344
  function buildSyncPlist(input) {
3283
3345
  const pathLine = ` <key>PATH</key><string>${escape(input.homeDir)}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
3346
+ `;
3347
+ const agentUserLine = ` <key>AGENT_USER</key><string>${escape(input.userName)}</string>
3284
3348
  `;
3285
3349
  const envBlock = input.troopUrl ? ` <key>EnvironmentVariables</key>
3286
3350
  <dict>
3287
3351
  <key>HOME</key><string>${escape(input.homeDir)}</string>
3288
- ${pathLine} <key>OPENAPE_TROOP_URL</key><string>${escape(input.troopUrl)}</string>
3352
+ ${pathLine}${agentUserLine} <key>OPENAPE_TROOP_URL</key><string>${escape(input.troopUrl)}</string>
3289
3353
  </dict>
3290
3354
  ` : ` <key>EnvironmentVariables</key>
3291
3355
  <dict>
3292
3356
  <key>HOME</key><string>${escape(input.homeDir)}</string>
3293
- ${pathLine} </dict>
3357
+ ${pathLine}${agentUserLine} </dict>
3294
3358
  `;
3295
3359
  return `<?xml version="1.0" encoding="UTF-8"?>
3296
3360
  <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
@@ -3298,8 +3362,6 @@ ${pathLine} </dict>
3298
3362
  <dict>
3299
3363
  <key>Label</key>
3300
3364
  <string>${escape(syncPlistLabel(input.agentName))}</string>
3301
- <key>UserName</key>
3302
- <string>${escape(input.userName)}</string>
3303
3365
  <key>ProgramArguments</key>
3304
3366
  <array>
3305
3367
  <string>${escape(input.apesBin)}</string>
@@ -3688,8 +3750,8 @@ async function resolveClaudeToken(opts) {
3688
3750
  }
3689
3751
 
3690
3752
  // src/commands/agents/sync.ts
3691
- import { existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, writeFileSync as writeFileSync6 } from "fs";
3692
- import { homedir as homedir10 } from "os";
3753
+ import { chownSync, existsSync as existsSync9, mkdirSync as mkdirSync4, readFileSync as readFileSync10, statSync, writeFileSync as writeFileSync6 } from "fs";
3754
+ import { homedir as homedir9 } from "os";
3693
3755
  import { join as join8 } from "path";
3694
3756
  import { defineCommand as defineCommand27 } from "citty";
3695
3757
  import consola24 from "consola";
@@ -3697,11 +3759,11 @@ import consola24 from "consola";
3697
3759
  // src/lib/launchd-reconcile.ts
3698
3760
  import { execFileSync as execFileSync8 } from "child_process";
3699
3761
  import { mkdirSync as mkdirSync3, readdirSync, readFileSync as readFileSync9, unlinkSync, writeFileSync as writeFileSync5 } from "fs";
3700
- import { homedir as homedir9, userInfo as userInfo2 } from "os";
3762
+ import { userInfo as userInfo2 } from "os";
3701
3763
  import { join as join7 } from "path";
3702
3764
  var PLIST_PREFIX = "openape.troop.";
3703
3765
  function plistDir() {
3704
- return join7(homedir9(), "Library", "LaunchAgents");
3766
+ return "/Library/LaunchDaemons";
3705
3767
  }
3706
3768
  function plistPath(agentName, taskId) {
3707
3769
  return join7(plistDir(), `${PLIST_PREFIX}${agentName}.${taskId}.plist`);
@@ -3786,6 +3848,8 @@ ${calendarBlocks}
3786
3848
  <dict>
3787
3849
  <key>Label</key>
3788
3850
  <string>${escape2(input.label)}</string>
3851
+ <key>UserName</key>
3852
+ <string>${escape2(input.userName)}</string>
3789
3853
  <key>ProgramArguments</key>
3790
3854
  <array>
3791
3855
  <string>${escape2(input.apesBin)}</string>
@@ -3799,6 +3863,8 @@ ${calendarBlocks}
3799
3863
  <dict>
3800
3864
  <key>HOME</key>
3801
3865
  <string>${escape2(input.homeDir)}</string>
3866
+ <key>PATH</key>
3867
+ <string>${escape2(input.homeDir)}/.bun/bin:/opt/homebrew/bin:/usr/local/bin:/usr/bin:/bin</string>
3802
3868
  </dict>
3803
3869
  ${calendarKey}
3804
3870
  <key>StandardOutPath</key>
@@ -3815,22 +3881,20 @@ function buildPlistContent(args) {
3815
3881
  apesBin: args.apesBin,
3816
3882
  taskId: args.task.taskId,
3817
3883
  schedule: cronToSchedule(args.task.cron),
3818
- homeDir: args.homeDir
3884
+ homeDir: args.homeDir,
3885
+ userName: args.userName ?? userInfo2().username
3819
3886
  });
3820
3887
  }
3821
- function uid() {
3822
- return userInfo2().uid;
3823
- }
3824
3888
  function bootstrap(label, path2) {
3825
3889
  try {
3826
- execFileSync8("/bin/launchctl", ["bootout", `gui/${uid()}/${label}`], { stdio: "ignore" });
3890
+ execFileSync8("/bin/launchctl", ["bootout", `system/${label}`], { stdio: "ignore" });
3827
3891
  } catch {
3828
3892
  }
3829
- execFileSync8("/bin/launchctl", ["bootstrap", `gui/${uid()}`, path2], { stdio: "ignore" });
3893
+ execFileSync8("/bin/launchctl", ["bootstrap", "system", path2], { stdio: "ignore" });
3830
3894
  }
3831
3895
  function bootout(label) {
3832
3896
  try {
3833
- execFileSync8("/bin/launchctl", ["bootout", `gui/${uid()}/${label}`], { stdio: "ignore" });
3897
+ execFileSync8("/bin/launchctl", ["bootout", `system/${label}`], { stdio: "ignore" });
3834
3898
  } catch {
3835
3899
  }
3836
3900
  }
@@ -3859,7 +3923,8 @@ function reconcile(input) {
3859
3923
  agentName: input.agentName,
3860
3924
  apesBin: input.apesBin,
3861
3925
  homeDir: input.homeDir,
3862
- task
3926
+ task,
3927
+ userName: input.userName
3863
3928
  });
3864
3929
  let existingContent = "";
3865
3930
  try {
@@ -3906,8 +3971,8 @@ function getHostname() {
3906
3971
  }
3907
3972
 
3908
3973
  // src/commands/agents/sync.ts
3909
- var AUTH_PATH3 = join8(homedir10(), ".config", "apes", "auth.json");
3910
- var TASK_CACHE_DIR2 = join8(homedir10(), ".openape", "agent", "tasks");
3974
+ var AUTH_PATH3 = join8(homedir9(), ".config", "apes", "auth.json");
3975
+ var TASK_CACHE_DIR2 = join8(homedir9(), ".openape", "agent", "tasks");
3911
3976
  function readAuthJson() {
3912
3977
  if (!existsSync9(AUTH_PATH3)) {
3913
3978
  throw new CliError(
@@ -3977,20 +4042,56 @@ var syncAgentCommand = defineCommand27({
3977
4042
  ownerEmail: auth.owner_email
3978
4043
  });
3979
4044
  consola24.info(sync.first_sync ? "\u2713 first sync \u2014 agent registered" : "\u2713 presence updated");
3980
- const tasks = await client.listTasks();
4045
+ const { system_prompt: systemPrompt, tasks } = await client.listTasks();
3981
4046
  consola24.info(`Pulled ${tasks.length} task${tasks.length === 1 ? "" : "s"}`);
4047
+ let agentUid = null;
4048
+ let agentGid = null;
4049
+ if (process.geteuid?.() === 0) {
4050
+ try {
4051
+ const homeStat = statSync(homedir9());
4052
+ agentUid = homeStat.uid;
4053
+ agentGid = homeStat.gid;
4054
+ } catch {
4055
+ }
4056
+ }
4057
+ function chownToAgent(path2) {
4058
+ if (agentUid !== null && agentGid !== null) {
4059
+ try {
4060
+ chownSync(path2, agentUid, agentGid);
4061
+ } catch {
4062
+ }
4063
+ }
4064
+ }
4065
+ const agentDir = join8(homedir9(), ".openape", "agent");
4066
+ mkdirSync4(agentDir, { recursive: true });
4067
+ chownToAgent(join8(homedir9(), ".openape"));
4068
+ chownToAgent(agentDir);
4069
+ const agentJsonPath = join8(agentDir, "agent.json");
4070
+ writeFileSync6(
4071
+ agentJsonPath,
4072
+ `${JSON.stringify({ systemPrompt }, null, 2)}
4073
+ `,
4074
+ { mode: 384 }
4075
+ );
4076
+ chownToAgent(agentJsonPath);
3982
4077
  mkdirSync4(TASK_CACHE_DIR2, { recursive: true });
4078
+ chownToAgent(TASK_CACHE_DIR2);
3983
4079
  for (const task of tasks) {
3984
4080
  const path2 = join8(TASK_CACHE_DIR2, `${task.taskId}.json`);
3985
4081
  writeFileSync6(path2, `${JSON.stringify(task, null, 2)}
3986
4082
  `, { mode: 384 });
4083
+ chownToAgent(path2);
3987
4084
  }
3988
4085
  const apesBin = findApesBin();
3989
4086
  const result = reconcile({
3990
4087
  agentName,
3991
4088
  apesBin,
3992
- homeDir: homedir10(),
3993
- desired: tasks
4089
+ homeDir: homedir9(),
4090
+ desired: tasks,
4091
+ // Sync runs as root in production — pass the agent username
4092
+ // explicitly for the UserName plist key (launchd will then run
4093
+ // each task daemon AS the agent, not as root).
4094
+ userName: process.env.AGENT_USER || void 0
3994
4095
  });
3995
4096
  if (result.added.length) consola24.success(`launchd: added ${result.added.join(", ")}`);
3996
4097
  if (result.updated.length) consola24.success(`launchd: updated ${result.updated.join(", ")}`);
@@ -5279,7 +5380,7 @@ var mcpCommand = defineCommand36({
5279
5380
  if (transport !== "stdio" && transport !== "sse") {
5280
5381
  throw new Error('Transport must be "stdio" or "sse"');
5281
5382
  }
5282
- const { startMcpServer } = await import("./server-PZEAFXN6.js");
5383
+ const { startMcpServer } = await import("./server-YDC3S4PC.js");
5283
5384
  await startMcpServer(transport, port);
5284
5385
  }
5285
5386
  });
@@ -5917,7 +6018,7 @@ async function bestEffortGrantCount(idp) {
5917
6018
  }
5918
6019
  }
5919
6020
  async function runHealth(args) {
5920
- const version = true ? "1.1.1" : "0.0.0";
6021
+ const version = true ? "1.2.1" : "0.0.0";
5921
6022
  const auth = loadAuth();
5922
6023
  if (!auth) {
5923
6024
  throw new CliError("Not logged in. Run `apes login` first.", 1);
@@ -6111,12 +6212,12 @@ var workflowsCommand = defineCommand47({
6111
6212
 
6112
6213
  // src/version-check.ts
6113
6214
  import { existsSync as existsSync13, mkdirSync as mkdirSync5, readFileSync as readFileSync13, writeFileSync as writeFileSync9 } from "fs";
6114
- import { homedir as homedir11 } from "os";
6215
+ import { homedir as homedir10 } from "os";
6115
6216
  import { join as join11 } from "path";
6116
6217
  import consola38 from "consola";
6117
6218
  var PACKAGE_NAME = "@openape/apes";
6118
6219
  var CACHE_TTL_MS = 24 * 60 * 60 * 1e3;
6119
- var CACHE_FILE = join11(homedir11(), ".config", "apes", ".version-check.json");
6220
+ var CACHE_FILE = join11(homedir10(), ".config", "apes", ".version-check.json");
6120
6221
  function readCache() {
6121
6222
  if (!existsSync13(CACHE_FILE)) return null;
6122
6223
  try {
@@ -6127,7 +6228,7 @@ function readCache() {
6127
6228
  }
6128
6229
  function writeCache(entry) {
6129
6230
  try {
6130
- const dir = join11(homedir11(), ".config", "apes");
6231
+ const dir = join11(homedir10(), ".config", "apes");
6131
6232
  if (!existsSync13(dir)) mkdirSync5(dir, { recursive: true, mode: 448 });
6132
6233
  writeFileSync9(CACHE_FILE, JSON.stringify(entry), { mode: 384 });
6133
6234
  } catch {
@@ -6190,10 +6291,10 @@ if (shellRewrite) {
6190
6291
  if (shellRewrite.action === "rewrite") {
6191
6292
  process.argv = shellRewrite.argv;
6192
6293
  } else if (shellRewrite.action === "version") {
6193
- console.log(`ape-shell ${"1.1.1"} (OpenApe DDISA shell wrapper)`);
6294
+ console.log(`ape-shell ${"1.2.1"} (OpenApe DDISA shell wrapper)`);
6194
6295
  process.exit(0);
6195
6296
  } else if (shellRewrite.action === "help") {
6196
- console.log(`ape-shell ${"1.1.1"} \u2014 OpenApe DDISA shell wrapper`);
6297
+ console.log(`ape-shell ${"1.2.1"} \u2014 OpenApe DDISA shell wrapper`);
6197
6298
  console.log("");
6198
6299
  console.log("Usage:");
6199
6300
  console.log(" ape-shell Start interactive grant-mediated REPL");
@@ -6251,7 +6352,7 @@ var configCommand = defineCommand48({
6251
6352
  var main = defineCommand48({
6252
6353
  meta: {
6253
6354
  name: "apes",
6254
- version: "1.1.1",
6355
+ version: "1.2.1",
6255
6356
  description: "Unified CLI for OpenApe"
6256
6357
  },
6257
6358
  subCommands: {
@@ -6306,7 +6407,7 @@ async function maybeRefreshAuth() {
6306
6407
  }
6307
6408
  }
6308
6409
  await maybeRefreshAuth();
6309
- await maybeWarnStaleVersion("1.1.1").catch(() => {
6410
+ await maybeWarnStaleVersion("1.2.1").catch(() => {
6310
6411
  });
6311
6412
  runMain(main).catch((err) => {
6312
6413
  if (err instanceof CliExit) {