nextclaw 0.3.1 → 0.3.3

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
@@ -17,6 +17,7 @@ import {
17
17
  buildReloadPlan,
18
18
  diffConfigPaths,
19
19
  getWorkspacePath,
20
+ expandHome,
20
21
  MessageBus,
21
22
  AgentLoop,
22
23
  LiteLLMProvider,
@@ -26,7 +27,9 @@ import {
26
27
  CronService,
27
28
  HeartbeatService,
28
29
  PROVIDERS,
29
- APP_NAME
30
+ APP_NAME,
31
+ DEFAULT_WORKSPACE_DIR,
32
+ DEFAULT_WORKSPACE_PATH
30
33
  } from "nextclaw-core";
31
34
  import { startUiServer } from "nextclaw-server";
32
35
  import {
@@ -39,7 +42,7 @@ import {
39
42
  rmSync as rmSync2,
40
43
  writeFileSync as writeFileSync2
41
44
  } from "fs";
42
- import { join as join2, resolve as resolve2 } from "path";
45
+ import { dirname, join as join2, resolve as resolve2 } from "path";
43
46
  import { spawn as spawn2, spawnSync } from "child_process";
44
47
  import { createInterface } from "readline";
45
48
  import { fileURLToPath as fileURLToPath2 } from "url";
@@ -80,19 +83,7 @@ async function findAvailablePort(port, host, attempts = 20) {
80
83
  }
81
84
  async function isPortAvailable(port, host) {
82
85
  const checkHost = normalizeHostForPortCheck(host);
83
- const hostsToCheck = [checkHost];
84
- if (checkHost === "127.0.0.1") {
85
- hostsToCheck.push("::1");
86
- } else if (checkHost === "::1") {
87
- hostsToCheck.push("127.0.0.1");
88
- }
89
- for (const candidate of hostsToCheck) {
90
- const ok = await canBindPort(port, candidate);
91
- if (ok) {
92
- return true;
93
- }
94
- }
95
- return false;
86
+ return await canBindPort(port, checkHost);
96
87
  }
97
88
  async function canBindPort(port, host) {
98
89
  return await new Promise((resolve3) => {
@@ -306,21 +297,46 @@ var CliRuntime = class {
306
297
  return getPackageVersion();
307
298
  }
308
299
  async onboard() {
300
+ console.warn(`Warning: ${APP_NAME} onboard is deprecated. Use "${APP_NAME} init" instead.`);
301
+ await this.init({ source: "onboard" });
302
+ }
303
+ async init(options = {}) {
304
+ const source = options.source ?? "init";
305
+ const prefix = options.auto ? "Auto init" : "Init";
309
306
  const configPath = getConfigPath();
310
- if (existsSync2(configPath)) {
311
- console.log(`Config already exists at ${configPath}`);
312
- }
313
- const config = ConfigSchema.parse({});
314
- saveConfig(config);
315
- console.log(`\u2713 Created config at ${configPath}`);
316
- const workspace = getWorkspacePath();
317
- console.log(`\u2713 Created workspace at ${workspace}`);
318
- this.createWorkspaceTemplates(workspace);
319
- console.log(`
320
- ${this.logo} ${APP_NAME} is ready!`);
321
- console.log("\nNext steps:");
322
- console.log(` 1. Add your API key to ${configPath}`);
323
- console.log(` 2. Chat: ${APP_NAME} agent -m "Hello!"`);
307
+ let createdConfig = false;
308
+ if (!existsSync2(configPath)) {
309
+ const config2 = ConfigSchema.parse({});
310
+ saveConfig(config2);
311
+ createdConfig = true;
312
+ }
313
+ const config = loadConfig();
314
+ const workspaceSetting = config.agents.defaults.workspace;
315
+ const workspacePath = !workspaceSetting || workspaceSetting === DEFAULT_WORKSPACE_PATH ? join2(getDataDir2(), DEFAULT_WORKSPACE_DIR) : expandHome(workspaceSetting);
316
+ const workspaceExisted = existsSync2(workspacePath);
317
+ mkdirSync2(workspacePath, { recursive: true });
318
+ const templateResult = this.createWorkspaceTemplates(workspacePath);
319
+ if (createdConfig) {
320
+ console.log(`\u2713 ${prefix}: created config at ${configPath}`);
321
+ }
322
+ if (!workspaceExisted) {
323
+ console.log(`\u2713 ${prefix}: created workspace at ${workspacePath}`);
324
+ }
325
+ for (const file of templateResult.created) {
326
+ console.log(`\u2713 ${prefix}: created ${file}`);
327
+ }
328
+ if (!createdConfig && workspaceExisted && templateResult.created.length === 0) {
329
+ console.log(`${prefix}: already initialized.`);
330
+ }
331
+ if (!options.auto) {
332
+ console.log(`
333
+ ${this.logo} ${APP_NAME} is ready! (${source})`);
334
+ console.log("\nNext steps:");
335
+ console.log(` 1. Add your API key to ${configPath}`);
336
+ console.log(` 2. Chat: ${APP_NAME} agent -m "Hello!"`);
337
+ } else {
338
+ console.log(`Tip: Run "${APP_NAME} init" to re-run initialization if needed.`);
339
+ }
324
340
  }
325
341
  async gateway(opts) {
326
342
  const uiOverrides = {};
@@ -352,6 +368,7 @@ ${this.logo} ${APP_NAME} is ready!`);
352
368
  await this.startGateway({ uiOverrides, allowMissingProvider: true });
353
369
  }
354
370
  async start(opts) {
371
+ await this.init({ source: "start", auto: true });
355
372
  const uiOverrides = {
356
373
  enabled: true,
357
374
  open: false
@@ -942,44 +959,55 @@ ${this.logo} ${APP_NAME} is ready!`);
942
959
  });
943
960
  }
944
961
  createWorkspaceTemplates(workspace) {
945
- const templates = {
946
- "AGENTS.md": "# Agent Instructions\n\nYou are a helpful AI assistant. Be concise, accurate, and friendly.\n\n## Guidelines\n\n- Always explain what you're doing before taking actions\n- Ask for clarification when the request is ambiguous\n- Use tools to help accomplish tasks\n- Remember important information in your memory files\n",
947
- "SOUL.md": `# Soul
948
-
949
- I am ${APP_NAME}, a lightweight AI assistant.
950
-
951
- ## Personality
952
-
953
- - Helpful and friendly
954
- - Concise and to the point
955
- - Curious and eager to learn
956
-
957
- ## Values
958
-
959
- - Accuracy over speed
960
- - User privacy and safety
961
- - Transparency in actions
962
-
963
- `,
964
- "USER.md": "# User\n\nInformation about the user goes here.\n\n## Preferences\n\n- Communication style: (casual/formal)\n- Timezone: (your timezone)\n- Language: (your preferred language)\n"
965
- };
966
- for (const [filename, content] of Object.entries(templates)) {
967
- const filePath = join2(workspace, filename);
968
- if (!existsSync2(filePath)) {
969
- writeFileSync2(filePath, content);
970
- }
962
+ const created = [];
963
+ const templateDir = this.resolveTemplateDir();
964
+ if (!templateDir) {
965
+ console.warn("Warning: Template directory not found. Skipping workspace templates.");
966
+ return { created };
971
967
  }
972
- const memoryDir = join2(workspace, "memory");
973
- mkdirSync2(memoryDir, { recursive: true });
974
- const memoryFile = join2(memoryDir, "MEMORY.md");
975
- if (!existsSync2(memoryFile)) {
976
- writeFileSync2(
977
- memoryFile,
978
- "# Long-term Memory\n\nThis file stores important information that should persist across sessions.\n\n## User Information\n\n(Important facts about the user)\n\n## Preferences\n\n(User preferences learned over time)\n\n## Important Notes\n\n(Things to remember)\n"
979
- );
968
+ const templateFiles = [
969
+ { source: "AGENTS.md", target: "AGENTS.md" },
970
+ { source: "SOUL.md", target: "SOUL.md" },
971
+ { source: "USER.md", target: "USER.md" },
972
+ { source: join2("memory", "MEMORY.md"), target: join2("memory", "MEMORY.md") }
973
+ ];
974
+ for (const entry of templateFiles) {
975
+ const filePath = join2(workspace, entry.target);
976
+ if (existsSync2(filePath)) {
977
+ continue;
978
+ }
979
+ const templatePath = join2(templateDir, entry.source);
980
+ if (!existsSync2(templatePath)) {
981
+ console.warn(`Warning: Template file missing: ${templatePath}`);
982
+ continue;
983
+ }
984
+ const raw = readFileSync2(templatePath, "utf-8");
985
+ const content = raw.replace(/\$\{APP_NAME\}/g, APP_NAME);
986
+ mkdirSync2(dirname(filePath), { recursive: true });
987
+ writeFileSync2(filePath, content);
988
+ created.push(entry.target);
980
989
  }
981
990
  const skillsDir = join2(workspace, "skills");
982
- mkdirSync2(skillsDir, { recursive: true });
991
+ if (!existsSync2(skillsDir)) {
992
+ mkdirSync2(skillsDir, { recursive: true });
993
+ created.push(join2("skills", ""));
994
+ }
995
+ return { created };
996
+ }
997
+ resolveTemplateDir() {
998
+ const override = process.env.NEXTCLAW_TEMPLATE_DIR?.trim();
999
+ if (override) {
1000
+ return override;
1001
+ }
1002
+ const cliDir = resolve2(fileURLToPath2(new URL(".", import.meta.url)));
1003
+ const pkgRoot = resolve2(cliDir, "..", "..");
1004
+ const candidates = [join2(pkgRoot, "templates")];
1005
+ for (const candidate of candidates) {
1006
+ if (existsSync2(candidate)) {
1007
+ return candidate;
1008
+ }
1009
+ }
1010
+ return null;
983
1011
  }
984
1012
  getBridgeDir() {
985
1013
  const userBridge = join2(getDataDir2(), "bridge");
@@ -1039,6 +1067,7 @@ var program = new Command();
1039
1067
  var runtime = new CliRuntime({ logo: LOGO });
1040
1068
  program.name(APP_NAME2).description(`${LOGO} ${APP_NAME2} - ${APP_TAGLINE}`).version(getPackageVersion(), "-v, --version", "show version");
1041
1069
  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());
1042
1071
  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));
1043
1072
  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));
