@sparkelf/dsh-dataops-managed 0.3.5 → 0.3.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/lib/client.js +338 -24
- package/lib/client.js.map +1 -1
- package/lib/index.js +254 -15
- package/lib/types/client/SkillPlazaSection.d.ts +22 -0
- package/lib/types/client/locales.d.ts +14 -0
- package/lib/types/client/skill-plaza-client.d.ts +33 -0
- package/lib/types/index.d.ts +5 -0
- package/lib/types/skill-plaza.d.ts +107 -0
- package/package.json +10 -6
package/lib/index.js
CHANGED
|
@@ -14,17 +14,17 @@ function modelIdOf(entry) {
|
|
|
14
14
|
return typeof id === "string" && id.trim() !== "" ? id : null;
|
|
15
15
|
}
|
|
16
16
|
/** Narrow an unknown value to a plain object. */
|
|
17
|
-
function asRecord$
|
|
17
|
+
function asRecord$2(value) {
|
|
18
18
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
19
19
|
}
|
|
20
20
|
/** Read provider profiles out of a settings value. */
|
|
21
21
|
function providersOf(value) {
|
|
22
|
-
const root = asRecord$
|
|
23
|
-
const providers = root === null ? null : asRecord$
|
|
22
|
+
const root = asRecord$2(value);
|
|
23
|
+
const providers = root === null ? null : asRecord$2(root[PROVIDERS_FIELD]);
|
|
24
24
|
if (providers === null) return {};
|
|
25
25
|
const result = {};
|
|
26
26
|
for (const [providerId, profile] of Object.entries(providers)) {
|
|
27
|
-
const narrowed = asRecord$
|
|
27
|
+
const narrowed = asRecord$2(profile);
|
|
28
28
|
if (narrowed !== null) result[providerId] = narrowed;
|
|
29
29
|
}
|
|
30
30
|
return result;
|
|
@@ -58,7 +58,7 @@ function modelIdsByProvider(providers) {
|
|
|
58
58
|
for (const [providerId, profile] of Object.entries(providers)) {
|
|
59
59
|
const models = profile[MODELS_FIELD];
|
|
60
60
|
if (!Array.isArray(models)) continue;
|
|
61
|
-
result[providerId] = models.map((entry) => asRecord$
|
|
61
|
+
result[providerId] = models.map((entry) => asRecord$2(entry)).filter((entry) => entry !== null).map((entry) => modelIdOf(entry)).filter((id) => id !== null);
|
|
62
62
|
}
|
|
63
63
|
return result;
|
|
64
64
|
}
|
|
@@ -79,7 +79,7 @@ function selectSharedModels(providers, sharedModelIds) {
|
|
|
79
79
|
const models = profile[MODELS_FIELD];
|
|
80
80
|
if (!Array.isArray(models)) continue;
|
|
81
81
|
const kept = models.filter((entry) => {
|
|
82
|
-
const narrowed = asRecord$
|
|
82
|
+
const narrowed = asRecord$2(entry);
|
|
83
83
|
const id = narrowed === null ? null : modelIdOf(narrowed);
|
|
84
84
|
return id !== null && offered.has(id);
|
|
85
85
|
});
|
|
@@ -113,9 +113,9 @@ function mergeProviders(local, adopted) {
|
|
|
113
113
|
continue;
|
|
114
114
|
}
|
|
115
115
|
const localModels = Array.isArray(localProfile[MODELS_FIELD]) ? localProfile[MODELS_FIELD] : [];
|
|
116
|
-
const localIds = new Set(localModels.map((entry) => asRecord$
|
|
116
|
+
const localIds = new Set(localModels.map((entry) => asRecord$2(entry)).filter((entry) => entry !== null).map((entry) => modelIdOf(entry)).filter((id) => id !== null));
|
|
117
117
|
const missing = adoptedModels.filter((entry) => {
|
|
118
|
-
const narrowed = asRecord$
|
|
118
|
+
const narrowed = asRecord$2(entry);
|
|
119
119
|
const id = narrowed === null ? null : modelIdOf(narrowed);
|
|
120
120
|
return id !== null && !localIds.has(id);
|
|
121
121
|
});
|
|
@@ -147,7 +147,7 @@ async function writeModels(ctx, providers, revision) {
|
|
|
147
147
|
//#endregion
|
|
148
148
|
//#region src/model-sync.ts
|
|
149
149
|
/** Narrow an unknown value to a plain object. */
|
|
150
|
-
function asRecord(value) {
|
|
150
|
+
function asRecord$1(value) {
|
|
151
151
|
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
152
152
|
}
|
|
153
153
|
/**
|
|
@@ -190,10 +190,10 @@ var ModelSync = class {
|
|
|
190
190
|
});
|
|
191
191
|
if (response.status === 401) return void 0;
|
|
192
192
|
if (!response.ok) throw new Error(`Shared model read failed with HTTP ${String(response.status)}`);
|
|
193
|
-
const body = asRecord(await response.json());
|
|
193
|
+
const body = asRecord$1(await response.json());
|
|
194
194
|
if (body === null) return void 0;
|
|
195
|
-
const payload = asRecord(body.payload);
|
|
196
|
-
const providers = payload === null ? null : asRecord(payload.providers);
|
|
195
|
+
const payload = asRecord$1(body.payload);
|
|
196
|
+
const providers = payload === null ? null : asRecord$1(payload.providers);
|
|
197
197
|
return {
|
|
198
198
|
sharingEnabled: body.sharingEnabled === true,
|
|
199
199
|
payload: {
|
|
@@ -256,7 +256,7 @@ var ModelSync = class {
|
|
|
256
256
|
const response = await fetch(new URL("/api/ai/shared-models/publication", this.apiBase()), { headers: { authorization: `Bearer ${token}` } });
|
|
257
257
|
if (response.status === 401) return null;
|
|
258
258
|
if (!response.ok) throw new Error(`Shared model status read failed with HTTP ${String(response.status)}`);
|
|
259
|
-
const body = asRecord(await response.json());
|
|
259
|
+
const body = asRecord$1(await response.json());
|
|
260
260
|
return {
|
|
261
261
|
sharingEnabled: body?.sharingEnabled === true,
|
|
262
262
|
canPublish: body?.canPublish === true
|
|
@@ -369,6 +369,156 @@ var ModelSync = class {
|
|
|
369
369
|
}
|
|
370
370
|
};
|
|
371
371
|
//#endregion
|
|
372
|
+
//#region src/skill-plaza.ts
|
|
373
|
+
/** Narrow an unknown value to a plain object. */
|
|
374
|
+
function asRecord(value) {
|
|
375
|
+
return typeof value === "object" && value !== null && !Array.isArray(value) ? value : null;
|
|
376
|
+
}
|
|
377
|
+
/**
|
|
378
|
+
* Read the skills this workspace holds.
|
|
379
|
+
*
|
|
380
|
+
* The official skill registry is the reader, so the list is the same one the skill center
|
|
381
|
+
* shows and includes every source. It is read-only here; sharing a copy of one is what the
|
|
382
|
+
* plaza does with it.
|
|
383
|
+
* @param ctx - DSH Host context carrying the skill registry.
|
|
384
|
+
* @returns Each skill's name, description, and body.
|
|
385
|
+
*/
|
|
386
|
+
async function readLocalSkills(ctx) {
|
|
387
|
+
const snapshot = await ctx.skills.snapshot({});
|
|
388
|
+
const candidates = [];
|
|
389
|
+
for (const summary of snapshot.skills) {
|
|
390
|
+
const definition = await ctx.skills.get(summary.name, {});
|
|
391
|
+
if (definition === void 0) continue;
|
|
392
|
+
candidates.push({
|
|
393
|
+
name: definition.name,
|
|
394
|
+
description: definition.description === "" ? void 0 : definition.description,
|
|
395
|
+
instruction: definition.content
|
|
396
|
+
});
|
|
397
|
+
}
|
|
398
|
+
return candidates;
|
|
399
|
+
}
|
|
400
|
+
/**
|
|
401
|
+
* The skill plaza, as this workspace sees it.
|
|
402
|
+
*
|
|
403
|
+
* Every call carries the workspace's own DataOps JWT, so DataOps decides who may read and
|
|
404
|
+
* share. The plaza copy lives in DataOps rather than here: a reader installs it into their own
|
|
405
|
+
* workspace and keeps working after the author edits or stops sharing.
|
|
406
|
+
*/
|
|
407
|
+
var SkillPlaza = class {
|
|
408
|
+
ctx;
|
|
409
|
+
config;
|
|
410
|
+
constructor(ctx, config) {
|
|
411
|
+
this.ctx = ctx;
|
|
412
|
+
this.config = config;
|
|
413
|
+
}
|
|
414
|
+
/** The DataOps API origin this workspace reports to. */
|
|
415
|
+
apiBase() {
|
|
416
|
+
return new URL(this.config.baseUrl).origin;
|
|
417
|
+
}
|
|
418
|
+
/**
|
|
419
|
+
* Read the DataOps JWT this workspace is currently authorized with.
|
|
420
|
+
* @returns The token, or undefined when the workspace has no session yet.
|
|
421
|
+
*/
|
|
422
|
+
async token() {
|
|
423
|
+
return (await this.ctx.credentials.resolve(credentialRef(this.config.credentialRef)))?.value;
|
|
424
|
+
}
|
|
425
|
+
/**
|
|
426
|
+
* Call one DataOps endpoint with this workspace's identity.
|
|
427
|
+
* @param path - API path below the origin.
|
|
428
|
+
* @param init - Method, body, and any extra headers.
|
|
429
|
+
* @returns The parsed JSON body, or undefined when the workspace is not authorized.
|
|
430
|
+
*/
|
|
431
|
+
async call(path, init = {}) {
|
|
432
|
+
const token = await this.token();
|
|
433
|
+
if (token === void 0) return void 0;
|
|
434
|
+
const response = await fetch(new URL(path, this.apiBase()), {
|
|
435
|
+
...init,
|
|
436
|
+
headers: {
|
|
437
|
+
authorization: `Bearer ${token}`,
|
|
438
|
+
...init.body === void 0 ? {} : { "content-type": "application/json" },
|
|
439
|
+
...init.headers
|
|
440
|
+
}
|
|
441
|
+
});
|
|
442
|
+
if (response.status === 401) return void 0;
|
|
443
|
+
if (!response.ok) throw new Error(`Skill plaza request failed with HTTP ${String(response.status)}`);
|
|
444
|
+
return response.status === 204 ? null : await response.json();
|
|
445
|
+
}
|
|
446
|
+
/**
|
|
447
|
+
* List plaza entries, optionally filtered.
|
|
448
|
+
* @param options - Tag and text filters.
|
|
449
|
+
* @returns Entries, newest first; empty when the workspace is not authorized.
|
|
450
|
+
*/
|
|
451
|
+
async list(options = {}) {
|
|
452
|
+
const params = new URLSearchParams();
|
|
453
|
+
if (options.tag !== void 0 && options.tag !== "") params.set("tag", options.tag);
|
|
454
|
+
if (options.query !== void 0 && options.query !== "") params.set("q", options.query);
|
|
455
|
+
const suffix = params.size === 0 ? "" : `?${params.toString()}`;
|
|
456
|
+
const body = await this.call(`/api/ai/skill-plaza/skills${suffix}`);
|
|
457
|
+
if (!Array.isArray(body)) return [];
|
|
458
|
+
return body.flatMap((row) => this.toPlazaSkill(row));
|
|
459
|
+
}
|
|
460
|
+
/**
|
|
461
|
+
* Every tag in use, so the browser offers existing labels.
|
|
462
|
+
* @returns Distinct tags.
|
|
463
|
+
*/
|
|
464
|
+
async tags() {
|
|
465
|
+
const body = await this.call("/api/ai/skill-plaza/tags");
|
|
466
|
+
if (!Array.isArray(body)) return [];
|
|
467
|
+
return body.filter((tag) => typeof tag === "string");
|
|
468
|
+
}
|
|
469
|
+
/**
|
|
470
|
+
* Publish one skill to the plaza.
|
|
471
|
+
* @param input - Skill content and its tags.
|
|
472
|
+
* @returns The stored entry.
|
|
473
|
+
*/
|
|
474
|
+
async share(input) {
|
|
475
|
+
const body = await this.call("/api/ai/skill-plaza/skills", {
|
|
476
|
+
method: "POST",
|
|
477
|
+
body: JSON.stringify(input)
|
|
478
|
+
});
|
|
479
|
+
return body === void 0 ? null : this.toPlazaSkill(body)[0] ?? null;
|
|
480
|
+
}
|
|
481
|
+
/**
|
|
482
|
+
* Stop sharing one entry.
|
|
483
|
+
* @param id - Plaza entry.
|
|
484
|
+
*/
|
|
485
|
+
async unshare(id) {
|
|
486
|
+
await this.call(`/api/ai/skill-plaza/skills/${String(id)}`, { method: "DELETE" });
|
|
487
|
+
}
|
|
488
|
+
/**
|
|
489
|
+
* Install one entry into this workspace.
|
|
490
|
+
* @param skillId - Plaza entry.
|
|
491
|
+
* @returns What happened, or null when the workspace is not authorized.
|
|
492
|
+
*/
|
|
493
|
+
async install(skillId) {
|
|
494
|
+
const body = await this.call("/api/ai/skill-plaza/install", {
|
|
495
|
+
method: "POST",
|
|
496
|
+
body: JSON.stringify({ skillId })
|
|
497
|
+
});
|
|
498
|
+
const record = body === void 0 ? null : asRecord(body);
|
|
499
|
+
if (record === null) return null;
|
|
500
|
+
return {
|
|
501
|
+
installed: record.installed === true,
|
|
502
|
+
skillKey: typeof record.skillKey === "string" ? record.skillKey : null,
|
|
503
|
+
keptLocal: record.keptLocal === true
|
|
504
|
+
};
|
|
505
|
+
}
|
|
506
|
+
/** Project one API row into the shape the browser reads. */
|
|
507
|
+
toPlazaSkill(value) {
|
|
508
|
+
const row = asRecord(value);
|
|
509
|
+
if (row === null || typeof row.id !== "number" || typeof row.name !== "string") return [];
|
|
510
|
+
return [{
|
|
511
|
+
id: row.id,
|
|
512
|
+
name: row.name,
|
|
513
|
+
description: typeof row.description === "string" ? row.description : void 0,
|
|
514
|
+
instruction: typeof row.instruction === "string" ? row.instruction : "",
|
|
515
|
+
tags: Array.isArray(row.tags) ? row.tags.filter((tag) => typeof tag === "string") : [],
|
|
516
|
+
sharedByMe: row.sharedByMe === true,
|
|
517
|
+
updatedAt: typeof row.updatedAt === "string" ? row.updatedAt : ""
|
|
518
|
+
}];
|
|
519
|
+
}
|
|
520
|
+
};
|
|
521
|
+
//#endregion
|
|
372
522
|
//#region src/index.ts
|
|
373
523
|
/** Cordis plugin name for the DataOps-managed integration. */
|
|
374
524
|
const name = "dataops-managed";
|
|
@@ -377,15 +527,24 @@ const inject = [
|
|
|
377
527
|
"credentials",
|
|
378
528
|
"webServer",
|
|
379
529
|
"tools",
|
|
380
|
-
"settings"
|
|
530
|
+
"settings",
|
|
531
|
+
"skills"
|
|
381
532
|
];
|
|
382
533
|
const MANAGED_AUTH_PATH = "/integrations/dataops/managed-auth";
|
|
383
534
|
/** This plugin's profile entry id, which owns its Config and sync state. */
|
|
384
535
|
const MANAGED_PLUGIN_ID = "dataops-managed";
|
|
385
536
|
/** Same-origin route the workspace settings pages use to read and change model sharing. */
|
|
386
537
|
const MODEL_SYNC_PATH = "/integrations/dataops/model-sync";
|
|
538
|
+
/** Same-origin route the skill center uses to read and change plaza sharing. */
|
|
539
|
+
const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
|
|
387
540
|
/** The live sync instance, so the settings routes can act on it after activation. */
|
|
388
541
|
let modelSyncRef;
|
|
542
|
+
/** The live plaza instance, so the skill center routes can act on it after activation. */
|
|
543
|
+
let skillPlazaRef;
|
|
544
|
+
/** The live skill plaza, once this plugin activated. */
|
|
545
|
+
function currentSkillPlaza() {
|
|
546
|
+
return skillPlazaRef;
|
|
547
|
+
}
|
|
389
548
|
/** The live model-sync instance, once this plugin activated. */
|
|
390
549
|
function currentModelSync() {
|
|
391
550
|
return modelSyncRef;
|
|
@@ -461,6 +620,10 @@ async function apply(ctx, config) {
|
|
|
461
620
|
}
|
|
462
621
|
});
|
|
463
622
|
modelSyncRef = modelSync;
|
|
623
|
+
skillPlazaRef = new SkillPlaza(ctx, {
|
|
624
|
+
baseUrl,
|
|
625
|
+
credentialRef: config.credentialRef
|
|
626
|
+
});
|
|
464
627
|
/**
|
|
465
628
|
* Adopt the publisher's models once the workspace has a session.
|
|
466
629
|
*
|
|
@@ -563,6 +726,82 @@ async function apply(ctx, config) {
|
|
|
563
726
|
}
|
|
564
727
|
}
|
|
565
728
|
}), "dataops-managed: model sync routes");
|
|
729
|
+
/**
|
|
730
|
+
* The skill plaza surface the workspace's skill center reads and writes.
|
|
731
|
+
*
|
|
732
|
+
* Same-origin only, like the model routes: the browser reaches these through the gateway
|
|
733
|
+
* that already authenticated it, and the DataOps JWT they act with is the one this plugin
|
|
734
|
+
* holds rather than a value the page supplies.
|
|
735
|
+
*/
|
|
736
|
+
ctx.effect(() => ctx.webServer.register({
|
|
737
|
+
kind: "exact",
|
|
738
|
+
path: SKILL_PLAZA_PATH,
|
|
739
|
+
handler: async (request, response) => {
|
|
740
|
+
const plaza = currentSkillPlaza();
|
|
741
|
+
if (plaza === void 0) {
|
|
742
|
+
writeJson(response, 503, { error: "skill plaza is not active" });
|
|
743
|
+
return;
|
|
744
|
+
}
|
|
745
|
+
try {
|
|
746
|
+
if (request.method === "GET") {
|
|
747
|
+
const url = new URL(request.url ?? "/", "http://localhost");
|
|
748
|
+
writeJson(response, 200, {
|
|
749
|
+
skills: await plaza.list({
|
|
750
|
+
tag: url.searchParams.get("tag") ?? void 0,
|
|
751
|
+
query: url.searchParams.get("q") ?? void 0
|
|
752
|
+
}),
|
|
753
|
+
tags: await plaza.tags()
|
|
754
|
+
});
|
|
755
|
+
return;
|
|
756
|
+
}
|
|
757
|
+
if (request.method !== "POST") {
|
|
758
|
+
response.writeHead(405, {
|
|
759
|
+
allow: "GET, POST",
|
|
760
|
+
"content-length": "0"
|
|
761
|
+
});
|
|
762
|
+
response.end();
|
|
763
|
+
return;
|
|
764
|
+
}
|
|
765
|
+
const body = await readJson(request);
|
|
766
|
+
const action = typeof body?.action === "string" ? body.action : "";
|
|
767
|
+
if (action === "local-skills") {
|
|
768
|
+
writeJson(response, 200, { skills: await readLocalSkills(ctx) });
|
|
769
|
+
return;
|
|
770
|
+
}
|
|
771
|
+
if (action === "share") {
|
|
772
|
+
const name = typeof body?.name === "string" ? body.name : "";
|
|
773
|
+
const instruction = typeof body?.instruction === "string" ? body.instruction : "";
|
|
774
|
+
const description = typeof body?.description === "string" ? body.description : void 0;
|
|
775
|
+
const tags = Array.isArray(body?.tags) ? body.tags.filter((tag) => typeof tag === "string") : [];
|
|
776
|
+
writeJson(response, 200, { skill: await plaza.share({
|
|
777
|
+
name,
|
|
778
|
+
description,
|
|
779
|
+
instruction,
|
|
780
|
+
tags
|
|
781
|
+
}) });
|
|
782
|
+
return;
|
|
783
|
+
}
|
|
784
|
+
if (action === "unshare") {
|
|
785
|
+
if (typeof body?.id === "number") await plaza.unshare(body.id);
|
|
786
|
+
writeJson(response, 200, { success: true });
|
|
787
|
+
return;
|
|
788
|
+
}
|
|
789
|
+
if (action === "install") {
|
|
790
|
+
writeJson(response, 200, (typeof body?.id === "number" ? await plaza.install(body.id) : null) ?? {
|
|
791
|
+
installed: false,
|
|
792
|
+
skillKey: null,
|
|
793
|
+
keptLocal: false
|
|
794
|
+
});
|
|
795
|
+
return;
|
|
796
|
+
}
|
|
797
|
+
writeJson(response, 400, { error: "unknown action" });
|
|
798
|
+
} catch (error) {
|
|
799
|
+
ctx.logger.warn("dataops-managed: skill plaza request failed");
|
|
800
|
+
ctx.logger.warn(error);
|
|
801
|
+
writeJson(response, 500, { error: error instanceof Error ? error.message : String(error) });
|
|
802
|
+
}
|
|
803
|
+
}
|
|
804
|
+
}), "dataops-managed: skill plaza routes");
|
|
566
805
|
}
|
|
567
806
|
/** Read and parse a JSON request body, tolerating an absent one. */
|
|
568
807
|
async function readJson(request) {
|
|
@@ -586,4 +825,4 @@ function writeJson(response, status, body) {
|
|
|
586
825
|
response.end(payload);
|
|
587
826
|
}
|
|
588
827
|
//#endregion
|
|
589
|
-
export { Config, MANAGED_AUTH_PATH, MANAGED_PLUGIN_ID, MODEL_SYNC_PATH, apply, currentModelSync, inject, name };
|
|
828
|
+
export { Config, MANAGED_AUTH_PATH, MANAGED_PLUGIN_ID, MODEL_SYNC_PATH, SKILL_PLAZA_PATH, apply, currentModelSync, currentSkillPlaza, inject, name };
|
|
@@ -0,0 +1,22 @@
|
|
|
1
|
+
import type { ReactNode } from 'react';
|
|
2
|
+
import type { InjectFace } from '@deepseek-ai/dsh-client-ui-slots';
|
|
3
|
+
import type { en } from './locales.ts';
|
|
4
|
+
/** Values the skill center passes to a contributed section. */
|
|
5
|
+
export interface SkillPlazaSectionInjected {
|
|
6
|
+
/** Translate one DataOps plaza message key. */
|
|
7
|
+
t: (key: keyof typeof en) => string;
|
|
8
|
+
}
|
|
9
|
+
/** Props accepted by the plaza section. */
|
|
10
|
+
export type SkillPlazaSectionProps = Partial<InjectFace<SkillPlazaSectionInjected>>;
|
|
11
|
+
/**
|
|
12
|
+
* Browse, share, and install skills other users published.
|
|
13
|
+
*
|
|
14
|
+
* The section appears only where this plugin is installed: the skill center renders the slot,
|
|
15
|
+
* and an empty slot renders nothing. Every action goes through this plugin's Host route, so a
|
|
16
|
+
* workspace without DataOps never reaches these calls.
|
|
17
|
+
* @param props - Skill center injection values.
|
|
18
|
+
* @returns The plaza section, or nothing before injection.
|
|
19
|
+
*/
|
|
20
|
+
export declare function SkillPlazaSection(props: SkillPlazaSectionProps): ReactNode;
|
|
21
|
+
/** Re-exported so the registration site reads the route from one place. */
|
|
22
|
+
export { SKILL_PLAZA_PATH } from './skill-plaza-client.ts';
|
|
@@ -29,6 +29,20 @@ export declare const en: {
|
|
|
29
29
|
readonly modelShareLabel: "Available to other users";
|
|
30
30
|
readonly modelsEmpty: "No models are configured in this workspace yet. Add them in Models settings first.";
|
|
31
31
|
readonly loadFailed: "The model configuration could not be loaded. Try again.";
|
|
32
|
+
readonly plazaTitle: "Skill plaza";
|
|
33
|
+
readonly plazaIntro: "Skills other users shared. Install one to use it in this workspace, or share a skill of your own.";
|
|
34
|
+
readonly plazaSearch: "Search skills";
|
|
35
|
+
readonly plazaTagFilter: "Filter by tag";
|
|
36
|
+
readonly plazaAllTags: "All tags";
|
|
37
|
+
readonly plazaMine: "Shared by me";
|
|
38
|
+
readonly plazaInstall: "Install";
|
|
39
|
+
readonly plazaInstalled: "Installed. It is now in your skill list.";
|
|
40
|
+
readonly plazaUnshare: "Stop sharing";
|
|
41
|
+
readonly plazaUnshared: "Removed from the plaza. Users who installed it keep their copy.";
|
|
42
|
+
readonly plazaEmpty: "No skills have been shared yet.";
|
|
43
|
+
readonly plazaUnavailable: "The skill plaza is available once this workspace is connected to DataOps.";
|
|
44
|
+
readonly plazaLoadFailed: "The skill plaza could not be loaded. Try again.";
|
|
45
|
+
readonly plazaActionFailed: "The action could not be completed. Try again.";
|
|
32
46
|
};
|
|
33
47
|
/** Stable key set shared by every managed DataOps locale. */
|
|
34
48
|
export type ManagedDataOpsKey = keyof typeof en;
|
|
@@ -0,0 +1,33 @@
|
|
|
1
|
+
/** Same-origin skill plaza calls from the DataOps skill center section. */
|
|
2
|
+
/** Route the plugin's Host half serves for the plaza. */
|
|
3
|
+
export declare const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
|
|
4
|
+
/** One plaza entry as the section reads it. */
|
|
5
|
+
export interface PlazaSkill {
|
|
6
|
+
id: number;
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
instruction: string;
|
|
10
|
+
tags: string[];
|
|
11
|
+
sharedByMe: boolean;
|
|
12
|
+
updatedAt: string;
|
|
13
|
+
}
|
|
14
|
+
/** The plaza listing and the tags in use. */
|
|
15
|
+
export interface PlazaListing {
|
|
16
|
+
skills: PlazaSkill[];
|
|
17
|
+
tags: string[];
|
|
18
|
+
}
|
|
19
|
+
/**
|
|
20
|
+
* Read the plaza.
|
|
21
|
+
* @param filter - Optional tag and text filters.
|
|
22
|
+
* @returns The listing, or null when this workspace is not connected to DataOps.
|
|
23
|
+
*/
|
|
24
|
+
export declare function fetchPlaza(filter?: {
|
|
25
|
+
tag?: string;
|
|
26
|
+
query?: string;
|
|
27
|
+
}): Promise<PlazaListing | null>;
|
|
28
|
+
/**
|
|
29
|
+
* Change the plaza.
|
|
30
|
+
* @param body - Action and its parameters.
|
|
31
|
+
* @returns The parsed reply, or null when the plugin is not active.
|
|
32
|
+
*/
|
|
33
|
+
export declare function postPlazaAction(body: Record<string, unknown>): Promise<unknown>;
|
package/lib/types/index.d.ts
CHANGED
|
@@ -2,6 +2,7 @@
|
|
|
2
2
|
import type { Context } from '@deepseek-ai/cordis';
|
|
3
3
|
import z from '@deepseek-ai/schemastery';
|
|
4
4
|
import { ModelSync } from './model-sync.ts';
|
|
5
|
+
import { SkillPlaza } from './skill-plaza.ts';
|
|
5
6
|
/** Cordis plugin name for the DataOps-managed integration. */
|
|
6
7
|
export declare const name = "dataops-managed";
|
|
7
8
|
/** Host services required by managed JWT and MCP ownership. */
|
|
@@ -11,6 +12,10 @@ export declare const MANAGED_AUTH_PATH = "/integrations/dataops/managed-auth";
|
|
|
11
12
|
export declare const MANAGED_PLUGIN_ID = "dataops-managed";
|
|
12
13
|
/** Same-origin route the workspace settings pages use to read and change model sharing. */
|
|
13
14
|
export declare const MODEL_SYNC_PATH = "/integrations/dataops/model-sync";
|
|
15
|
+
/** Same-origin route the skill center uses to read and change plaza sharing. */
|
|
16
|
+
export declare const SKILL_PLAZA_PATH = "/integrations/dataops/skill-plaza";
|
|
17
|
+
/** The live skill plaza, once this plugin activated. */
|
|
18
|
+
export declare function currentSkillPlaza(): SkillPlaza | undefined;
|
|
14
19
|
/** The live model-sync instance, once this plugin activated. */
|
|
15
20
|
export declare function currentModelSync(): ModelSync | undefined;
|
|
16
21
|
/** Configuration for the DataOps-managed MCP connection. */
|
|
@@ -0,0 +1,107 @@
|
|
|
1
|
+
/** Skills users share with each other, reached through the DataOps API. */
|
|
2
|
+
import type { Context } from '@deepseek-ai/cordis';
|
|
3
|
+
/** One plaza entry as the browser reads it. */
|
|
4
|
+
export interface PlazaSkill {
|
|
5
|
+
/** Plaza entry id, used to install or stop sharing it. */
|
|
6
|
+
id: number;
|
|
7
|
+
name: string;
|
|
8
|
+
description?: string;
|
|
9
|
+
instruction: string;
|
|
10
|
+
tags: string[];
|
|
11
|
+
/** Whether the current user shared this entry. */
|
|
12
|
+
sharedByMe: boolean;
|
|
13
|
+
updatedAt: string;
|
|
14
|
+
}
|
|
15
|
+
/** A skill the local workspace holds, offered by name for sharing. */
|
|
16
|
+
export interface LocalSkillCandidate {
|
|
17
|
+
name: string;
|
|
18
|
+
description?: string;
|
|
19
|
+
instruction: string;
|
|
20
|
+
}
|
|
21
|
+
/** What installing one entry did. */
|
|
22
|
+
export interface PlazaInstallResult {
|
|
23
|
+
installed: boolean;
|
|
24
|
+
skillKey: string | null;
|
|
25
|
+
keptLocal: boolean;
|
|
26
|
+
}
|
|
27
|
+
/** Configuration the plaza reads from the profile. */
|
|
28
|
+
export interface PlazaConfig {
|
|
29
|
+
/** DataOps browser/API origin reachable from the DSH Host. */
|
|
30
|
+
baseUrl: string;
|
|
31
|
+
/** DSH credential reference holding the current DataOps access JWT. */
|
|
32
|
+
credentialRef: string;
|
|
33
|
+
}
|
|
34
|
+
/**
|
|
35
|
+
* Read the skills this workspace holds.
|
|
36
|
+
*
|
|
37
|
+
* The official skill registry is the reader, so the list is the same one the skill center
|
|
38
|
+
* shows and includes every source. It is read-only here; sharing a copy of one is what the
|
|
39
|
+
* plaza does with it.
|
|
40
|
+
* @param ctx - DSH Host context carrying the skill registry.
|
|
41
|
+
* @returns Each skill's name, description, and body.
|
|
42
|
+
*/
|
|
43
|
+
export declare function readLocalSkills(ctx: Context): Promise<LocalSkillCandidate[]>;
|
|
44
|
+
/**
|
|
45
|
+
* The skill plaza, as this workspace sees it.
|
|
46
|
+
*
|
|
47
|
+
* Every call carries the workspace's own DataOps JWT, so DataOps decides who may read and
|
|
48
|
+
* share. The plaza copy lives in DataOps rather than here: a reader installs it into their own
|
|
49
|
+
* workspace and keeps working after the author edits or stops sharing.
|
|
50
|
+
*/
|
|
51
|
+
export declare class SkillPlaza {
|
|
52
|
+
private readonly ctx;
|
|
53
|
+
private readonly config;
|
|
54
|
+
constructor(ctx: Context, config: PlazaConfig);
|
|
55
|
+
/** The DataOps API origin this workspace reports to. */
|
|
56
|
+
private apiBase;
|
|
57
|
+
/**
|
|
58
|
+
* Read the DataOps JWT this workspace is currently authorized with.
|
|
59
|
+
* @returns The token, or undefined when the workspace has no session yet.
|
|
60
|
+
*/
|
|
61
|
+
private token;
|
|
62
|
+
/**
|
|
63
|
+
* Call one DataOps endpoint with this workspace's identity.
|
|
64
|
+
* @param path - API path below the origin.
|
|
65
|
+
* @param init - Method, body, and any extra headers.
|
|
66
|
+
* @returns The parsed JSON body, or undefined when the workspace is not authorized.
|
|
67
|
+
*/
|
|
68
|
+
private call;
|
|
69
|
+
/**
|
|
70
|
+
* List plaza entries, optionally filtered.
|
|
71
|
+
* @param options - Tag and text filters.
|
|
72
|
+
* @returns Entries, newest first; empty when the workspace is not authorized.
|
|
73
|
+
*/
|
|
74
|
+
list(options?: {
|
|
75
|
+
tag?: string;
|
|
76
|
+
query?: string;
|
|
77
|
+
}): Promise<PlazaSkill[]>;
|
|
78
|
+
/**
|
|
79
|
+
* Every tag in use, so the browser offers existing labels.
|
|
80
|
+
* @returns Distinct tags.
|
|
81
|
+
*/
|
|
82
|
+
tags(): Promise<string[]>;
|
|
83
|
+
/**
|
|
84
|
+
* Publish one skill to the plaza.
|
|
85
|
+
* @param input - Skill content and its tags.
|
|
86
|
+
* @returns The stored entry.
|
|
87
|
+
*/
|
|
88
|
+
share(input: {
|
|
89
|
+
name: string;
|
|
90
|
+
description?: string;
|
|
91
|
+
instruction: string;
|
|
92
|
+
tags: string[];
|
|
93
|
+
}): Promise<PlazaSkill | null>;
|
|
94
|
+
/**
|
|
95
|
+
* Stop sharing one entry.
|
|
96
|
+
* @param id - Plaza entry.
|
|
97
|
+
*/
|
|
98
|
+
unshare(id: number): Promise<void>;
|
|
99
|
+
/**
|
|
100
|
+
* Install one entry into this workspace.
|
|
101
|
+
* @param skillId - Plaza entry.
|
|
102
|
+
* @returns What happened, or null when the workspace is not authorized.
|
|
103
|
+
*/
|
|
104
|
+
install(skillId: number): Promise<PlazaInstallResult | null>;
|
|
105
|
+
/** Project one API row into the shape the browser reads. */
|
|
106
|
+
private toPlazaSkill;
|
|
107
|
+
}
|
package/package.json
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
{
|
|
2
2
|
"name": "@sparkelf/dsh-dataops-managed",
|
|
3
|
-
"version": "0.3.
|
|
3
|
+
"version": "0.3.6",
|
|
4
4
|
"description": "DataOps JWT intake and credential-backed MCP tools for managed DeepSeek Harness workspaces.",
|
|
5
5
|
"type": "module",
|
|
6
6
|
"main": "./lib/index.js",
|
|
@@ -42,6 +42,11 @@
|
|
|
42
42
|
"README.md",
|
|
43
43
|
"LICENSE"
|
|
44
44
|
],
|
|
45
|
+
"scripts": {
|
|
46
|
+
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown",
|
|
47
|
+
"bundle": "tsdown",
|
|
48
|
+
"prepack": "pnpm run build"
|
|
49
|
+
},
|
|
45
50
|
"engines": {
|
|
46
51
|
"node": "^22.19.0 || >=24.0.0"
|
|
47
52
|
},
|
|
@@ -66,6 +71,7 @@
|
|
|
66
71
|
"@deepseek-ai/dsh-credentials": ">=0.1.7-rc.2",
|
|
67
72
|
"@deepseek-ai/dsh-host-webserver": ">=0.1.7-rc.2",
|
|
68
73
|
"@deepseek-ai/dsh-settings": ">=0.1.7-rc.2",
|
|
74
|
+
"@deepseek-ai/dsh-skill": "0.1.7-rc.2",
|
|
69
75
|
"@deepseek-ai/dsh-tools": ">=0.1.7-rc.2",
|
|
70
76
|
"@deepseek-ai/schemastery": ">=3.18.1",
|
|
71
77
|
"@sparkelf/dsh-plugin-mcp-credentials": ">=0.1.0-rc.14",
|
|
@@ -83,16 +89,14 @@
|
|
|
83
89
|
"@deepseek-ai/dsh-credentials": "0.1.7-rc.2",
|
|
84
90
|
"@deepseek-ai/dsh-host-webserver": "0.1.7-rc.2",
|
|
85
91
|
"@deepseek-ai/dsh-settings": "0.1.7-rc.2",
|
|
92
|
+
"@deepseek-ai/dsh-skill": "0.1.7-rc.2",
|
|
86
93
|
"@deepseek-ai/dsh-tools": "0.1.7-rc.2",
|
|
87
94
|
"@deepseek-ai/schemastery": "3.18.2",
|
|
95
|
+
"@sparkelf/dsh-client-ui-skill-center": "0.2.0-rc.46",
|
|
88
96
|
"@sparkelf/dsh-plugin-mcp-credentials": "0.1.0-rc.14",
|
|
89
97
|
"@types/react": "~18.3.1",
|
|
90
98
|
"lightningcss": "^1.32.0",
|
|
91
99
|
"react": "^18.2.0",
|
|
92
100
|
"tsdown": "^0.22.2"
|
|
93
|
-
},
|
|
94
|
-
"scripts": {
|
|
95
|
-
"build": "rm -rf lib && tsc -p tsconfig.build.json && tsdown",
|
|
96
|
-
"bundle": "tsdown"
|
|
97
101
|
}
|
|
98
|
-
}
|
|
102
|
+
}
|