dsh-supermemory 0.1.0 → 0.1.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/README.md CHANGED
@@ -199,28 +199,55 @@ Requires Node `^22.19.0 || >=24.0.0`.
199
199
 
200
200
  ## Releasing
201
201
 
202
- Publishing runs from GitHub Actions through npm **trusted publishing (OIDC)** — no `NPM_TOKEN` secret exists, and every release carries provenance.
202
+ Publishing runs from GitHub Actions through npm **trusted publishing (OIDC)** — no `NPM_TOKEN` secret exists, and every release carries provenance. CI does not publish on its own: it *stages* the release, and a human approves it with a 2FA code.
203
203
 
204
- One-time setup on npm: open <https://www.npmjs.com/package/dsh-supermemory/access>, add a trusted publisher, choose GitHub Actions, and enter organization `nmindz`, repository `dsh-supermemory`, workflow `.github/workflows/release.yml`, environment `npm`.
204
+ One-time setup on npm: open <https://www.npmjs.com/package/dsh-supermemory/access>, add a trusted publisher, choose GitHub Actions, and enter organization `nmindz`, repository `dsh-supermemory`, workflow `.github/workflows/release.yml`, environment `production`. Under **Allowed actions**, leave *publish directly* unchecked — staged publishing is all this workflow needs, and it keeps CI from holding a credential that can ship a version by itself.
205
205
 
206
- Then each release is a version bump and a tag:
206
+ ### Conventional Commits drive the version
207
+
208
+ There is no manual version bump and no hand-written tag. [semantic-release](https://semantic-release.gitbook.io) reads the [Conventional Commits](https://www.conventionalcommits.org) since the last release and decides everything else — the version, `CHANGELOG.md`, the git tag, and the GitHub Release.
209
+
210
+ | Commit | Result |
211
+ |---|---|
212
+ | `fix: …` | patch — `0.1.0` → `0.1.1` |
213
+ | `feat: …` | minor — `0.1.0` → `0.2.0` |
214
+ | `feat!: …` or a `BREAKING CHANGE:` footer | major |
215
+ | `perf: …`, `refactor: …` | patch |
216
+ | `docs: …`, `test: …`, `chore: …`, `ci: …`, `build: …`, `style: …` | no release |
217
+
218
+ So a release is just a merge to `master`:
219
+
220
+ ```sh
221
+ git commit -m 'fix: render the status line from a symlinked install path'
222
+ git push origin master
223
+ ```
224
+
225
+ CI validates the commit, the release workflow runs once CI is green, and if the commits warrant a version it bumps `package.json`, writes `CHANGELOG.md`, tags `vX.Y.Z`, opens the GitHub Release, and stages the npm publish. Pull requests get their commit messages linted so a malformed one cannot silently cost a release.
226
+
227
+ The version then waits for you:
207
228
 
208
229
  ```sh
209
- npm version 0.1.1 --no-git-tag-version # edits package.json only
210
- git commit -am 'Release v0.1.1'
211
- git tag v0.1.1
212
- git push origin master --tags
230
+ npm stage list dsh-supermemory # find the stage id
231
+ npm stage view <stage-id> # inspect the tarball and provenance
232
+ npm stage approve <stage-id> # publishes it; asks for your 2FA code
233
+ npm stage reject <stage-id> # discards it instead
213
234
  ```
214
235
 
215
- The workflow refuses to publish when the tag and `package.json` version disagree, and runs `pnpm run check` before it publishes.
236
+ Requires npm 11.19 locally (`npm install -g npm@latest`).
216
237
 
217
- To publish by hand instead (requires `npm login` or a configured token):
238
+ To publish by hand instead, skipping staging entirely:
218
239
 
219
240
  ```sh
220
241
  pnpm run check
221
242
  npm publish --access public
222
243
  ```
223
244
 
245
+ Run semantic-release in CI only. It moves git refs as part of its work, and in a jj-colocated checkout jj re-imports those refs and rolls the working copy back onto them — locally it looks like it ate your unpushed commits. They are recoverable with `jj op log` and `jj op restore <op-id>`, but the tool has no business running there.
246
+
247
+ ### What a release costs
248
+
249
+ The package is built exactly once per commit. CI's `build` job produces `lib/`, verifies the tarball still carries the skill body and both auth templates, and uploads it; the release workflow downloads that artifact rather than rebuilding, and stages with `--ignore-scripts` so `prepublishOnly` does not rebuild either. Installs in CI use `--ignore-scripts` too, since the package's `prepare` hook exists only to make a git install usable. Runs on a superseded commit cancel themselves.
250
+
224
251
  ## License
225
252
 
226
253
  MIT. Derived from the MIT-licensed Claude Code `supermemory` plugin — see [NOTICE](NOTICE) for the file-by-file attribution. Supermemory, the `◪` mark, and the Supermemory brand belong to Supermemory; this is an independent port, not an official release.
package/lib/index.d.mts CHANGED
@@ -178,6 +178,15 @@ declare function promptFrom(messages: readonly UserMessage[]): string;
178
178
  declare function readOnlyToolOf(toolName: string, serverName: string): string | null;
179
179
  //#endregion
180
180
  //#region src/status.d.ts
181
+ /**
182
+ * dsh-tui renders a host command's result as a one-line toast: it strips ANSI,
183
+ * collapses newlines to spaces, and truncates at 200 cells. Every report here
184
+ * therefore leads with a summary that fits, and puts the field-per-line detail
185
+ * after it for clients that render newlines.
186
+ */
187
+ declare const TOAST_CELLS = 200;
188
+ /** Join summary fields the way the toast will show them. */
189
+ declare function summaryLine(fields: readonly string[]): string;
181
190
  /** Never print a full key: at most the first 6 and last 4 characters. */
182
191
  declare function maskKey(key: string): string;
183
192
  //#endregion
@@ -197,4 +206,4 @@ declare function splitFrontmatter(source: string): {
197
206
  declare const name = "supermemory";
198
207
  declare function apply(ctx: Context, config: PluginConfig): void;
199
208
  //#endregion
200
- export { PluginConfig as Config, PluginConfig, PLUGIN_SOURCE, SupermemoryRuntime, TranscriptBlock, TranscriptDelta, TranscriptEntry, TranscriptEvent, TranscriptSession, TranscriptTurn, apply, cleanContent, createRuntime, cwdOf, entriesFromEvents, findSignalTurnIndices, formatContext, formatEntry, formatNewEntries, formatRecall, formatSignalEntries, formatToolInputCompact, getLastCapturedSeq, getTextFromEntry, getTurnsAroundSignals, groupEntriesIntoTurns, hashText, isSubagent, maskKey, name, promptFrom, readOnlyToolOf, resolveSkillDir, resultText, sessionIdOf, setLastCapturedSeq, shouldSkip, splitFrontmatter, truncate };
209
+ export { PluginConfig as Config, PluginConfig, PLUGIN_SOURCE, SupermemoryRuntime, TOAST_CELLS, TranscriptBlock, TranscriptDelta, TranscriptEntry, TranscriptEvent, TranscriptSession, TranscriptTurn, apply, cleanContent, createRuntime, cwdOf, entriesFromEvents, findSignalTurnIndices, formatContext, formatEntry, formatNewEntries, formatRecall, formatSignalEntries, formatToolInputCompact, getLastCapturedSeq, getTextFromEntry, getTurnsAroundSignals, groupEntriesIntoTurns, hashText, isSubagent, maskKey, name, promptFrom, readOnlyToolOf, resolveSkillDir, resultText, sessionIdOf, setLastCapturedSeq, shouldSkip, splitFrontmatter, summaryLine, truncate };
package/lib/index.mjs CHANGED
@@ -1002,7 +1002,7 @@ function statuslineTip() {
1002
1002
  if (fs.existsSync(STATUSLINE_TIP_FILE)) return null;
1003
1003
  fs.mkdirSync(path.dirname(STATUSLINE_TIP_FILE), { recursive: true });
1004
1004
  fs.writeFileSync(STATUSLINE_TIP_FILE, (/* @__PURE__ */ new Date()).toISOString());
1005
- return `${MARK} supermemory status is live — render it anywhere with \`node -e "require('dsh-supermemory/statusline')" <<< '{"session_id":"…"}'\`, or read ~/.supermemory-claude/statusline.`;
1005
+ return `${MARK} supermemory status is live — render it anywhere with \`echo '{"session_id":"…"}' | node -e "import('dsh-supermemory/statusline')"\`, or read ~/.supermemory-claude/statusline.`;
1006
1006
  } catch {
1007
1007
  return null;
1008
1008
  }
@@ -1147,6 +1147,17 @@ function registerSessionStart(ctx, rt, config) {
1147
1147
  //#endregion
1148
1148
  //#region src/status.ts
1149
1149
  const PROBE_TIMEOUT_MS = 8e3;
1150
+ /**
1151
+ * dsh-tui renders a host command's result as a one-line toast: it strips ANSI,
1152
+ * collapses newlines to spaces, and truncates at 200 cells. Every report here
1153
+ * therefore leads with a summary that fits, and puts the field-per-line detail
1154
+ * after it for clients that render newlines.
1155
+ */
1156
+ const TOAST_CELLS = 200;
1157
+ /** Join summary fields the way the toast will show them. */
1158
+ function summaryLine(fields) {
1159
+ return `◪ supermemory · ${fields.filter(Boolean).join(" · ")}`;
1160
+ }
1150
1161
  /** Never print a full key: at most the first 6 and last 4 characters. */
1151
1162
  function maskKey(key) {
1152
1163
  if (key.length <= 10) return `${key.slice(0, 2)}…`;
@@ -1155,17 +1166,20 @@ function maskKey(key) {
1155
1166
  function resolveKey(cwd) {
1156
1167
  if (process.env.SUPERMEMORY_CC_API_KEY) return {
1157
1168
  key: process.env.SUPERMEMORY_CC_API_KEY,
1158
- source: "env SUPERMEMORY_CC_API_KEY"
1169
+ source: "env SUPERMEMORY_CC_API_KEY",
1170
+ shortSource: "env"
1159
1171
  };
1160
1172
  const projectConfig = loadProjectConfig(cwd);
1161
1173
  if (projectConfig?.apiKey) return {
1162
1174
  key: projectConfig.apiKey,
1163
- source: ".claude/.supermemory-claude/config.json"
1175
+ source: ".claude/.supermemory-claude/config.json",
1176
+ shortSource: "project config"
1164
1177
  };
1165
1178
  const credentials = loadCredentials();
1166
1179
  if (credentials?.apiKey) return {
1167
1180
  key: credentials.apiKey,
1168
- source: CREDENTIALS_FILE
1181
+ source: CREDENTIALS_FILE,
1182
+ shortSource: "credentials.json"
1169
1183
  };
1170
1184
  return null;
1171
1185
  }
@@ -1189,13 +1203,28 @@ async function probe(baseUrl, key, containerTag) {
1189
1203
  }),
1190
1204
  signal: AbortSignal.timeout(PROBE_TIMEOUT_MS)
1191
1205
  });
1192
- if (response.status === 200) return "reachable — 200, the key works";
1193
- if (response.status === 401 || response.status === 403) return `reachable, but the key is invalid or revoked — ${response.status}. Re-authenticate at ${AUTH_BASE_URL}`;
1194
- if (response.status >= 500) return `API error ${response.status}, service temporarily unavailable`;
1195
- return `unexpected response — ${response.status}`;
1206
+ if (response.status === 200) return {
1207
+ short: "api 200",
1208
+ detail: "reachable200, the key works"
1209
+ };
1210
+ if (response.status === 401 || response.status === 403) return {
1211
+ short: `api ${response.status} KEY REVOKED`,
1212
+ detail: `reachable, but the key is invalid or revoked — ${response.status}. Re-authenticate at ${AUTH_BASE_URL}`
1213
+ };
1214
+ if (response.status >= 500) return {
1215
+ short: `api ${response.status}`,
1216
+ detail: `API error — ${response.status}, service temporarily unavailable`
1217
+ };
1218
+ return {
1219
+ short: `api ${response.status}`,
1220
+ detail: `unexpected response — ${response.status}`
1221
+ };
1196
1222
  } catch (err) {
1197
1223
  const error = err;
1198
- return `UNREACHABLE — ${error.name === "TimeoutError" ? `timed out after ${PROBE_TIMEOUT_MS}ms` : error.message}`;
1224
+ return {
1225
+ short: "api UNREACHABLE",
1226
+ detail: `UNREACHABLE — ${error.name === "TimeoutError" ? `timed out after ${PROBE_TIMEOUT_MS}ms` : error.message}`
1227
+ };
1199
1228
  }
1200
1229
  }
