@remnic/cli 9.3.768 → 9.3.769

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.
Files changed (2) hide show
  1. package/dist/index.js +375 -0
  2. package/package.json +28 -28
package/dist/index.js CHANGED
@@ -5819,6 +5819,376 @@ async function cmdStatus(json) {
5819
5819
  clearTimeout(timeoutId);
5820
5820
  }
5821
5821
  }
5822
+ function oauthReadConfigRecord(configPath) {
5823
+ try {
5824
+ const parsed = JSON.parse(fs7.readFileSync(configPath, "utf8"));
5825
+ if (parsed && typeof parsed === "object" && !Array.isArray(parsed)) {
5826
+ return parsed;
5827
+ }
5828
+ return void 0;
5829
+ } catch {
5830
+ return void 0;
5831
+ }
5832
+ }
5833
+ function oauthResolveBaseUrl() {
5834
+ const configPath = resolveConfigPath();
5835
+ let port = 4318;
5836
+ let host = "127.0.0.1";
5837
+ const raw = oauthReadConfigRecord(configPath);
5838
+ if (raw && "server" in raw) {
5839
+ const server = raw.server;
5840
+ if (server && typeof server === "object") {
5841
+ const hostCandidate = server.host;
5842
+ if (typeof hostCandidate === "string" && hostCandidate.length > 0) {
5843
+ host = hostCandidate;
5844
+ }
5845
+ const portCandidate = server.port;
5846
+ if (typeof portCandidate === "number" && Number.isInteger(portCandidate)) {
5847
+ port = portCandidate;
5848
+ }
5849
+ }
5850
+ }
5851
+ const envHost = readCompatEnv("REMNIC_HOST", "ENGRAM_HOST");
5852
+ if (typeof envHost === "string" && envHost.length > 0) {
5853
+ host = envHost;
5854
+ }
5855
+ const envPortRaw = readCompatEnv("REMNIC_PORT", "ENGRAM_PORT");
5856
+ if (typeof envPortRaw === "string" && envPortRaw.length > 0) {
5857
+ const envPort = Number(envPortRaw);
5858
+ if (!Number.isInteger(envPort) || envPort < 1 || envPort > 65535) {
5859
+ throw new Error(
5860
+ `Invalid REMNIC_PORT/ENGRAM_PORT "${envPortRaw}": expected an integer in [1, 65535].`
5861
+ );
5862
+ }
5863
+ port = envPort;
5864
+ }
5865
+ return `http://${host}:${port}`;
5866
+ }
5867
+ function oauthResolveOperatorToken() {
5868
+ const envToken = readCompatEnv("REMNIC_AUTH_TOKEN", "ENGRAM_AUTH_TOKEN");
5869
+ if (typeof envToken === "string" && envToken.length > 0) return envToken;
5870
+ const raw = oauthReadConfigRecord(resolveConfigPath());
5871
+ if (raw && "server" in raw) {
5872
+ const server = raw.server;
5873
+ if (server && typeof server === "object" && "authToken" in server) {
5874
+ const candidate = server.authToken;
5875
+ if (typeof candidate === "string" && candidate.length > 0) {
5876
+ return candidate;
5877
+ }
5878
+ }
5879
+ }
5880
+ return void 0;
5881
+ }
5882
+ async function oauthFetch(method, path12, token, body) {
5883
+ const controller = new AbortController();
5884
+ const timeoutId = setTimeout(() => controller.abort(), 5e3);
5885
+ try {
5886
+ const headers = {
5887
+ authorization: `Bearer ${token}`,
5888
+ accept: "application/json"
5889
+ };
5890
+ if (body !== void 0) {
5891
+ headers["content-type"] = "application/json";
5892
+ }
5893
+ const init = {
5894
+ method,
5895
+ signal: controller.signal,
5896
+ headers
5897
+ };
5898
+ if (body !== void 0) {
5899
+ init.body = JSON.stringify(body);
5900
+ }
5901
+ const response = await fetch(`${oauthResolveBaseUrl()}${path12}`, init);
5902
+ if (response.status === 401) {
5903
+ throw new Error(
5904
+ "operator token rejected by remnic-server (HTTP 401). Update `server.authToken` or `REMNIC_AUTH_TOKEN` to match the running daemon."
5905
+ );
5906
+ }
5907
+ if (response.status === 404) {
5908
+ throw new Error("unknown or expired ref");
5909
+ }
5910
+ if (response.status === 409) {
5911
+ let description = "request conflicts with current state";
5912
+ try {
5913
+ const payload = await response.json();
5914
+ if (payload && typeof payload === "object" && "error_description" in payload) {
5915
+ const candidate = payload.error_description;
5916
+ if (typeof candidate === "string" && candidate.length > 0) {
5917
+ description = candidate;
5918
+ }
5919
+ }
5920
+ } catch {
5921
+ }
5922
+ throw new Error(description);
5923
+ }
5924
+ if (!response.ok) {
5925
+ throw new Error(`remnic-server returned HTTP ${response.status} ${response.statusText}`);
5926
+ }
5927
+ const text = await response.text();
5928
+ if (text.length === 0) return null;
5929
+ try {
5930
+ return JSON.parse(text);
5931
+ } catch {
5932
+ throw new Error("remnic-server returned a non-JSON response");
5933
+ }
5934
+ } catch (err) {
5935
+ if (err instanceof Error) {
5936
+ const msg = err.message;
5937
+ if (msg.includes("ECONNREFUSED") || msg.includes("ECONNRESET") || msg.includes("fetch failed") || msg.includes("aborted") || msg.includes("ENOTFOUND")) {
5938
+ throw new Error(
5939
+ `cannot reach remnic-server at ${oauthResolveBaseUrl()} \u2014 is remnic-server running? Start it with \`remnic daemon start\`.`
5940
+ );
5941
+ }
5942
+ throw err;
5943
+ }
5944
+ throw err;
5945
+ } finally {
5946
+ clearTimeout(timeoutId);
5947
+ }
5948
+ }
5949
+ function oauthFormatPendingText(pending) {
5950
+ if (pending.length === 0) {
5951
+ return "No pending OAuth authorizations.";
5952
+ }
5953
+ const lines = [`Pending OAuth authorizations (${pending.length}):`];
5954
+ for (const txn of pending) {
5955
+ const scopes = txn.scopes.length === 0 ? "(none)" : txn.scopes.join(" ");
5956
+ const resource = txn.resource === null || txn.resource.length === 0 ? "(none)" : txn.resource;
5957
+ lines.push(
5958
+ ` ref=${txn.ref} client=${txn.clientId} redirect=${txn.redirectUri}`,
5959
+ ` scopes=${scopes} resource=${resource} expires=${txn.expiresAt}`
5960
+ );
5961
+ }
5962
+ return lines.join("\n");
5963
+ }
5964
+ async function oauthPromptYesNo(question) {
5965
+ if (!process.stdin.isTTY || !process.stdout.isTTY) {
5966
+ return false;
5967
+ }
5968
+ process.stdout.write(`${question} [y/N] `);
5969
+ return new Promise((resolve) => {
5970
+ let buffer = "";
5971
+ const onData = (chunk) => {
5972
+ const text = typeof chunk === "string" ? chunk : chunk.toString("utf8");
5973
+ buffer += text;
5974
+ if (text.includes("\n")) {
5975
+ process.stdin.removeListener("data", onData);
5976
+ process.stdin.pause();
5977
+ const answer = buffer.trim().toLowerCase();
5978
+ resolve(answer === "y" || answer === "yes");
5979
+ }
5980
+ };
5981
+ process.stdin.resume();
5982
+ process.stdin.on("data", onData);
5983
+ });
5984
+ }
5985
+ function oauthPrintApprovalDetails(txn) {
5986
+ const scopeList = txn.scopes.length === 0 ? "(none)" : txn.scopes.join(" ");
5987
+ const resource = txn.resource === null || txn.resource === void 0 ? "(none)" : txn.resource;
5988
+ console.log("Pending OAuth authorization request:");
5989
+ console.log(` ref: ${txn.ref}`);
5990
+ console.log(` client: ${txn.clientId}`);
5991
+ console.log(` redirect: ${txn.redirectUri}`);
5992
+ console.log(` scopes: ${scopeList}`);
5993
+ console.log(` resource: ${resource}`);
5994
+ console.log(` created: ${txn.createdAt}`);
5995
+ console.log(` expires: ${txn.expiresAt}`);
5996
+ console.log(
5997
+ "WARNING: approval grants the requesting application an MCP access token tied to this Remnic instance."
5998
+ );
5999
+ }
6000
+ var OAUTH_USAGE = `Usage: remnic oauth <pending|approve|deny> [options]
6001
+
6002
+ Manage pending OAuth authorizations (ChatGPT MCP).
6003
+
6004
+ Subcommands:
6005
+ remnic oauth pending [--format json|text] List pending authorization requests
6006
+ remnic oauth approve <ref> [--yes] Approve a pending request
6007
+ remnic oauth deny <ref> Deny a pending request
6008
+
6009
+ Options:
6010
+ --format <name> Output format for \`pending\`: "text" (default) or "json"
6011
+ --yes Required for \`approve\` when stdin is not a TTY
6012
+
6013
+ Server endpoints (operator bearer auth):
6014
+ GET /oauth/pending
6015
+ POST /oauth/pending/<ref>/approve
6016
+ POST /oauth/pending/<ref>/deny`;
6017
+ function oauthRequireOperatorToken() {
6018
+ const token = oauthResolveOperatorToken();
6019
+ if (!token) {
6020
+ console.error(
6021
+ "remnic oauth: no operator token configured. Set `server.authToken` in remnic.config.json or export REMNIC_AUTH_TOKEN."
6022
+ );
6023
+ process.exit(1);
6024
+ }
6025
+ return token;
6026
+ }
6027
+ function oauthValidateArgs(args, spec) {
6028
+ const positionals = [];
6029
+ const seen = /* @__PURE__ */ new Set();
6030
+ for (let i = 0; i < args.length; i++) {
6031
+ const arg = args[i];
6032
+ if (arg !== void 0 && arg.startsWith("-")) {
6033
+ if (seen.has(arg)) {
6034
+ console.error(`Duplicate option "${arg}". ${spec.usage}`);
6035
+ process.exit(1);
6036
+ }
6037
+ seen.add(arg);
6038
+ const kind = spec.flags[arg];
6039
+ if (kind === void 0) {
6040
+ console.error(`Unknown option "${arg}". ${spec.usage}`);
6041
+ process.exit(1);
6042
+ }
6043
+ if (kind === "value") {
6044
+ const next = args[i + 1];
6045
+ if (next === void 0 || next.startsWith("-")) {
6046
+ console.error(`${arg} requires a value. ${spec.usage}`);
6047
+ process.exit(1);
6048
+ }
6049
+ i++;
6050
+ }
6051
+ continue;
6052
+ }
6053
+ if (arg !== void 0) positionals.push(arg);
6054
+ }
6055
+ if (positionals.length > spec.maxPositionals) {
6056
+ console.error(`Unexpected argument "${positionals[spec.maxPositionals]}". ${spec.usage}`);
6057
+ process.exit(1);
6058
+ }
6059
+ return positionals;
6060
+ }
6061
+ async function cmdOAuth(rest) {
6062
+ if (rest.length === 0 || rest[0] === "--help" || rest[0] === "-h") {
6063
+ console.log(OAUTH_USAGE);
6064
+ return;
6065
+ }
6066
+ const subcommand = rest[0];
6067
+ const subRest = rest.slice(1);
6068
+ switch (subcommand) {
6069
+ case "pending": {
6070
+ oauthValidateArgs(subRest, {
6071
+ flags: { "--format": "value" },
6072
+ maxPositionals: 0,
6073
+ usage: "Usage: remnic oauth pending [--format json|text]"
6074
+ });
6075
+ const formatRaw = resolveFlag(subRest, "--format");
6076
+ const formatPresent = hasFlag(subRest, "--format");
6077
+ const format = (() => {
6078
+ if (!formatPresent || formatRaw === void 0 || formatRaw === null) return "text";
6079
+ const normalized = String(formatRaw).trim().toLowerCase();
6080
+ if (normalized !== "text" && normalized !== "json") {
6081
+ console.error(`Invalid --format "${formatRaw}". Allowed: text, json.`);
6082
+ process.exit(1);
6083
+ }
6084
+ return normalized;
6085
+ })();
6086
+ const token = oauthRequireOperatorToken();
6087
+ try {
6088
+ const payload = await oauthFetch("GET", "/oauth/pending", token, void 0);
6089
+ const pending = Array.isArray(payload?.pending) ? payload.pending : [];
6090
+ if (format === "json") {
6091
+ process.stdout.write(JSON.stringify(payload ?? { pending: [] }, null, 2) + "\n");
6092
+ return;
6093
+ }
6094
+ console.log(oauthFormatPendingText(pending));
6095
+ } catch (err) {
6096
+ console.error(err instanceof Error ? err.message : String(err));
6097
+ process.exit(1);
6098
+ }
6099
+ return;
6100
+ }
6101
+ case "approve": {
6102
+ const positionals = oauthValidateArgs(subRest, {
6103
+ flags: { "--yes": "boolean", "-y": "boolean" },
6104
+ maxPositionals: 1,
6105
+ usage: "Usage: remnic oauth approve <ref> [--yes]"
6106
+ });
6107
+ const ref = positionals[0];
6108
+ if (!ref) {
6109
+ console.error("Usage: remnic oauth approve <ref> [--yes]");
6110
+ process.exit(1);
6111
+ }
6112
+ const yes = hasFlag(subRest, "--yes") || hasFlag(subRest, "-y");
6113
+ const token = oauthRequireOperatorToken();
6114
+ try {
6115
+ const payload = await oauthFetch("GET", "/oauth/pending", token, void 0);
6116
+ const pending = Array.isArray(payload?.pending) ? payload.pending : [];
6117
+ const txn = pending.find((entry) => entry.ref === ref);
6118
+ if (!txn) {
6119
+ console.error(
6120
+ `remnic oauth approve: no pending authorization with ref "${ref}". Run \`remnic oauth pending\` to see active requests.`
6121
+ );
6122
+ process.exit(1);
6123
+ }
6124
+ oauthPrintApprovalDetails(txn);
6125
+ let confirmed = yes;
6126
+ if (!confirmed) {
6127
+ if (!process.stdin.isTTY) {
6128
+ console.error(
6129
+ "remnic oauth approve: refusing to send the approval without an explicit --yes flag (stdin is not a TTY). Re-run with --yes to confirm."
6130
+ );
6131
+ process.exit(1);
6132
+ }
6133
+ confirmed = await oauthPromptYesNo("Approve this request?");
6134
+ if (!confirmed) {
6135
+ console.log("Approval cancelled.");
6136
+ return;
6137
+ }
6138
+ }
6139
+ const result = await oauthFetch(
6140
+ "POST",
6141
+ `/oauth/pending/${encodeURIComponent(ref)}/approve`,
6142
+ token,
6143
+ {}
6144
+ );
6145
+ const status = result?.status ?? "approved";
6146
+ const redirect = result?.redirect;
6147
+ console.log(`Approved ref=${ref} (status: ${status}).`);
6148
+ if (typeof redirect === "string" && redirect.length > 0) {
6149
+ console.log(`Client redirect: ${redirect}`);
6150
+ }
6151
+ } catch (err) {
6152
+ console.error(err instanceof Error ? err.message : String(err));
6153
+ process.exit(1);
6154
+ }
6155
+ return;
6156
+ }
6157
+ case "deny": {
6158
+ const positionals = oauthValidateArgs(subRest, {
6159
+ flags: {},
6160
+ maxPositionals: 1,
6161
+ usage: "Usage: remnic oauth deny <ref>"
6162
+ });
6163
+ const ref = positionals[0];
6164
+ if (!ref) {
6165
+ console.error("Usage: remnic oauth deny <ref>");
6166
+ process.exit(1);
6167
+ }
6168
+ const token = oauthRequireOperatorToken();
6169
+ try {
6170
+ const result = await oauthFetch(
6171
+ "POST",
6172
+ `/oauth/pending/${encodeURIComponent(ref)}/deny`,
6173
+ token,
6174
+ {}
6175
+ );
6176
+ const status = result?.status ?? "denied";
6177
+ console.log(`Denied ref=${ref} (status: ${status}).`);
6178
+ } catch (err) {
6179
+ console.error(err instanceof Error ? err.message : String(err));
6180
+ process.exit(1);
6181
+ }
6182
+ return;
6183
+ }
6184
+ default: {
6185
+ console.error(
6186
+ `Unknown oauth subcommand "${String(subcommand)}". Run \`remnic oauth --help\` for usage.`
6187
+ );
6188
+ process.exit(1);
6189
+ }
6190
+ }
6191
+ }
5822
6192
  function queryResultText(memory) {
5823
6193
  return memory.content?.trim() || memory.preview?.trim() || memory.context?.trim() || "(no preview available)";
5824
6194
  }
