@9000ai/cli 0.5.1 → 0.5.2

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/CHANGELOG.md CHANGED
@@ -1,4 +1,11 @@
1
- ## What's new in v0.5.1
1
+ ## What's new in v0.5.2
2
+
3
+ - `9000ai init --force` now compares file content — only updates changed system files
4
+ - User data (profile/, claims/, inbox/, projects/) is never overwritten
5
+ - postinstall shows which skill files were added or updated
6
+ - Hub SKILL.md: added system update instructions
7
+
8
+ ## v0.5.0
2
9
 
3
10
  - `9000ai init --path <dir>` — one command to setup workspace
4
11
  - `9000ai search keyword --wait` — auto-poll, returns results directly
@@ -1,14 +1,24 @@
1
- import { existsSync, mkdirSync, copyFileSync, readdirSync, statSync } from "fs";
1
+ import { existsSync, mkdirSync, copyFileSync, readFileSync, readdirSync, statSync } from "fs";
2
2
  import { homedir } from "os";
3
3
  import { join, relative } from "path";
4
4
  const TEMPLATES_DIR = join(homedir(), ".9000ai", "skills", "9000AI-hub", "init", "templates");
5
- /** Files under profile/ are never overwritten user may have edited them. */
6
- const NEVER_OVERWRITE_DIRS = ["profile"];
7
- function copyTemplates(src, dest, relBase) {
5
+ /** User data directories never overwrite even with --force */
6
+ const USER_DATA_DIRS = ["profile", "claims", "inbox", "projects"];
7
+ function filesEqual(a, b) {
8
+ try {
9
+ return readFileSync(a).equals(readFileSync(b));
10
+ }
11
+ catch {
12
+ return false;
13
+ }
14
+ }
15
+ function copyTemplates(src, dest, relBase, force) {
8
16
  const created = [];
17
+ const updated = [];
9
18
  const skipped = [];
19
+ const outdated = [];
10
20
  if (!existsSync(src))
11
- return { created, skipped };
21
+ return { created, updated, skipped, outdated };
12
22
  const entries = readdirSync(src);
13
23
  for (const entry of entries) {
14
24
  const srcPath = join(src, entry);
@@ -19,35 +29,47 @@ function copyTemplates(src, dest, relBase) {
19
29
  if (!existsSync(destPath)) {
20
30
  mkdirSync(destPath, { recursive: true });
21
31
  }
22
- const sub = copyTemplates(srcPath, destPath, relBase);
32
+ const sub = copyTemplates(srcPath, destPath, relBase, force);
23
33
  created.push(...sub.created);
34
+ updated.push(...sub.updated);
24
35
  skipped.push(...sub.skipped);
36
+ outdated.push(...sub.outdated);
25
37
  }
26
38
  else {
27
- if (existsSync(destPath)) {
28
- // Never overwrite profile files
29
- const topDir = rel.split(/[/\\]/)[0];
30
- if (NEVER_OVERWRITE_DIRS.includes(topDir)) {
31
- skipped.push(rel);
32
- continue;
33
- }
34
- // Other existing files: also skip (idempotent)
35
- skipped.push(rel);
36
- }
37
- else {
39
+ if (!existsSync(destPath)) {
38
40
  mkdirSync(join(destPath, ".."), { recursive: true });
39
41
  copyFileSync(srcPath, destPath);
40
42
  created.push(rel);
41
43
  }
44
+ else if (filesEqual(srcPath, destPath)) {
45
+ // identical, skip silently
46
+ skipped.push(rel);
47
+ }
48
+ else {
49
+ // content differs
50
+ const topDir = rel.split(/[/\\]/)[0];
51
+ if (USER_DATA_DIRS.includes(topDir)) {
52
+ // user data, never touch
53
+ skipped.push(rel);
54
+ }
55
+ else if (force) {
56
+ copyFileSync(srcPath, destPath);
57
+ updated.push(rel);
58
+ }
59
+ else {
60
+ outdated.push(rel);
61
+ }
62
+ }
42
63
  }
43
64
  }
44
- return { created, skipped };
65
+ return { created, updated, skipped, outdated };
45
66
  }
46
67
  export function registerInitCommand(program) {
47
68
  program
48
69
  .command("init")
49
- .description("Initialize content workspace — copy templates to project directory")
70
+ .description("Initialize or update content workspace")
50
71
  .requiredOption("--path <dir>", "Project directory to initialize")
72
+ .option("--force", "Update existing files (except profile/) to latest version")
51
73
  .action((opts) => {
52
74
  const dest = opts.path;
53
75
  // Check templates exist
@@ -60,24 +82,31 @@ export function registerInitCommand(program) {
60
82
  if (!existsSync(dest)) {
61
83
  mkdirSync(dest, { recursive: true });
62
84
  }
63
- const { created, skipped } = copyTemplates(TEMPLATES_DIR, dest, dest);
85
+ const { created, updated, skipped, outdated } = copyTemplates(TEMPLATES_DIR, dest, dest, !!opts.force);
64
86
  // Output summary
65
87
  if (created.length > 0) {
66
88
  console.log(`Created ${created.length} files:`);
67
89
  created.forEach((f) => console.log(` + ${f}`));
68
90
  }
69
- if (skipped.length > 0) {
70
- console.log(`Skipped ${skipped.length} existing files:`);
71
- skipped.forEach((f) => console.log(` - ${f}`));
91
+ if (updated.length > 0) {
92
+ console.log(`Updated ${updated.length} files:`);
93
+ updated.forEach((f) => console.log(` ~ ${f}`));
72
94
  }
73
- if (created.length === 0 && skipped.length === 0) {
74
- console.log("No template files found.");
95
+ if (outdated.length > 0) {
96
+ console.log(`\n${outdated.length} files have newer versions available:`);
97
+ outdated.forEach((f) => console.log(` ! ${f}`));
98
+ console.log(`\nRun with --force to update: 9000ai init --path ${dest} --force`);
99
+ }
100
+ if (created.length === 0 && updated.length === 0 && outdated.length === 0) {
101
+ console.log("Everything is up to date.");
75
102
  return;
76
103
  }
77
104
  console.log(`\nWorkspace ready: ${dest}`);
78
- console.log(`\nNext steps:`);
79
- console.log(` 1. Fill in profile/ (identity.md, voice.md, topics.md, product.md)`);
80
- console.log(` 2. cd ${dest}`);
81
- console.log(` 3. Open Claude Code and start working`);
105
+ if (created.length > 0 && updated.length === 0) {
106
+ console.log(`\nNext steps:`);
107
+ console.log(` 1. Fill in profile/ (identity.md, voice.md, topics.md, product.md)`);
108
+ console.log(` 2. cd ${dest}`);
109
+ console.log(` 3. Start working`);
110
+ }
82
111
  });
83
112
  }
package/dist/index.js CHANGED
@@ -13,7 +13,7 @@ const program = new Command();
13
13
  program
14
14
  .name("9000ai")
15
15
  .description("9000AI Toolbox CLI — unified interface for 9000AI platform")
16
- .version("0.5.0");
16
+ .version("0.5.2");
17
17
  registerConfigCommands(program);
18
18
  registerAuthCommands(program);
19
19
  registerSearchCommands(program);
@@ -1,5 +1,5 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * postinstall: copy bundled skills to ~/.9000ai/skills/ and show changelog
3
+ * postinstall: copy bundled skills to ~/.9000ai/skills/ and show what changed
4
4
  */
5
5
  export {};
@@ -1,9 +1,9 @@
1
1
  #!/usr/bin/env node
2
2
  /**
3
- * postinstall: copy bundled skills to ~/.9000ai/skills/ and show changelog
3
+ * postinstall: copy bundled skills to ~/.9000ai/skills/ and show what changed
4
4
  */
5
- import { existsSync, mkdirSync, cpSync, readFileSync } from "fs";
6
- import { dirname, join } from "path";
5
+ import { existsSync, mkdirSync, cpSync, readFileSync, readdirSync, statSync } from "fs";
6
+ import { dirname, join, relative } from "path";
7
7
  import { homedir } from "os";
8
8
  import { fileURLToPath } from "url";
9
9
  const __dirname = dirname(fileURLToPath(import.meta.url));
@@ -19,6 +19,38 @@ catch { }
19
19
  if (!existsSync(src)) {
20
20
  process.exit(0);
21
21
  }
22
+ // Diff before copy
23
+ const created = [];
24
+ const updated = [];
25
+ function diffDir(srcDir, destDir) {
26
+ if (!existsSync(srcDir))
27
+ return;
28
+ for (const entry of readdirSync(srcDir)) {
29
+ const s = join(srcDir, entry);
30
+ const d = join(destDir, entry);
31
+ if (statSync(s).isDirectory()) {
32
+ diffDir(s, d);
33
+ }
34
+ else {
35
+ const rel = relative(dest, d);
36
+ if (!existsSync(d)) {
37
+ created.push(rel);
38
+ }
39
+ else {
40
+ try {
41
+ if (!readFileSync(s).equals(readFileSync(d))) {
42
+ updated.push(rel);
43
+ }
44
+ }
45
+ catch {
46
+ updated.push(rel);
47
+ }
48
+ }
49
+ }
50
+ }
51
+ }
52
+ diffDir(src, dest);
53
+ // Copy
22
54
  mkdirSync(dest, { recursive: true });
23
55
  cpSync(src, dest, { recursive: true, force: true });
24
56
  // Read changelog
@@ -27,11 +59,24 @@ try {
27
59
  changelog = readFileSync(join(__dirname, "..", "CHANGELOG.md"), "utf-8");
28
60
  }
29
61
  catch { }
62
+ // Output
30
63
  console.log(`\n@9000ai/cli v${version} installed\n`);
31
- console.log(`Skills updated: ${dest}\n`);
64
+ if (created.length > 0 || updated.length > 0) {
65
+ if (created.length > 0) {
66
+ console.log(`New files:`);
67
+ created.forEach((f) => console.log(` + ${f}`));
68
+ }
69
+ if (updated.length > 0) {
70
+ console.log(`Updated files:`);
71
+ updated.forEach((f) => console.log(` ~ ${f}`));
72
+ }
73
+ console.log("");
74
+ }
75
+ else {
76
+ console.log(`Skills: up to date\n`);
77
+ }
32
78
  if (changelog) {
33
79
  console.log(changelog);
34
- console.log("");
35
80
  }
36
81
  console.log(`Get started:`);
37
82
  console.log(` 1. 9000ai config set --base-url <server-url> --api-key <key>`);
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@9000ai/cli",
3
- "version": "0.5.1",
3
+ "version": "0.5.2",
4
4
  "description": "9000AI Toolbox CLI — unified command-line interface for 9000AI platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -115,6 +115,16 @@ output-format: routing-only
115
115
 
116
116
  > 画像建好了。现在可以开始——比如"帮我做选题调研"、"看看最近热榜"。
117
117
 
118
+ ## 更新系统
119
+
120
+ ```bash
121
+ # 更新 CLI 和 skills
122
+ npm update -g @9000ai/cli
123
+
124
+ # 同步最新模板到项目目录(profile/ 不会被覆盖)
125
+ 9000ai init --path <项目目录> --force
126
+ ```
127
+
118
128
  ## 参考文档
119
129
 
120
130
  需要更详细的使用规范时查阅: