dsh-plugin-shop 0.4.8 → 0.4.10

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/lib/index.js CHANGED
@@ -1,12 +1,12 @@
1
1
  import { Remote, TypertRemoteService } from "@deepseek-ai/dsh-typert-protocol";
2
2
  import { loadOptionalPatches, readProfileManifest, resolveProfileDir } from "@deepseek-ai/dsh-app-boot";
3
3
  import { lt, minVersion, valid } from "semver";
4
+ import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
5
+ import { spawn, spawnSync } from "node:child_process";
4
6
  import { fileURLToPath } from "node:url";
5
7
  import { basename, dirname, join } from "node:path";
6
- import { closeSync, existsSync, mkdirSync, openSync, readFileSync, realpathSync, renameSync, writeFileSync } from "node:fs";
7
8
  import { createHash, randomUUID } from "node:crypto";
8
9
  import { z } from "zod";
9
- import { spawn } from "node:child_process";
10
10
  import { dump } from "js-yaml";
11
11
  //#region src/own-version.ts
12
12
  /** The shop's own published version, read from the package.json that ships
@@ -37,7 +37,8 @@ const entrySchema = z.object({
37
37
  ]),
38
38
  metadata: z.enum(["declared", "derived"]),
39
39
  review: z.object({
40
- reviewedVersion: z.string(),
40
+ reviewedVersion: z.string().optional(),
41
+ reviewedCommit: z.string().optional(),
41
42
  reviewer: z.string(),
42
43
  reviewCommit: z.string(),
43
44
  notes: z.string()
@@ -56,7 +57,9 @@ const entrySchema = z.object({
56
57
  zh: z.string().optional()
57
58
  }),
58
59
  capabilities: z.array(z.string())
59
- }).optional()
60
+ }).optional(),
61
+ source: z.enum(["npm", "github"]).default("npm"),
62
+ repo: z.string().optional()
60
63
  });
61
64
  const dataSchema = z.object({
62
65
  schemaVersion: z.number(),
@@ -140,13 +143,13 @@ async function loadCatalog(options) {
140
143
  const readCached = () => {
141
144
  try {
142
145
  const pointer = pointerSchema.parse(JSON.parse(fsImpl.read(indexPath)));
143
- if (pointer.schemaVersion > 2) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (2)`);
146
+ if (pointer.schemaVersion > 3) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (3)`);
144
147
  const dataPath = join(cacheDir, basename(pointer.plugins.url));
145
148
  const dataText = fsImpl.read(dataPath);
146
149
  const actual = createHash("sha256").update(dataText).digest("hex");
147
150
  if (actual !== pointer.plugins.sha256) throw new Error(`cached catalog data failed integrity check: expected ${pointer.plugins.sha256}, got ${actual}`);
148
151
  const data = dataSchema.parse(JSON.parse(dataText));
149
- if (data.schemaVersion > 2) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (2)`);
152
+ if (data.schemaVersion > 3) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (3)`);
150
153
  let stars = {};
151
154
  if (pointer.stars !== void 0) try {
152
155
  const starsText = fsImpl.read(join(cacheDir, basename(pointer.stars.url)));
@@ -187,7 +190,7 @@ async function loadCatalog(options) {
187
190
  throw error;
188
191
  }
189
192
  const pointer = pointerSchema.parse(JSON.parse(pointerText));
190
- if (pointer.schemaVersion > 2) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (2)`);
193
+ if (pointer.schemaVersion > 3) throw new Error(`catalog schemaVersion ${pointer.schemaVersion} is newer than this build supports (3)`);
191
194
  const dataUrl = resolveDataUrl(baseUrl, pointer.plugins.url);
192
195
  let dataText;
193
196
  try {
@@ -205,7 +208,7 @@ async function loadCatalog(options) {
205
208
  const actual = createHash("sha256").update(dataText).digest("hex");
206
209
  if (actual !== pointer.plugins.sha256) throw new Error(`catalog data failed integrity check: expected ${pointer.plugins.sha256}, got ${actual}`);
207
210
  const data = dataSchema.parse(JSON.parse(dataText));
208
- if (data.schemaVersion > 2) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (2)`);
211
+ if (data.schemaVersion > 3) throw new Error(`catalog schemaVersion ${data.schemaVersion} is newer than this build supports (3)`);
209
212
  let stars = {};
210
213
  if (pointer.stars !== void 0) try {
211
214
  const starsResponse = await fetchImpl(resolveDataUrl(baseUrl, pointer.stars.url));
@@ -497,6 +500,25 @@ async function fetchLatestVersion(fetchFn = fetch) {
497
500
  }
498
501
  }
499
502
  //#endregion
503
+ //#region src/host/repo-pins.ts
504
+ /** Read the pins file; any irregularity degrades to an empty record. */
505
+ function readRepoPins(fs, path) {
506
+ if (!fs.exists(path)) return {};
507
+ try {
508
+ const parsed = JSON.parse(fs.read(path));
509
+ if (typeof parsed !== "object" || parsed === null || Array.isArray(parsed)) return {};
510
+ const out = {};
511
+ for (const [name, commit] of Object.entries(parsed)) if (typeof commit === "string" && /^[0-9a-f]{40}$/.test(commit)) out[name] = commit;
512
+ return out;
513
+ } catch {
514
+ return {};
515
+ }
516
+ }
517
+ /** Write the pins file atomically enough for its purpose (a cache). */
518
+ function writeRepoPins(fs, path, pins) {
519
+ fs.write(path, `${JSON.stringify(pins, null, 2)}\n`);
520
+ }
521
+ //#endregion
500
522
  //#region src/host/profile.ts
