knowly 6.12.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/LICENSE ADDED
@@ -0,0 +1,21 @@
1
+ MIT License
2
+
3
+ Copyright (c) 2026 ygs
4
+
5
+ Permission is hereby granted, free of charge, to any person obtaining a copy
6
+ of this software and associated documentation files (the "Software"), to deal
7
+ in the Software without restriction, including without limitation the rights
8
+ to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
9
+ copies of the Software, and to permit persons to whom the Software is
10
+ furnished to do so, subject to the following conditions:
11
+
12
+ The above copyright notice and this permission notice shall be included in all
13
+ copies or substantial portions of the Software.
14
+
15
+ THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
16
+ IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
17
+ FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
18
+ AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
19
+ LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
20
+ OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
21
+ SOFTWARE.
package/README.md ADDED
@@ -0,0 +1,282 @@
1
+ # knas (Knowledge Async)
2
+
3
+ 🚀 让每一次复制,都成为知识复利。
4
+
5
+ Knas (Knowledge Async) 不是另一个剪贴板工具。它是你的 私有知识管道。
6
+
7
+ 你在 Mac 上复制一段文字、一张截图、一个链接——Knas 在后台静默捕获,通过 SSH 安全地存入你的私有 NAS,按日期归档为 Markdown。你手机上的灵感,也能通过公网中继自动汇入。
8
+
9
+ 捕获时零摩擦,沉淀时零丢失。 从此,每一次 Cmd+C 都是在为你的第二大脑添砖加瓦。配合内置的 Blog/Podcast/IMA 发布引擎,一次输入,三重输出。
10
+
11
+ 对抗遗忘,积累复利。让知识管理,从“复制”这一步开始。
12
+
13
+ 📖 什么是 K.N.A.S.?
14
+
15
+ 从“Knowledge Async”出发,knas 这个名字完全可以生长出一组丰富的内涵。以下是根据项目核心理念,为 K.N.A.S. 四个字母赋予的多重联想,每一个都对应着 knas 的一个侧面:
16
+
17
+ 🧠 官方正解(原名)
18
+
19
+ · Knowledge Async
20
+ 知识的异步传输。这是名字的起点,强调了“不打断心流、后台静默运行”的核心特征。
21
+
22
+ 🏛️ 哲学层(对抗熵增)
23
+
24
+ · Keep Notions Always Saved
25
+ 让一切念头永不丢失。对抗遗忘,对抗数字熵增,这是 knas 的终极使命。
26
+ · Knowledge Never Accidentally Sinks
27
+ 知识永不意外沉没。每一条碎片都被打捞,放进你的私人知识方舟。
28
+
29
+ ⚙️ 架构层(技术本质)
30
+
31
+ · Kernel for Networked Archival Sync
32
+ 网络归档同步的内核。一个轻量、专注的守护进程,像操作系统内核一样可靠。
33
+ · Kapture, Normalize, Archive, Syndicate
34
+ 捕获、标准化、归档、分发。这是 knas 数据流四部曲的精确描述。
35
+
36
+ 🌊 产品层(用户体验)
37
+
38
+ · Kindle Notes Auto Sync
39
+ 灵感笔记自动同步。这里的“Kindle”作动词,意为“点燃灵感”,每次复制都是一次火花的捕捉。
40
+ · Knowledge Nirvana Automation System
41
+ 知识涅槃自动化系统。有点中二,但精准表达了从碎片到复利的质变过程。
42
+
43
+ 🔒 价值观层(数据主权)
44
+
45
+ · Keep Nas As Sanctuary
46
+ 让 NAS 成为知识圣殿。强调私有化、自主可控的存储哲学。
47
+
48
+ 🎁 彩蛋(趣味向)
49
+
50
+ · Kitchen Never Asks Silly
51
+ 厨房从不多问。把 knas 比作一个沉默的厨房帮工,你扔进去食材(碎片),它默默处理成菜肴(知识),从不抱怨。
52
+ · Knowledge Not Another Silo
53
+ 知识不再是孤岛。打破 App 之间的壁垒,让剪贴板成为统一入口。
54
+
55
+ ---
56
+
57
+
58
+ 剪切板自动同步工具 - 在 Mac 本机监听剪切板并同步到 Ubuntu 服务器。
59
+
60
+ ## 功能特点
61
+
62
+ - 🔔 **实时监听**: 500ms 轮询剪切板变化,CPU 占用极低
63
+ - 🖼️ **多模态支持**: 自动识别并归档截图(PNG),实现图文同档
64
+ - 🔒 **安全传输**: 通过 SSH 协议加密传输,无需在服务器安装接收端
65
+ - 🧠 **智能识别**: 自动识别 URL 并抓取网页标题
66
+ - 🎯 **智能过滤**: 支持敏感词过滤,避免同步密码等敏感信息
67
+ - 📁 **自动归档**: 按日期自动归档到 `YYYY/MM/DD/` 目录结构
68
+ - 🔄 **高韧性重试**: 指数退避重试机制(Exponential Backoff + Jitter),网络抖动自动抚平
69
+ - ⏪ **历史回溯**: `knas history` 查看最近同步记录,`knas restore <id>` 一键找回被覆盖的内容
70
+ - 🚀 **后台运行**: 支持 macOS LaunchAgent,开机自启动
71
+
72
+ ## 安装
73
+
74
+ ```bash
75
+ # 全局安装
76
+ npm install -g knas
77
+
78
+ # 或从源码安装
79
+ git clone https://github.com/yuanguangshan/knas.git
80
+ cd knas
81
+ npm install
82
+ npm run build
83
+ npm link
84
+ ```
85
+
86
+ ## 快速开始
87
+
88
+ ### 1. 初始化配置
89
+
90
+ ```bash
91
+ knas init
92
+ ```
93
+
94
+ 交互式配置:
95
+ - SSH 服务器地址
96
+ - SSH 端口(默认 22)
97
+ - SSH 用户名(默认 root)
98
+ - SSH 密钥路径(默认 ~/.ssh/id_rsa)
99
+ - 远程存储路径(默认 ~/knas_archive)
100
+ - 最小同步长度(默认 100 字符)
101
+
102
+ ### 2. 启动守护进程
103
+
104
+ ```bash
105
+ knas start
106
+ ```
107
+
108
+ ### 3. 查看状态
109
+
110
+ ```bash
111
+ knas status
112
+ ```
113
+
114
+ ### 4. 查看日志
115
+
116
+ ```bash
117
+ knas log # 查看日志
118
+ knas log -f # 实时跟踪日志
119
+ ```
120
+
121
+ ## 命令
122
+
123
+ | 命令 | 说明 |
124
+ |------|------|
125
+ | `knas init` | 初始化配置 |
126
+ | `knas start` | 启动守护进程 |
127
+ | `knas stop` | 停止守护进程 |
128
+ | `knas status` | 查看运行状态 |
129
+ | `knas log` | 查看日志 |
130
+ | `knas config` | 查看/编辑配置 |
131
+ | `knas history [n]` | 查看最近 n 条同步记录(默认 20) |
132
+ | `knas restore <id>` | 将指定记录回填到剪贴板 |
133
+ | `knas service install` | 安装为 macOS 系统服务 |
134
+
135
+ ## macOS 系统服务
136
+
137
+ 安装为 LaunchAgent 后,knas 会在登录时自动启动,崩溃自动重启:
138
+
139
+ ```bash
140
+ # 停止服务
141
+ launchctl unload ~/Library/LaunchAgents/com.knas.daemon.plist
142
+
143
+ # 启动服务
144
+ launchctl load ~/Library/LaunchAgents/com.knas.daemon.plist
145
+
146
+ # 查看状态
147
+ launchctl list | grep knas
148
+
149
+ # 卸载服务(不再开机自启)
150
+ launchctl unload ~/Library/LaunchAgents/com.knas.daemon.plist
151
+ rm ~/Library/LaunchAgents/com.knas.daemon.plist
152
+ ```
153
+
154
+ ## 配置文件
155
+
156
+ 配置文件位于 `~/.knas/config.json`:
157
+
158
+ ```json
159
+ {
160
+ "ssh": {
161
+ "host": "your-server.com",
162
+ "port": "22",
163
+ "user": "root",
164
+ "key_path": "~/.ssh/id_rsa",
165
+ "base_path": "~/knas_archive"
166
+ },
167
+ "clipboard": {
168
+ "min_length": 100,
169
+ "poll_interval_ms": 500,
170
+ "exclude_words": ["password", "密码", "token"]
171
+ },
172
+ "sync": {
173
+ "enabled": true,
174
+ "max_retries": 3,
175
+ "retry_delay_ms": 5000
176
+ }
177
+ }
178
+ ```
179
+
180
+ ## 远程文件结构
181
+
182
+ 同步的文件按以下结构存储:
183
+
184
+ ```~/knas_archive/
185
+ ├── 2026/
186
+ │ ├── 04/
187
+ │ │ ├── 18/
188
+ │ │ │ ├── 133119_knas_已成功运行.md <-- 语义化后缀
189
+ │ │ │ ├── 142545_关于量化交易的思.md
190
+ │ │ │ ├── 150405_image.png <-- 自动归档的截图
191
+ │ │ │ └── ...
192
+
193
+ ```
194
+
195
+ 每个文件包含:
196
+
197
+ ```markdown
198
+ ---
199
+ sync_time: 2026-04-18 14:20:30
200
+ source: clipboard
201
+ ---
202
+
203
+ [剪切板内容]
204
+ ```
205
+
206
+ ## 架构
207
+
208
+ ```
209
+ ┌─────────────────┐
210
+ │ Mac 本机 │
211
+ │ │
212
+ │ ┌───────────┐ │ SSH ┌─────────────┐
213
+ │ │ 剪切板监听 │ │ ────────> │ Ubuntu │
214
+ │ │ (Go) │ │ │ 服务器 │
215
+ │ └───────────┘ │ └─────────────┘
216
+ │ ↓ │ │
217
+ │ 内容处理 │ ↓
218
+ │ (URL标题抓取) │ 文件存储
219
+ │ ↓ │ (按日期归档)
220
+ │ ┌───────────┐ │
221
+ │ │ SSH 同步 │ │
222
+ │ └───────────┘ │
223
+ └─────────────────┘
224
+ ```
225
+
226
+ ## 技术栈
227
+
228
+ - **剪切板监听**: Go + `golang.design/x/clipboard` (原生支持 Text + Image)
229
+ - **重试机制**: Go + `internal/retry` (指数退避 + Full Jitter)
230
+ - **SSH 传输**: Go + `golang.org/x/crypto/ssh`
231
+ - **CLI 工具**: Go (纯原生编译,无 Node.js 依赖)
232
+ - **系统服务**: macOS LaunchAgent
233
+
234
+ ## 开发
235
+
236
+ ```bash
237
+ # 克隆仓库
238
+ git clone https://github.com/yuanguangshan/knas.git
239
+ cd knas
240
+
241
+ # 安装 Go 依赖
242
+ go mod tidy
243
+
244
+ # 构建二进制文件
245
+ go build -o knas ./cmd/knas
246
+
247
+ # 运行
248
+ ./knas init
249
+ ./knas start
250
+ ```
251
+
252
+ ## 注意事项
253
+
254
+ 1. **SSH 密钥**: 确保 Mac 的 SSH 公钥已添加到服务器的 `~/.ssh/authorized_keys`
255
+ 2. **网络连接**: 确保能访问远程服务器
256
+ 3. **权限**: 确保远程路径有写入权限
257
+ 4. **敏感信息**: 默认排除包含 "password"、"密码"、"token" 的内容
258
+
259
+ ## 许可证
260
+
261
+ MIT
262
+
263
+ ---
264
+
265
+ ## v1.7.0 新增:历史回溯与找回
266
+
267
+ ### 使用场景
268
+ 1. **手滑覆盖**:复制了新内容,想找回上一条被覆盖的长文本。
269
+ 2. **截图归档**:确认截图是否已成功同步到 NAS。
270
+
271
+ ### 示例
272
+ ```bash
273
+ # 查看最近 20 条同步记录
274
+ $ knas history
275
+ [20260418184501_a1b2c3d4] (text) 关于量化交易的思考...
276
+ [20260418184430_e5f6g7h8] (image) [IMAGE] 102400 bytes
277
+ [20260418184315_i9j0k1l2] (text) https://github.com/yuanguangshan/knas
278
+
279
+ # 找回被覆盖的文本
280
+ $ knas restore 20260418184501_a1b2c3d4
281
+ ✓ 已将记录 20260418184501_a1b2c3d4 恢复到剪贴板
282
+ ```
Binary file
Binary file
Binary file
package/bin/knowly.js ADDED
@@ -0,0 +1,396 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { program } = require('commander');
4
+ const inquirer = require('inquirer');
5
+ const chalk = require('chalk');
6
+ const fs = require('fs');
7
+ const path = require('path');
8
+ const { spawn, execSync } = require('child_process');
9
+ const os = require('os');
10
+
11
+ program
12
+ .name('knowly')
13
+ .description('Knowledge Async - Clipboard to NAS sync tool')
14
+ .version(require('../package.json').version);
15
+
16
+ const CONFIG_DIR = path.join(os.homedir(), '.knowly');
17
+ const CONFIG_FILE = path.join(CONFIG_DIR, 'config.json');
18
+ const LOG_FILE = path.join(CONFIG_DIR, 'knowly.log');
19
+ const PID_FILE = path.join(CONFIG_DIR, 'knowly.pid');
20
+
21
+ // 获取平台对应的二进制文件名
22
+ function getBinaryPath() {
23
+ const platform = os.platform();
24
+ const arch = os.arch();
25
+ const binDir = path.join(__dirname, '..', 'bin');
26
+
27
+ let binaryName;
28
+ if (platform === 'darwin' && arch === 'arm64') {
29
+ binaryName = 'knowly-darwin-arm64';
30
+ } else if (platform === 'darwin') {
31
+ binaryName = 'knowly-darwin';
32
+ } else if (platform === 'linux') {
33
+ binaryName = 'knowly-linux';
34
+ } else {
35
+ throw new Error(`Unsupported platform: ${platform}-${arch}`);
36
+ }
37
+
38
+ return path.join(binDir, binaryName);
39
+ }
40
+
41
+ // 检查配置是否存在
42
+ function isConfigured() {
43
+ return fs.existsSync(CONFIG_FILE);
44
+ }
45
+
46
+ // 读取配置
47
+ function loadConfig() {
48
+ if (!isConfigured()) {
49
+ return null;
50
+ }
51
+ return JSON.parse(fs.readFileSync(CONFIG_FILE, 'utf8'));
52
+ }
53
+
54
+ // 保存配置
55
+ function saveConfig(config) {
56
+ if (!fs.existsSync(CONFIG_DIR)) {
57
+ fs.mkdirSync(CONFIG_DIR, { recursive: true });
58
+ }
59
+ fs.writeFileSync(CONFIG_FILE, JSON.stringify(config, null, 2));
60
+ }
61
+
62
+ // 检查是否在运行
63
+ function isRunning() {
64
+ if (!fs.existsSync(PID_FILE)) {
65
+ return false;
66
+ }
67
+
68
+ try {
69
+ const pid = parseInt(fs.readFileSync(PID_FILE, 'utf8'));
70
+ process.kill(pid, 0); // 检查进程是否存在
71
+ return true;
72
+ } catch (e) {
73
+ return false;
74
+ }
75
+ }
76
+
77
+ // 执行二进制文件
78
+ function execBinary(args = [], detached = false) {
79
+ const binary = getBinaryPath();
80
+
81
+ if (!fs.existsSync(binary)) {
82
+ console.error(chalk.red(`Error: Binary not found at ${binary}`));
83
+ console.error(chalk.yellow('Please run: npm run build'));
84
+ process.exit(1);
85
+ }
86
+
87
+ const options = { stdio: 'inherit' };
88
+ if (detached) {
89
+ const logFd = fs.openSync(LOG_FILE, 'a');
90
+ options.stdio = ['ignore', logFd, logFd];
91
+ options.detached = true;
92
+ options.windowsHide = true;
93
+ }
94
+
95
+ const child = spawn(binary, args, options);
96
+
97
+ if (detached) {
98
+ child.unref();
99
+ }
100
+
101
+ return child;
102
+ }
103
+
104
+ // 初始化命令
105
+ program
106
+ .command('init')
107
+ .description('Initialize knowly configuration')
108
+ .action(async () => {
109
+ console.log(chalk.cyan('Welcome to knowly (Knowledge Async)!\n'));
110
+
111
+ const defaultConfig = {
112
+ ssh: {
113
+ host: '',
114
+ port: '22',
115
+ user: 'root',
116
+ key_path: path.join(os.homedir(), '.ssh', 'id_rsa'),
117
+ base_path: '~/knowly_archive'
118
+ },
119
+ clipboard: {
120
+ min_length: 100,
121
+ max_length: 1048576,
122
+ poll_interval_ms: 500,
123
+ exclude_words: ['password', '密码', 'token']
124
+ },
125
+ sync: {
126
+ enabled: true,
127
+ max_retries: 3,
128
+ retry_delay_ms: 5000
129
+ }
130
+ };
131
+
132
+ const answers = await inquirer.prompt([
133
+ {
134
+ type: 'input',
135
+ name: 'host',
136
+ message: 'SSH host address:',
137
+ validate: input => input.length > 0 || 'Host is required'
138
+ },
139
+ {
140
+ type: 'input',
141
+ name: 'port',
142
+ message: 'SSH port:',
143
+ default: '22'
144
+ },
145
+ {
146
+ type: 'input',
147
+ name: 'user',
148
+ message: 'SSH user:',
149
+ default: 'root'
150
+ },
151
+ {
152
+ type: 'input',
153
+ name: 'key_path',
154
+ message: 'SSH private key path:',
155
+ default: path.join(os.homedir(), '.ssh', 'id_rsa'),
156
+ validate: input => fs.existsSync(input) || 'Key file does not exist'
157
+ },
158
+ {
159
+ type: 'input',
160
+ name: 'base_path',
161
+ message: 'Remote base path:',
162
+ default: '~/knowly_archive'
163
+ },
164
+ {
165
+ type: 'number',
166
+ name: 'min_length',
167
+ message: 'Minimum clipboard length to sync:',
168
+ default: 100
169
+ }
170
+ ]);
171
+
172
+ defaultConfig.ssh.host = answers.host;
173
+ defaultConfig.ssh.port = answers.port;
174
+ defaultConfig.ssh.user = answers.user;
175
+ defaultConfig.ssh.key_path = answers.key_path;
176
+ defaultConfig.ssh.base_path = answers.base_path;
177
+ defaultConfig.clipboard.min_length = answers.min_length;
178
+
179
+ saveConfig(defaultConfig);
180
+
181
+ console.log(chalk.green('\n✓ Configuration saved to'), CONFIG_FILE);
182
+ console.log(chalk.cyan('\nYou can now start the daemon with: knowly start'));
183
+ });
184
+
185
+ // 启动命令
186
+ program
187
+ .command('start')
188
+ .description('Start knowly daemon')
189
+ .action(() => {
190
+ if (!isConfigured()) {
191
+ console.error(chalk.red('Error: knowly is not configured'));
192
+ console.error(chalk.yellow('Run "knowly init" to configure'));
193
+ process.exit(1);
194
+ }
195
+
196
+ if (isRunning()) {
197
+ console.log(chalk.yellow('knowly daemon is already running'));
198
+ return;
199
+ }
200
+
201
+ console.log(chalk.cyan('Starting knowly daemon...'));
202
+ execBinary(['--daemon'], true);
203
+ console.log(chalk.green('✓ knowly daemon started'));
204
+ console.log(chalk.gray(`Log file: ${LOG_FILE}`));
205
+ });
206
+
207
+ // 停止命令
208
+ program
209
+ .command('stop')
210
+ .description('Stop knowly daemon')
211
+ .action(() => {
212
+ if (!isRunning()) {
213
+ console.log(chalk.yellow('knowly daemon is not running'));
214
+ return;
215
+ }
216
+
217
+ console.log(chalk.cyan('Stopping knowly daemon...'));
218
+ execBinary(['--stop']);
219
+ console.log(chalk.green('✓ knowly daemon stopped'));
220
+ });
221
+
222
+ // 状态命令
223
+ program
224
+ .command('status')
225
+ .description('Show knowly daemon status')
226
+ .action(() => {
227
+ execBinary(['--status']);
228
+ });
229
+
230
+ // 日志命令
231
+ program
232
+ .command('log')
233
+ .description('Show knowly logs')
234
+ .option('-f, --follow', 'Follow log output')
235
+ .action((options) => {
236
+ if (!fs.existsSync(LOG_FILE)) {
237
+ console.log(chalk.yellow('No log file found'));
238
+ return;
239
+ }
240
+
241
+ if (options.follow) {
242
+ const tail = spawn('tail', ['-f', LOG_FILE], { stdio: 'inherit' });
243
+ tail.on('error', () => {
244
+ console.log(chalk.yellow('Log file not found or cannot be read'));
245
+ });
246
+ } else {
247
+ const logContent = fs.readFileSync(LOG_FILE, 'utf8');
248
+ console.log(logContent);
249
+ }
250
+ });
251
+
252
+ // 服务安装命令
253
+ program
254
+ .command('service install')
255
+ .description('Install knowly as macOS Login Item (auto-start on login)')
256
+ .action(() => {
257
+ if (!isConfigured()) {
258
+ console.error(chalk.red('Error: knowly is not configured'));
259
+ console.error(chalk.yellow('Run "knowly init" to configure'));
260
+ process.exit(1);
261
+ }
262
+
263
+ // 清理旧的 LaunchAgent(如果存在)
264
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.knowly.daemon.plist');
265
+ if (fs.existsSync(plistPath)) {
266
+ try {
267
+ execSync('launchctl unload ~/Library/LaunchAgents/com.knowly.daemon.plist 2>/dev/null');
268
+ } catch (e) { /* ignore */ }
269
+ fs.unlinkSync(plistPath);
270
+ console.log(chalk.gray('Removed old LaunchAgent'));
271
+ }
272
+
273
+ // 创建 AppleScript Helper App
274
+ const helperAppPath = path.join(CONFIG_DIR, 'KnowlyHelper.app');
275
+ const scriptContent = `on run\ndo shell script "nohup ${getBinaryPath()} --daemon >> ${LOG_FILE} 2>&1 &"\nend run`;
276
+
277
+ try {
278
+ // 写入临时 .scpt 文件
279
+ const tmpScript = path.join(os.tmpdir(), 'knowly_helper.scpt');
280
+ fs.writeFileSync(tmpScript, scriptContent);
281
+ execSync(`osacompile -o "${helperAppPath}" "${tmpScript}"`, { stdio: 'pipe' });
282
+ fs.unlinkSync(tmpScript);
283
+
284
+ // 添加到登录项
285
+ try {
286
+ execSync(`osascript -e 'tell application "System Events" to delete login item "KnowlyHelper"' 2>/dev/null`, { stdio: 'pipe' });
287
+ } catch (e) { /* not existing, ignore */ }
288
+ execSync(`osascript -e 'tell application "System Events" to make login item at end with properties {path:"${helperAppPath}", hidden:true}'`, { stdio: 'pipe' });
289
+
290
+ console.log(chalk.green('✓ KnowlyHelper installed as Login Item'));
291
+ console.log(chalk.gray(` App: ${helperAppPath}`));
292
+ console.log(chalk.cyan('\nKnowly will auto-start on login.'));
293
+ console.log(chalk.gray('Run "knowly service uninstall" to remove.'));
294
+ } catch (e) {
295
+ console.error(chalk.red('Error installing service:'), e.message);
296
+ process.exit(1);
297
+ }
298
+ });
299
+
300
+ // 服务卸载命令
301
+ program
302
+ .command('service uninstall')
303
+ .description('Uninstall knowly auto-start service')
304
+ .action(() => {
305
+ const helperAppPath = path.join(CONFIG_DIR, 'KnowlyHelper.app');
306
+
307
+ try {
308
+ // 从登录项移除
309
+ try {
310
+ execSync(`osascript -e 'tell application "System Events" to delete login item "KnowlyHelper"'`, { stdio: 'pipe' });
311
+ console.log(chalk.green('✓ Removed from Login Items'));
312
+ } catch (e) {
313
+ console.log(chalk.yellow('KnowlyHelper not found in Login Items'));
314
+ }
315
+
316
+ // 删除 Helper App
317
+ if (fs.existsSync(helperAppPath)) {
318
+ fs.rmSync(helperAppPath, { recursive: true });
319
+ console.log(chalk.green('✓ Removed KnowlyHelper.app'));
320
+ }
321
+
322
+ // 清理旧的 LaunchAgent(如果存在)
323
+ const plistPath = path.join(os.homedir(), 'Library', 'LaunchAgents', 'com.knowly.daemon.plist');
324
+ if (fs.existsSync(plistPath)) {
325
+ try { execSync('launchctl unload ~/Library/LaunchAgents/com.knowly.daemon.plist 2>/dev/null'); } catch (e) { /* ignore */ }
326
+ fs.unlinkSync(plistPath);
327
+ console.log(chalk.green('✓ Removed old LaunchAgent'));
328
+ }
329
+ } catch (e) {
330
+ console.error(chalk.red('Error uninstalling service:'), e.message);
331
+ process.exit(1);
332
+ }
333
+ });
334
+
335
+ // 配置命令
336
+ program
337
+ .command('config')
338
+ .description('Show or edit configuration')
339
+ .option('-e, --edit', 'Edit configuration')
340
+ .action((options) => {
341
+ if (!isConfigured()) {
342
+ console.error(chalk.red('Error: knowly is not configured'));
343
+ console.error(chalk.yellow('Run "knowly init" to configure'));
344
+ process.exit(1);
345
+ }
346
+
347
+ const cfg = loadConfig();
348
+
349
+ if (options.edit) {
350
+ const editor = process.env.EDITOR || 'vim';
351
+ spawn(editor, [CONFIG_FILE], { stdio: 'inherit' });
352
+ } else {
353
+ console.log(JSON.stringify(cfg, null, 2));
354
+ }
355
+ });
356
+
357
+ // 历史命令
358
+ program
359
+ .command('history [n]')
360
+ .description('Show recent history entries (default: 20)')
361
+ .action((n) => {
362
+ const args = ['history'];
363
+ if (n) args.push(String(n));
364
+ execBinary(args);
365
+ });
366
+
367
+ // 恢复命令
368
+ program
369
+ .command('restore <id>')
370
+ .description('Restore a history entry to clipboard')
371
+ .action((id) => {
372
+ execBinary(['restore', id]);
373
+ });
374
+
375
+ // Web UI 命令
376
+ program
377
+ .command('web [port]')
378
+ .description('启动 Web 管理界面 (默认端口: 8090)')
379
+ .action((port) => {
380
+ const args = ['web'];
381
+ if (port) args.push(port.startsWith(':') ? port : ':' + port);
382
+ const child = execBinary(args);
383
+ child.on('exit', (code) => process.exit(code));
384
+ });
385
+
386
+ // 版本命令
387
+ program
388
+ .command('version')
389
+ .description('Show version information')
390
+ .action(() => {
391
+ const pkg = require('../package.json');
392
+ console.log(`knowly v${pkg.version}`);
393
+ });
394
+
395
+ // 解析命令行参数
396
+ program.parse(process.argv);
package/package.json ADDED
@@ -0,0 +1,57 @@
1
+ {
2
+ "name": "knowly",
3
+ "version": "6.12.0",
4
+ "description": "Knowledge Async - Clipboard monitor and sync to remote server via SSH",
5
+ "main": "index.js",
6
+ "bin": {
7
+ "knowly": "./bin/knowly.js"
8
+ },
9
+ "scripts": {
10
+ "install": "node scripts/install.js",
11
+ "postinstall": "node scripts/postinstall.js",
12
+ "preuninstall": "node scripts/preuninstall.js",
13
+ "build": "node scripts/build.js",
14
+ "start": "node bin/knowly.js start",
15
+ "prepublishOnly": "npm run build"
16
+ },
17
+ "files": [
18
+ "bin/",
19
+ "scripts/",
20
+ "README.md",
21
+ "LICENSE",
22
+ "package.json"
23
+ ],
24
+ "keywords": [
25
+ "clipboard",
26
+ "sync",
27
+ "ssh",
28
+ "knowledge",
29
+ "automation",
30
+ "productivity",
31
+ "backup",
32
+ "macos"
33
+ ],
34
+ "author": "yuanguangshan <苑广山>",
35
+ "license": "MIT",
36
+ "repository": {
37
+ "type": "git",
38
+ "url": "git+https://github.com/yuanguangshan/knas.git"
39
+ },
40
+ "homepage": "https://github.com/yuanguangshan/knas#readme",
41
+ "bugs": {
42
+ "url": "https://github.com/yuanguangshan/knas/issues"
43
+ },
44
+ "dependencies": {
45
+ "chalk": "^4.1.2",
46
+ "commander": "^11.1.0",
47
+ "inquirer": "^9.2.12",
48
+ "ora": "^5.4.1"
49
+ },
50
+ "engines": {
51
+ "node": ">=16.0.0"
52
+ },
53
+ "os": [
54
+ "darwin",
55
+ "linux"
56
+ ]
57
+ }
@@ -0,0 +1,63 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+ const os = require('os');
7
+
8
+ console.log('Building knowly...\n');
9
+
10
+ // 确保 bin 目录存在
11
+ const binDir = path.join(__dirname, '..', 'bin');
12
+ if (!fs.existsSync(binDir)) {
13
+ fs.mkdirSync(binDir, { recursive: true });
14
+ }
15
+
16
+ // 定义构建目标
17
+ const targets = [
18
+ { goos: 'darwin', goarch: 'amd64', output: 'knowly-darwin' },
19
+ { goos: 'darwin', goarch: 'arm64', output: 'knowly-darwin-arm64' },
20
+ { goos: 'linux', goarch: 'amd64', output: 'knowly-linux' },
21
+ ];
22
+
23
+ // 构建每个目标
24
+ targets.forEach(target => {
25
+ console.log(`Building for ${target.goos}-${target.goarch}...`);
26
+
27
+ const env = {
28
+ ...process.env,
29
+ GOOS: target.goos,
30
+ GOARCH: target.goarch,
31
+ CGO_ENABLED: target.goos === 'darwin' ? '1' : '0'
32
+ };
33
+
34
+ if (target.goos === 'darwin') {
35
+ try {
36
+ env.SDKROOT = execSync('xcrun --sdk macosx --show-sdk-path', { encoding: 'utf8' }).trim();
37
+ } catch (_) {}
38
+ }
39
+
40
+ const outputPath = path.join(binDir, target.output);
41
+
42
+ try {
43
+ execSync(`go build -o ${outputPath} -ldflags="-s -w" ./cmd/knowly`, {
44
+ env,
45
+ stdio: 'inherit'
46
+ });
47
+
48
+ // 设置可执行权限
49
+ fs.chmodSync(outputPath, '755');
50
+
51
+ console.log(`✓ Built ${target.output}`);
52
+ } catch (e) {
53
+ console.error(`✗ Failed to build ${target.output}`);
54
+ process.exit(1);
55
+ }
56
+ });
57
+
58
+ console.log('\n✓ Build complete!');
59
+ console.log('\nBinaries:');
60
+ targets.forEach(target => {
61
+ const size = fs.statSync(path.join(binDir, target.output)).size;
62
+ console.log(` ${target.output}: ${(size / 1024).toFixed(2)} KB`);
63
+ });
@@ -0,0 +1,54 @@
1
+ #!/usr/bin/env node
2
+
3
+ const { execSync } = require('child_process');
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ // CI 环境中跳过编译,由 prepublishOnly 负责构建
8
+ if (process.env.CI === 'true') {
9
+ console.log('CI environment detected, skipping binary compilation');
10
+ process.exit(0);
11
+ }
12
+
13
+ console.log('Installing knowly...\n');
14
+
15
+ // 检查二进制文件是否已存在
16
+ const binDir = path.join(__dirname, '../bin');
17
+ const platform = process.platform;
18
+ const arch = process.arch;
19
+ const binaryName = platform === 'darwin' && arch === 'arm64'
20
+ ? 'knowly-darwin-arm64'
21
+ : platform === 'darwin'
22
+ ? 'knowly-darwin'
23
+ : platform === 'linux'
24
+ ? 'knowly-linux'
25
+ : null;
26
+
27
+ const binaryPath = binaryName ? path.join(binDir, binaryName) : null;
28
+ const binaryExists = binaryPath && fs.existsSync(binaryPath);
29
+
30
+ if (binaryExists) {
31
+ console.log('✓ Pre-built binary found');
32
+ } else {
33
+ // 检查 Go 环境
34
+ try {
35
+ execSync('go version', { stdio: 'pipe' });
36
+ console.log('✓ Go environment found');
37
+ } catch (e) {
38
+ console.error('✗ Go is not installed and no pre-built binary found');
39
+ console.error('Please install Go from https://golang.org/dl/');
40
+ process.exit(1);
41
+ }
42
+
43
+ // 运行构建
44
+ console.log('\nBuilding binaries...');
45
+ try {
46
+ execSync('node scripts/build.js', { stdio: 'inherit' });
47
+ } catch (e) {
48
+ console.error('✗ Build failed');
49
+ process.exit(1);
50
+ }
51
+ }
52
+
53
+ console.log('\n✓ Installation complete!');
54
+ console.log('\nRun "knowly init" to configure.');
@@ -0,0 +1,20 @@
1
+ #!/usr/bin/env node
2
+
3
+ // postinstall 脚本 - 安装后检查
4
+ const fs = require('fs');
5
+ const path = require('path');
6
+
7
+ const binDir = path.join(__dirname, '..', 'bin');
8
+ const requiredBinaries = ['knowly-darwin', 'knowly-darwin-arm64', 'knowly-linux'];
9
+
10
+ let allExist = true;
11
+ requiredBinaries.forEach(bin => {
12
+ const binPath = path.join(binDir, bin);
13
+ if (!fs.existsSync(binPath)) {
14
+ allExist = false;
15
+ }
16
+ });
17
+
18
+ if (!allExist) {
19
+ console.warn('Warning: Some binaries are missing. Run "npm run build" to build them.');
20
+ }
@@ -0,0 +1,19 @@
1
+ #!/usr/bin/env node
2
+
3
+ // preuninstall 脚本 - 卸载前清理
4
+ const { execSync } = require('child_process');
5
+ const fs = require('fs');
6
+ const path = require('path');
7
+
8
+ const pidFile = path.join(require('os').homedir(), '.knowly', 'knowly.pid');
9
+
10
+ // 尝试停止运行中的守护进程
11
+ if (fs.existsSync(pidFile)) {
12
+ try {
13
+ const pid = fs.readFileSync(pidFile, 'utf8').trim();
14
+ execSync(`kill ${pid} 2>/dev/null`);
15
+ console.log('Stopped knowly daemon');
16
+ } catch (e) {
17
+ // 忽略错误
18
+ }
19
+ }