@@ -11752,6 +12122,10 @@ Options:
11752
12122
  await cmdOffline(action, rest.slice(1), json);
11753
12123
  break;
11754
12124
  }
12125
+ case "oauth": {
12126
+ await cmdOAuth(rest);
12127
+ break;
12128
+ }
11755
12129
  case "dedup": {
11756
12130
  const json = rest.includes("--json");
11757
12131
  cmdDedup(json);
@@ -12112,6 +12486,7 @@ Usage:
12112
12486
  remnic review <list|approve|dismiss|flag> [id] Review inbox
12113
12487
  remnic sync <run|watch> [--source <dir>] Diff-aware sync
12114
12488
  remnic offline <prepare|sync|status|watch> Remote/offline memory sync
12489
+ remnic oauth <pending|approve|deny> Manage pending OAuth authorizations (ChatGPT MCP)
12115
12490
  remnic dedup [--json] Find duplicate memories
12116
12491
  remnic connectors <list|install|remove|doctor|marketplace> [id] Manage connectors
12117
12492
  marketplace generate Generate marketplace.json for Codex
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@remnic/cli",
3
- "version": "9.3.768",
3
+ "version": "9.3.769",
4
4
  "description": "CLI for Remnic memory — init, query, doctor, daemon management",
5
5
  "type": "module",
6
6
  "main": "dist/index.js",
@@ -26,23 +26,23 @@
26
26
  },