1044
1073
  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.1",
3
+ "version": "0.3.3",
4
4
  "description": "Lightweight personal AI assistant with CLI, multi-provider routing, and channel integrations.",
5
5
  "private": false,
6
6
  "type": "module",
@@ -32,7 +32,8 @@
32
32
  "files": [
33
33
  "dist",
34
34
  "bridge",
35
- "ui-dist"
35
+ "ui-dist",
36
+ "templates"
36
37
  ],
37
38
  "dependencies": {
38
39
  "chokidar": "^3.6.0",
@@ -0,0 +1,10 @@
1
+ # Agent Instructions
2
+
3
+ You are a helpful AI assistant. Be concise, accurate, and friendly.
4
+
5
+ ## Guidelines
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
@@ -0,0 +1,15 @@
1
+ # Soul
2
+
3
+ I am ${APP_NAME}, a lightweight AI assistant.
4
+
5
+ ## Personality
6
+
7
+ - Helpful and friendly
8
+ - Concise and to the point
9
+ - Curious and eager to learn
10
+
11
+ ## Values
12
+
13
+ - Accuracy over speed
14
+ - User privacy and safety
15
+ - Transparency in actions
@@ -0,0 +1,9 @@
1
+ # User
2
+
3
+ Information about the user goes here.
4
+
5
+ ## Preferences
6
+
7
+ - Communication style: (casual/formal)
8
+ - Timezone: (your timezone)
9
+ - Language: (your preferred language)
@@ -0,0 +1,15 @@
1
+ # Long-term Memory
2
+
3
+ This file stores important information that should persist across sessions.
4
+
5
+ ## User Information
6
+
7
+ (Important facts about the user)
8
+
9
+ ## Preferences
10
+
11
+ (User preferences learned over time)
12
+
13
+ ## Important Notes
14
+
15
+ (Things to remember)