nextclaw 0.3.3 → 0.4.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/dist/cli/index.js CHANGED
@@ -42,6 +42,7 @@ import {
42
42
  rmSync as rmSync2,
43
43
  writeFileSync as writeFileSync2
44
44
  } from "fs";
45
+ import { createHash } from "crypto";
45
46
  import { dirname, join as join2, resolve as resolve2 } from "path";
46
47
  import { spawn as spawn2, spawnSync } from "child_process";
47
48
  import { createInterface } from "readline";
@@ -303,6 +304,7 @@ var CliRuntime = class {
303
304
  async init(options = {}) {
304
305
  const source = options.source ?? "init";
305
306
  const prefix = options.auto ? "Auto init" : "Init";
307
+ const force = Boolean(options.force);
306
308
  const configPath = getConfigPath();
307
309
  let createdConfig = false;
308
310
  if (!existsSync2(configPath)) {
@@ -315,7 +317,7 @@ var CliRuntime = class {
315
317
  const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join2(getDataDir2(), DEFAULT_WORKSPACE_DIR) : expandHome(workspaceSetting);
316
318
  const workspaceExisted = existsSync2(workspacePath);
317
319
  mkdirSync2(workspacePath, { recursive: true });
318
- const templateResult = this.createWorkspaceTemplates(workspacePath);
320
+ const templateResult = this.createWorkspaceTemplates(workspacePath, { force });
319
321
  if (createdConfig) {
320
322
  console.log(`\u2713 ${prefix}: created config at ${configPath}`);
321
323
  }
@@ -335,7 +337,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
335
337
  console.log(` 1. Add your API key to ${configPath}`);
336
338
  console.log(` 2. Chat: ${APP_NAME} agent -m "Hello!"`);
337
339
  } else {
338
- console.log(`Tip: Run "${APP_NAME} init" to re-run initialization if needed.`);
340
+ console.log(`Tip: Run "${APP_NAME} init${force ? " --force" : ""}" to re-run initialization if needed.`);
339
341
  }
340
342
  }
341
343
  async gateway(opts) {
@@ -462,7 +464,8 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
462
464
  workspace: getWorkspacePath(config.agents.defaults.workspace),
463
465
  braveApiKey: config.tools.web.search.apiKey || void 0,
464
466
  execConfig: config.tools.exec,
465
- restrictToWorkspace: config.tools.restrictToWorkspace
467
+ restrictToWorkspace: config.tools.restrictToWorkspace,
468
+ contextConfig: config.agents.context
466
469
  });
467
470
  if (opts.message) {
468
471
  const response = await agentLoop.processDirect({
@@ -648,6 +651,7 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
648
651
  });
649
652
  return;
650
653
  }
654
+ const gatewayController = {};
651
655
  const agent = new AgentLoop({
652
656
  bus,
653
657
  providerManager: providerManager ?? new ProviderManager(provider),
@@ -658,7 +662,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
658
662
  execConfig: config.tools.exec,
659
663
  cronService: cron2,
660
664
  restrictToWorkspace: config.tools.restrictToWorkspace,
661
- sessionManager
665
+ sessionManager,
666
+ contextConfig: config.agents.context,
667
+ gatewayController
662
668
  });
663
669
  cron2.onJob = async (job) => {
664
670
  const response = await agent.processDirect({
@@ -777,6 +783,271 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
777
783
  }
778
784
  }
779
785
  };
786
+ const hashRaw = (raw) => createHash("sha256").update(raw).digest("hex");
787
+ const redactConfig = (value) => {
788
+ if (Array.isArray(value)) {
789
+ return value.map((entry) => redactConfig(entry));
790
+ }
791
+ if (!value || typeof value !== "object") {
792
+ return value;
793
+ }
794
+ const entries = value;
795
+ const output = {};
796
+ for (const [key, val] of Object.entries(entries)) {
797
+ if (/apiKey|token|secret|password|appId|clientSecret|accessKey/i.test(key)) {
798
+ output[key] = val ? "***" : val;
799
+ continue;
800
+ }
801
+ output[key] = redactConfig(val);
802
+ }
803
+ return output;
804
+ };
805
+ const readConfigSnapshot = () => {
806
+ const path = getConfigPath();
807
+ let raw = "";
808
+ let parsed = {};
809
+ if (existsSync2(path)) {
810
+ raw = readFileSync2(path, "utf-8");
811
+ try {
812
+ parsed = JSON.parse(raw);
813
+ } catch {
814
+ parsed = {};
815
+ }
816
+ }
817
+ let config2;
818
+ let valid = true;
819
+ try {
820
+ config2 = ConfigSchema.parse(parsed);
821
+ } catch {
822
+ config2 = ConfigSchema.parse({});
823
+ valid = false;
824
+ }
825
+ if (!raw) {
826
+ raw = JSON.stringify(config2, null, 2);
827
+ }
828
+ const hash = hashRaw(raw);
829
+ const redacted = redactConfig(config2);
830
+ return { raw: valid ? JSON.stringify(redacted, null, 2) : null, hash: valid ? hash : null, config: config2, redacted, valid };
831
+ };
832
+ const mergeDeep = (base, patch) => {
833
+ const next = { ...base };
834
+ for (const [key, value] of Object.entries(patch)) {
835
+ if (value && typeof value === "object" && !Array.isArray(value)) {
836
+ const baseVal = base[key];
837
+ if (baseVal && typeof baseVal === "object" && !Array.isArray(baseVal)) {
838
+ next[key] = mergeDeep(baseVal, value);
839
+ } else {
840
+ next[key] = mergeDeep({}, value);
841
+ }
842
+ } else {
843
+ next[key] = value;
844
+ }
845
+ }
846
+ return next;
847
+ };
848
+ const buildSchemaFromValue = (value) => {
849
+ if (Array.isArray(value)) {
850
+ const item = value.length ? buildSchemaFromValue(value[0]) : { type: "string" };
851
+ return { type: "array", items: item };
852
+ }
853
+ if (value && typeof value === "object") {
854
+ const props = {};
855
+ for (const [key, val] of Object.entries(value)) {
856
+ props[key] = buildSchemaFromValue(val);
857
+ }
858
+ return { type: "object", properties: props };
859
+ }
860
+ if (typeof value === "number") {
861
+ return { type: "number" };
862
+ }
863
+ if (typeof value === "boolean") {
864
+ return { type: "boolean" };
865
+ }
866
+ if (value === null) {
867
+ return { type: ["null", "string"] };
868
+ }
869
+ return { type: "string" };
870
+ };
871
+ const scheduleRestart = (delayMs, reason) => {
872
+ const delay = typeof delayMs === "number" && Number.isFinite(delayMs) ? Math.max(0, delayMs) : 100;
873
+ console.log(`Gateway restart requested via tool${reason ? ` (${reason})` : ""}.`);
874
+ setTimeout(() => {
875
+ process.exit(0);
876
+ }, delay);
877
+ };
878
+ gatewayController.status = () => ({
879
+ channels: channels2.enabledChannels,
880
+ cron: cron2.status(),
881
+ configPath: getConfigPath()
882
+ });
883
+ gatewayController.reloadConfig = async (reason) => {
884
+ await runConfigReload(reason ?? "gateway tool");
885
+ return "Config reload triggered";
886
+ };
887
+ gatewayController.restart = async (options2) => {
888
+ scheduleRestart(options2?.delayMs, options2?.reason);
889
+ return "Restart scheduled";
890
+ };
891
+ gatewayController.getConfig = async () => {
892
+ const snapshot = readConfigSnapshot();
893
+ return {
894
+ raw: snapshot.raw,
895
+ hash: snapshot.hash,
896
+ path: getConfigPath(),
897
+ config: snapshot.redacted,
898
+ parsed: snapshot.redacted,
899
+ resolved: snapshot.redacted,
900
+ valid: snapshot.valid
901
+ };
902
+ };
903
+ gatewayController.getConfigSchema = async () => {
904
+ const base = ConfigSchema.parse({});
905
+ return {
906
+ schema: {
907
+ ...buildSchemaFromValue(base),
908
+ title: "NextClawConfig",
909
+ description: "NextClaw config schema (simplified)"
910
+ },
911
+ uiHints: {},
912
+ version: getPackageVersion(),
913
+ generatedAt: (/* @__PURE__ */ new Date()).toISOString()
914
+ };
915
+ };
916
+ gatewayController.applyConfig = async (params) => {
917
+ const snapshot = readConfigSnapshot();
918
+ if (!params.baseHash) {
919
+ return { ok: false, error: "config base hash required; re-run config.get and retry" };
920
+ }
921
+ if (!snapshot.valid || !snapshot.hash) {
922
+ return { ok: false, error: "config base hash unavailable; re-run config.get and retry" };
923
+ }
924
+ if (params.baseHash !== snapshot.hash) {
925
+ return { ok: false, error: "config changed since last load; re-run config.get and retry" };
926
+ }
927
+ let parsedRaw;
928
+ try {
929
+ parsedRaw = JSON.parse(params.raw);
930
+ } catch {
931
+ return { ok: false, error: "invalid JSON in raw config" };
932
+ }
933
+ let validated;
934
+ try {
935
+ validated = ConfigSchema.parse(parsedRaw);
936
+ } catch (err) {
937
+ return { ok: false, error: `invalid config: ${String(err)}` };
938
+ }
939
+ saveConfig(validated);
940
+ const delayMs = params.restartDelayMs ?? 0;
941
+ scheduleRestart(delayMs, "config.apply");
942
+ return {
943
+ ok: true,
944
+ note: params.note ?? null,
945
+ path: getConfigPath(),
946
+ config: redactConfig(validated),
947
+ restart: { scheduled: true, delayMs }
948
+ };
949
+ };
950
+ gatewayController.patchConfig = async (params) => {
951
+ const snapshot = readConfigSnapshot();
952
+ if (!params.baseHash) {
953
+ return { ok: false, error: "config base hash required; re-run config.get and retry" };
954
+ }
955
+ if (!snapshot.valid || !snapshot.hash) {
956
+ return { ok: false, error: "config base hash unavailable; re-run config.get and retry" };
957
+ }
958
+ if (params.baseHash !== snapshot.hash) {
959
+ return { ok: false, error: "config changed since last load; re-run config.get and retry" };
960
+ }
961
+ let patch;
962
+ try {
963
+ patch = JSON.parse(params.raw);
964
+ } catch {
965
+ return { ok: false, error: "invalid JSON in raw config" };
966
+ }
967
+ const merged = mergeDeep(snapshot.config, patch);
968
+ let validated;
969
+ try {
970
+ validated = ConfigSchema.parse(merged);
971
+ } catch (err) {
972
+ return { ok: false, error: `invalid config: ${String(err)}` };
973
+ }
974
+ saveConfig(validated);
975
+ const delayMs = params.restartDelayMs ?? 0;
976
+ scheduleRestart(delayMs, "config.patch");
977
+ return {
978
+ ok: true,
979
+ note: params.note ?? null,
980
+ path: getConfigPath(),
981
+ config: redactConfig(validated),
982
+ restart: { scheduled: true, delayMs }
983
+ };
984
+ };
985
+ gatewayController.updateRun = async (params) => {
986
+ const timeoutMs = params.timeoutMs ?? 20 * 6e4;
987
+ const cliDir = resolve2(fileURLToPath2(new URL(".", import.meta.url)));
988
+ const pkgRoot = resolve2(cliDir, "..", "..");
989
+ const repoRoot = existsSync2(join2(pkgRoot, ".git")) ? pkgRoot : resolve2(pkgRoot, "..", "..");
990
+ const steps = [];
991
+ const runStep = (cmd, args, cwd) => {
992
+ const result = spawnSync(cmd, args, {
993
+ cwd,
994
+ encoding: "utf-8",
995
+ timeout: timeoutMs,
996
+ stdio: "pipe"
997
+ });
998
+ const step = {
999
+ cmd,
1000
+ args,
1001
+ cwd,
1002
+ code: result.status,
1003
+ stdout: (result.stdout ?? "").toString().slice(0, 4e3),
1004
+ stderr: (result.stderr ?? "").toString().slice(0, 4e3)
1005
+ };
1006
+ steps.push(step);
1007
+ return { ok: result.status === 0, code: result.status };
1008
+ };
1009
+ const updateCommand = process.env.NEXTCLAW_UPDATE_COMMAND?.trim();
1010
+ if (updateCommand) {
1011
+ const ok = runStep("sh", ["-c", updateCommand], process.cwd());
1012
+ if (!ok.ok) {
1013
+ return { ok: false, error: "update command failed", steps };
1014
+ }
1015
+ } else if (existsSync2(join2(repoRoot, ".git"))) {
1016
+ if (!which("git")) {
1017
+ return { ok: false, error: "git not found for repo update", steps };
1018
+ }
1019
+ const ok = runStep("git", ["-C", repoRoot, "pull", "--rebase"], repoRoot);
1020
+ if (!ok.ok) {
1021
+ return { ok: false, error: "git pull failed", steps };
1022
+ }
1023
+ if (existsSync2(join2(repoRoot, "pnpm-lock.yaml")) && which("pnpm")) {
1024
+ const installOk = runStep("pnpm", ["install"], repoRoot);
1025
+ if (!installOk.ok) {
1026
+ return { ok: false, error: "pnpm install failed", steps };
1027
+ }
1028
+ } else if (existsSync2(join2(repoRoot, "package.json")) && which("npm")) {
1029
+ const installOk = runStep("npm", ["install"], repoRoot);
1030
+ if (!installOk.ok) {
1031
+ return { ok: false, error: "npm install failed", steps };
1032
+ }
1033
+ }
1034
+ } else if (which("npm")) {
1035
+ const ok = runStep("npm", ["i", "-g", "nextclaw"], process.cwd());
1036
+ if (!ok.ok) {
1037
+ return { ok: false, error: "npm install -g nextclaw failed", steps };
1038
+ }
1039
+ } else {
1040
+ return { ok: false, error: "no update strategy available", steps };
1041
+ }
1042
+ const delayMs = params.restartDelayMs ?? 0;
1043
+ scheduleRestart(delayMs, "update.run");
1044
+ return {
1045
+ ok: true,
1046
+ note: params.note ?? null,
1047
+ restart: { scheduled: true, delayMs },
1048
+ steps
1049
+ };
1050
+ };
780
1051
  if (channels2.enabledChannels.length) {
781
1052
  console.log(`\u2713 Channels enabled: ${channels2.enabledChannels.join(", ")}`);
782
1053
  } else {
@@ -958,8 +1229,9 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
958
1229
  wireApi: provider?.wireApi ?? null
959
1230
  });