27
27
  "dependencies": {
28
28
  "yaml": "^2.4.2",
29
- "@remnic/server": "^9.3.768",
30
- "@remnic/core": "^9.3.768",
31
- "@remnic/plugin-pi": "^9.3.768"
29
+ "@remnic/plugin-pi": "^9.3.769",
30
+ "@remnic/core": "^9.3.769",
31
+ "@remnic/server": "^9.3.769"
32
32
  },
33
33
  "peerDependencies": {
34
- "@remnic/bench": "^9.3.768",
35
- "@remnic/export-weclone": "^9.3.768",
36
- "@remnic/import-weclone": "^9.3.768",
37
- "@remnic/import-chatgpt": "^9.3.768",
38
- "@remnic/import-claude": "^9.3.768",
39
- "@remnic/import-gemini": "^9.3.768",
40
- "@remnic/import-lossless-claw": "^9.3.768",
41
- "@remnic/import-mem0": "^9.3.768",
42
- "@remnic/import-supermemory": "^9.3.768",
43
- "@remnic/connector-limitless": "^9.3.768",
44
- "@remnic/connector-bee": "^9.3.768",
45
- "@remnic/connector-omi": "^9.3.768"
34
+ "@remnic/bench": "^9.3.769",
35
+ "@remnic/export-weclone": "^9.3.769",
36
+ "@remnic/import-weclone": "^9.3.769",
37
+ "@remnic/import-chatgpt": "^9.3.769",
38
+ "@remnic/import-claude": "^9.3.769",
39
+ "@remnic/import-gemini": "^9.3.769",
40
+ "@remnic/import-lossless-claw": "^9.3.769",
41
+ "@remnic/import-mem0": "^9.3.769",
42
+ "@remnic/import-supermemory": "^9.3.769",
43
+ "@remnic/connector-limitless": "^9.3.769",
44
+ "@remnic/connector-bee": "^9.3.769",
45
+ "@remnic/connector-omi": "^9.3.769"
46
46
  },