501
523
  /** Profile directory discovery and user-layer writes (§8: hot enable/disable). */
502
524
  /**
@@ -759,6 +781,8 @@ let ShopGateway = (() => {
759
781
  restartExitDelayMs;
760
782
  restartParentPid;
761
783
  latestVersion;
784
+ hasGit;
785
+ pinFs;
762
786
  /** The install gate runs against the last loaded snapshot, never a fresh
763
787
  * fetch per request (§7.2: the Host's cached snapshot is the truth). */
764
788
  /** Finished install records retained, so a poll sees the true terminal
@@ -786,6 +810,19 @@ let ShopGateway = (() => {
786
810
  this.restartExitDelayMs = options.restartExitDelayMs ?? ShopGateway.RESTART_EXIT_DELAY_MS;
787
811
  this.restartParentPid = options.restartParentPid ?? process.pid;
788
812
  this.latestVersion = options.fetchLatestVersion ?? (() => fetchLatestVersion());
813
+ this.hasGit = options.hasGit ?? (() => spawnSync("git", ["--version"], { stdio: "ignore" }).status === 0);
814
+ this.pinFs = options.pinFs ?? {
815
+ exists: (path) => existsSync(path),
816
+ read: (path) => readFileSync(path, "utf8"),
817
+ write: (path, data) => {
818
+ mkdirSync(dirname(path), { recursive: true });
819
+ writeFileSync(path, data);
820
+ }
821
+ };
822
+ }
823
+ /** The pins file lives in the shop's own cache, next to the catalog cache. */
824
+ pinsPath() {
825
+ return join(this.rowConfig().cacheDir, "github-pins.json");
789
826
  }
