@qnote/q-ai-note 1.0.5 → 1.0.6

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/README.md CHANGED
@@ -2,6 +2,15 @@
2
2
 
3
3
  AI-assisted personal work sandbox and diary system.
4
4
 
5
+ ## Documentation
6
+
7
+ Project specs and development guides are organized under `docs/`:
8
+
9
+ - `docs/spec/functional-spec.md`
10
+ - `docs/spec/design-spec.md`
11
+ - `docs/spec/ui-spec.md`
12
+ - `docs/process/development-guide.md`
13
+
5
14
  ## Install
6
15
 
7
16
  Use without global install:
@@ -16,6 +25,7 @@ Install globally:
16
25
  npm i -g @qnote/q-ai-note
17
26
  q-ai-note
18
27
  q-ai-note-server
28
+ q-ai-note-my-server
19
29
  ```
20
30
 
21
31
  ## Usage
@@ -23,16 +33,24 @@ q-ai-note-server
23
33
  ```bash
24
34
  q-ai-note --port 3200
25
35
  q-ai-note .
36
+ q-ai-note --readonly
26
37
  ```
27
38
 
28
39
  Then open `http://localhost:3200`.
29
40
 
41
+ ### Command differences
42
+
43
+ - `q-ai-note`: local-first, defaults `127.0.0.1:3000`
44
+ - `q-ai-note-server`: public server, defaults `0.0.0.0:8614`
45
+ - `q-ai-note-my-server`: mixed policy server, defaults `0.0.0.0:3000`
46
+
30
47
  ### Public server mode
31
48
 
32
49
  If you need external access, use:
33
50
 
34
51
  ```bash
35
52
  q-ai-note-server
53
+ q-ai-note-server --readonly
36
54
  ```
37
55
 
38
56
  Defaults:
@@ -41,6 +59,32 @@ Defaults:
41
59
 
42
60
  Then open `http://<server-ip>:8614`.
43
61
 
62
+ ### My server mode
63
+
64
+ If you want one server with mixed access policy, use:
65
+
66
+ ```bash
67
+ q-ai-note-my-server
68
+ ```
69
+
70
+ Defaults:
71
+ - host: `0.0.0.0`
72
+ - port: `3000`
73
+
74
+ Policy:
75
+ - access from `localhost` / `127.0.0.1` / local machine IP -> full features (readwrite)
76
+ - access from other IPs -> readonly (both UI and write APIs)
77
+
78
+ ### Readonly mode
79
+
80
+ Use `--readonly` (or env `Q_AI_NOTE_READONLY=true`) to start in readonly mode:
81
+
82
+ - hide chat UI and chat buttons
83
+ - hide settings page
84
+ - hide add/edit/delete operations
85
+ - reject all write API requests (`403`)
86
+ - reject settings API (`/api/settings`) for readonly requests (`403`)
87
+
44
88
  ## Data and config paths
45
89
 
46
90
  - Config: `~/.q-ai-note/config.json`
package/dist/cli.js CHANGED
@@ -1,10 +1,23 @@
1
1
  #!/usr/bin/env node
2
2
  import path from 'path';
3
+ import { basename } from 'path';
3
4
  import { startServer } from './server/index.js';
4
- function parseArgs(argv) {
5
+ function resolveCliDefaults(argv0) {
6
+ const commandName = basename(argv0 || 'q-ai-note');
7
+ const isServerMode = commandName === 'q-ai-note-server';
8
+ const isMyServerMode = commandName === 'q-ai-note-my-server';
9
+ return {
10
+ commandName,
11
+ defaultPort: isMyServerMode ? 3000 : isServerMode ? 8614 : 3000,
12
+ defaultHost: (isServerMode || isMyServerMode) ? '0.0.0.0' : '127.0.0.1',
13
+ localWriteOnly: isMyServerMode,
14
+ };
15
+ }
16
+ function parseArgs(argv, defaults) {
5
17
  const options = {
6
- port: Number(process.env.PORT || 3000),
7
- host: String(process.env.HOST || '127.0.0.1'),
18
+ port: Number(process.env.PORT || defaults.defaultPort),
19
+ host: String(process.env.HOST || defaults.defaultHost),
20
+ readonly: String(process.env.Q_AI_NOTE_READONLY || '').toLowerCase() === 'true',
8
21
  help: false,
9
22
  dataDir: undefined,
10
23
  };
@@ -33,46 +46,83 @@ function parseArgs(argv) {
33
46
  }
34
47
  continue;
35
48
  }
49
+ if (arg === '--readonly') {
50
+ options.readonly = true;
51
+ continue;
52
+ }
36
53
  if (!arg.startsWith('-') && !options.dataDir) {
37
54
  options.dataDir = path.resolve(arg);
38
55
  }
39
56
  }