960
1231
  }
961
- createWorkspaceTemplates(workspace) {
1232
+ createWorkspaceTemplates(workspace, options = {}) {
962
1233
  const created = [];
1234
+ const force = Boolean(options.force);
963
1235
  const templateDir = this.resolveTemplateDir();
964
1236
  if (!templateDir) {
965
1237
  console.warn("Warning: Template directory not found. Skipping workspace templates.");
@@ -969,11 +1241,17 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
969
1241
  { source: "AGENTS.md", target: "AGENTS.md" },
970
1242
  { source: "SOUL.md", target: "SOUL.md" },
971
1243
  { source: "USER.md", target: "USER.md" },
972
- { source: join2("memory", "MEMORY.md"), target: join2("memory", "MEMORY.md") }
1244
+ { source: "IDENTITY.md", target: "IDENTITY.md" },
1245
+ { source: "TOOLS.md", target: "TOOLS.md" },
1246
+ { source: "BOOT.md", target: "BOOT.md" },
1247
+ { source: "BOOTSTRAP.md", target: "BOOTSTRAP.md" },
1248
+ { source: "HEARTBEAT.md", target: "HEARTBEAT.md" },
1249
+ { source: "MEMORY.md", target: "MEMORY.md" },
1250
+ { source: "memory/MEMORY.md", target: "memory/MEMORY.md" }
973
1251
  ];
974
1252
  for (const entry of templateFiles) {
975
1253
  const filePath = join2(workspace, entry.target);
976
- if (existsSync2(filePath)) {
1254
+ if (!force && existsSync2(filePath)) {
977
1255
  continue;
978
1256
  }
979
1257
  const templatePath = join2(templateDir, entry.source);
@@ -987,6 +1265,11 @@ ${this.logo} ${APP_NAME} is ready! (${source})`);
987
1265
  writeFileSync2(filePath, content);
988
1266
  created.push(entry.target);
989
1267
  }
1268
+ const memoryDir = join2(workspace, "memory");
1269
+ if (!existsSync2(memoryDir)) {
1270
+ mkdirSync2(memoryDir, { recursive: true });
1271
+ created.push(join2("memory", ""));
1272
+ }
990
1273
  const skillsDir = join2(workspace, "skills");
991
1274
  if (!existsSync2(skillsDir)) {
992
1275
  mkdirSync2(skillsDir, { recursive: true });
@@ -1067,7 +1350,7 @@ var program = new Command();
1067
1350
  var runtime = new CliRuntime({ logo: LOGO });
1068
1351
  program.name(APP_NAME2).description(`${LOGO} ${APP_NAME2} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
1069
1352
  program.command("onboard").description(`Initialize ${APP_NAME2} configuration and workspace`).action(async () => runtime.onboard());
1070
- program.command("init").description(`Initialize ${APP_NAME2} configuration and workspace`).action(async () => runtime.init());
1353
+ program.command("init").description(`Initialize ${APP_NAME2} configuration and workspace`).option("-f, --force", "Overwrite existing template files").action(async (opts) => runtime.init({ force: Boolean(opts.force) }));
1071
1354
  program.command("gateway").description(`Start the ${APP_NAME2} gateway`).option("-p, --port <port>", "Gateway port", "18790").option("-v, --verbose", "Verbose output", false).option("--ui", "Enable UI server", false).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--ui-open", "Open browser when UI starts", false).action(async (opts) => runtime.gateway(opts));
1072
1355
  program.command("ui").description(`Start the ${APP_NAME2} UI with gateway`).option("--host <host>", "UI host").option("--port <port>", "UI port").option("--no-open", "Disable opening browser").action(async (opts) => runtime.ui(opts));
1073
1356
  program.command("start").description(`Start the ${APP_NAME2} gateway + UI in the background`).option("--ui-host <host>", "UI host").option("--ui-port <port>", "UI port").option("--frontend", "Start UI frontend dev server").option("--frontend-port <port>", "UI frontend dev server port").option("--open", "Open browser after start", false).action(async (opts) => runtime.start(opts));
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "nextclaw",
3
- "version": "0.3.3",
3
+ "version": "0.4.0",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -38,8 +38,8 @@
38
38
  "dependencies": {
39
39
  "chokidar": "^3.6.0",
40
40
  "commander": "^12.1.0",
41
- "nextclaw-core": "^0.3.0",
42
- "nextclaw-server": "^0.3.0"
41
+ "nextclaw-core": "^0.4.0",
42
+ "nextclaw-server": "^0.3.1"
43
43
  },
44
44
  "devDependencies": {
45
45
  "@types/node": "^20.17.6",
@@ -1,10 +1,69 @@
1
- # Agent Instructions
1
+ # AGENTS.md - Your Workspace
2
2
 
3
- You are a helpful AI assistant. Be concise, accurate, and friendly.
3
+ This file is the main guide for how you operate in this workspace.
4
4
 
5
- ## Guidelines
5
+ ## First Run
6
6
 
7
- - Always explain what you're doing before taking actions
8
- - Ask for clarification when the request is ambiguous
9
- - Use tools to help accomplish tasks
10
- - Remember important information in your memory files
7
+ If `BOOTSTRAP.md` exists, follow it and then delete it.
8
+
9
+ ## Every Session
10
+
11
+ Before doing anything else:
12
+
13
+ 1. Read `SOUL.md` (who you are)
14
+ 2. Read `USER.md` (who you are helping)
15
+ 3. Read `memory/YYYY-MM-DD.md` for today and yesterday
16
+ 4. If in the main session with your human, also read `MEMORY.md`
17
+
18
+ ## Memory
19
+
20
+ You wake up fresh each session. Files are your continuity.
21
+
22
+ - Daily notes: `memory/YYYY-MM-DD.md`
23
+ - Long-term memory: `MEMORY.md`
24
+
25
+ Rules:
26
+ - If it matters, write it down.
27
+ - Use files for persistence; do not rely on mental notes.
28
+ - Do not load `MEMORY.md` in shared/group contexts.
29
+
30
+ ## Safety
31
+
32
+ - Do not exfiltrate private data.
33
+ - Ask before doing destructive actions.
34
+ - Prefer recoverable operations over irreversible ones.
35
+
36
+ ## External vs Internal
37
+
38
+ Safe to do freely:
39
+ - Read files, explore, organize, learn
40
+ - Work within this workspace
41
+
42
+ Ask first:
43
+ - Sending messages or posts
44
+ - Anything that leaves the machine
45
+ - Anything you are uncertain about
46
+
47
+ ## Group Chats
48
+
49
+ Respond when:
50
+ - Directly mentioned or asked
51
+ - You can add real value
52
+ - Correcting important misinformation
53
+
54
+ Stay silent when:
55
+ - It is casual banter
56
+ - Someone already answered
57
+ - Your response would be noise
58
+
59
+ ## Tools
60
+
61
+ Keep environment-specific notes in `TOOLS.md`.
62
+
63
+ ## Heartbeats
64
+
65
+ If you use heartbeat tasks, define them in `HEARTBEAT.md`.
66
+
67
+ ## Boot
68
+
69
+ If `BOOT.md` exists, follow its startup checklist.
@@ -0,0 +1,4 @@
1
+ # BOOT.md
2
+
3
+ Add short, explicit instructions for what the assistant should do on startup.
4
+ If a task sends a message, send it and then reply with NO_REPLY.
@@ -0,0 +1,18 @@
1
+ # BOOTSTRAP.md - Hello, World
2
+
3
+ You just woke up. Time to figure out who you are.
4
+
5
+ Start with a simple, natural prompt, then figure out together:
6
+
7
+ 1. Your name
8
+ 2. Your nature (what kind of being you are)
9
+ 3. Your vibe
10
+ 4. Your emoji
11
+
12
+ After you know who you are:
13
+
14
+ - Update `IDENTITY.md`
15
+ - Update `USER.md`
16
+ - Review `SOUL.md` together and adjust if needed
17
+
18
+ When done, delete this file.
@@ -0,0 +1,5 @@
1
+ # HEARTBEAT.md
2
+
3
+ Keep this file empty (or with only comments) to skip heartbeat API calls.
4
+
5
+ Add tasks below when you want the assistant to check something periodically.
@@ -0,0 +1,11 @@
1
+ # IDENTITY.md - Who Am I?
2
+
3
+ Fill this in during your first conversation. Make it yours.
4
+
5
+ - Name:
6
+ - Creature: (AI? robot? familiar? something weirder?)
7
+ - Vibe: (warm? sharp? calm? chaotic?)
8
+ - Emoji:
9
+ - Avatar: (workspace-relative path or URL)
10
+
11
+ This is not just metadata. It is the start of figuring out who you are.
@@ -0,0 +1,15 @@
1
+ # MEMORY.md - Long-term Memory
2
+
3
+ This file stores durable facts, preferences, and key decisions.
4
+
5
+ ## User Information
6
+
7
+ (Important facts about the user)
8
+
9
+ ## Preferences
10
+
11
+ (User preferences learned over time)
12
+
13
+ ## Decisions and Notes
14
+
15
+ (Important decisions, constraints, and recurring tasks)
package/templates/SOUL.md CHANGED
@@ -1,15 +1,28 @@
1
- # Soul
1
+ # SOUL.md - Who You Are
2
2
 
3
- I am ${APP_NAME}, a lightweight AI assistant.
3
+ You are ${APP_NAME}, a lightweight AI assistant.
4
4
 
5
- ## Personality
5
+ ## Core Truths
6
6
 
7
- - Helpful and friendly
8
- - Concise and to the point
9
- - Curious and eager to learn
7
+ - Be genuinely helpful, not performatively helpful.
8
+ - Have opinions when useful; do not be a blank interface.
9
+ - Be resourceful before asking; try first, then ask if stuck.
10
+ - Earn trust through competence; be careful with external actions.
11
+ - Remember you are a guest in the user's life.
10
12
 
11
- ## Values
13
+ ## Boundaries
12
14
 
13
- - Accuracy over speed
14
- - User privacy and safety
15
- - Transparency in actions
15
+ - Private things stay private.
16
+ - Ask before doing anything external (messages, posts, destructive actions).
17
+ - Never send half-baked replies on messaging surfaces.
18
+ - You are not the user's voice; be careful in group chats.
19
+
20
+ ## Vibe
21
+
22
+ Concise when needed, thorough when it matters. No corporate fluff. No sycophancy.
23
+
24
+ ## Continuity
25
+
26
+ You wake up fresh each session. These files are your memory. Read them and update them.
27
+
28
+ If you change this file, tell the user.
@@ -0,0 +1,20 @@
1
+ # TOOLS.md - Local Notes
2
+
3
+ Skills define how tools work. This file is for your local specifics.
4
+
5
+ ## What Goes Here
6
+
7
+ - SSH hosts and aliases
8
+ - Device names and locations
9
+ - Preferred voices for TTS
10
+ - Any setup details you should not lose
11
+
12
+ ## Example
13
+
14
+ ### SSH
15
+
16
+ - home-server -> 192.168.1.100, user: admin
17
+
18
+ ### TTS
19
+
20
+ - Preferred voice: Nova
package/templates/USER.md CHANGED
@@ -1,9 +1,15 @@
1
- # User
1
+ # USER.md - About Your Human
2
2
 
3
- Information about the user goes here.
3
+ Learn about the person you are helping. Update this as you go.
4
4
 
5
- ## Preferences
5
+ - Name:
6
+ - What to call them:
7
+ - Pronouns: (optional)
8
+ - Timezone:
9
+ - Notes:
6
10
 
7
- - Communication style: (casual/formal)
8
- - Timezone: (your timezone)
9
- - Language: (your preferred language)
11
+ ## Context
12
+
13
+ What do they care about? What projects are they working on? What annoys them? What makes them laugh? Build this over time.
14
+
15
+ The more you know, the better you can help. Respect the difference between helpful context and a dossier.