790
827
  /** The boot's Loader root directory (the active profile's `cordis.yml`
791
828
  * directory, carried on `ctx.baseUrl`), when present. A `link:` install
@@ -882,12 +919,39 @@ let ShopGateway = (() => {
882
919
  code: verdict.code,
883
920
  detail: verdict.detail
884
921
  };
922
+ const entry = this.lastSnapshot.entries.find((e) => e.name === args.name);
923
+ if (entry === void 0) return {
924
+ ok: false,
925
+ code: "not-in-catalog",
926
+ detail: `dsh-plugin-shop: ${args.name} is not in the catalog`
927
+ };
928
+ let spec;
929
+ if (entry.source === "github") {
930
+ if (entry.repo === void 0 || !/^[0-9a-f]{40}$/.test(args.version)) return {
931
+ ok: false,
932
+ code: "version-mismatch",
933
+ detail: `dsh-plugin-shop: ${args.name} has no installable commit`
934
+ };
935
+ if (!this.hasGit()) return {
936
+ ok: false,
937
+ code: "git-missing",
938
+ detail: "dsh-plugin-shop: git is not on PATH, which github installs require; install git and retry"
939
+ };
940
+ spec = `github:${entry.repo}#${args.version}`;
941
+ } else spec = `${args.name}@${args.version}`;
885
942
  const running = startInstall({
886
943
  profile: this.profile,
887
- spec: `${args.name}@${args.version}`,
944
+ spec,
888
945
  dshBin: this.dshBin,
889
946
  expectedName: args.name
890
947
  });
948
+ if (entry.source === "github") {
949
+ const pins = readRepoPins(this.pinFs, this.pinsPath());
950
+ writeRepoPins(this.pinFs, this.pinsPath(), {
951
+ ...pins,
952
+ [args.name]: args.version
953
+ });
954
+ }
891
955
  this.installs.set(running.installId, running);
892
956
  this.installOrder.push(running.installId);
893
957
  this.evictFinishedInstalls();
@@ -936,11 +1000,20 @@ let ShopGateway = (() => {
936
1000
  this.lastSnapshot = snapshot;
937
1001
  }
938
1002
  const dependencies = readProfileManifest("dsh-plugin-shop", this.profileDirResolved()).dependencies ?? {};
1003
+ const pins = readRepoPins(this.pinFs, this.pinsPath());
939
1004
  const installed = [];
940
1005
  for (const entry of this.lastSnapshot.entries) {
941
1006
  const spec = dependencies[entry.name];
942
1007
  if (spec === void 0) continue;
943
- installed.push({
1008
+ if (entry.source === "github") {
1009
+ const pin = pins[entry.name];
1010
+ installed.push({
1011
+ name: entry.name,
1012
+ installed: pin ?? spec,
1013
+ latest: entry.version,
1014
+ outdated: pin !== void 0 && pin !== entry.version
1015
+ });
1016
+ } else installed.push({
944
1017
  name: entry.name,
945
1018
  installed: spec,
946
1019
  latest: entry.version,
@@ -992,6 +1065,11 @@ let ShopGateway = (() => {
992
1065
  dshBin: this.dshBin,
993
1066
  expectedName: args.name
994
1067
  });
1068
+ const pins = readRepoPins(this.pinFs, this.pinsPath());
1069
+ if (pins[args.name] !== void 0) {
1070
+ delete pins[args.name];
1071
+ writeRepoPins(this.pinFs, this.pinsPath(), pins);
1072
+ }
995
1073
  this.installs.set(running.installId, running);
996
1074
  this.installOrder.push(running.installId);
997
1075
  this.evictFinishedInstalls();
@@ -18,7 +18,8 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
18
18
  'tier': z.union([z.literal("verified"), z.literal("verified-stale"), z.literal("community")]),
19
19
  'metadata': z.union([z.literal("declared"), z.literal("derived")]),
20
20
  'review': z.union([z.undefined(), z.object({
21
- 'reviewedVersion': z.string(),
21
+ 'reviewedVersion': z.union([z.undefined(), z.string()]).optional(),
22
+ 'reviewedCommit': z.union([z.undefined(), z.string()]).optional(),
22
23
  'reviewer': z.string(),
23
24
  'reviewCommit': z.string(),
24
25
  'notes': z.string(),
@@ -31,6 +32,8 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
31
32
  }),
32
33
  'capabilities': z.array(z.string()),
33
34
  })]).optional(),
35
+ 'source': z.union([z.literal("npm"), z.literal("github")]),
36
+ 'repo': z.union([z.undefined(), z.string()]).optional(),
34
37
  })),
35
38
  'denied': z.array(z.object({
36
39
  'name': z.string(),
@@ -54,7 +57,7 @@ const dsh_plugin_shop_shop_installStart_result$schema = z.union([z.object({
54
57
  'installId': z.string(),
55
58
  }), z.object({
56
59
  'ok': z.literal(false),
57
- 'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement")]),
60
+ 'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement"), z.literal("git-missing")]),
58
61
  'detail': z.string(),
59
62
  })])
60
63
  const dsh_plugin_shop_shop_installStatus_parameter_0$schema = z.object({
@@ -137,7 +140,7 @@ export const TYPERT = {
137
140
  typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
138
141
  schema: dsh_plugin_shop_shop_catalog_result$schema,
139
142
  },
140
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":244,"column":9},
143
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":270,"column":9},
141
144
  },
142
145
  {
143
146
  id: 'dsh-plugin-shop#shop/installed',
@@ -152,7 +155,7 @@ export const TYPERT = {
152
155
  typeSymbol: 'dsh-plugin-shop#shop/installed:result',
153
156
  schema: dsh_plugin_shop_shop_installed_result$schema,
154
157
  },
155
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":319,"column":9},
158
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":374,"column":9},
156
159
  },
157
160
  {
158
161
  id: 'dsh-plugin-shop#shop/installStart',
@@ -178,7 +181,7 @@ export const TYPERT = {
178
181
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
179
182
  schema: dsh_plugin_shop_shop_installStart_result$schema,
180
183
  },
181
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":270,"column":9},
184
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":296,"column":9},
182
185
  },
183
186
  {
184
187
  id: 'dsh-plugin-shop#shop/installStatus',
@@ -203,7 +206,7 @@ export const TYPERT = {
203
206
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
204
207
  schema: dsh_plugin_shop_shop_installStatus_result$schema,
205
208
  },
206
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":308,"column":3},
209
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":363,"column":3},
207
210
  },
208
211
  {
209
212
  id: 'dsh-plugin-shop#shop/restart',
@@ -218,7 +221,7 @@ export const TYPERT = {
218
221
  typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
219
222
  schema: dsh_plugin_shop_shop_restart_result$schema,
220
223
  },
221
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":392,"column":9},
224
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":469,"column":9},
222
225
  },
223
226
  {
224
227
  id: 'dsh-plugin-shop#shop/setEnabled',
@@ -243,7 +246,7 @@ export const TYPERT = {
243
246
  typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
244
247
  schema: dsh_plugin_shop_shop_setEnabled_result$schema,
245
248
  },
246
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":213,"column":3},
249
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":239,"column":3},
247
250
  },
248
251
  {
249
252
  id: 'dsh-plugin-shop#shop/uninstallStart',
@@ -269,7 +272,7 @@ export const TYPERT = {
269
272
  typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
270
273
  schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
271
274
  },
272
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":359,"column":9},
275
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":429,"column":9},
273
276
  },
274
277
  {
275
278
  id: 'dsh-plugin-shop#shop/updateStart',
@@ -294,7 +297,7 @@ export const TYPERT = {
294
297
  typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
295
298
  schema: dsh_plugin_shop_shop_updateStart_result$schema,
296
299
  },
297
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":441,"column":9},
300
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":518,"column":9},
298
301
  },
299
302
  {
300
303
  id: 'dsh-plugin-shop#shop/version',
@@ -309,7 +312,7 @@ export const TYPERT = {
309
312
  typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
310
313
  schema: dsh_plugin_shop_shop_version_result$schema,
311
314
  },
312
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":426,"column":9},
315
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":503,"column":9},
313
316
  },
314
317
  ],
315
318
  model: {
@@ -395,7 +398,7 @@ export const TYPERT = {
395
398
  "types": [
396
399
  {
397
400
  "name": "CatalogEntry",
398
- "declaration": "export interface CatalogEntry {\n name: string;\n version: string;\n integrity: string | null;\n publishedAt: string | null;\n repository: string | null;\n license: string | null;\n tier: 'verified' | 'verified-stale' | 'community';\n metadata: 'declared' | 'derived';\n review?: { reviewedVersion: string; reviewer: string; reviewCommit: string; notes: string; };\n catalog?: CatalogSection;\n}"
401
+ "declaration": "export interface CatalogEntry {\n name: string;\n version: string;\n integrity: string | null;\n publishedAt: string | null;\n repository: string | null;\n license: string | null;\n tier: 'verified' | 'verified-stale' | 'community';\n metadata: 'declared' | 'derived';\n review?: { reviewedVersion?: string; reviewedCommit?: string; reviewer: string; reviewCommit: string; notes: string; };\n catalog?: CatalogSection;\n source: 'npm' | 'github';\n repo?: string;\n}"
399
402
  },
400
403
  {
401
404
  "name": "CatalogSection",
@@ -415,7 +418,7 @@ export const TYPERT = {
415
418
  },
416
419
  {
417
420
  "name": "InstallRejectionCode",
418
- "declaration": "export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement';"
421
+ "declaration": "export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | 'git-missing';"
419
422
  },
420
423
  {
421
424
  "name": "InstallState",
@@ -18,7 +18,8 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
18
18
  'tier': z.union([z.literal("verified"), z.literal("verified-stale"), z.literal("community")]),
19
19
  'metadata': z.union([z.literal("declared"), z.literal("derived")]),
20
20
  'review': z.union([z.undefined(), z.object({
21
- 'reviewedVersion': z.string(),
21
+ 'reviewedVersion': z.union([z.undefined(), z.string()]).optional(),
22
+ 'reviewedCommit': z.union([z.undefined(), z.string()]).optional(),
22
23
  'reviewer': z.string(),
23
24
  'reviewCommit': z.string(),
24
25
  'notes': z.string(),
@@ -31,6 +32,8 @@ const dsh_plugin_shop_shop_catalog_result$schema = z.object({
31
32
  }),
32
33
  'capabilities': z.array(z.string()),
33
34
  })]).optional(),
35
+ 'source': z.union([z.literal("npm"), z.literal("github")]),
36
+ 'repo': z.union([z.undefined(), z.string()]).optional(),
34
37
  })),
35
38
  'denied': z.array(z.object({
36
39
  'name': z.string(),
@@ -54,7 +57,7 @@ const dsh_plugin_shop_shop_installStart_result$schema = z.union([z.object({
54
57
  'installId': z.string(),
55
58
  }), z.object({
56
59
  'ok': z.literal(false),
57
- 'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement")]),
60
+ 'code': z.union([z.literal("not-in-catalog"), z.literal("denied"), z.literal("version-mismatch"), z.literal("needs-acknowledgement"), z.literal("git-missing")]),
58
61
  'detail': z.string(),
59
62
  })])
60
63
  const dsh_plugin_shop_shop_installStatus_parameter_0$schema = z.object({
@@ -134,7 +137,7 @@ export const TYPERT_REMOTE = {
134
137
  typeSymbol: 'dsh-plugin-shop/types#ShopCatalogResult',
135
138
  schema: dsh_plugin_shop_shop_catalog_result$schema,
136
139
  },
137
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":244,"column":9},
140
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":270,"column":9},
138
141
  },
139
142
  {
140
143
  id: 'dsh-plugin-shop#shop/installed',
@@ -149,7 +152,7 @@ export const TYPERT_REMOTE = {
149
152
  typeSymbol: 'dsh-plugin-shop#shop/installed:result',
150
153
  schema: dsh_plugin_shop_shop_installed_result$schema,
151
154
  },
152
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":319,"column":9},
155
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":374,"column":9},
153
156
  },
154
157
  {
155
158
  id: 'dsh-plugin-shop#shop/installStart',
@@ -175,7 +178,7 @@ export const TYPERT_REMOTE = {
175
178
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallResult',
176
179
  schema: dsh_plugin_shop_shop_installStart_result$schema,
177
180
  },
178
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":270,"column":9},
181
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":296,"column":9},
179
182
  },
180
183
  {
181
184
  id: 'dsh-plugin-shop#shop/installStatus',
@@ -200,7 +203,7 @@ export const TYPERT_REMOTE = {
200
203
  typeSymbol: 'dsh-plugin-shop/types#ShopInstallStatusResult',
201
204
  schema: dsh_plugin_shop_shop_installStatus_result$schema,
202
205
  },
203
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":308,"column":3},
206
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":363,"column":3},
204
207
  },
205
208
  {
206
209
  id: 'dsh-plugin-shop#shop/restart',
@@ -215,7 +218,7 @@ export const TYPERT_REMOTE = {
215
218
  typeSymbol: 'dsh-plugin-shop/types#ShopRestartResult',
216
219
  schema: dsh_plugin_shop_shop_restart_result$schema,
217
220
  },
218
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":392,"column":9},
221
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":469,"column":9},
219
222
  },
220
223
  {
221
224
  id: 'dsh-plugin-shop#shop/setEnabled',
@@ -240,7 +243,7 @@ export const TYPERT_REMOTE = {
240
243
  typeSymbol: 'dsh-plugin-shop/types#ShopSetEnabledResult',
241
244
  schema: dsh_plugin_shop_shop_setEnabled_result$schema,
242
245
  },
243
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":213,"column":3},
246
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":239,"column":3},
244
247
  },
245
248
  {
246
249
  id: 'dsh-plugin-shop#shop/uninstallStart',
@@ -266,7 +269,7 @@ export const TYPERT_REMOTE = {
266
269
  typeSymbol: 'dsh-plugin-shop/types#ShopUninstallResult',
267
270
  schema: dsh_plugin_shop_shop_uninstallStart_result$schema,
268
271
  },
269
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":359,"column":9},
272
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":429,"column":9},
270
273
  },
271
274
  {
272
275
  id: 'dsh-plugin-shop#shop/updateStart',
@@ -291,7 +294,7 @@ export const TYPERT_REMOTE = {
291
294
  typeSymbol: 'dsh-plugin-shop/types#ShopUpdateResult',
292
295
  schema: dsh_plugin_shop_shop_updateStart_result$schema,
293
296
  },
294
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":441,"column":9},
297
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":518,"column":9},
295
298
  },
296
299
  {
297
300
  id: 'dsh-plugin-shop#shop/version',
@@ -306,7 +309,7 @@ export const TYPERT_REMOTE = {
306
309
  typeSymbol: 'dsh-plugin-shop/types#ShopVersionResult',
307
310
  schema: dsh_plugin_shop_shop_version_result$schema,
308
311
  },
309
- sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":426,"column":9},
312
+ sourceLocation: {"file":"packages/dsh-plugin-shop/src/host/index.ts","line":503,"column":9},
310
313
  },
311
314
  ],
312
315
  }
@@ -44,6 +44,8 @@ export declare const zh: {
44
44
  notInCatalogCode: string;
45
45
  versionMismatchCode: string;
46
46
  needsAcknowledgementCode: string;
47
+ gitMissingCode: string;
48
+ githubSource: string;
47
49
  installedSection: string;
48
50
  installedVersion: string;
49
51
  latestVersion: string;
@@ -65,6 +67,7 @@ export declare const zh: {
65
67
  updateTransportFailed: string;
66
68
  checkUpdate: string;
67
69
  upToDate: string;
70
+ github: string;
68
71
  enabledSwitch: string;
69
72
  toggleFailed: string;
70
73
  hotApplyNote: string;
@@ -116,6 +119,8 @@ export declare const en: {
116
119
  notInCatalogCode: string;
117
120
  versionMismatchCode: string;
118
121
  needsAcknowledgementCode: string;
122
+ gitMissingCode: string;
123
+ githubSource: string;
119
124
  installedSection: string;
120
125
  installedVersion: string;
121
126
  latestVersion: string;
@@ -137,6 +142,7 @@ export declare const en: {
137
142
  updateTransportFailed: string;
138
143
  checkUpdate: string;
139
144
  upToDate: string;
145
+ github: string;
140
146
  enabledSwitch: string;
141
147
  toggleFailed: string;
142
148
  hotApplyNote: string;
@@ -17,6 +17,11 @@ export declare const ACKNOWLEDGEMENT_ZH = "\u5B89\u88C5\u540E\uFF0C\u6B64\u63D2\
17
17
  /** Install rejection code → locale key, for the rejected-state code label
18
18
  * (§7.2): every rejection the host can return has a human label here. */