1201
1230
  function registerStatusCommand(ctx, config) {
@@ -1211,25 +1240,42 @@ function registerStatusCommand(ctx, config) {
1211
1240
  const resolved = resolveKey(cwd);
1212
1241
  const prefix = `mcp__${config.mcpServerName ?? "supermemory"}__`;
1213
1242
  const mcpTools = (ctx.get("tools")?.schemas(agent.id) ?? []).map((schema) => schema.name).filter((name) => name.startsWith(prefix));
1214
- const lines = [
1215
- `${MARK} supermemory`,
1216
- "",
1217
- `project ${projectName}`,
1218
- `container tag ${containerTag}`,
1219
- `settings ${SETTINGS_FILE}`
1220
- ];
1221
- if (!resolved) {
1222
- lines.push("authenticated NO", "", `Start a new session to open the browser login automatically, or set SUPERMEMORY_CC_API_KEY. Login page: ${AUTH_BASE_URL}`);
1223
- return {
1224
- kind: "success",
1225
- text: lines.join("\n")
1226
- };
1227
- }
1243
+ const mcpShort = mcpTools.length > 0 ? `mcp ${mcpTools.length} tools` : "mcp NONE";
1244
+ if (!resolved) return {
1245
+ kind: "success",
1246
+ text: [
1247
+ summaryLine([
1248
+ "NOT authenticated",
1249
+ "set SUPERMEMORY_CC_API_KEY or start a new session to log in",
1250
+ projectName
1251
+ ]),
1252
+ "",
1253
+ `container tag ${containerTag}`,
1254
+ `settings ${SETTINGS_FILE}`,
1255
+ `login page ${AUTH_BASE_URL}`
1256
+ ].join("\n")
1257
+ };
1228
1258
  const baseUrl = getBaseUrl(cwd);
1229
- lines.push("authenticated yes", `key ${maskKey(resolved.key)} (from ${resolved.source})`, `api ${baseUrl}`, `api probe ${await probe(baseUrl, resolved.key, containerTag)}`, `mcp ${mcpTools.length > 0 ? `${mcpTools.length} tool${mcpTools.length === 1 ? "" : "s"} under ${prefix}` : `no ${prefix}* tools registered`}`);
1259
+ const probed = await probe(baseUrl, resolved.key, containerTag);
1230
1260
  return {
1231
1261
  kind: "success",
1232
- text: lines.join("\n")
1262
+ text: [
1263
+ summaryLine([
1264
+ `auth ok (${resolved.shortSource})`,
1265
+ probed.short,
1266
+ mcpShort,
1267
+ containerTag
1268
+ ]),
1269
+ "",
1270
+ `project ${projectName}`,
1271
+ `container tag ${containerTag}`,
1272
+ `authenticated yes`,
1273
+ `key ${maskKey(resolved.key)} (from ${resolved.source})`,
1274
+ `api ${baseUrl}`,
1275
+ `api probe ${probed.detail}`,
1276
+ `mcp ${mcpTools.length > 0 ? `${mcpTools.length} tool${mcpTools.length === 1 ? "" : "s"} under ${prefix}` : `no ${prefix}* tools registered`}`,
1277
+ `settings ${SETTINGS_FILE}`
1278
+ ].join("\n")
1233
1279
  };
1234
1280
  }
1235
1281
  });
@@ -1279,4 +1325,4 @@ function apply(ctx, config) {
1279
1325
  });
1280
1326
  }
1281
1327
  //#endregion
1282
- export { PluginConfig as Config, PluginConfig, PLUGIN_SOURCE, apply, cleanContent, createRuntime, cwdOf, entriesFromEvents, findSignalTurnIndices, formatContext, formatEntry, formatNewEntries, formatRecall, formatSignalEntries, formatToolInputCompact, getLastCapturedSeq, getTextFromEntry, getTurnsAroundSignals, groupEntriesIntoTurns, hashText, isSubagent, maskKey, name, promptFrom, readOnlyToolOf, resolveSkillDir, resultText, sessionIdOf, setLastCapturedSeq, shouldSkip, splitFrontmatter, truncate };
1328
+ export { PluginConfig as Config, PluginConfig, PLUGIN_SOURCE, TOAST_CELLS, apply, cleanContent, createRuntime, cwdOf, entriesFromEvents, findSignalTurnIndices, formatContext, formatEntry, formatNewEntries, formatRecall, formatSignalEntries, formatToolInputCompact, getLastCapturedSeq, getTextFromEntry, getTurnsAroundSignals, groupEntriesIntoTurns, hashText, isSubagent, maskKey, name, promptFrom, readOnlyToolOf, resolveSkillDir, resultText, sessionIdOf, setLastCapturedSeq, shouldSkip, splitFrontmatter, summaryLine, truncate };
@@ -1,3 +1,4 @@
1
+ import { fileURLToPath } from "node:url";
1
2
  import fs from "node:fs";