40
57
  return options;
41
58
  }
42
- function printHelp() {
59
+ function printHelp(defaults) {
60
+ const isServerMode = defaults.commandName === 'q-ai-note-server';
61
+ const isMyServerMode = defaults.commandName === 'q-ai-note-my-server';
62
+ const defaultPortText = isMyServerMode ? '3000 or PORT env' : isServerMode ? '8614' : '3000 or PORT env';
63
+ const examples = isMyServerMode
64
+ ? [
65
+ ' q-ai-note-my-server',
66
+ ' q-ai-note-my-server --port 3000',
67
+ ' q-ai-note-my-server --host 0.0.0.0 --port 3000',
68
+ ' q-ai-note-my-server ./my-data --port 3000',
69
+ ]
70
+ : isServerMode
71
+ ? [
72
+ ' q-ai-note-server',
73
+ ' q-ai-note-server --port 8614',
74
+ ' q-ai-note-server --host 0.0.0.0 --port 8614',
75
+ ' q-ai-note-server ./my-data --port 8614',
76
+ ]
77
+ : [
78
+ ' npx @qnote/q-ai-note',
79
+ ' q-ai-note .',
80
+ ' q-ai-note --port 3200',
81
+ ' q-ai-note --host 127.0.0.1 --port 3200',
82
+ ' q-ai-note ./my-data --port 3200',
83
+ ' PORT=3200 q-ai-note',
84
+ ];
43
85
  console.log(`
44
- q-ai-note
86
+ ${defaults.commandName}
45
87
 
46
88
  Usage:
47
- q-ai-note [data-dir] [--port <port>] [--host <host>]
89
+ ${defaults.commandName} [data-dir] [--port <port>] [--host <host>] [--readonly]
48
90
 
49
91
  Options:
50
92
  data-dir Directory for JSON data files (optional)
51
- -p, --port Set server port (default: 3000 or PORT env)
52
- --host Set listen host (default: 127.0.0.1)
93
+ -p, --port Set server port (default: ${defaultPortText})
94
+ --host Set listen host (default: ${defaults.defaultHost})
95
+ --readonly Start in readonly mode
53
96
  -h, --help Show this help message
54
97
 
55
98
  Examples:
56
- npx @qnote/q-ai-note
57
- q-ai-note .
58
- q-ai-note --port 3200
59
- q-ai-note --host 127.0.0.1 --port 3200
60
- q-ai-note ./my-data --port 3200
61
- PORT=3200 q-ai-note
99
+ ${examples.join('\n')}
100
+
101
+ ${isMyServerMode ? 'Behavior:\n local access (localhost/127.0.0.1/local machine IP) => readwrite\n non-local access => readonly' : ''}
62
102
  `);
63
103
  }
64
104
  function bootstrap() {
65
- const options = parseArgs(process.argv.slice(2));
105
+ const defaults = resolveCliDefaults(process.argv[1]);
106
+ const options = parseArgs(process.argv.slice(2), defaults);
66
107
  if (options.help) {
67
- printHelp();
108
+ printHelp(defaults);
68
109
  return;
69
110
  }
70
111
  if (options.dataDir) {
71
112
  process.env.Q_AI_NOTE_DATA_DIR = options.dataDir;
72
113
  }
73
- startServer(options.port, options.host);
74
- console.log(`q-ai-note is ready at http://localhost:${options.port}`);
114
+ startServer(options.port, options.host, {
115
+ readonly: options.readonly,
116
+ localWriteOnly: defaults.localWriteOnly && !options.readonly,
117
+ });
118
+ console.log(`${defaults.commandName} is ready at http://localhost:${options.port}`);
75
119
  console.log(`listen: ${options.host}:${options.port}`);
120
+ const modeText = options.readonly
121
+ ? 'readonly'
122
+ : defaults.localWriteOnly
123
+ ? 'local-write-only'
124
+ : 'readwrite';
125
+ console.log(`mode: ${modeText}`);
76
126
  if (options.dataDir) {
77
127
  console.log(`data dir: ${options.dataDir}`);
78
128
  }
package/dist/cli.js.map CHANGED
@@ -1 +1 @@
1
- {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"cli.js","sourceRoot":"","sources":["../src/cli.ts"],"names":[],"mappings":"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"}
@@ -1,5 +1,10 @@
1
1
  import type { Server } from 'node:http';
2
- export declare function createApp(): import("express-serve-static-core").Express;
3
- export declare function startServer(port?: number, host?: string): Server;
2
+ export interface AccessPolicy {
3
+ readonly?: boolean;
4
+ localWriteOnly?: boolean;
5
+ }
6
+ export declare function isReadonlyForIp(ip: string, policyInput?: boolean | AccessPolicy, localIpSet?: Set<string>): boolean;
7
+ export declare function createApp(inputPolicy?: boolean | AccessPolicy): import("express-serve-static-core").Express;
8
+ export declare function startServer(port?: number, host?: string, policyInput?: boolean | AccessPolicy): Server;
4
9
  export declare function stopServer(): Promise<void>;
5
10
  //# sourceMappingURL=index.d.ts.map
@@ -1 +1 @@
1
- {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAkBxC,wBAAgB,SAAS,gDAoBxB;AAED,wBAAgB,WAAW,CAAC,IAAI,SAAO,EAAE,IAAI,SAAO,GAAG,MAAM,CAiB5D;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBhD"}
1
+ {"version":3,"file":"index.d.ts","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"AACA,OAAO,KAAK,EAAE,MAAM,EAAE,MAAM,WAAW,CAAC;AAqBxC,MAAM,WAAW,YAAY;IAC3B,QAAQ,CAAC,EAAE,OAAO,CAAC;IACnB,cAAc,CAAC,EAAE,OAAO,CAAC;CAC1B;AAoBD,wBAAgB,eAAe,CAC7B,EAAE,EAAE,MAAM,EACV,WAAW,GAAE,OAAO,GAAG,YAAoB,EAC3C,UAAU,GAAE,GAAG,CAAC,MAAM,CAAqB,GAC1C,OAAO,CAKT;AAYD,wBAAgB,SAAS,CAAC,WAAW,GAAE,OAAO,GAAG,YAAuB,+CA4CvE;AAED,wBAAgB,WAAW,CACzB,IAAI,SAAO,EACX,IAAI,SAAO,EACX,WAAW,GAAE,OAAO,GAAG,YAAuE,GAC7F,MAAM,CAmBR;AAED,wBAAsB,UAAU,IAAI,OAAO,CAAC,IAAI,CAAC,CAkBhD"}
@@ -1,5 +1,6 @@
1
1
  import express from 'express';
2
2
  import path from 'path';
3
+ import os from 'os';
3
4
  import { fileURLToPath, pathToFileURL } from 'url';
4
5
  import { initDb, closeDb } from './db.js';
5
6
  import sandboxRouter from './api/sandbox.js';
@@ -11,13 +12,68 @@ import nodeEntitiesRouter from './api/nodeEntities.js';
11
12
  const __dirname = path.dirname(fileURLToPath(import.meta.url));
12
13
  const PORT = Number(process.env.PORT || 3000);
13
14
  const HOST = String(process.env.HOST || '127.0.0.1');
15
+ const READONLY = String(process.env.Q_AI_NOTE_READONLY || '').toLowerCase() === 'true';
16
+ const LOCAL_WRITE_ONLY = String(process.env.Q_AI_NOTE_LOCAL_WRITE_ONLY || '').toLowerCase() === 'true';
14
17
  let serverInstance = null;
15
18
  let dbInitialized = false;
16
- export function createApp() {
19
+ function normalizeIpAddress(ip) {
20
+ const raw = String(ip || '').trim();
21
+ if (!raw)
22
+ return '';
23
+ return raw.startsWith('::ffff:') ? raw.slice(7) : raw;
24
+ }
25
+ function buildLocalIpSet() {
26
+ const set = new Set(['127.0.0.1', '::1', 'localhost']);
27
+ const nets = os.networkInterfaces();
28
+ Object.values(nets).forEach((rows) => {
29
+ (rows || []).forEach((row) => {
30
+ if (!row || typeof row.address !== 'string')
31
+ return;
32
+ set.add(normalizeIpAddress(row.address));
33
+ });
34
+ });
35
+ return set;
36
+ }
37
+ export function isReadonlyForIp(ip, policyInput = false, localIpSet = buildLocalIpSet()) {
38
+ const policy = resolveAccessPolicy(policyInput);
39
+ if (policy.readonly)
40
+ return true;
41
+ if (!policy.localWriteOnly)
42
+ return false;
43
+ return !localIpSet.has(normalizeIpAddress(ip));
44
+ }
45
+ function resolveAccessPolicy(input) {
46
+ if (typeof input === 'boolean') {
47
+ return { readonly: input, localWriteOnly: false };
48
+ }
49
+ return {
50
+ readonly: Boolean(input?.readonly),
51
+ localWriteOnly: Boolean(input?.localWriteOnly),
52
+ };
53
+ }
54
+ export function createApp(inputPolicy = READONLY) {
55
+ const policy = resolveAccessPolicy(inputPolicy);
56
+ const localIpSet = buildLocalIpSet();
57
+ const isReadonlyRequest = (req) => isReadonlyForIp(req.ip || req.socket?.remoteAddress || '', policy, localIpSet);
17
58
  const app = express();
18
59
  app.use(express.json());
19
60
  app.use('/node_modules', express.static(path.join(__dirname, '../../node_modules')));
20
61
  app.use(express.static(path.join(__dirname, '../web')));
62
+ app.get('/api/runtime', (req, res) => {
63
+ res.json({ success: true, data: { readonly: isReadonlyRequest(req) } });
64
+ });
65
+ app.use('/api/settings', (req, res, next) => {
66
+ if (!isReadonlyRequest(req))
67
+ return next();
68
+ return res.status(403).json({ success: false, error: 'Readonly mode enabled' });
69
+ });
70
+ app.use('/api', (req, res, next) => {
71
+ if (!isReadonlyRequest(req))
72
+ return next();
73
+ if (req.method === 'GET' || req.method === 'HEAD' || req.method === 'OPTIONS')
74
+ return next();
75
+ return res.status(403).json({ success: false, error: 'Readonly mode enabled' });
76
+ });
21
77
  app.use('/api/sandboxes', sandboxRouter);
22
78
  app.use('/api/sandboxes/:sandboxId/items', workItemRouter);
23
79
  app.use('/api/items', workItemRouter);
@@ -30,7 +86,7 @@ export function createApp() {
30
86
  });
31
87
  return app;
32
88
  }
33
- export function startServer(port = PORT, host = HOST) {
89
+ export function startServer(port = PORT, host = HOST, policyInput = { readonly: READONLY, localWriteOnly: LOCAL_WRITE_ONLY }) {
34
90
  if (serverInstance) {
35
91
  return serverInstance;
36
92
  }
@@ -38,10 +94,12 @@ export function startServer(port = PORT, host = HOST) {
38
94
  initDb();
39
95
  dbInitialized = true;
40
96
  }
41
- const app = createApp();
97
+ const policy = resolveAccessPolicy(policyInput);
98
+ const app = createApp(policy);
42
99
  serverInstance = app.listen(port, host, () => {
43
100
  const printableHost = host === '0.0.0.0' ? 'localhost' : host;
44
- console.log(`Server running at http://${printableHost}:${port}`);
101
+ const mode = policy.readonly ? 'readonly' : policy.localWriteOnly ? 'local-write-only' : 'readwrite';
102
+ console.log(`Server running at http://${printableHost}:${port} (${mode})`);
45
103
  });
46
104
  return serverInstance;
47
105
  }
@@ -1 +1 @@
1
- {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"index.js","sourceRoot":"","sources":["../../src/server/index.ts"],"names":[],"mappings":"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"}