47
47
  "peerDependenciesMeta": {
48
48
  "@remnic/bench": {
@@ -85,18 +85,18 @@
85
85
  "devDependencies": {
86
86
  "tsup": "^8.5.1",
87
87
  "typescript": "^5.9.3",
88
- "@remnic/bench": "9.3.768",
89
- "@remnic/export-weclone": "9.3.768",
90
- "@remnic/import-weclone": "9.3.768",
91
- "@remnic/import-lossless-claw": "9.3.768",
92
- "@remnic/import-chatgpt": "9.3.768",
93
- "@remnic/import-mem0": "9.3.768",
94
- "@remnic/import-supermemory": "9.3.768",
95
- "@remnic/connector-limitless": "9.3.768",
96
- "@remnic/import-gemini": "9.3.768",
97
- "@remnic/connector-bee": "9.3.768",
98
- "@remnic/connector-omi": "9.3.768",
99
- "@remnic/import-claude": "9.3.768"
88
+ "@remnic/bench": "9.3.769",
89
+ "@remnic/export-weclone": "9.3.769",
90
+ "@remnic/import-weclone": "9.3.769",
91
+ "@remnic/import-claude": "9.3.769",
92
+ "@remnic/import-chatgpt": "9.3.769",
93
+ "@remnic/import-gemini": "9.3.769",
94
+ "@remnic/import-supermemory": "9.3.769",
95
+ "@remnic/import-mem0": "9.3.769",
96
+ "@remnic/connector-limitless": "9.3.769",
97
+ "@remnic/import-lossless-claw": "9.3.769",
98
+ "@remnic/connector-omi": "9.3.769",
99
+ "@remnic/connector-bee": "9.3.769"
100
100
  },
101
101
  "license": "MIT",
102
102
  "repository": {