19
19
  export declare function rejectionCodeKey(code: InstallRejectionCode): ShopLocaleKey;
20
+ /** The version a card displays: full for npm, the short commit for github. */
21
+ export declare function displayVersion(entry: {
22
+ source: 'npm' | 'github';
23
+ version: string;
24
+ }): string;
20
25
  /** One polled install status (§7.3 wire data), structural. */
21
26
  export interface InstallStatusShape {
22
27
  found: boolean;
@@ -1,7 +1,8 @@
1
1
  /** Catalog fetch, verification, and disk cache — the Host's only network path. */
2
2
  import type { CatalogEntry, DeniedEntry } from './types.ts';
3
- /** Highest schemaVersion this build understands; a higher one is refused (§10). */
4
- export declare const SUPPORTED_SCHEMA_VERSION = 2;
3
+ /** Highest schemaVersion this build understands; a higher one is refused (§10).
4
+ * 3 adds `source` and repo entries (github install channel). */
5
+ export declare const SUPPORTED_SCHEMA_VERSION = 3;
5
6
  export interface CatalogSnapshot {
6
7
  schemaVersion: number;
7
8
  builtAt: string;
@@ -6,6 +6,7 @@ import type { CatalogEntry, DeniedEntry } from './types.ts';
6
6
  import { type InstallArgs, type InstallRejectionCode } from './install.ts';
7
7
  import { type InstallStatus } from './executor.ts';
8
8
  import { type RestartOutcome } from './restart.ts';
9
+ import { type RepoPinFs } from './repo-pins.ts';
9
10
  export type { InstallArgs, InstallRejectionCode } from './install.ts';
10
11
  export type { CatalogEntry } from './types.ts';
11
12
  /** One Loader inventory entry, structurally — the shop never depends on
@@ -46,6 +47,11 @@ export interface ShopGatewayOptions {
46
47
  /** How long the gateway waits after a successful restart response before
47
48
  * exiting the old process; test-only shortening, production uses 2s. */
48
49
  restartExitDelayMs?: number;
50
+ /** Whether `git` is on PATH — a github entry cannot install without it;
51
+ * test-only injection, production probes the real binary. */
52
+ hasGit?: () => boolean;
53
+ /** Test-only injection: the pins file's filesystem; production uses node:fs. */
54
+ pinFs?: RepoPinFs;
49
55
  }
50
56
  /** `shop/installStart` result (§7.3): rejections are typed wire values with an
51
57
  * author-readable `detail`, not thrown RPC errors. */
@@ -138,6 +144,8 @@ export declare class ShopGateway extends TypertRemoteService {
138
144
  private readonly restartExitDelayMs;
139
145
  private readonly restartParentPid;
140
146
  private readonly latestVersion;
147
+ private readonly hasGit;
148
+ private readonly pinFs;
141
149
  /** The install gate runs against the last loaded snapshot, never a fresh
142
150
  * fetch per request (§7.2: the Host's cached snapshot is the truth). */
143
151
  /** Finished install records retained, so a poll sees the true terminal
@@ -154,6 +162,8 @@ export declare class ShopGateway extends TypertRemoteService {
154
162
  /** Every install id in insertion order, oldest first; finished-record eviction walks this from the front. */
155
163
  private readonly installOrder;
156
164
  constructor(ctx: Context, options?: ShopGatewayOptions);
165
+ /** The pins file lives in the shop's own cache, next to the catalog cache. */
166
+ private pinsPath;
157
167
  /** The boot's Loader root directory (the active profile's `cordis.yml`
158
168
  * directory, carried on `ctx.baseUrl`), when present. A `link:` install
159
169
  * keeps this package at its source location, so the walk-up from
@@ -1,6 +1,6 @@
1
1
  /** Install gate: the four rejection paths of §7.2, as a pure function. */
2
2
  import type { CatalogSnapshot } from './catalog.ts';
3
- export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement';
3
+ export type InstallRejectionCode = 'not-in-catalog' | 'denied' | 'version-mismatch' | 'needs-acknowledgement' | 'git-missing';
4
4
  export interface InstallArgs {
5
5
  name: string;
6
6
  version: string;
@@ -0,0 +1,22 @@
1
+ /**
2
+ * The Host's memory of which commit it installed for each github entry.
3
+ *
4
+ * pnpm writes the manifest dependency as `github:owner/slug` — no commit —
5
+ * and the resolved commit lives only in the lockfile. The shop records the
6
+ * commit it pinned at install time in its own cache so `installed()` can
7
+ * report `outdated` honestly without parsing pnpm's lockfile. The profile
8
+ * manifest remains the source of truth for *presence*: a pin whose bundle
9
+ * is not a manifest dependency is a leftover from a failed install and is
10
+ * never reported.
11
+ */
12
+ export interface RepoPinFs {
13
+ exists: (path: string) => boolean;
14
+ read: (path: string) => string;
15
+ write: (path: string, data: string) => void;
16
+ }
17
+ /** Bundle name to the pinned commit. */
18
+ export type RepoPins = Record<string, string>;
19
+ /** Read the pins file; any irregularity degrades to an empty record. */
20
+ export declare function readRepoPins(fs: RepoPinFs, path: string): RepoPins;
21
+ /** Write the pins file atomically enough for its purpose (a cache). */
22
+ export declare function writeRepoPins(fs: RepoPinFs, path: string, pins: RepoPins): void;
@@ -18,12 +18,17 @@ export interface CatalogEntry {
18
18
  tier: 'verified' | 'verified-stale' | 'community';
19
19
  metadata: 'declared' | 'derived';
20
20
  review?: {
21
- reviewedVersion: string;
21
+ reviewedVersion?: string;
22
+ reviewedCommit?: string;
22
23
  reviewer: string;
23
24
  reviewCommit: string;
24
25
  notes: string;
25
26
  };
26
27
  catalog?: CatalogSection;
28
+ /** Where the entry installs from. */
29
+ source: 'npm' | 'github';
30
+ /** `owner/slug`; present exactly when `source` is github. */
31
+ repo?: string;
27
32
  }
28
33
  export interface DeniedEntry {
29
34
  name: string;
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "dsh-plugin-shop",
3
- "version": "0.4.8",
3
+ "version": "0.4.10",
4
4
  "description": "The DeepSeek Harness plugin shop: browse, install, enable, and update dsh plugins from a git-auditable catalog.",
5
5
  "repository": {
6
6
  "type": "git",