@9000ai/cli 0.5.1 → 0.5.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/CHANGELOG.md CHANGED
@@ -1,4 +1,10 @@
1
- ## What's new in v0.5.1
1
+ ## What's new in v0.5.3
2
+
3
+ - `9000ai update` — sync skills after npm update, shows what changed
4
+ - `9000ai init --force` — smart diff, only updates changed system files
5
+ - User data (profile/, claims/, inbox/, projects/) is never overwritten
6
+
7
+ ## v0.5.0
2
8
 
3
9
  - `9000ai init --path <dir>` — one command to setup workspace
4
10
  - `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
  }
@@ -0,0 +1,2 @@
1
+ import { Command } from "commander";
2
+ export declare function registerUpdateCommand(program: Command): void;
@@ -0,0 +1,84 @@
1
+ import { existsSync, mkdirSync, cpSync, readFileSync, readdirSync, statSync } from "fs";
2
+ import { dirname, join, relative } from "path";
3
+ import { homedir } from "os";
4
+ import { fileURLToPath } from "url";
5
+ const __dirname = dirname(fileURLToPath(import.meta.url));
6
+ export function registerUpdateCommand(program) {
7
+ program
8
+ .command("update")
9
+ .description("Sync skills to latest version after npm update")
10
+ .action(() => {
11
+ const src = join(__dirname, "..", "skills");
12
+ const dest = join(homedir(), ".9000ai", "skills");
13
+ if (!existsSync(src)) {
14
+ console.error("Error: Skills not found in package. Reinstall: npm install -g @9000ai/cli");
15
+ process.exit(1);
16
+ }
17
+ // Diff before copy
18
+ const created = [];
19
+ const updated = [];
20
+ function diffDir(srcDir, destDir) {
21
+ if (!existsSync(srcDir))
22
+ return;
23
+ for (const entry of readdirSync(srcDir)) {
24
+ const s = join(srcDir, entry);
25
+ const d = join(destDir, entry);
26
+ if (statSync(s).isDirectory()) {
27
+ diffDir(s, d);
28
+ }
29
+ else {
30
+ const rel = relative(dest, d);
31
+ if (!existsSync(d)) {
32
+ created.push(rel);
33
+ }
34
+ else {
35
+ try {
36
+ if (!readFileSync(s).equals(readFileSync(d))) {
37
+ updated.push(rel);
38
+ }
39
+ }
40
+ catch {
41
+ updated.push(rel);
42
+ }
43
+ }
44
+ }
45
+ }
46
+ }
47
+ diffDir(src, dest);
48
+ // Copy
49
+ mkdirSync(dest, { recursive: true });
50
+ cpSync(src, dest, { recursive: true, force: true });
51
+ // Read version
52
+ let version = "unknown";
53
+ try {
54
+ const pkg = JSON.parse(readFileSync(join(__dirname, "..", "package.json"), "utf-8"));
55
+ version = pkg.version;
56
+ }
57
+ catch { }
58
+ // Read changelog
59
+ let changelog = "";
60
+ try {
61
+ changelog = readFileSync(join(__dirname, "..", "CHANGELOG.md"), "utf-8");
62
+ }
63
+ catch { }
64
+ // Output
65
+ console.log(`@9000ai/cli v${version}\n`);
66
+ if (created.length > 0 || updated.length > 0) {
67
+ if (created.length > 0) {
68
+ console.log(`New files:`);
69
+ created.forEach((f) => console.log(` + ${f}`));
70
+ }
71
+ if (updated.length > 0) {
72
+ console.log(`Updated files:`);
73
+ updated.forEach((f) => console.log(` ~ ${f}`));
74
+ }
75
+ console.log("");
76
+ }
77
+ else {
78
+ console.log(`Skills: already up to date\n`);
79
+ }
80
+ if (changelog) {
81
+ console.log(changelog);
82
+ }
83
+ });
84
+ }
package/dist/index.js CHANGED
@@ -9,11 +9,12 @@ import { registerTaskCommands } from "./commands/task.js";
9
9
  import { registerFeedbackCommands } from "./commands/feedback.js";
10
10
  import { registerSkillCommands } from "./commands/skill.js";
11
11
  import { registerInitCommand } from "./commands/init.js";
12
+ import { registerUpdateCommand } from "./commands/update.js";
12
13
  const program = new Command();
13
14
  program
14
15
  .name("9000ai")
15
16
  .description("9000AI Toolbox CLI — unified interface for 9000AI platform")
16
- .version("0.5.0");
17
+ .version("0.5.3");
17
18
  registerConfigCommands(program);
18
19
  registerAuthCommands(program);
19
20
  registerSearchCommands(program);
@@ -23,6 +24,7 @@ registerTaskCommands(program);
23
24
  registerFeedbackCommands(program);
24
25
  registerSkillCommands(program);
25
26
  registerInitCommand(program);
27
+ registerUpdateCommand(program);
26
28
  program.parseAsync(process.argv).catch((err) => {
27
29
  console.error(err);
28
30
  process.exit(1);
@@ -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.3",
4
4
  "description": "9000AI Toolbox CLI — unified command-line interface for 9000AI platform",
5
5
  "type": "module",
6
6
  "bin": {
@@ -115,6 +115,22 @@ output-format: routing-only
115
115
 
116
116
  > 画像建好了。现在可以开始——比如"帮我做选题调研"、"看看最近热榜"。
117
117
 
118
+ ## 更新系统
119
+
120
+ ```bash
121
+ npm update -g @9000ai/cli && 9000ai update
122
+ ```
123
+
124
+ 第一条更新 CLI,第二条同步 skills 并显示更新内容。
125
+
126
+ 项目目录的模板也要同步时:
127
+
128
+ ```bash
129
+ 9000ai init --path <项目目录> --force
130
+ ```
131
+
132
+ profile/ 和 claims/ 等用户数据不会被覆盖。
133
+
118
134
  ## 参考文档
119
135
 
120
136
  需要更详细的使用规范时查阅: