@feiyang666/dsh-usage-plugin 1.9.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/package.json ADDED
@@ -0,0 +1,69 @@
1
+ {
2
+ "name": "@feiyang666/dsh-usage-plugin",
3
+ "version": "1.9.0",
4
+ "description": "DeepSeek Harness usage & cost tracker plugin: per-call token/cache-hit stats, peak/off-peak billing, DeepSeek balance query, CSV/JSON/PNG export with custom destination, and persistent local storage. Ships a host half plus a web client half in one npm package; installs into a DSH profile as a dsh.bundle with one command (dsh plugin --profile web add @feiyang666/dsh-usage-plugin).",
5
+ "keywords": [
6
+ "deepseek",
7
+ "harness",
8
+ "dsh",
9
+ "plugin",
10
+ "usage",
11
+ "cost",
12
+ "tokens",
13
+ "cache",
14
+ "balance"
15
+ ],
16
+ "license": "MIT",
17
+ "type": "module",
18
+ "main": "lib/index.js",
19
+ "repository": {
20
+ "type": "git",
21
+ "url": "git+https://github.com/feiyang-dev/dsh-usage-plugin.git"
22
+ },
23
+ "contributors": [
24
+ {
25
+ "name": "liu3734",
26
+ "url": "https://github.com/liu3734"
27
+ }
28
+ ],
29
+ "homepage": "https://github.com/feiyang-dev/dsh-usage-plugin#readme",
30
+ "bugs": {
31
+ "url": "https://github.com/feiyang-dev/dsh-usage-plugin/issues"
32
+ },
33
+ "exports": {
34
+ ".": "./lib/index.js",
35
+ "./client": "./lib/client.js",
36
+ "./package.json": "./package.json"
37
+ },
38
+ "engines": {
39
+ "node": ">=18"
40
+ },
41
+ "dsh": {
42
+ "bundle": {
43
+ "patch": "./cordis.patch.yml"
44
+ },
45
+ "client": {
46
+ "platform": "web",
47
+ "inject": [
48
+ "@deepseek-ai/dsh-client-ui-conversation"
49
+ ]
50
+ }
51
+ },
52
+ "files": [
53
+ "lib",
54
+ "scripts",
55
+ "cordis.patch.yml",
56
+ "README.md",
57
+ "README.en.md",
58
+ "CHANGELOG.md"
59
+ ],
60
+ "scripts": {
61
+ "wire": "node scripts/wire.js",
62
+ "check": "node scripts/check-package.js",
63
+ "prepublishOnly": "node scripts/check-package.js",
64
+ "pack": "npm pack"
65
+ },
66
+ "peerDependencies": {
67
+ "@deepseek-ai/cordis": "^4.0.1"
68
+ }
69
+ }
@@ -0,0 +1,84 @@
1
+ /**
2
+ * dsh-usage-plugin — pre-publish sanity gate (dependency-free).
3
+ *
4
+ * Run by `npm run check` and `npm run prepublishOnly` before the package is
5
+ * packed/uploaded. It verifies every piece of the DSH plugin contract that
6
+ * would otherwise fail LOUDLY at boot or client-load time when missing:
7
+ *
8
+ * 1. `dsh.bundle.patch` — the profile bundle patch file exists and carries a
9
+ * valid insert entry naming this package (auto-activation via
10
+ * `dsh plugin --profile <name> add <pkg>` depends on it).
11
+ * 2. `dsh.client` — the web client declaration and `exports["./client"]`
12
+ * bundle exist (the host serves it at `/plugins/<name>/client.js`).
13
+ * 3. `files` — the publish whitelist covers every artifact above.
14
+ *
15
+ * This is a structural gate, not a full parser; the authoritative YAML +
16
+ * loader-composition validation is done by DSH itself at profile boot.
17
+ */
18
+ import { readFileSync, existsSync } from "node:fs";
19
+ import { resolve, dirname } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+
22
+ const pkgDir = resolve(dirname(fileURLToPath(import.meta.url)), "..");
23
+
24
+ const fail = (message) => {
25
+ console.error(`[check-package] FAIL: ${message}`);
26
+ process.exitCode = 1;
27
+ };
28
+
29
+ let ok = true;
30
+ const check = (condition, message) => {
31
+ if (!condition) {
32
+ fail(message);
33
+ ok = false;
34
+ }
35
+ };
36
+
37
+ let pkg;
38
+ try {
39
+ pkg = JSON.parse(readFileSync(resolve(pkgDir, "package.json"), "utf8"));
40
+ } catch (error) {
41
+ console.error(`[check-package] FAIL: cannot read package.json: ${String(error && error.message || error)}`);
42
+ process.exit(1);
43
+ }
44
+
45
+ // ── identity ───────────────────────────────────────────────────────────────
46
+ check(typeof pkg.name === "string" && pkg.name.length > 0, "package.json must declare a name");
47
+ check(typeof pkg.version === "string" && /^\d+\.\d+\.\d+/.test(pkg.version), `invalid version ${JSON.stringify(pkg.version)}`);
48
+
49
+ // ── dsh.bundle patch (host-side auto-activation) ───────────────────────────
50
+ const bundlePatch = pkg.dsh && pkg.dsh.bundle && pkg.dsh.bundle.patch;
51
+ check(typeof bundlePatch === "string" && bundlePatch.length > 0, 'package.json must declare "dsh": { "bundle": { "patch": "..." } }');
52
+ if (typeof bundlePatch === "string") {
53
+ const patchPath = resolve(pkgDir, bundlePatch);
54
+ check(existsSync(patchPath), `dsh.bundle.patch file not found: ${bundlePatch}`);
55
+ if (existsSync(patchPath)) {
56
+ const text = readFileSync(patchPath, "utf8");
57
+ check(text.includes("- insert:"), `bundle patch ${bundlePatch} must contain a top-level "- insert:" entry`);
58
+ check(text.includes(`name: '${pkg.name}'`) || text.includes(`name: "${pkg.name}"`), `bundle patch ${bundlePatch} must insert an entry with name: '${pkg.name}'`);
59
+ const required = ["fs", "webServer", "subprocess", "credentials", "sandboxPolicy", "agents"];
60
+ const missing = required.filter((service) => !text.includes(`- ${service}`));
61
+ check(missing.length === 0, `bundle patch ${bundlePatch} is missing inject entries: ${missing.join(", ")}`);
62
+ }
63
+ }
64
+
65
+ // ── dsh.client + client bundle (browser half) ──────────────────────────────
66
+ const clientDecl = pkg.dsh && pkg.dsh.client;
67
+ check(clientDecl !== null && typeof clientDecl === "object", 'package.json must declare "dsh": { "client": { "platform": "web", ... } }');
68
+ if (clientDecl !== null && typeof clientDecl === "object") {
69
+ check(clientDecl.platform === "web", `dsh.client.platform must be "web" (got ${JSON.stringify(clientDecl.platform)})`);
70
+ check(Array.isArray(clientDecl.inject) && clientDecl.inject.length > 0, "dsh.client.inject must be a non-empty string array");
71
+ }
72
+ const clientRel = pkg.exports && (typeof pkg.exports["./client"] === "string" ? pkg.exports["./client"] : (pkg.exports["./client"] && pkg.exports["./client"].default));
73
+ check(typeof clientRel === "string", 'package.json must export "./client" pointing at the client bundle');
74
+ if (typeof clientRel === "string") {
75
+ check(existsSync(resolve(pkgDir, clientRel)), `client bundle not found: ${clientRel} (run the client build before publishing)`);
76
+ }
77
+
78
+ // ── files whitelist ────────────────────────────────────────────────────────
79
+ check(Array.isArray(pkg.files), "package.json must declare a files whitelist");
80
+ for (const entry of pkg.files || []) {
81
+ check(existsSync(resolve(pkgDir, entry)), `files entry not found: ${entry}`);
82
+ }
83
+
84
+ if (ok) console.log(`[check-package] OK — ${pkg.name}@${pkg.version} satisfies the DSH plugin contract`);
@@ -0,0 +1,108 @@
1
+ /**
2
+ * dsh-usage-plugin — wiring script (LEGACY fallback).
3
+ *
4
+ * PREFERRED INSTALL: this package declares `dsh.bundle`, so it auto-activates
5
+ * with a single command and NO manual wiring:
6
+ *
7
+ * dsh plugin --profile web add @feiyang666/dsh-usage-plugin
8
+ *
9
+ * This script exists only for the manual fallback path (installing the package
10
+ * by hand into a profile's node_modules): it appends the plugin row to the
11
+ * profile's `cordis.patch.yml` so the plugin loads with the web app:
12
+ *
13
+ * npm run wire
14
+ *
15
+ * It is idempotent: running it again never duplicates the row. If it cannot
16
+ * find a profile patch it prints what to add manually and exits non-zero.
17
+ */
18
+ import { readFileSync, writeFileSync, existsSync, readdirSync } from "node:fs";
19
+ import { resolve, join, dirname } from "node:path";
20
+ import { fileURLToPath } from "node:url";
21
+
22
+ const here = dirname(fileURLToPath(import.meta.url));
23
+ const pkgDir = resolve(here, "..");
24
+ const ROW_ID = "usage-plugin";
25
+ const PACKAGE_NAME = JSON.parse(readFileSync(join(pkgDir, "package.json"), "utf8")).name || "dsh-usage-plugin";
26
+
27
+ function candidateRoots() {
28
+ const roots = [];
29
+ // When installed at <profilesRoot>/node_modules/dsh-usage-plugin, the
30
+ // profiles root is two levels up; try several depths for other layouts.
31
+ for (let up = 1; up <= 4; up++) {
32
+ let p = pkgDir;
33
+ for (let i = 0; i < up; i++) p = resolve(p, "..");
34
+ roots.push(p);
35
+ }
36
+ if (process.env.DSH_HOME) {
37
+ roots.push(join(process.env.DSH_HOME, "profiles"));
38
+ roots.push(process.env.DSH_HOME);
39
+ }
40
+ return roots;
41
+ }
42
+
43
+ function findPatches(roots) {
44
+ const seen = new Set();
45
+ const patches = [];
46
+ for (const root of roots) {
47
+ let entries;
48
+ try {
49
+ entries = readdirSync(root, { withFileTypes: true });
50
+ } catch {
51
+ continue;
52
+ }
53
+ for (const entry of entries) {
54
+ if (!entry.isDirectory() || entry.name === "node_modules") continue;
55
+ const patch = join(root, entry.name, "cordis.patch.yml");
56
+ if (existsSync(patch) && !seen.has(patch)) {
57
+ seen.add(patch);
58
+ patches.push(patch);
59
+ }
60
+ }
61
+ }
62
+ return patches;
63
+ }
64
+
65
+ function alreadyWired(text) {
66
+ return text.includes(`id: ${ROW_ID}`);
67
+ }
68
+
69
+ function wire(patchPath) {
70
+ const text = readFileSync(patchPath, "utf8");
71
+ if (alreadyWired(text)) {
72
+ console.log(`[dsh-usage-plugin] already wired in ${patchPath}`);
73
+ return true;
74
+ }
75
+ const block =
76
+ "\n# " + PACKAGE_NAME + ": usage & cost tracking (installed via npm).\n" +
77
+ "- insert:\n" +
78
+ " - id: " + ROW_ID + "\n" +
79
+ " name: '" + PACKAGE_NAME + "'\n" +
80
+ " inject:\n" +
81
+ " - fs\n" +
82
+ " - webServer\n" +
83
+ " - subprocess\n" +
84
+ " - credentials\n" +
85
+ " - sandboxPolicy\n" +
86
+ " - agents\n";
87
+ writeFileSync(patchPath, text.endsWith("\n") ? text + block : text + "\n" + block, "utf8");
88
+ console.log(`[dsh-usage-plugin] wired into ${patchPath}`);
89
+ return true;
90
+ }
91
+
92
+ const patches = findPatches(candidateRoots());
93
+ if (patches.length === 0) {
94
+ console.error(
95
+ "[dsh-usage-plugin] no DSH profile patch found. Preferred: install with `dsh plugin --profile web add " + PACKAGE_NAME + "` (auto-wires via dsh.bundle, no manual editing).\n" +
96
+ "Manual fallback — add this row to your profile's cordis.patch.yml, then restart the app:\n\n" +
97
+ "- insert:\n" +
98
+ " - id: " + ROW_ID + "\n" +
99
+ " name: '" + PACKAGE_NAME + "'\n"
100
+ );
101
+ process.exit(1);
102
+ }
103
+ let ok = true;
104
+ for (const patch of patches) ok = wire(patch) && ok;
105
+ if (ok) {
106
+ console.log("[dsh-usage-plugin] done. Restart the DeepSeek Harness web app for the plugin to load.");
107
+ }
108
+ process.exit(ok ? 0 : 1);