2
3
  import path from "node:path";
3
4
  import os from "node:os";
@@ -198,6 +199,24 @@ async function main() {
198
199
  if (output) process.stdout.write(output);
199
200
  } catch {}
200
201
  }
201
- if (process.argv[1] && import.meta.url === `file://${process.argv[1]}`) main();
202
+ /**
203
+ * Whether this module was run as the entry point.
204
+ *
205
+ * Comparing `import.meta.url` to `file://${process.argv[1]}` is wrong twice
206
+ * over: the template leaves spaces and other reserved characters unescaped,
207
+ * and `import.meta.url` is already resolved through symlinks while `argv[1]`
208
+ * is not — so on macOS a script under `/tmp` (a symlink to `/private/tmp`)
209
+ * never matched and the renderer silently printed nothing. Compare real paths.
210
+ */
211
+ function invokedDirectly() {
212
+ const entry = process.argv[1];
213
+ if (!entry) return false;
214
+ try {
215
+ return fs.realpathSync(fileURLToPath(import.meta.url)) === fs.realpathSync(entry);
216
+ } catch {
217
+ return false;
218
+ }
219
+ }
220
+ if (invokedDirectly()) main();
202
221
  //#endregion
203
222
  export { CONTEXT_TTL_MS, ERROR_TTL_MS, SAVING_TTL_MS, STATUSLINE_INPUT_TIMEOUT_MS, TICK_MS, getStatusLabel, readState, readStatuslineInput, renderStatusline };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-supermemory",
3
- "version": "0.1.0",
3
+ "version": "0.1.2",
4
4
  "description": "Persistent memory across DeepSeek Harness (DSH) sessions using Supermemory. A native port of the Claude Code supermemory plugin.",
5
5
  "keywords": [
6
6
  "dsh",
@@ -85,6 +85,8 @@
85
85
  }
86
86
  },
87
87
  "devDependencies": {
88
+ "@commitlint/cli": "^21.2.2",
89
+ "@commitlint/config-conventional": "^21.2.2",
88
90
  "@deepseek-ai/cordis": "^4.0.2",
89
91
  "@deepseek-ai/dsh-agent": "0.1.2-rc.1",
90
92
  "@deepseek-ai/dsh-commands": "0.1.2-rc.1",
@@ -93,7 +95,16 @@
93
95
  "@deepseek-ai/dsh-session": "0.1.2-rc.1",
94
96
  "@deepseek-ai/dsh-skill": "0.1.2-rc.1",
95
97
  "@deepseek-ai/dsh-tools": "0.1.2-rc.1",
98
+ "@semantic-release/changelog": "^7.0.0",
99
+ "@semantic-release/commit-analyzer": "^13.0.1",
100
+ "@semantic-release/exec": "^7.1.0",
101
+ "@semantic-release/git": "^11.0.1",
102
+ "@semantic-release/github": "^12.0.9",
103
+ "@semantic-release/npm": "^13.1.5",
104
+ "@semantic-release/release-notes-generator": "^14.1.1",
96
105
  "@types/node": "^24.10.0",
106
+ "conventional-changelog-conventionalcommits": "^9.3.1",
107
+ "semantic-release": "^25.0.9",
97
108
  "tsdown": "^0.22.2",
98
109
  "typescript": "^6.0.0"
99
110
  },
@@ -109,6 +120,14 @@
109
120
  ]
110
121
  }
111
122
  },
123
+ "scripts": {
124
+ "build": "tsdown",
125
+ "prepare": "tsdown",
126
+ "typecheck": "tsc --noEmit",
127
+ "test": "node --test --experimental-strip-types test/*.test.ts",
128
+ "check": "npm run typecheck && npm test && npm run build",
129
+ "prepublishOnly": "npm run check"
130
+ },
112
131
  "license": "MIT",
113
132
  "repository": {
114
133
  "type": "git",
@@ -117,11 +136,5 @@
117
136
  "bugs": {
118
137
  "url": "https://github.com/nmindz/dsh-supermemory/issues"
119
138
  },
120
- "homepage": "https://github.com/nmindz/dsh-supermemory#readme",
121
- "scripts": {
122
- "build": "tsdown",
123
- "typecheck": "tsc --noEmit",
124
- "test": "node --test --experimental-strip-types test/*.test.ts",
125
- "check": "npm run typecheck && npm test && npm run build"
126
- }
127
- }
139
+ "homepage": "https://github.com/nmindz/dsh-supermemory#readme"
140
+ }